Test Strategy¶
What this page covers: How to build and run the test suite, code coverage, the forensic scan, test categories, environments, coverage targets, test patterns, and known gaps.
Prerequisites: Quick Start for the basic Zephyr + UBI build.
Building and running tests¶
Prerequisites¶
Tool |
Purpose |
Install |
|---|---|---|
Zephyr meta-tool (build, flash, manage manifests) |
|
|
Cross-compilation toolchain |
See Zephyr docs |
|
Flash erase and programming (hardware only) |
ST website |
|
Serial terminal for UART output (hardware only) |
|
Initialise the workspace¶
west init -l .
west update --narrow -o=--depth=1
Build for native_sim (simulator)¶
Build and run the test suite:
west build -p --build-dir build/native_sim/tests -b native_sim ./tests/
./build/native_sim/tests/zephyr/zephyr.exe
Build and run the sample application:
west build -p --build-dir build/native_sim/sample -b native_sim ./sample/
./build/native_sim/sample/zephyr/zephyr.exe
Build for STM32U5 (b_u585i_iot02a)¶
west build -p --build-dir build/stm32u5/tests -b b_u585i_iot02a ./tests/
STM32_Programmer_CLI -c port=SWD -e all
STM32_Programmer_CLI -c port=SWD -d ./build/stm32u5/tests/zephyr/zephyr.hex
picocom -b 115200 /dev/ttyACM0
Build for nRF5340 DK (nrf5340dk/nrf5340/cpuapp)¶
west build -p --build-dir build/nrf5340dk/tests -b nrf5340dk/nrf5340/cpuapp ./tests/
west flash --build-dir build/nrf5340dk/tests
picocom -b 115200 /dev/ttyACM0
Running the test modes¶
Tests run on native_sim in three modes:
# Plain tests (default)
bash scripts/run_tests.sh native_sim plain
# Secure tests
bash scripts/run_tests.sh native_sim secure
# Chunked secure tests
bash scripts/run_tests.sh native_sim chunked
Code coverage¶
Generate separate HTML coverage reports for plain and secure:
# Plain coverage
bash scripts/coverage.sh plain
# Secure coverage
bash scripts/coverage.sh secure
Reports are written to build/coverage-plain/html/ and
build/coverage-secure/html/.
Forensic scan¶
After running secure tests, scan the flash image for forbidden plaintext:
python3 scripts/scan_flash.py flash.bin
Test description check¶
Every ZTEST() must carry the three required Doxygen tags \brief,
\details, \expected in the docblock immediately preceding it. Three
additional tags are optional and reported only as warnings:
Tag |
Status |
Purpose |
|---|---|---|
|
required |
One-line summary of what the test verifies. |
|
required |
Step-by-step scenario, including the relevant configuration / fixture state. |
|
required |
Pass/fail criteria phrased as observable behaviour. |
|
optional |
The numeric / observational oracle that proves the test (e.g. |
|
optional |
Back-link to a spec section or requirement id (e.g. |
|
optional |
Non-obvious environmental setup the test relies on (Kconfig, fault hooks, geometry). |
Run the checker as part of the local pre-push loop:
# Default — fail only if a required tag is missing; report optional gaps as warnings.
python3 scripts/check_test_descriptions.py tests/src/
# Strict mode — also fail if any of the three optional tags is missing.
python3 scripts/check_test_descriptions.py tests/src/ --strict
The optional tags should be added to new and modified tests; existing tests that predate the template are not retro-fitted in a single sweep. The warning channel exists so the pattern can be adopted incrementally without blocking unrelated changes.
Code formatting¶
Apply the project’s .clang-format rules:
./scripts/format.sh
Dry-run check (mirrors the CI format-check job):
./scripts/format.sh --check
Executive Summary¶
Suite |
File |
Tests |
Focus |
Environment |
|---|---|---|---|---|
|
|
2 |
Device init, deinit, get_info |
native_sim |
|
|
6 |
Volume create, remove, resize, get_info |
native_sim |
|
|
3 |
LEB map, unmap, is_mapped |
native_sim |
|
|
5 |
LEB write, read, get_size |
native_sim |
|
|
2 |
PEB erase, dirty-to-free recycling |
native_sim |
|
|
1 |
Multi-volume cross-functional workflows |
native_sim |
|
|
97 |
NULL params, out-of-range, no-space, contract tests, corrupt headers, reserved PEB corruption, degraded recovery, LEB edge cases |
native_sim |
|
|
6 |
Max LEB capacity, alignment, sqnum persistence |
native_sim |
|
|
30 |
Corruption, dual-bank, sqnum conflicts, degraded mode, multi-volume recovery, free vs. uncommitted PEB classification |
native_sim |
|
|
6 |
Transactional safety under allocation failures, COW overwrite, invariant checks, per-kind allocator fault selector |
native_sim |
|
|
26 |
Malloc/flash-write/flash-erase fault sweeps across init, volume, and I/O paths; commit-order fault injection (VID write failure preserves old mapping) |
native_sim |
|
|
16 |
Plain-backend internal I/O API defensive guards: NULL/range rejection on |
native_sim |
|
|
33 |
Geometry validation, partition guard, format failures, header corruption at init |
native_sim |
|
|
4 |
Full utilization, init cycling, wear leveling |
native_sim (simulator only) |
|
|
4 |
Randomized churn with reboots, multi-volume operations, persistence across reinit, EC counter equality after 500 cycles |
native_sim (simulator only) |
|
|
5 |
Bad-block torture, erase retry, degraded transitions |
native_sim (simulator only) |
|
|
3 |
Basic lifecycle, persistence, stress cycles (board-portable smoke) |
native_sim |
|
|
5 |
Multi-threaded readers/writers, deinit quiescence, partition guard |
native_sim |
|
|
6 |
Erased-value helper unit tests ( |
native_sim |
|
|
5 |
Central mutation gate: write-shutdown blocks all mutators, degraded mode blocks reserved-metadata only, runtime PEB corruption recovered transparently, runtime degradation sets flag and blocks mutations, erase_peb recovers reserved bank and clears flag |
native_sim |
|
|
4 |
Persistent vol_id high-watermark: same-boot reuse prevention, cross-reboot persistence, slot re-indexing stability, overflow fail-closed |
native_sim |
Total (plain) |
269 |
Secure Backend Parity Tests¶
All secure tests require CONFIG_UBI_SECURE=y and run with a PSA-imported test root key.
Suite |
File |
Tests |
Focus |
Environment |
|---|---|---|---|---|
|
|
7 |
Crypto type sizes, secure format on blank, plain unaffected by secure types, runtime backend selection sanity, getter |
native_sim |
|
|
10 |
Format/attach, mode mismatch, freshness rejection, NULL callbacks, allowlist validation, allowlist duplicates rejected, requested write_kv downgrade rejected |
native_sim |
|
|
2 |
Secure init/deinit, info sane, reboot persistence |
native_sim |
|
|
8 |
Create, remove, resize, shrink, multi-volume persistence, vid_counter_floor reconstruction (parity with |
native_sim |
|
|
3 |
Single/multi LEB write/read, overwrite (parity with |
native_sim |
|
|
4 |
Map/unmap lifecycle, dirty PEB accounting, unmap→reboot persistence, unmap→erase→reboot (parity with |
native_sim |
|
|
4 |
Fill-unmap-erase cycle, anchor wear-leveling migration, stale-anchor rejection after reboot, reclaim continuity witness preservation (parity with |
native_sim |
|
|
1 |
Multi-volume create/write/remove/resize/map/reboot (parity with |
native_sim |
|
|
2 |
LEB data tampering smoke, reserved PEB tampering smoke |
native_sim |
|
|
3 |
Replay of authentic EC/VID/LEB record to a different physical PEB — parent-child AAD binding ( |
native_sim |
|
|
2 |
Plain UBI device on |
native_sim |
|
|
26 |
Sticky crypto read-only, event escalation, freshness sync cadence, sync failure events, reads in read-only, per-domain (LEB / DEV / VOL / EC / VID) budget SOON/NOW thresholds, budget reset on key-rotation reattach, KEY_RETIRABLE, allowlist reject, missing key, rollback mismatch, sticky read-only reinit, mixed-key rotation, forced rekey with stale free/dirty/mapped objects |
native_sim |
|
|
9 |
Interrupted data write COW preservation, interrupted VID commit, first-write-leaves-unmapped, reboot after partial write, interrupted anchor rewrite continuity, reserved generation replay rejection, interrupted reserved PEB commit, interrupted anchor creation during volume create, init-time anchor re-creation for orphaned volumes |
native_sim |
|
|
12 |
Chunked LEB geometry, geometry reject, single/multi-chunk write/read, partial reads within and across chunk boundaries, last-chunk padding, reboot persistence, overwrite, chunk tamper isolation, zero-length map fallback, 48-bit AEAD counter overflow rejection (requires |
native_sim |
|
|
6 |
Portable forensic scan: plaintext data absence, volume name absence, key material absence, post-overwrite+erase absence, negative test validates scanner on plain backend, magic-prefix sweep |
native_sim |
|
|
74 |
Secure-side parity for plain |
native_sim |
|
|
6 |
Transactional safety on secure backend under malloc/flash faults during create/write; per-kind allocator fault selector and SCRATCH-allocation faults on |
native_sim |
|
|
1 |
Central mutation gate sanity on secure backend (write-shutdown blocks mutators with |
native_sim |
|
|
3 |
Persistent vol_id high-watermark on secure backend (parity with plain |
native_sim |
|
|
16 |
Branch-coverage-driven secure-path tests: dirty PEB accounting, LEB overwrite recovery and persistence, leb_get_size, map/unmap lifecycle, volume-remove variants, flash-write fault on EC/VID, erase-all-dirty |
native_sim |
|
|
18 |
Crypto primitive fault hooks (AEAD encrypt/decrypt fail, RNG fail, HKDF fail) inserted via |
native_sim |
|
|
109 |
Defensive header-shape validation: |
native_sim |
Total (secure) |
321 |
Counts above are for the
securebuild (CONFIG_UBI_SECURE=y,CONFIG_UBI_SECURE_LEB_CHUNKED=n). Thechunkedbuild adds theubi_secure_chunkedsuite (12 ZTEST). Plain tests from the section above are also linked into the secure build (so the secure binary executes 251 + 311 = 562 ZTEST in total, 41 suites).
What native_sim Proves vs. What Hardware Proves¶
Aspect |
native_sim (simulator) |
Hardware (b_u585i_iot02a, nrf5340dk) |
|---|---|---|
Functional correctness |
Full — all 562 tests run (560 pass, 2 skip, 41 suites) in the |
Build verification only (CI cross-compiles) |
Flash timing / latency |
Not representative |
Realistic |
Power-loss behavior |
Not tested (simulator has no power-loss model) |
Not currently tested (no HIL power-loss setup) |
Bad block behavior |
Simulated via |
Real flash errors (rare on NOR) |
Wear-leveling distribution |
Verified via erase counter checks |
Observable but not systematically tested |
Memory footprint |
Approximate (host allocator) |
Precise (Cortex-M33 heap) |
Test Categories¶
1. Unit / Functional Tests¶
Core API verification organized by functional area:
Suite |
File |
Focus |
|---|---|---|
|
|
Device init, deinit, get_info |
|
|
Volume create, remove, resize, get_info |
|
|
LEB map, unmap, is_mapped |
|
|
LEB write, read, get_size |
|
|
PEB erase, dirty-to-free recycling |
|
|
Multi-volume and cross-functional workflows |
2. Error Handling Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
NULL parameters, out-of-range LEB numbers, no-space conditions, resize edge cases, overwrite semantics, no-volumes paths, contract tests (idempotent unmap, no-op map, static volume write, invalid type, zero LEBs, capacity accounting), corrupt EC/VID headers, reserved PEB corruption, degraded recovery, LEB edge cases, wrong vol_id removal |
3. Boundary Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Max LEB capacity writes, exceeds-capacity errors, exact read offsets, alignment boundaries, sub-alignment writes |
4. Recovery / Corruption Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Corrupt EC header -> bad PEB, corrupt VID CRC -> bad PEB, valid EC + erased VID + erased data -> free PEB, valid EC + erased VID + non-erased data -> dirty PEB (uncommitted write), orphan vol_id -> dirty PEB, duplicate LEB sqnum conflict resolution, erase_peb no-op when clean, dual-bank recovery during resize, degraded-mode blocking, multi-volume recovery, reinit after interrupted commit preserves old data |
5. Fault Injection Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Transactional safety: malloc failure during create (P0.2), COW overwrite preserves old data on failure (P0.7), invariant checker after create/write/remove and resize/shrink cycles. Requires |
5b. I/O Fault Injection Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Systematic malloc-failure sweeps (init with no volumes, with volume, with orphans, with duplicates, with bad VID CRC, with bad EC), scratch alloc faults, diag alloc fault, volume create/remove alloc sweeps, flash erase failure -> bad PEB transition, commit-order tests (VID write failure after data write preserves old mapping; unmapped LEB stays unmapped on VID failure). Requires |
5c. Init Error Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Geometry validation (erase block size, write block size, partition size), partition guard ( |
6. Stress Tests (simulator only)¶
Suite |
File |
Focus |
|---|---|---|
|
|
Full volume utilization, init-deinit cycling, PEB wear leveling, multi-volume concurrent usage |
|
|
Randomized churn with reboots, multi-volume mixed operations, persistence across reinit, EC counter equality after 500 cycles |
7. Torture Tests (simulator only)¶
Suite |
File |
Focus |
|---|---|---|
|
|
Bad-block torture, erase retry logic, degraded-mode transitions |
8. HIL Smoke Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Board-portable smoke: basic lifecycle, persistence across reinit, stress cycles. Runs on both native_sim and hardware targets. |
9. Concurrency Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Multi-threaded concurrent readers, reader-writer interleave, deinit-after-quiescence, double-init partition guard ( |
10. Mutation Gate Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Central mutation gate: write-shutdown flag blocks all 7 public mutators ( |
11. Volume ID Watermark Tests¶
Suite |
File |
Focus |
|---|---|---|
|
|
Persistent vol_id high-watermark: same-boot reuse prevention (create, remove, create → new id), cross-reboot persistence (create, remove, reinit, create → watermark survives), slot re-indexing stability (remove middle volume, remaining vol_ids unchanged), overflow fail-closed (vol_id_watermark = UINT32_MAX → |
Test Environment¶
Primary: native_sim¶
Platform: Zephyr
native_simboard with flash simulatorConfig:
CONFIG_FLASH_SIMULATOR=y,CONFIG_FLASH_SIMULATOR_DOUBLE_WRITES=y,CONFIG_FLASH_SIMULATOR_EXPLICIT_ERASE=yUsage: All test suites run here; stress and torture tests are simulator-only
Flash Geometry Variants¶
All functional and stress tests run against three flash geometries to exercise
different erase-block sizes, write-block alignment requirements, and partition
capacities. The geometry is selected via DTC overlay files in tests/boards/.
Variant |
Overlay file |
Erase block |
Write block |
Partition |
PEB count |
|---|---|---|---|---|---|
Default |
|
8192 B |
1 B |
128 KB |
16 |
nRF5340 |
|
4096 B |
4 B |
64 KB |
16 |
STM32U5 |
|
8192 B |
16 B |
128 KB |
16 |
Running locally with a geometry overlay:
# nRF5340 geometry
west build -p always -b native_sim ubi/tests -- \
-DDTC_OVERLAY_FILE="boards/native_sim_nrf5340.overlay"
./build/zephyr/zephyr.exe
# STM32U5 geometry
west build -p always -b native_sim ubi/tests -- \
-DDTC_OVERLAY_FILE="boards/native_sim_stm32u5.overlay"
./build/zephyr/zephyr.exe
CI: The native-tests job matrix includes geometry: [default, nrf5340, stm32u5],
producing 6 jobs (2 mem backends × 3 geometries).
Twister: testcase.yaml contains 16 geometry-specific scenarios using
extra_dtc_overlay_files (8 per geometry variant).
Secondary: b_u585i_iot02a¶
Platform: STM32U585AI hardware target (8 KB erase blocks, 128 KB UBI partition)
Usage: Cross-compilation verification (build only in CI)
Secondary: nrf5340dk/nrf5340/cpuapp¶
Platform: Nordic nRF5340 DK, application core (4 KB erase blocks, 64 KB UBI partition)
Usage: Cross-compilation verification (build only in CI)
Coverage¶
Targets¶
Metric |
Source of truth |
Long-term target (post-1.0) |
|---|---|---|
Line coverage |
Codecov badge in the project README |
>= 80% |
Branch coverage |
Codecov badge in the project README |
>= 70% |
Live coverage is reported by Codecov on every push to main; the
badge in the README is the authoritative current number. The targets
above are post-1.0 goals — branch coverage in particular is tracked
as a roadmap item.
Tooling¶
Instrumentation: Zephyr
CONFIG_COVERAGE=y+CONFIG_COVERAGE_GCOV=yCollection:
lcov --capturefiltered tolib/src/*Report:
genhtmlwith--branch-coverage
Running Locally¶
bash scripts/coverage.sh
HTML report is generated at build/coverage/html/index.html.
CI¶
The coverage job in .github/workflows/ci.yml builds with coverage, runs tests,
collects lcov data, and uploads the HTML report as a build artifact.
Test Patterns¶
Fixture Pattern¶
Each test suite uses the standard ZTest fixture. Shared helpers in tests/src/ eliminate boilerplate:
ubi_test_fixture.h—ubi_test_setup_mtd(),ubi_test_erase_partition(),ubi_test_init_device(),ubi_test_reinit_device()ubi_test_memory.h—ubi_test_memory_snapshot(),ubi_test_memory_check_no_leak()(requiresCONFIG_SYS_HEAP_RUNTIME_STATS)ubi_test_raw_flash.h—ubi_test_raw_write_ec_hdr(),ubi_test_raw_write_vid_hdr()for corruption injection
#include "ubi_test_fixture.h"
static struct ubi_flash_desc flash = { 0 };
static void *ztest_suite_setup(void) {
ubi_test_setup_mtd(&flash);
return NULL;
}
static void ztest_testcase_before(void *ctx) {
(void)ctx;
ubi_test_erase_partition();
}
ZTEST_SUITE(suite_name, NULL, ztest_suite_setup, ztest_testcase_before,
ztest_testcase_teardown, NULL);
Every test starts with a fully erased flash partition, ensuring isolation.
Corruption Testing Pattern¶
Recovery tests use raw flash_area_write() to inject corrupt headers:
Initialize UBI normally (creates device/volume headers on reserved PEBs)
Deinit
Directly write corrupt EC or VID headers on data PEBs
Re-init and verify PEB classification (
bad_peb_count,dirty_peb_count, etc.)
Compile Flags¶
Tests build with strict warnings to catch issues at compile time:
-Werror -Wextra -Wshadow -Wdouble-promotion -Wformat=2
-Wnull-dereference -Wunused -Wno-unused-parameter
Known Gaps¶
Gap |
Impact |
Mitigation |
|---|---|---|
Partial flash write failure coverage |
Flash write fault injection covers |
|
HIL smoke only (no CI hardware) |
|
Manual hardware testing during development; HIL CI planned |
Non-0xFF erased value end-to-end |
Erased-value helpers are unit-tested for 0x00, but the flash simulator only supports 0xFF |
Helpers are trivial; integration tests on 0xFF backend cover the full scan path |
Secure tamper detection precision |
Tamper tests verify no-crash and graceful handling but do not assert specific AUTH_FAILURE events (depends on PEB layout) |
Forensic scan suite ( |
Secure data size limit |
Secure LEB read requires scratch memory for decryption ( |
Use data within scratch budget in tests; increase |
How to Add a New Test¶
Choose the appropriate test file based on test category (see executive summary table)
Add a
ZTEST(suite_name, test_name)functionFollow the fixture pattern: init -> operate -> assert -> deinit
If testing a new file, add it to
tests/CMakeLists.txtBuild and run:
bash scripts/run_tests.sh
ZTEST traceability for Secure UBI¶
The tables below map each Secure UBI lifecycle step, recovery
scenario, and release-checklist item to the specific ZTESTs that
exercise it. All tests live under tests/src/secure/ and run as part
of the secure ZTEST suite (bash scripts/run_tests.sh native_sim secure). Items marked review-only are design / code-review
constraints with no direct runtime test (intentional; documented for
traceability).
The corresponding normative behaviour is specified in Secure On-Flash Format Specification chapters 19 (Secure volume lifecycle), 20 (Secure recovery scenarios), and 21 (Runtime policy).
Lifecycle step → ZTEST coverage¶
Lifecycle step |
Implementation |
ZTEST(s) |
|---|---|---|
Create |
|
|
Resize (grow) |
|
|
Shrink |
|
|
Remove |
|
|
Unmap |
|
|
Erase and reclaim |
|
|
Reboot recovery |
|
|
Recovery scenario → ZTEST coverage¶
Scenario |
ZTEST(s) |
|---|---|
|
|
|
|
|
|
|
|
|
|
Anchor migration during erase |
|
Per-volume LEB floor continuity at runtime |
|
Stale anchor after reboot |
|
Emergency reserve refill |
|
Dual-bank reserved metadata recovery |
|
Release checklist → ZTEST coverage¶
Critical format constraints¶
Checklist bullet |
ZTEST(s) |
|---|---|
Reserved-generation fit against geometry |
|
Single-tag CCM payload limit / require chunked or reject SECURE |
|
Zero-length LEB encoding fixed |
|
Single-tag tail-padding behaviour fixed |
|
Reject cross-mode attach; mixed-mode migration out of scope |
|
Important implementation notes¶
Checklist bullet |
ZTEST(s) |
|---|---|
Operational retirement levels (§13.8) |
|
Authenticated parent |
|
Zeroize plaintext scratch and software-derived child-key buffers |
review-only (audited in |
|
|
Authenticated |
|
|
review-only (locked by |
Validation expected before upstream¶
Checklist bullet |
ZTEST(s) |
|---|---|
Power-cut testing: |
|
PSA-only failure paths (RNG fail, missing key material) |
|
Zero-length, mixed-key, chunked, alignment, hidden-anchor recovery |
|
|
|
|
|
|
|
Refcount-driven |
|