Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,34 @@ python scripts/generate_config.py configs/example_boot.yaml /tmp/generated/

## Test Suites

eboot includes 7 unit test suites that run natively on the host:
eboot includes 17 C unit test suites that run natively on the host, plus a
set of Python tests that check the build files and tooling statically:

| Test | Covers |
|---|---|
| `test_bootctl` | Boot control block save/load, CRC, rollback |
| `test_crypto` | SHA-256 against known vectors |
| `test_image_verify` | Image header parse bounds |
| `test_recovery` | Recovery-mode UART protocol handler |
| `test_slot_size_bounds` | `verify_slot()` rejects `image_size` larger than the slot |
| `test_fw_transport` | UART raw / XMODEM / YMODEM firmware transport framing |
| `test_device_table` | Device table create, add, validate |
| `test_runtime_svc` | Runtime variable get/set/delete |
| `test_board_config` | Pin/memory/IRQ config lookup |
| `test_multicore` | Core state management, SMP/AMP init |
| `test_board_registry` | Board register, find, activate |
| `test_slot_manager` | Production firmware slot manager |
| `test_boot_log` | Boot log subsystem |
| `test_ed25519` | Ed25519 signature verification (RFC 8032) |
| `test_keystore` | Boot keystore management |
| `test_rollback` | Anti-rollback security counter |
| `test_storage` | Unified storage bounds checking |

Every `tests/unit/test_*.c` suite must be registered in `tests/CMakeLists.txt`
with both an `add_executable()` and an `add_test()`. A suite that is not
registered is never compiled and never run, and nothing else in the build
reports it as missing, so `tests/unit/test_cmake_test_registration.py` checks
this and fails if a suite is left out.

Run all tests:

Expand Down
7 changes: 6 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ add_executable(test_recovery unit/test_recovery.c)
target_link_libraries(test_recovery PRIVATE eboot_core eboot_stage1)
add_test(NAME test_recovery COMMAND test_recovery)

# --- test_fw_transport: UART raw/XMODEM/YMODEM firmware transport ---
add_executable(test_fw_transport unit/test_fw_transport.c)
target_link_libraries(test_fw_transport PRIVATE eboot_core)
add_test(NAME test_fw_transport COMMAND test_fw_transport)

# --- test_slot_size_bounds: verify_slot() must reject image_size > slot capacity ---
add_executable(test_slot_size_bounds unit/test_slot_size_bounds.c)
target_link_libraries(test_slot_size_bounds PRIVATE eboot_core)
Expand Down Expand Up @@ -89,7 +94,7 @@ if(VALGRIND)
test_device_table test_runtime_svc test_board_config
test_multicore test_board_registry test_slot_manager
test_boot_log test_image_verify test_recovery
test_slot_size_bounds)
test_slot_size_bounds test_fw_transport)
add_test(
NAME valgrind_${TEST_NAME}
COMMAND ${VALGRIND} ${VALGRIND_OPTS} $<TARGET_FILE:${TEST_NAME}>
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/test_cmake_test_registration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Regression tests for the CTest registrations in tests/CMakeLists.txt.

Every C suite under tests/unit/ has to be named in tests/CMakeLists.txt to be
compiled and run at all. Nothing else notices when one is left out: the suite
stops building, ctest reports one fewer test, and the run still goes green.

That is how test_fw_transport.c -- the 12-case regression suite covering the
unbounded raw length prefix, the unbounded YMODEM block-0 filename scan and
the missing block-number validation -- stopped running. A merge replaced its
registration block instead of appending a new one, so the file stayed in the
tree while the target that built it disappeared.

These parse tests/CMakeLists.txt statically, so no cmake or compiler is needed.
"""

import re
from pathlib import Path

TESTS_DIR = Path(__file__).resolve().parent
CMAKELISTS = TESTS_DIR.parent / "CMakeLists.txt"

ADD_EXECUTABLE_RE = re.compile(r"add_executable\(\s*(\w+)\s+([^)]*?)\)", re.S)
ADD_TEST_RE = re.compile(r"add_test\(\s*NAME\s+(\w+)\s+COMMAND\s+(\w+)")


def _cmake_text():
return CMAKELISTS.read_text(encoding="utf-8")


def _c_suites():
"""Every C suite file under tests/unit/, by file name."""
return sorted(p.name for p in TESTS_DIR.glob("test_*.c"))


def _registered_sources():
"""Source file names named by an add_executable() in tests/CMakeLists.txt."""
sources = set()
for _target, source_list in ADD_EXECUTABLE_RE.findall(_cmake_text()):
for source in source_list.split():
sources.add(Path(source).name)
return sources


def test_every_c_suite_is_built():
suites = _c_suites()
assert suites, "expected to find test_*.c suites in tests/unit/"

registered = _registered_sources()
missing = [name for name in suites if name not in registered]

assert not missing, (
"these suites exist under tests/unit/ but no add_executable() in "
f"tests/CMakeLists.txt builds them, so they never run: {missing}"
)


def test_every_built_suite_is_registered_with_ctest():
text = _cmake_text()
targets = {target for target, _sources in ADD_EXECUTABLE_RE.findall(text)}
commands = {command for _name, command in ADD_TEST_RE.findall(text)}

unregistered = sorted(targets - commands)
assert not unregistered, (
"these test executables are built but never added to ctest, so a "
f"failure in them cannot fail the build: {unregistered}"
)


def test_fw_transport_suite_is_registered():
"""Pin the specific suite that was dropped, by name."""
assert "test_fw_transport.c" in _registered_sources(), (
"test_fw_transport.c is not built by tests/CMakeLists.txt -- the UART "
"transport regression suite would silently stop running again"
)