Ken Van Hoeylandt 79d3dd4fdb
Improvements for apps, filesystems, terminal and shell (#659)
app-module

- Apps now have support for:
  - environment variables (inherit from parent app)
  - current workdir
  - window size (when running in terminal)
- Add type aliases like `AppId` and `PackageId`
- Remove data duplication between ledger and app instance records: create `AppManifestSet`
- `AppManifest` usage safety improved: pass by value where needed, or use `AppId` instead of a manifest.

Terminal and shell

- Moved shell commands into coreutils-module as proper apps.
- Refine stack size for terminal and shell because apps now run with their own task and stack (including in-memory apps)

Platform

- Fix for listing root dir on ESP32: wrap posix directory functions and wire in file systems.
- Moved all platform-specific file system init code to platform subprojects

Other

- Fix for screenshot capture (issue with paths)
- Fixes for coreutils app correctness
- Memory optimizations (use more PSRAM where possible) in various locations such as app-module, shell and terminal.
- `ls` and `help` commands now use terminal width and show columns of data by default
- Created fs helpers at `TactilityKernel/include/tactility/filesystem/fs.h` (partially ported from `ShellFs`)
2026-09-25 08:29:27 +02:00

80 lines
3.0 KiB
C++

#include "doctest.h"
#include <cstring>
#include <tactility/filesystem/file_system.h>
#include <tactility/paths.h>
// paths_get_data_path() resolves its root via file_system_find_by_name("data") (see
// TactilityKernel/source/paths.cpp), registered in real builds by platform-esp32/platform-posix's
// own module start(). Tests can't depend on that having found a real "data" directory relative to
// wherever the test binary happens to run, so each test here registers its own fake one instead.
namespace {
error_t fake_mount(void*) { return ERROR_NONE; }
error_t fake_unmount(void*) { return ERROR_NONE; }
// Never actually mount()ed below, so file_system_remove()'s "must be unmounted first"
// precondition needs this to stay false, not true.
bool fake_is_mounted(void*) { return false; }
error_t fake_get_path(void* data, char* out_path, size_t out_path_size) {
const char* path = static_cast<const char*>(data);
if (std::strlen(path) + 1 > out_path_size) {
return ERROR_BUFFER_OVERFLOW;
}
std::strcpy(out_path, path);
return ERROR_NONE;
}
const FileSystemApi fake_data_api = {
.mount = fake_mount,
.unmount = fake_unmount,
.is_mounted = fake_is_mounted,
.get_path = fake_get_path,
};
// Registers a fake "data" filesystem whose path is the literal string "data" (matching what this
// file's assertions used to hardcode, back when paths_get_data_path() had no filesystem to ask),
// for the lifetime of one test case.
struct FakeDataFs {
char path[16] = "data";
FileSystem* fs = file_system_add("data", &fake_data_api, path);
~FakeDataFs() { file_system_remove(fs); }
};
} // namespace
TEST_CASE("paths_get_data_path succeeds when the buffer exactly fits") {
FakeDataFs fake;
char buffer[32] = { 0 };
CHECK_EQ(paths_get_data_path(buffer, sizeof(buffer)), ERROR_NONE);
CHECK_EQ(std::strcmp(buffer, "data/tactility"), 0);
}
TEST_CASE("paths_get_data_path succeeds with a buffer sized to exactly fit the string and terminator") {
FakeDataFs fake;
char buffer[15] = { 0 }; // strlen("data/tactility") + 1
CHECK_EQ(paths_get_data_path(buffer, sizeof(buffer)), ERROR_NONE);
CHECK_EQ(std::strcmp(buffer, "data/tactility"), 0);
}
TEST_CASE("paths_get_data_path reports a buffer overflow when the buffer is one byte too small") {
FakeDataFs fake;
char buffer[14] = { 0 }; // strlen("data/tactility"), no room for the terminator
CHECK_EQ(paths_get_data_path(buffer, sizeof(buffer)), ERROR_BUFFER_OVERFLOW);
}
TEST_CASE("paths_get_data_path reports a buffer overflow for a zero-size buffer") {
FakeDataFs fake;
char buffer[1] = { 'x' };
CHECK_EQ(paths_get_data_path(buffer, 0), ERROR_BUFFER_OVERFLOW);
CHECK_EQ(buffer[0], 'x'); // untouched
}
TEST_CASE("paths_get_data_path reports ERROR_NOT_FOUND when no 'data' filesystem is registered") {
CHECK_EQ(file_system_find_by_name("data"), nullptr); // precondition: nothing left over
char buffer[32] = { 0 };
CHECK_EQ(paths_get_data_path(buffer, sizeof(buffer)), ERROR_NOT_FOUND);
}