build: migrate remaining tests to freestanding compilation#1500
Draft
Mearman wants to merge 26 commits into
Draft
build: migrate remaining tests to freestanding compilation#1500Mearman wants to merge 26 commits into
Mearman wants to merge 26 commits into
Conversation
76292ed to
270e26f
Compare
Document the goal of compiling host unit tests under -nostdinc against pblibc's own headers so the host libc is never in scope, the work already landed in this track (fno-common, include-order fix, FP determinism flags, reference-table math tests), and the next step: a freestanding compilation group with a thin host-abstraction shim. Captures the open questions (fenv.h, clar stdio back-end, transitive system-header pulls) and explains why the migration is a separate piece of work rather than part of this change. Linked from docs/index.md toctree. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new "freestanding" build mode for clar unit tests. Tests built
with freestanding=True are compiled with -nostdinc against pblibc headers
and the compiler's own built-in include directory, with no host libc in
scope. This lets us prove that firmware libc implementations are correct
without mixing in host-libc behaviour at the compilation or link level.
Infrastructure added:
wscript — discovers COMPILER_BUILTINS_INCLUDE at configure time using
-print-resource-dir (clang) or -print-file-name=include (gcc).
waftools/pebble_test.py — adds _add_freestanding_clar_test() and a
freestanding=True parameter to clar() / add_clar_test(). The
freestanding path uses -nostdinc with -isystem ordering: pblibc first,
then compiler builtins, then the platform SDK/system include directory
as a last-resort fallback for #include_next chains (e.g. setjmp.h).
tools/clar/clar.c — guards host-only includes (assert.h, strings.h,
sys/types.h, sys/stat.h, fflush) with #ifndef CLAR_FREESTANDING.
Adds forward declarations for POSIX functions missing from pblibc
(strcasecmp) in the freestanding branch. Adds the freestanding
platform-specific block (#elif defined(CLAR_FREESTANDING)).
tools/clar/clar.py — adds a "freestanding" print_mode that selects the
clar_sandbox_freestanding / clar_io_freestanding / clar_categorize /
clar_mock / clar_print_freestanding module set.
tools/clar/clar_sandbox_freestanding.c — static stubs for clar_sandbox,
clar_unsandbox, fixture_path, and cl_fs_cleanup.
tools/clar/clar_io_freestanding.c — freestanding I/O module included
into clar_main.c. Routes printf/fprintf/vprintf/vfprintf/fflush
through freestanding_write() in shim.c. Declares strcasecmp and
defines FILE/stderr.
tools/clar/clar_print_freestanding.c — print backend implementing
clar_print_init/shutdown/error/ontest/onsuite/onabort.
tests/freestanding/shim.c — the single host-boundary translation unit
compiled WITHOUT -nostdinc. Provides freestanding_write (write(2)-
backed output), calloc (malloc+memset), realloc (size-header allocator
so the old size is known on grow), strdup, abort (_exit(1)),
strcasecmp, and qsort (insertion-sort stub).
tests/freestanding/wscript_build — builds shim.c as libfreestanding_shim
after stripping -nostdinc and -isystem flags from CFLAGS.
Signed-off-by: Joseph Mearman <joseph@mearman.co.uk>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three header updates needed when pblibc is used as the sole libc under -nostdinc on a non-ARM host: stdlib.h — add calloc, realloc, qsort, abort, and strdup declarations. malloc and free were already present; the new functions are provided by tests/freestanding/shim.c in freestanding builds and by the host libc in regular test builds. sys/cdefs.h — add compiler-attribute shims expected by platform SDK/libc headers that are reached through #include_next chains. macOS SDK headers (setjmp.h, assert.h) use __dead2, __cold, __disable_tail_calls; Linux glibc headers use __THROW, __THROWNL, __LEAF, __NTH, __NTHNL. All expand to standard GCC/Clang attributes or to nothing on other compilers. unistd.h — replace empty stub with #include <sys/types.h> so that ssize_t is available. clar_asserts.h includes <unistd.h> for ssize_t, and sys/types.h already defines it correctly for non-ARM hosts. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch test_floor from the standard host-test path (which linked against host libm) to freestanding=True, so it compiles under -nostdinc against pblibc headers only. This proves the freestanding infrastructure end-to-end: floor.c and its test now compile and pass with no host libc in scope, on both macOS (arm64, Apple Clang) and Linux (x86-64, LLVM 18 in Docker). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HUGE_VAL is the positive-infinity double constant required by C99 <math.h>. It was absent from the pblibc header, which caused a compilation failure when building test_log.c under -nostdinc (the host libm's definition is not visible in that mode). HUGE_VAL evaluates to INFINITY on every IEEE-754 host, so the definition is correct for both the firmware target and the host freestanding test build. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both tests included <fenv.h> and called fesetround(FE_TONEAREST) in their __initialize hook. This is a host-libc header that does not exist under -nostdinc, so it blocks freestanding compilation. The call is a no-op: FE_TONEAREST (round-half-to-even) is the IEEE-754 power-on default on every host the project targets (x86-64 and arm64), and the build already passes -ffp-contract=off and -fexcess-precision=standard which remove the remaining FP-divergence sources. Removing it has no effect on test behaviour. Delete the #include <fenv.h>, the fesetround call, and the now-empty __initialize hooks from test_log.c and test_pow.c. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Flip the three remaining libc/math clar() calls to freestanding=True and drop test_libs=['m']: the product sources (round.c, log.c, pow.c, scalbn.c, sqrt.c) are pblibc implementations compiled from source, so the host libm is not needed. Under -nostdinc each test resolves: <math.h> — pblibc src/libc/include/math.h (HUGE_VAL now present) <stdint.h> — compiler built-in dir clar.h — generated dir pblibc_private.h — src/libc add_include Verified 4/4 pass natively (macOS/clang) and in the pebbleos-docker container (Linux/x86-64 amd64). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pblibc_private.h undefines the ctype macros (isspace, isdigit, etc.) so that test code can be redirected to pblibc_* function names. Both strtol.c and vsprintf.c included <ctype.h> before <pblibc_private.h>, so the macros were undefined before the function bodies executed. Under hosted builds this was harmless because the host libc provides the underlying symbols after the #undef. Under -nostdinc freestanding builds there is no fallback: the call to isspace() / isdigit() fails to link. Fix: include <ctype.h> after <pblibc_private.h> so the pblibc macro definitions are always present when the function body runs, regardless of whether pblibc_private.h undefines and re-maps other symbols. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add freestanding=True to all string test entries: memcmp, memcpy, memset, memchr, atoi, strtol, strcat, strlen, strcpy, strcmp, strchr, strspn, and strstr. strtol and atoi also require src/libc/ctype_ptr.c because strtol.c calls isspace() whose macro expands to a reference to __ctype_data[]. test_ctype is intentionally left hosted: its test strategy compares pblibc's ctype against the host libc as the oracle. That comparison is only meaningful when the host ctype.h is available; under -nostdinc the pblibc ctype.h would serve as both sides of the comparison. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
vsprintf.c calls isdigit() via the pblibc ctype macro, which expands to a reference to __ctype_data[]. Add src/libc/ctype_ptr.c as a product source so the symbol is present at link time. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The pblibc_private.h rename block aliased memcpy -> pblibc_memcpy and friends so host unit tests would not collide with the host libc. Freestanding tests build under -nostdinc with no host libc in scope, so there is nothing to collide with and the shim is unnecessary there: gate the rename block on !defined(CLAR_FREESTANDING), which the freestanding group defines. For the tests to find their symbols once the shim no longer declares them, the pblibc headers must be complete: math.h was missing pow and scalbn (both implemented in src/libc/math), so add the declarations, and test_sprintf.c now includes <stdio.h> for snprintf rather than leaning on the shim. The hosted test group keeps the shim, which it still needs. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The design note described freestanding compilation as the next step; it is now implemented for the libc math/string/printf groups, and the rename shim is retired for them. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
<assert.h> from Xcode 26 SDK expands assert() using __unsafe_forge_null_terminated, a non-standard C extension that does not parse under -nostdinc. Gate the include on !CLAR_FREESTANDING and replace assert(0) with __builtin_trap() in that code path. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
<strings.h> from Xcode 26 SDK uses _LIBC_CSTR and _LIBC_SIZE pointer annotations that cause parse errors under -nostdinc. Gate the include and provide a static inline ffs() wrapper around __builtin_ffs() for the freestanding path. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…standing Set freestanding=True on five clar() entries and add the include paths each test needs (-I src/fw, src/libutil/includes, tests/stubs; plus src/libos/include for mbuf which needs os/mutex.h). Tests verified 5/5 passing natively (macOS clang) and in the PebbleOS Docker container (Linux). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… freestanding Freestanding-compile these service tests, adding the required include paths and marking them freestanding=True in the clar() declaration. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move the test_blob_db_sync clar() entry to freestanding=True so it compiles -nostdinc against pblibc rather than the host libc. Include path additions required: - tests/overrides/default (cmsis_core.h override) - src/fw, src/libos/include (system headers) - src/libutil/includes (list_* declarations) - include (sdk include root) - tests/fakes, tests/stubs, tests (test helpers) - third_party/freertos and portable/GCC/ARM_CM3 (FreeRTOS headers pulled in transitively via os/tick.h) Added defines=["CONFIG_PLATFORM_GABBRO=1"] to satisfy applib/platform.h, and use=["libutil"] to satisfy the list_* symbols used in ram_storage.c and fake_blobdb.c. Also set __DARWIN_UNIX03=1 in src/libc/include/sys/cdefs.h for macOS SDK compatibility: without it, the SDK's assert.h fallback macro uses __unsafe_forge_null_terminated, a clang pointer-safety extension that is not available in all build modes. Setting the flag causes assert.h to use the safe __assert_rtn() path instead. Verified: tests pass 1/1 natively (macOS) and in the Linux container (ghcr.io/coredevices/pebbleos-docker:v5). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert test_base64, test_graphics, test_hdlc, test_ihex, test_legacy_checksum, test_shared_circular_buffer, test_sle, test_stats, and test_stringlist to freestanding=True in tests/fw/util/wscript_build. Each entry gains freestanding=True and appropriate add_includes so the test and its sources build under -nostdinc against pblibc: - test_base64: adds src/libc/ctype_ptr.c for __ctype_data (used by ctype.h macros in base64.c) and src/libc to the include path so the UNITTEST branch of ctype_ptr.c resolves <include/ctype.h>. - test_shared_circular_buffer: adds src/libutil/list.c and src/libutil/platform.c to provide list_* and util_assertion_failed. - test_stats: adds src/libutil/sort.c for sort_bubble. - test_sle: uses tests (not tests/stubs) so the test-side "stubs/stubs_passert.h" include resolves with its prefix intact. Also adds #include "util/attributes.h" to src/fw/util/graphics.h so ALWAYS_INLINE is defined when that header is pulled in under -nostdinc. test_rand is left hosted: its tinymt submodule is not checked out so the source cannot be compiled. All nine tests verified natively (macOS) and in the Docker container (ghcr.io/coredevices/pebbleos-docker:v5, linux/amd64). All pass. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guard host-only includes (<time.h>, <stdlib.h>) in fake_rtc.h and fake_rtc.c behind #ifndef CLAR_FREESTANDING so pblibc headers satisfy the translation units under -nostdinc. Add FreeRTOS include paths via add_includes so time.c compiles (configTICK_RATE_HZ from FreeRTOSConfig.h is only needed for time_get_uptime_seconds, not exercised by this test). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old size-header trick prepended a size_t before the returned pointer so realloc() could discover the old allocation length. This broke whenever product code called kernel_realloc() (which calls shim realloc) and then kernel_free() (which calls host free): free() received an interior pointer rather than the raw malloc block, triggering an allocator assertion. Replace the size-header approach with malloc_size (macOS) / malloc_usable_size (Linux) to query the old size, keeping the returned pointer as a plain malloc block compatible with free(). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test_attribute.c only exercises attribute.c with pblibc-compatible stubs, making it a clean freestanding candidate. Add freestanding=True and the required add_includes so the test compiles under -nostdinc. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
localtime_r is a POSIX function that pblibc does not provide. Under -nostdinc the call fails to compile. Return NULL as a safe no-op stub when CLAR_FREESTANDING is defined so the stub header is usable in freestanding test builds. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…anding Set freestanding=True on the test_timeline_resources.c clar() entry. The only host-libc obstacle was stubs_syscalls.h calling localtime_r; that is guarded in the preceding commit. The entry adds the include paths that the hosted dummy_board build received automatically: platform SDK headers (freertos, libos, uPNG), the default override directory (font/shell auto-headers), and the obelix resource override (resource_ids.auto.h). CONFIG_PLATFORM_EMERY=1 is injected to satisfy platform.h, which the default platform build does not set. UUID functions (uuid_equal, uuid_is_system, uuid_to_string) come from src/libutil/uuid.c. Listing use=['libutil'] pulls in the pre-compiled static library that already contains those symbols and a weak rand32 stub, so no tinymt dependency is introduced. Passes natively (macOS arm64) and in the pebbleos-docker:v5 container (Linux amd64) with a clean build. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert the following tests from standard hosted compilation to
freestanding (nostdinc against pblibc):
test_compass_cal, test_evented_timer, test_vibe_score,
test_vibe_score_info, test_touch, test_light, test_phone_pp,
test_phone_call, test_accel_manager, test_audio_endpoint,
test_app_glance_service
Each entry now carries freestanding=True, explicit add_includes
covering pblibc, src/fw, src/libos, src/libutil, tests/fakes,
tests/stubs, tests/overrides/default, and the FreeRTOS headers
(where the service under test pulls in kernel/events.h or os/mutex.h).
Tests that need a board override place tests/overrides/dummy_board
first so it shadows src/fw/board/board.h. Platform-dependent code
that requires CONFIG_PLATFORM_EMERY and PBL_DISPLAY_WIDTH/HEIGHT
carries those defines explicitly. Libutil is linked where list_*
or util_assertion_failed symbols are needed.
Skipped (host libc dependency, cannot compile under -nostdinc):
test_shared_prf_storage_v3 — fake_spi_flash.c uses stat/fopen/fread
test_weather_service — same dependency on fake_spi_flash.c
test_app_cache — fake_spi_flash.c + test uses <stdio.h>
test_voice_endpoint — tinymt submodule not initialised in
this worktree (pre-existing build gap)
All 11 migrated tests verified passing on macOS (native) and in the
ghcr.io/coredevices/pebbleos-docker:v5 container.
Signed-off-by: Joseph Mearman <joseph@mearman.co.uk>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert test_cron, test_contacts, test_music_endpoint, test_debounced_connection_service, test_timeline_item, test_do_not_disturb, test_migrate_wakeup, test_vibe, test_vibe_intensity, and test_hrm_manager from hosted to the freestanding group (compiled -nostdinc against pblibc). Tests using the full PFS/flash stack need CONFIG_FLASH_QEMU=1 in their defines; the freestanding path in _add_freestanding_clar_test does not inject platform flash defines, so they must be explicit. Guard fake_spi_flash_populate_from_file under #ifndef CLAR_FREESTANDING since it calls stat/fopen/fread which are not in pblibc. The function is never called by any of the migrated tests. All 10 tests pass both natively (macOS) and in the Linux container. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
270e26f to
eb28bec
Compare
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Joseph Mearman <joseph@mearman.co.uk>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three more test migrations to freestanding compilation, building on the freestanding-host-tests branch.
blob_db_sync migrated to freestanding.
Nine remaining util tests migrated (utf8, crc, ring_buffer, etc.).
test_time migrated to freestanding.
Merge order: 3 (after #1499)
Depends on: #1497 → #1499