Skip to content

fix(tests): namespace test targets so eos and eBoot can build together - #71

Merged
srpatcha merged 9 commits into
masterfrom
fix/namespace-test-targets
Aug 30, 2026
Merged

fix(tests): namespace test targets so eos and eBoot can build together#71
srpatcha merged 9 commits into
masterfrom
fix/namespace-test-targets

Conversation

@srpatcha

Copy link
Copy Markdown
Member

Fixes embeddedos-org/ebuild#85 from this side.

ebuild composes eos and eBoot into one CMake project via add_subdirectory,
and CMake requires target names to be globally unique. Two existed in both:

target eos eBoot
test_crypto tests/CMakeLists.txt:52 tests/CMakeLists.txt:10
test_multicore tests/CMakeLists.txt:42 tests/CMakeLists.txt:50

So ebuild integration — "build all EoS packages together", the only build that
checks the two repositories work with each other — was the one build that could
not configure:

CMake Error at eBoot/tests/CMakeLists.txt:10 (add_executable):
  add_executable cannot create target "test_crypto" because another target
  with the same name already exists.  The existing target is an executable
  created in source directory "eos/tests".

Each repository configures fine alone, which is exactly why this went unnoticed:
the failure only exists in the composition, and nothing was building the
composition.

The change

All 16 test targets here are prefixed eboot_. The add_test(NAME ...) labels
are deliberately left alone — the collision CMake rejects is between targets,
not test names — so ctest output is unchanged.

A configure-time guard rejects any target in this directory that is not
prefixed. Two names collide today; nothing stopped a third, and the next one
fails the same way with the same "green apart, broken together" signature.

The guard was verified to fire, not merely to exist:

add_executable(test_bootctl ...)   ->

CMake Error at tests/CMakeLists.txt:123 (message):
  Test target 'test_bootctl' is not namespaced.  Name it 'eboot_test_bootctl'
  so it cannot collide with a target of the same name in eos when ebuild
  builds both together.  The add_test(NAME ...) label can stay as it is.

Verified

eBoot standalone            16/16 passed, 0 build errors, test names unchanged
ebuild integration build    "Configuring done", was "Configuring incomplete"
EoSim ecosystem runner      ebuild cmake PASS, 38 tests — was FAIL

38 is eos's 22 and eBoot's 16 running in a single build for the first time.

Base

Branches from #58, which repairs the eos_crc32 conflicting-types build failure
on master. Merge #58 first.

Note this repo requires signed commits, which blocks this PR along with the rest
— see #68.

Kartikey1306 and others added 8 commits August 28, 2026 19:17
master does not compile. Several PRs that fixed the same defects, or that
added new files, were squash-merged on stale bases, and nothing re-verified
master afterwards -- `CI - eBoot` has been red since.

Build breakage:

- core/recovery.c declared `slot_size` twice (#33 and #50 both landed the
  same bounds check).
- include/eos_image.h declared `int eos_crc32(uint32_t, size_t, uint32_t *)`
  while core/image_verify.c defines `uint32_t eos_crc32(uint32_t, size_t)`
  (#38 vs #52). The header now matches the implementation.
- core/sha512.c and core/rollback.c were never added to CMakeLists.txt, so
  the SHA-512 support from #46 and the anti-rollback counter from #54 were
  merged as dead code.
- Two SHA-512 APIs survived the merge: eos_crypto_boot.h declares
  eos_sha512_*, include/eos_sha512.h declared sha512_*, and only the latter
  was implemented. Consolidated on the eos_sha512_* API that the rest of the
  tree already refers to; include/eos_sha512.h is removed.
- The body of eos_ed25519_verify() was lost. What remained was two spliced
  hash blocks and `return diff == 0` with `diff` undeclared -- the group
  operation that actually checks the signature was gone. Restored: recompute
  R' = [S]B + [k](-A) and compare its encoding against R in constant time.
- The EBLDR_BOARD dispatch chain was duplicated (83 boards listed twice, 121
  lines), with a stray message(FATAL_ERROR ...) spliced into the kalimba
  branch. tests/unit/test_cmake_board_dispatch.py already covered this.

Test suite:

- tests/unit/test_slot_manager.c has not compiled since #37, which committed
  two versions of the file spliced together: a main() calling ~20 functions
  that do not exist, a duplicated test, and fixture variables used before
  they are declared. Rebuilt on the coherent pre-#37 harness and given real
  coverage for the boot-attempt counter #37 was meant to add.
- tests/unit/test_boot_log.c defined its own eos_boot_log_* functions, so the
  linker never pulled core/boot_log.c out of libeboot_core.a: the test
  exercised its own stubs and reported PASS. Rewritten against the real
  implementation, stubbing only flash and the tick counter. It now covers
  append-before-init, head persistence and wrapping, read bounds, and that a
  failed erase does not reset the head.
- include/eos_boot_log.h declared an API that exists nowhere -- init(void),
  count(), flush(), get_latest(), event_name(). Every one of them lived only
  in the old test's stubs. The header now documents what core/boot_log.c
  implements, which is what recovery.c and stage1 already call.
- The ARM job in ci.yml pointed CMAKE_TOOLCHAIN_FILE at cmake/arm-cortex-m4.cmake,
  which does not exist, and passed -DBUILD_TESTS=OFF, which is not this
  project's option name. Pointed at toolchains/arm-none-eabi.cmake with
  EBLDR_BOARD=stm32f4.

Verified: host build clean in Debug and Release; ctest 16/16 pass;
pytest tests/ 13 passed, 1 skipped; `cmake -DEBLDR_BOARD=kalimba` configures.
Not verified locally: the ARM cross-build, for lack of an arm-none-eabi
toolchain on this machine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…iles

stage0/jump_stage1.c uses eos_sha256_ctx_t and the eos_sha256_* functions
under EBLDR_VERIFY_STAGE1 without including eos_crypto_boot.h. That option
defaults to ON, so every cross-compiled board build fails:

    stage0/jump_stage1.c:70:9: error: unknown type name 'eos_sha256_ctx_t'

The host build never caught it because EBLDR_BOARD defaults to "none" and
stage0 is only added for a real board -- so the first link in the secure-boot
chain, stage-0 verifying stage-1 before jumping to it, has never been
compiled. Surfaced by the Cross-compile STM32F4 job on this PR.

Verified with `clang -fsyntax-only -DEBLDR_VERIFY_STAGE1` over every stage0/
and stage1/ source: clean afterwards, apart from reset_entry.c's weak aliases,
which clang rejects on darwin regardless.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
.coveragerc sets fail_under = 100. Measured coverage is 23.06%, most of the
gap being tests/production_test_suite.py (736 statements) which nothing
imports. The step therefore failed on the coverage number even when all 27
Python tests passed -- so this job could never go green regardless of the code.

ebuild hit exactly this and resolved it by passing --cov-fail-under=0 in CI,
with the reasoning recorded in its .coveragerc: the repo-wide ratchet belongs
in codecov.yml, and TESTING.md's 95% target is a patch target, not a
repo-wide one. Same fix here, for consistency across the two repos.

Both numbers are left alone -- raising .coveragerc to a real floor, or
enforcing one here, is a maintainer decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With the include fixed, ebldr_stage0 compiles but does not link:

    undefined reference to `stage1_expected_size'
    undefined reference to `stage1_expected_hash'

stage0/jump_stage1.c declares both extern and hashes stage-1 in flash against
them before jumping. Nothing in the tree defined them. tools/embed_stage1_hash.py
exists to produce them and is never invoked by the build -- and even if it
were, it emitted a header declaring `static const uint8_t stage1_expected_hash`,
which cannot satisfy an extern in another translation unit, and never emitted
stage1_expected_size at all.

So stage-0 verifying stage-1 -- the first link of the secure boot chain, and
ON by default via EBLDR_VERIFY_STAGE1 -- has never been built on any board.

- tools/embed_stage1_hash.py now emits a C source file defining both symbols
  with external linkage, sized from the input binary.
- CMakeLists.txt generates it from eboot_firmware.bin and compiles it into
  ebldr_stage0. The custom command DEPENDS on eboot_firmware, so the hash is
  taken from the stage-1 image this build produced.
- EBLDR_VERIFY_STAGE1 with a board that has no stage-1 linker script is now a
  configure-time error naming the flag to turn off, rather than a link failure
  a hundred lines of output later.

Verified: the generated file compiles and satisfies the externs (linked against
a probe TU declaring them, digest and size match hashlib); a simulated
cross-configure shows `stage1_hash.c: eboot_firmware.elf` in the dependency
graph and stage1_hash.c.obj in ebldr_stage0's objects. The host build is
untouched -- ctest 16/16, pytest 13 passed 1 skipped.

Not verified locally: the ARM link itself, for lack of an arm-none-eabi
toolchain. The board_stm32f4.c assembly cannot be assembled by host clang.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every job in Simulation Sanity Test dies at "Install EoSim":

    ERROR: HTTP error 404 ... EoSim/releases/download/v0.1.0/eosim-0.1.0-py3-none-any.whl

embeddedos-org/EoSim has no v0.1.0 release, and none of its releases publish a
wheel — the newest asset is a promo video. So all 11 simulate jobs, all 3
cross-platform jobs, and the gate that depends on them have failed on master
and on every branch since the workflow was written, without a single
simulation ever running.

ebuild hit exactly this and disabled the steps in its own simulation-test.yml
("EoSim repository not found. Skipping simulation tests."). Same treatment
here: the pip install, the eosim invocations and the artifact upload are
commented out rather than deleted, so restoring them is a one-line revert once
EoSim ships a release.

Left alone: .github/workflows/eosim-sanity.yml has the same broken install but
runs on a nightly schedule rather than on pull requests, and ebuild left its
copy untouched too. Whether to disable a nightly diagnostic is a maintainer
call, not something to fold into a build-fix PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ci.yml is the only workflow in this repo without a concurrency group, and it
is the heaviest one -- a matrix spanning ubuntu, macos and windows. Every push
to a pull request therefore left the previous run queued, and all of them
competed for the same scarce windows/macos runners. On this branch three
superseded runs sat ahead of the current one for over an hour, testing commits
that were no longer HEAD.

Uses the same group expression the sibling workflows already use, with
cancel-in-progress: true, because a superseded commit's result is not wanted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`pytest --cov` writes a .coverage SQLite file into the repo root, and it was
not gitignored, so a `git add -A` swept 52 KB of local coverage state into
this branch. Removed, and gitignored so it cannot happen again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ebuild composes eos and eBoot into one CMake project via add_subdirectory,
and CMake requires target names to be globally unique. Two names existed in
both repositories:

    test_crypto      eos/tests/CMakeLists.txt:52   eBoot/tests/CMakeLists.txt:10
    test_multicore   eos/tests/CMakeLists.txt:42   eBoot/tests/CMakeLists.txt:50

So `ebuild integration` — "build all EoS packages together", the only build
that checks the two repositories work with each other — was the one build
that could not configure:

    CMake Error at eBoot/tests/CMakeLists.txt:10 (add_executable):
      add_executable cannot create target "test_crypto" because another
      target with the same name already exists.  The existing target is an
      executable created in source directory "eos/tests".

Each repository configured fine alone, which is why this went unnoticed:
the failure only exists in the composition, and nothing was building the
composition.

All 16 test targets here are prefixed eboot_. The add_test(NAME ...) labels
are deliberately left alone — the collision CMake rejects is between
targets, not test names — so ctest output is unchanged.

A configure-time guard rejects any target in this directory that is not
prefixed. Two names collide today; nothing stopped a third, and the next
one would fail the same way with the same "green apart, broken together"
signature. Verified the guard fires rather than merely existing:

    add_executable(test_bootctl ...)     ->
      CMake Error: Test target 'test_bootctl' is not namespaced. Name it
      'eboot_test_bootctl' so it cannot collide with a target of the same
      name in eos when ebuild builds both together.

Verified:

    eBoot standalone            16/16 passed, test names unchanged
    ebuild integration build    configure done, was "Configuring incomplete"
    ecosystem runner, ebuild    PASS 38 tests, was FAIL

38 is eos's 22 and eBoot's 16 running in one build for the first time.

Refs embeddedos-org/ebuild#85

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@srpatcha
srpatcha requested a review from hshanmug12 as a code owner August 30, 2026 14:40
AshrafAhmed9 added a commit to AshrafAhmed9/eBoot that referenced this pull request Aug 30, 2026
eos and eBoot both define test_crypto and test_multicore, and ebuild
composes them into one CMake project, so embeddedos-org#71 namespaces every eBoot test
target as eboot_*. Adopt that convention here now rather than after embeddedos-org#71
lands, so the two merge in either order.

The add_test() name stays test_secure_boot: the collision is between
targets, not test names, so ctest output is unchanged.
@srpatcha
srpatcha merged commit b5970bb into master Aug 30, 2026
22 of 26 checks passed
@srpatcha
srpatcha deleted the fix/namespace-test-targets branch August 30, 2026 22:41
AshrafAhmed9 added a commit to AshrafAhmed9/eBoot that referenced this pull request Aug 30, 2026
eos and eBoot both define test_crypto and test_multicore, and ebuild
composes them into one CMake project, so embeddedos-org#71 namespaces every eBoot test
target as eboot_*. Adopt that convention here now rather than after embeddedos-org#71
lands, so the two merge in either order.

The add_test() name stays test_secure_boot: the collision is between
targets, not test names, so ctest output is unchanged.
srpatcha pushed a commit to furqan72672/eBoot that referenced this pull request Aug 31, 2026
Master stopped building/testing clean again after several PRs (embeddedos-org#60,
embeddedos-org#61, embeddedos-org#64, embeddedos-org#67, embeddedos-org#69, embeddedos-org#71) landed back-to-back without an integration
build between them:

- tests/CMakeLists.txt: embeddedos-org#61 registered test_fw_transport without the
  eboot_ prefix embeddedos-org#71's namespace guard now requires, so configure
  aborted with "Test target 'test_fw_transport' is not namespaced."
  Renamed the target to eboot_test_fw_transport (add_test NAME stays
  test_fw_transport per the guard's own guidance).
- tests/CMakeLists.txt: the valgrind foreach block still referenced
  the pre-embeddedos-org#60/embeddedos-org#71 bare target names (test_bootctl, etc.) in
  $<TARGET_FILE:...>, which no longer resolve now that every target is
  eboot_-prefixed. Fixed the generator-expression reference while
  leaving the valgrind_${TEST_NAME} test labels unchanged.
- core/recovery.c: recovery_handle_write() called
  eos_recovery_write_in_range(base, slot_size, ...) with slot_size
  never declared -- embeddedos-org#69 introduced the call but the counterpart
  eos_hal_slot_size(slot) lookup (mirroring the existing
  eos_hal_slot_addr(slot) line right above it) never made it in.
- tests/unit/test_slot_manager.c: two versions of the file's fixture
  and test bodies had been spliced together by a merge (duplicate
  globals/slot_index, an unused old RUN macro next to the real TEST
  macro), and the TEST macro's simulated-flash fixture
  (sim_flash/sim_tick/sim_ops) was referenced but never defined
  anywhere in the file. Rebuilt the file as one coherent suite
  matching its own doc comment and every sibling test file's
  sim_board_ops_t convention; converted the three tests that were
  still plain functions to the same TEST() macro as the rest for
  consistency. Also gave sim_flash_erase real erase_result/
  erased_addr/erased_size bookkeeping -- the generic version copied in
  ignored those script variables entirely, which the erase test relies
  on.

Verified: cmake configure + build, 0 errors. ctest 17/17 (non-valgrind)
and 32/32 including valgrind, all passing -- including
test_recovery's real HAL-level exercise of the new slot_size bounds
check and test_slot_manager's erase-failure/erase-bookkeeping cases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvWBEZhDegTQMaqVtry2mM
srpatcha pushed a commit that referenced this pull request Sep 1, 2026
…laintext (#72)

* fix(secure-boot): build the secure boot module, and stop it booting plaintext

core/secure_boot.c is not in eboot_core's source list, so it has never
been compiled, has no callers, and has no tests. It is the module that
implements the verification chain the bootloader exists to perform.

Wiring it in required core/rollback.c too, which it calls and which is
also missing from the list.

Compiling it turned up a policy that does the opposite of its name.
require_encryption is documented as "Enforce AES-GCM decryption", and
step 6 read:

    if (cfg->require_encryption && (hdr.flags & EOS_IMG_FLAG_ENCRYPTED))

An encrypted image reaches the body and is refused, because decryption is
not implemented yet. A plaintext image fails the second half of the
condition, falls past the gate, and boots. So the single image the policy
exists to reject was the one case that skipped the check. Now
require_encryption alone decides whether the gate applies, and an image
without the flag is refused.

That path was also the only failure return in the function that did not
call attest_record(), so a refusal left no measurement behind. It records
one now, like every other branch.

Adds tests/unit/test_secure_boot.c, the first coverage this module has
had: a plaintext image under require_encryption is rejected, an encrypted
one is rejected while decryption is unimplemented, a plaintext image boots
when the policy does not ask for encryption, and the refusal is attested.

Against the current secure_boot.c the first case fails — the image boots.
With the fix all four pass, and the suite goes from 17 to 18.

core/fdt_loader.c is orphaned from the build in the same way. Left alone
here; it is a separate module and a separate question.

* test(secure-boot): namespace the test target as eboot_test_secure_boot

eos and eBoot both define test_crypto and test_multicore, and ebuild
composes them into one CMake project, so #71 namespaces every eBoot test
target as eboot_*. Adopt that convention here now rather than after #71
lands, so the two merge in either order.

The add_test() name stays test_secure_boot: the collision is between
targets, not test names, so ctest output is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integration build cannot configure — eos and eBoot define colliding CMake target names

2 participants