mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-19 03:45:06 +00:00
- The `/system` FAT volume is removed. Its files are embedded in the firmware as generated C arrays. - Default-size fonts are generated at build time and used from flash. - The LVGL task and the Launcher run on PSRAM-preferred stacks. A new kernel `file_ops` API takes the file work off those threads. - `MainDispatcher` is reimplemented as an on-demand task. - More long-lived state is allocated with the PSRAM-preferring helpers. - Other fixes and improvements
154 lines
4.6 KiB
C++
154 lines
4.6 KiB
C++
#include "doctest.h"
|
|
|
|
#include <tactility/delay.h>
|
|
#include <tactility/file_ops.h>
|
|
|
|
#include <atomic>
|
|
|
|
namespace {
|
|
|
|
struct Recorder {
|
|
std::atomic<int> order[8];
|
|
std::atomic<int> count { 0 };
|
|
/** Set after the last write of the work, so a test can wait for the work to finish with the recorder */
|
|
std::atomic<int> done { 0 };
|
|
};
|
|
|
|
struct Job {
|
|
Recorder* recorder;
|
|
int id;
|
|
};
|
|
|
|
void record(void* context) {
|
|
auto* job = static_cast<Job*>(context);
|
|
const int index = job->recorder->count.fetch_add(1);
|
|
job->recorder->order[index] = job->id;
|
|
job->recorder->done.fetch_add(1);
|
|
}
|
|
|
|
void block_briefly(void* context) {
|
|
delay_ticks(5);
|
|
record(context);
|
|
}
|
|
|
|
}
|
|
|
|
TEST_CASE("file_ops_await returns when the queued work has completed") {
|
|
TaskEventGroup group {};
|
|
task_event_group_construct(&group);
|
|
FileOpsSubscription sub {};
|
|
REQUIRE_EQ(file_ops_subscribe(&sub, &group), ERROR_NONE);
|
|
|
|
Recorder recorder;
|
|
Job job { &recorder, 1 };
|
|
CHECK_EQ(file_ops_queue(record, &job, &sub), ERROR_NONE);
|
|
CHECK_EQ(file_ops_await(&sub, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
|
CHECK_EQ(recorder.count.load(), 1);
|
|
CHECK_EQ(file_ops_poll(&sub), ERROR_TIMEOUT);
|
|
|
|
file_ops_unsubscribe(&sub);
|
|
task_event_group_destruct(&group);
|
|
}
|
|
|
|
TEST_CASE("file_ops runs work in the order it was queued, with one completion for each") {
|
|
TaskEventGroup group {};
|
|
task_event_group_construct(&group);
|
|
FileOpsSubscription sub {};
|
|
REQUIRE_EQ(file_ops_subscribe(&sub, &group), ERROR_NONE);
|
|
|
|
Recorder recorder;
|
|
Job jobs[3] = { { &recorder, 1 }, { &recorder, 2 }, { &recorder, 3 } };
|
|
for (auto& job : jobs) {
|
|
CHECK_EQ(file_ops_queue(block_briefly, &job, &sub), ERROR_NONE);
|
|
}
|
|
for (int i = 0; i < 3; i++) {
|
|
CHECK_EQ(file_ops_await(&sub, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
|
}
|
|
CHECK_EQ(recorder.order[0].load(), 1);
|
|
CHECK_EQ(recorder.order[1].load(), 2);
|
|
CHECK_EQ(recorder.order[2].load(), 3);
|
|
CHECK_EQ(file_ops_poll(&sub), ERROR_TIMEOUT);
|
|
|
|
file_ops_unsubscribe(&sub);
|
|
task_event_group_destruct(&group);
|
|
}
|
|
|
|
TEST_CASE("file_ops starts a new task after the queue was emptied") {
|
|
TaskEventGroup group {};
|
|
task_event_group_construct(&group);
|
|
FileOpsSubscription sub {};
|
|
REQUIRE_EQ(file_ops_subscribe(&sub, &group), ERROR_NONE);
|
|
|
|
Recorder recorder;
|
|
for (int i = 0; i < 3; i++) {
|
|
Job job { &recorder, i };
|
|
CHECK_EQ(file_ops_queue(record, &job, &sub), ERROR_NONE);
|
|
CHECK_EQ(file_ops_await(&sub, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
|
delay_ticks(5);
|
|
}
|
|
CHECK_EQ(recorder.count.load(), 3);
|
|
|
|
file_ops_unsubscribe(&sub);
|
|
task_event_group_destruct(&group);
|
|
}
|
|
|
|
TEST_CASE("file_ops runs work without a subscription") {
|
|
TaskEventGroup group {};
|
|
task_event_group_construct(&group);
|
|
FileOpsSubscription sub {};
|
|
REQUIRE_EQ(file_ops_subscribe(&sub, &group), ERROR_NONE);
|
|
|
|
Recorder recorder;
|
|
Job first { &recorder, 1 };
|
|
Job last { &recorder, 2 };
|
|
CHECK_EQ(file_ops_queue(block_briefly, &first, nullptr), ERROR_NONE);
|
|
CHECK_EQ(file_ops_queue(record, &last, &sub), ERROR_NONE);
|
|
CHECK_EQ(file_ops_await(&sub, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
|
CHECK_EQ(recorder.count.load(), 2);
|
|
|
|
file_ops_unsubscribe(&sub);
|
|
task_event_group_destruct(&group);
|
|
}
|
|
|
|
TEST_CASE("file_ops_unsubscribe is safe while work is still queued") {
|
|
TaskEventGroup group {};
|
|
task_event_group_construct(&group);
|
|
FileOpsSubscription sub {};
|
|
REQUIRE_EQ(file_ops_subscribe(&sub, &group), ERROR_NONE);
|
|
|
|
Recorder recorder;
|
|
Job job { &recorder, 1 };
|
|
CHECK_EQ(file_ops_queue(block_briefly, &job, &sub), ERROR_NONE);
|
|
CHECK_EQ(file_ops_unsubscribe(&sub), ERROR_NONE);
|
|
CHECK_EQ(file_ops_unsubscribe(&sub), ERROR_NOT_FOUND);
|
|
|
|
// The bit is free again immediately, and the work still runs
|
|
uint32_t bit;
|
|
CHECK_EQ(task_event_group_claim_bit(&group, &bit), ERROR_NONE);
|
|
task_event_group_release_bit(&group, bit);
|
|
while (recorder.done.load() == 0) {
|
|
delay_ticks(1);
|
|
}
|
|
|
|
task_event_group_destruct(&group);
|
|
}
|
|
|
|
TEST_CASE("file_ops_queue rejects a subscription that isn't subscribed") {
|
|
FileOpsSubscription sub {};
|
|
Recorder recorder;
|
|
Job job { &recorder, 1 };
|
|
CHECK_EQ(file_ops_queue(record, &job, &sub), ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
TEST_CASE("file_ops_await times out when nothing was queued") {
|
|
TaskEventGroup group {};
|
|
task_event_group_construct(&group);
|
|
FileOpsSubscription sub {};
|
|
REQUIRE_EQ(file_ops_subscribe(&sub, &group), ERROR_NONE);
|
|
|
|
CHECK_EQ(file_ops_await(&sub, pdMS_TO_TICKS(20)), ERROR_TIMEOUT);
|
|
|
|
file_ops_unsubscribe(&sub);
|
|
task_event_group_destruct(&group);
|
|
}
|