Files
trail-mate/apps/linux_lvgl/tests/linux_config_persistence_test.cpp
T
Trail Mate Dev b10cbf316b fix: stabilize CM0 1seg reception and repair CI regressions
Keep IQ capture isolated from decoder startup and rendering, bound transport
backpressure, validate DMA batches before publication, and expose playback
telemetry. Preserve radio restoration and add capture, transport, media and
SDL regression coverage. Document user-accepted CM0 and Sony reception and
the limits of the one-shot kernel experiment.

Repair stale preview TileContext initialization, missing simulator link
sources and Cardputer SDL dependencies. Align menu and radio-log contracts
with current implementations, keep AutoInterface port admission covered,
refresh source inventories, and apply the CI clang-format 14 rules.

Validation: 45 Agenda tests, 30 1seg tests, 8 LR2021 IQ tests, 17 native config
interop tests, ESP stack hygiene, platform/root guards, source inventories,
clang-format 14, git diff --check, and staged GitNexus detect_changes.
2026-11-01 09:39:20 +08:00

97 lines
4.8 KiB
C++

#include "app/linux_app_services.h"
#include "platform/ui/settings_store.h"
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <thread>
#include <unistd.h>
#include <vector>
int main()
{
const auto root = std::filesystem::temp_directory_path() / ("trailmate-config-" + std::to_string(getpid()));
const auto sd = root / "sd";
std::filesystem::create_directories(sd / "trailmate/reticulum");
setenv("TRAIL_MATE_SETTINGS_ROOT", (root / "settings").c_str(), 1);
setenv("TRAIL_MATE_CACHE_ROOT", (root / "cache").c_str(), 1);
setenv("TRAIL_MATE_SD_ROOT", sd.c_str(), 1);
setenv("TRAIL_MATE_GPS_DEVICE", "/dev/trailmate-test-missing", 1);
std::ofstream(sd / "trailmate/reticulum/groups.tsv") << "version\t1\n1\tLegacy group\t0123456789abcdef0123456789abcdef\n";
trailmate::linux_app::LinuxAppServicesOptions options;
options.runtime_mode = platform::linux_runtime::LinuxRuntimeMode::DeviceLocal;
{
trailmate::linux_app::LinuxAppServices services(options);
assert(services.initialize());
assert(std::string(services.getConfig().reticulumConfig().reticulum_groups[0].name) == "Legacy group");
auto edit = services.beginConfigEdit();
assert(edit);
edit.config().mesh_protocol = chat::MeshProtocol::Reticulum;
auto& group = edit.config().reticulumConfig().reticulum_groups[0];
std::strcpy(group.name, "CM0 Ab!9");
edit.commit(app::AppConfigChangeSet::mesh());
services.applyMeshConfig();
assert(std::string(services.getConfig().reticulumConfig().reticulum_groups[0].name) == "CM0 Ab!9");
// Match the explicit update loop used by SDL and Cardputer, without tick().
bool persisted = false;
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
while (!persisted && std::chrono::steady_clock::now() < deadline)
{
services.updateCoreServices();
std::vector<uint8_t> blob;
const std::string expected = "CM0 Ab!9";
persisted = platform::ui::settings_store::get_blob("linux_app_facade", "app_config_v1", blob) &&
std::search(blob.begin(), blob.end(), expected.begin(), expected.end()) != blob.end();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
assert(persisted); // Must reach durable storage before shutdown.
services.shutdown();
}
{
trailmate::linux_app::LinuxAppServices reopened(options);
assert(reopened.initialize());
const auto& group = reopened.getConfig().reticulumConfig().reticulum_groups[0];
assert(group.enabled && std::string(group.name) == "CM0 Ab!9");
// The unchanged old file must never overwrite a modern saved config.
reopened.applyMeshConfig();
assert(std::string(reopened.getConfig().reticulumConfig().reticulum_groups[0].name) == "CM0 Ab!9");
auto edit = reopened.beginConfigEdit();
edit.config().reticulumConfig().reticulum_groups[0] = {};
edit.commit(app::AppConfigChangeSet::mesh());
reopened.applyMeshConfig();
reopened.shutdown();
}
{
trailmate::linux_app::LinuxAppServices empty(options);
assert(empty.initialize());
assert(!chat::hasReticulumDestinationIdentity(empty.getConfig().reticulumConfig().reticulum_groups[0].identity));
auto edit = empty.beginConfigEdit();
edit.config().mesh_protocol = chat::MeshProtocol::Meshtastic;
edit.commit(app::AppConfigChangeSet::mesh());
empty.applyMeshConfig();
empty.shutdown(); // A modern empty list must not resurrect the old file.
}
// Recreate a version-2 snapshot with an empty list, as produced by the old
// Linux owner. Its legacy file must be imported once, even on an inactive protocol.
std::vector<uint8_t> old_blob;
assert(platform::ui::settings_store::get_blob("linux_app_facade", "app_config_v1", old_blob));
assert(old_blob.size() > 8);
const uint32_t old_version = 2;
std::memcpy(old_blob.data() + 4, &old_version, sizeof(old_version));
assert(platform::ui::settings_store::put_blob("linux_app_facade", "app_config_v1", old_blob.data(), old_blob.size()));
{
trailmate::linux_app::LinuxAppServices migrated(options);
assert(migrated.initialize());
assert(std::string(migrated.getConfig().reticulumConfig().reticulum_groups[0].name) == "Legacy group");
migrated.shutdown();
}
std::vector<uint8_t> upgraded;
assert(platform::ui::settings_store::get_blob("linux_app_facade", "app_config_v1", upgraded));
uint32_t version = 0;
std::memcpy(&version, upgraded.data() + 4, sizeof(version));
assert(version == 3);
std::filesystem::remove_all(root);
}