mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 10:55:06 +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.
167 lines
4.9 KiB
C++
167 lines
4.9 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
#include "doctest.h"
|
|
|
|
#include <app/private/resources.h>
|
|
#include <app/private/scheduler.h>
|
|
#include <app/resources.h>
|
|
#include <app/scheduler.h>
|
|
|
|
#include <tactility/delay.h>
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "task.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <atomic>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
namespace {
|
|
|
|
// Far above any id the ledger hands out during these tests
|
|
constexpr AppInstanceId TRACKED_ID = 0x7FFF0001;
|
|
|
|
std::atomic<int> deleted_tasks { 0 };
|
|
|
|
void delete_task(void* handle) {
|
|
deleted_tasks++;
|
|
vTaskDelete(static_cast<TaskHandle_t>(handle));
|
|
}
|
|
|
|
bool is_open(int fd) {
|
|
return fcntl(fd, F_GETFD) != -1;
|
|
}
|
|
|
|
struct TaskParameters {
|
|
AppInstanceId app_instance_id;
|
|
bool ends_by_itself;
|
|
};
|
|
|
|
void task_main(void* context) {
|
|
auto* parameters = static_cast<TaskParameters*>(context);
|
|
app_resources_enter_task(parameters->app_instance_id);
|
|
if (parameters->ends_by_itself) {
|
|
app_resources_untrack_task(xTaskGetCurrentTaskHandle());
|
|
vTaskDelete(nullptr);
|
|
}
|
|
while (true) {
|
|
delay_millis(10);
|
|
}
|
|
}
|
|
|
|
bool create_task(void* context, void** out_handle) {
|
|
TaskHandle_t handle = nullptr;
|
|
if (xTaskCreate(task_main, "resources_test", 4096, context, 1, &handle) != pdPASS) {
|
|
return false;
|
|
}
|
|
*out_handle = handle;
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("app resources: nothing is tracked without a registered instance") {
|
|
app_scheduler_set_current_app_id(TRACKED_ID);
|
|
CHECK_EQ(app_resources_current_app(), 0);
|
|
const int fd = open("/dev/null", O_RDONLY);
|
|
app_resources_track_fd(fd);
|
|
app_scheduler_set_current_app_id(0);
|
|
|
|
REQUIRE_EQ(app_resources_register(TRACKED_ID), ERROR_NONE);
|
|
app_resources_release_tasks(TRACKED_ID);
|
|
app_resources_release(TRACKED_ID);
|
|
CHECK(is_open(fd));
|
|
close(fd);
|
|
}
|
|
|
|
TEST_CASE("app resources: release closes and frees what is still tracked") {
|
|
REQUIRE_EQ(app_resources_register(TRACKED_ID), ERROR_NONE);
|
|
app_scheduler_set_current_app_id(TRACKED_ID);
|
|
CHECK_EQ(app_resources_current_app(), TRACKED_ID);
|
|
|
|
const int leaked_fd = open("/dev/null", O_RDONLY);
|
|
const int closed_fd = open("/dev/null", O_RDONLY);
|
|
REQUIRE_NE(leaked_fd, -1);
|
|
REQUIRE_NE(closed_fd, -1);
|
|
app_resources_track_fd(leaked_fd);
|
|
app_resources_track_fd(closed_fd);
|
|
app_resources_untrack_fd(closed_fd);
|
|
|
|
FILE* file = fopen("/dev/null", "r");
|
|
REQUIRE_NE(file, nullptr);
|
|
app_resources_track_file(file);
|
|
const int file_fd = fileno(file);
|
|
|
|
DIR* dir = opendir("/");
|
|
REQUIRE_NE(dir, nullptr);
|
|
app_resources_track_dir(dir);
|
|
const int dir_fd = dirfd(dir);
|
|
|
|
app_resources_track_alloc(malloc(16));
|
|
|
|
app_scheduler_set_current_app_id(0);
|
|
app_resources_release_tasks(TRACKED_ID);
|
|
app_resources_release(TRACKED_ID);
|
|
|
|
CHECK_FALSE(is_open(leaked_fd));
|
|
CHECK_FALSE(is_open(file_fd));
|
|
CHECK_FALSE(is_open(dir_fd));
|
|
CHECK(is_open(closed_fd));
|
|
close(closed_fd);
|
|
|
|
CHECK_FALSE(app_resources_enter_task(TRACKED_ID));
|
|
}
|
|
|
|
TEST_CASE("app resources: a task that ends within the grace period is not deleted") {
|
|
REQUIRE_EQ(app_resources_register(TRACKED_ID), ERROR_NONE);
|
|
app_scheduler_set_current_app_id(TRACKED_ID);
|
|
deleted_tasks = 0;
|
|
TaskParameters parameters { TRACKED_ID, true };
|
|
CHECK(app_resources_create_task(create_task, ¶meters, delete_task, nullptr));
|
|
app_scheduler_set_current_app_id(0);
|
|
|
|
app_resources_release_tasks(TRACKED_ID);
|
|
app_resources_release(TRACKED_ID);
|
|
CHECK_EQ(deleted_tasks.load(), 0);
|
|
}
|
|
|
|
TEST_CASE("app resources: a task still running after the grace period is deleted") {
|
|
REQUIRE_EQ(app_resources_register(TRACKED_ID), ERROR_NONE);
|
|
app_scheduler_set_current_app_id(TRACKED_ID);
|
|
deleted_tasks = 0;
|
|
TaskParameters parameters { TRACKED_ID, false };
|
|
CHECK(app_resources_create_task(create_task, ¶meters, delete_task, nullptr));
|
|
app_scheduler_set_current_app_id(0);
|
|
|
|
app_resources_release_tasks(TRACKED_ID);
|
|
app_resources_release(TRACKED_ID);
|
|
CHECK_EQ(deleted_tasks.load(), 1);
|
|
}
|
|
|
|
TEST_CASE("app resources: what is freed and closed between the two phases is not released again") {
|
|
REQUIRE_EQ(app_resources_register(TRACKED_ID), ERROR_NONE);
|
|
app_scheduler_set_current_app_id(TRACKED_ID);
|
|
void* memory = malloc(16);
|
|
app_resources_track_alloc(memory);
|
|
const int fd = open("/dev/null", O_RDONLY);
|
|
app_resources_track_fd(fd);
|
|
app_scheduler_set_current_app_id(0);
|
|
|
|
app_resources_release_tasks(TRACKED_ID);
|
|
CHECK_FALSE(app_resources_enter_task(TRACKED_ID));
|
|
|
|
// As the binary's static destructors do while it's unloaded
|
|
app_resources_untrack_alloc(memory);
|
|
free(memory);
|
|
app_resources_untrack_fd(fd);
|
|
close(fd);
|
|
const int reused_fd = open("/dev/null", O_RDONLY);
|
|
REQUIRE_EQ(reused_fd, fd);
|
|
|
|
app_resources_release(TRACKED_ID);
|
|
CHECK(is_open(reused_fd));
|
|
close(reused_fd);
|
|
}
|