mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 19:05:07 +00:00
- External apps can opt into automatic cleanup when they exit, including tracked tasks, threads, files, directories, and memory. - App-relative file paths resolve against the app’s working directory, with `.` and `..` normalized. - The shell identifies ELF binaries that cannot be executed instead of treating them as scripts. - Shared icon fonts now offer distinct default and large sizes, with updated icons across the interface. - ESP32 apps can load additional ELF formats when address mirroring is enabled. - Terminal colors render clearly on monochrome displays, and toolbar buttons have cleaner styling. - GPS settings safely refresh their device list when their window is hidden or its widgets are destroyed.
131 lines
4.5 KiB
C++
131 lines
4.5 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
#include "doctest.h"
|
|
|
|
#include <app/loader.h>
|
|
#include <app/manager.h>
|
|
#include <app/start.h>
|
|
|
|
#include <service/manager.h>
|
|
|
|
#include <tactility/delay.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
extern ServiceManifest loader_service_manifest;
|
|
|
|
namespace {
|
|
|
|
struct LeakedFds {
|
|
int fd = -1;
|
|
int file_fd = -1;
|
|
int dir_fd = -1;
|
|
};
|
|
|
|
bool is_open(int fd) {
|
|
return fcntl(fd, F_GETFD) != -1;
|
|
}
|
|
|
|
void ensure_path_loader_registered() {
|
|
if (service_manager_find_instance(APP_LOADER_PATH_SERVICE_ID) == nullptr) {
|
|
service_manager_add(&loader_service_manifest, /*auto_start=*/true);
|
|
}
|
|
}
|
|
|
|
/** Runs the leak fixture as a top-level app and reads back the fds it leaked. */
|
|
bool run_leak_fixture(uint8_t flags, bool with_thread, LeakedFds& out_fds) {
|
|
ensure_path_loader_registered();
|
|
|
|
char output_path[] = "/tmp/cleanup_test_XXXXXX";
|
|
const int output_fd = mkstemp(output_path);
|
|
if (output_fd == -1) {
|
|
return false;
|
|
}
|
|
close(output_fd);
|
|
|
|
AppManifest manifest { "test.posix.leak", "Leak", APP_CATEGORY_USER, { APP_LOCATION_PATH, const_cast<char*>(LEAK_FIXTURE_APP_PATH) }, flags };
|
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
|
|
|
const char* argv[] = { LEAK_FIXTURE_APP_PATH, output_path, with_thread ? "thread" : "" };
|
|
AppStartContext context;
|
|
REQUIRE_EQ(app_start_context_from_id("test.posix.leak", &context), ERROR_NONE);
|
|
app_start_context_set_arguments_ext(&context, 3, argv);
|
|
AppInstanceId app_instance_id = 0;
|
|
REQUIRE_EQ(app_start_with_context(&context, &app_instance_id), ERROR_NONE);
|
|
|
|
// Beyond the task grace period and thread join timeout
|
|
for (int waited = 0; waited < 5000 && app_manager_get_state(app_instance_id) != APP_INSTANCE_STATE_STOPPED; waited += 10) {
|
|
delay_millis(10);
|
|
}
|
|
const bool stopped = app_manager_get_state(app_instance_id) == APP_INSTANCE_STATE_STOPPED;
|
|
app_manager_remove("test.posix.leak");
|
|
|
|
FILE* output = fopen(output_path, "r");
|
|
const bool read = output != nullptr && fscanf(output, "%d %d %d", &out_fds.fd, &out_fds.file_fd, &out_fds.dir_fd) == 3;
|
|
if (output != nullptr) {
|
|
fclose(output);
|
|
}
|
|
unlink(output_path);
|
|
return stopped && read;
|
|
}
|
|
|
|
/** Runs the static fixture with cleanup, which crashes on a double free or double close if releasing happens too early. */
|
|
bool run_static_fixture(bool exit) {
|
|
ensure_path_loader_registered();
|
|
AppManifest manifest { "test.posix.static", "Static", APP_CATEGORY_USER, { APP_LOCATION_PATH, const_cast<char*>(STATIC_FIXTURE_APP_PATH) }, APP_MANIFEST_FLAG_CLEANUP };
|
|
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
|
|
|
|
const char* argv[] = { STATIC_FIXTURE_APP_PATH, exit ? "exit" : "" };
|
|
AppStartContext context;
|
|
REQUIRE_EQ(app_start_context_from_id("test.posix.static", &context), ERROR_NONE);
|
|
app_start_context_set_arguments_ext(&context, 2, argv);
|
|
AppInstanceId app_instance_id = 0;
|
|
REQUIRE_EQ(app_start_with_context(&context, &app_instance_id), ERROR_NONE);
|
|
for (int waited = 0; waited < 5000 && app_manager_get_state(app_instance_id) != APP_INSTANCE_STATE_STOPPED; waited += 10) {
|
|
delay_millis(10);
|
|
}
|
|
const bool stopped = app_manager_get_state(app_instance_id) == APP_INSTANCE_STATE_STOPPED;
|
|
// Waits out a deferred unload, which releases the resources
|
|
delay_millis(100);
|
|
app_manager_remove("test.posix.static");
|
|
return stopped;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("an app with APP_MANIFEST_FLAG_CLEANUP has what its static destructors release left alone") {
|
|
CHECK(run_static_fixture(false));
|
|
}
|
|
|
|
TEST_CASE("an app with APP_MANIFEST_FLAG_CLEANUP that calls exit() has what its static destructors release left alone") {
|
|
CHECK(run_static_fixture(true));
|
|
}
|
|
|
|
TEST_CASE("an app with APP_MANIFEST_FLAG_CLEANUP has its leaked files closed when it ends") {
|
|
LeakedFds fds;
|
|
REQUIRE(run_leak_fixture(APP_MANIFEST_FLAG_CLEANUP, false, fds));
|
|
CHECK_FALSE(is_open(fds.fd));
|
|
CHECK_FALSE(is_open(fds.file_fd));
|
|
CHECK_FALSE(is_open(fds.dir_fd));
|
|
}
|
|
|
|
TEST_CASE("an app with APP_MANIFEST_FLAG_CLEANUP has its leftover thread ended before it's unloaded") {
|
|
LeakedFds fds;
|
|
REQUIRE(run_leak_fixture(APP_MANIFEST_FLAG_CLEANUP, true, fds));
|
|
CHECK_FALSE(is_open(fds.fd));
|
|
}
|
|
|
|
TEST_CASE("an app without APP_MANIFEST_FLAG_CLEANUP keeps leaking") {
|
|
LeakedFds fds;
|
|
REQUIRE(run_leak_fixture(0, false, fds));
|
|
CHECK(is_open(fds.fd));
|
|
CHECK(is_open(fds.file_fd));
|
|
CHECK(is_open(fds.dir_fd));
|
|
close(fds.fd);
|
|
close(fds.file_fd);
|
|
close(fds.dir_fd);
|
|
}
|