SDK Host Unit Testingο
This guide describes the reusable host-side unit test harness for SDK code and app example libraries.
Layoutο
SDK/Tests/Host/
βββ CMakeLists.txt
βββ cmake/
β βββ FetchGoogleTest.cmake
βββ support/
β βββ KernelTestDoubles.hpp
β βββ KernelTestDoubles.cpp
βββ apps/
βββ Running/
βββ Settings_test.cpp
βββ SettingsSerializer_test.cpp
cmake/: third-party bootstrap (GoogleTest).support/: reusable fakes and fixtures (kernel facade dependencies, in-memory filesystem).apps/<AppName>/: app-specific test cases.
Build and Runο
From SDK root:
cmake -S Tests/Host -B build-host -DCMAKE_BUILD_TYPE=Debug
cmake --build build-host
ctest --test-dir build-host --output-on-failure
On Windows multi-config generators:
ctest --test-dir build-host -C Debug --output-on-failure
Expected: una-sdk-host-tests passes.
Add Tests For Another Appο
Create a folder under
Tests/Host/apps/<YourApp>/.Add one or more
*_test.cppfiles.Add test files and required app sources to
Tests/Host/CMakeLists.txtexecutable source list.Reuse support doubles from
support/KernelTestDoubles.*when the code requiresSDK::Kernelor filesystem interactions.
Serializer/Settings Test Patternο
Use SDK::TestSupport::KernelFixture:
fixture.kernelprovides a readySDK::Kernelwith stubbed dependencies.fixture.fileSystem.seedFile(path, json)seeds in-memory input files.Instantiate serializer with
SettingsSerializer(fixture.kernel, "settings.json").Verify load/save behavior without device firmware or simulator runtime.
Testing Code That Scans A Directoryο
InMemoryFileSystem enumerates what you seed, so code that walks a directory
(kernel.fs.dir(path) then readNext()) can be tested directly. Seeding a
file implies the directories along its path, with no mkdir needed:
KernelFixture fixture;
fixture.fileSystem.seedFile("Activity/morning.fit", contents);
fixture.fileSystem.seedFile("Activity/archive/old.fit", contents);
auto dir = fixture.kernel.fs.dir("Activity");
ASSERT_TRUE(dir->open());
SDK::Interface::IFileSystem::ObjectInfo item{};
while (dir->readNext(item)) {
// "morning.fit" (isDir false), then "archive" (isDir true)
}
dir->close();
Worth knowing:
Direct children only.
Activity/archive/old.fitshows up as the directoryarchivewhen listingActivity.mkdir()creates parents and succeeds if the directory already exists;IDirectory::create()does not create parents.A listing never reports the same name twice, for every name that fits
ObjectInfo::name.Opening a directory that does not exist fails, so assert on
open().
Prove the scan is liveο
A test whose expectation is βnothing was foundβ cannot tell a correct decision from a scan that never ran. Where the expected outcome is a negative, assert first that the directory really enumerates what you seeded, or pair the test with a positive control that differs in exactly the one property under test.
Divergences from a real backendο
Check these before writing a test that leans on one:
Entries are listed in the order they were created, which is not name order, so seed in the order a test needs. Overwriting a file keeps its place; a rename, or a file removed and created again, goes last, where FatFs would reuse the first free slot. An implied directory sits where its oldest remaining content does.
A listing is a snapshot taken at
open(). Files seeded mid-scan appear afterreadNext(item, /*reset=*/true).An implied directory is only as durable as its contents. It stops existing when the last thing under it is removed. Call
mkdir()if a test needs it to outlive its children.Leading and trailing slashes are not significant, and runs of
/collapse:/a.txt,a.txt,a.txt/are one object, as area//banda/b.closeGateis passed the path without leading or trailing slashes.openHandlesandflushCountsread the same for either spelling.Names are case-sensitive.
Walk.uappandWALK.UAPPare two files.A file may be created under a directory that does not exist.
mkdir()the parents first if a test needs that failure.No
./..resolution. Both are ordinary path segments.Directory rename is not modelled; it returns false.
isHiddenis derived from a leading.in the name.A name longer than
ObjectInfo::namecomes back clipped, and two names differing only past that length come back as identical entries. FatFs caps a long file name at 255 characters, which fits, so only a test can seed such a name. A 255-character name is real, and cannot be joined onto any directory withinskMaxPathLen.
The fake refuses: a mkdir(), IDirectory::create(), write-open() or
seedFile() where the other kind of object already holds the name;
a removed path as the source of rename() or copy(); a destination that is
the root, an existing directory, or under a file; a rename() onto an
existing file, which FatFs f_rename also refuses; removing a directory that
is not empty; and close() on a handle that is not open, so
EXPECT_TRUE(h->close()) is a real assertion. failRenames makes every
rename() fail.
Troubleshootingο
If
core_json.his missing, confirm include path:SDK/ThirdParty/coreJSON/source/include.If linker errors mention logger symbols, ensure
SDK/Libs/Source/UnaLogger/Logger.cppis linked.If app headers are not found, add app lib header paths to
target_include_directories(...)for the test target.