This is the main entry point for test documentation in the Cardano Ledger app.
Python-based test tooling in this repository uses a shared virtual environment at
tests/venv.
Create it once from the repository root:
python3 -m venv tests/venv
source tests/venv/bin/activate
pip install -r tests/requirements.txtUse this environment for:
tests/standalone/ragger teststests/application_client/tests/unit/generators/- other Python helpers under
tests/
On Ubuntu, install the following required dependencies for building and running unit tests:
sudo apt update
sudo apt install cmake libcmocka-dev lcovFor standalone functional tests (ragger), you also need:
sudo apt install qemu-user-staticThe project uses Ruff for Python linting and formatting. Ruff is included in the shared tests/requirements.txt.
Always run Ruff before committing Python changes:
# From repository root
source tests/venv/bin/activate
# Format files
ruff format . --exclude tests/venv
# Run linter and apply automatic fixes
ruff check --fix . --exclude tests/venvRun the Python lint and type checks used in CI from the repository root:
source tests/venv/bin/activate
make -C tests python-checksNotes:
- The project follows absolute import patterns (
from tests.pkg...). - Most
E402(imports not at top) errors have been resolved by moving path bootstrapping to module execution (python3 -m ...). Avoid introducing newsys.pathhacks.
Activation examples:
# From repository root
source tests/venv/bin/activate
# From tests/unit
source ../venv/bin/activateNotes:
- Do not rely on the system
python3for these workflows; missing packages and import-path mismatches are common outside the shared venv.
- Unit tests:
tests/unit/README.md - Standalone functional tests (ragger):
tests/standalone/README.md - Swap/library-mode tests:
tests/swap/README.md - Fuzzing:
tests/fuzzing/FUZZING.md
- When C code is modified, run unit tests.
- After unit tests pass, run a fuzzing build as a compile-health gate.
- Regenerate unit-test fixtures when
tests/standalone/input_files/ortests/application_client/changes via:PYTHONPATH=. tests/venv/bin/python -m tests.unit.generators.generate_unit_tests_from_ragger allfrom the repository root. - When
clang-format-14is available, generated unit-test C/H files are normalized by the generator itself, andmake -C tests generated-fixture-driftverifies that generated output still matchesclang-format-14. - Happy-path unit fixtures must have explicit expected output values. Missing unit expected results are a hard error: generators must report them, and unit tests must not silently skip response verification for those fixtures.
- For the concrete edit/regenerate/build/run sequence, see
tests/unit/README.md->Fixture Workflow. - Use the shared venv above when running generator scripts or other Python-based test tooling.
- Run ragger and swap tests only when explicitly requested.
- Convenience wrappers from the repository root:
make -C tests python-checksruns Ruff format check, pylint, and mypy.make -C tests clang-format-src-checkchecksclang-format-14onsrc/**/*.candsrc/**/*.h.make -C tests clang-format-generated-checkchecksclang-format-14on generated unit-test C/H files.make -C tests tests-unitregenerates unit fixtures, checks drift and generated C formatting, builds, and runs unit tests.make -C tests unit-coveragebuilds unit tests, runs them, and generates an HTML coverage report attests/unit/coverage/index.html.make -C tests fuzzingbuilds fuzzing harnesses and runs each for 1 second by default (override withFUZZ_SECONDS=<n>, requiresBOLOS_SDK).
Use LCOV_EXCL_* only for invariant-only dead paths that are intentionally
unreachable after existing validation, not for behavior branches.
Good candidates:
defaultarms on validated enum domains.- Impossible request/state-machine transitions.
- Defensive invariant checks used to catch internal contract violations.
Avoid excluding:
- parser/validation failures from malformed APDU, buffer, or Tx input,
- user-visible policy branches that can produce a deny/ reject outcome,
- missing-error paths that should be validated by unit fixtures.
Recommended style:
- Prefer
// LCOV_EXCL_LINEfor single-line invariant assertions. - For
default:switch cases, use// LCOV_EXCL_START/// LCOV_EXCL_STOParound the entire block including thedefault:label, becauselcovwill otherwise flag thedefault:keyword itself as uncovered. - Keep the check as
LEDGER_ASSERT(false, "...")(orASSERT(false)for low-level checks). - If a branch is reachable with realistic malformed-but-not-impossible input, add a test instead of excluding it.
Example:
switch (status) {
case VALID:
...
break;
// LCOV_EXCL_START
default:
LEDGER_ASSERT(false, "Unknown status: %u", status);
return;
// LCOV_EXCL_STOP
}Use these terms consistently across ragger tests, generator scripts, unit-test fixtures, and unit-test runners:
- Deny: app-level refusal (non-
9000) enforced by app policy/validation.- This may happen before review UI, during review flow processing, or later in witness phase after review approval.
- Example: invalid input, forbidden path/signing mode combination, malformed APDU payload, witness-path policy failure.
- Naming: use
denyin test names, fixture sets, generated fixture labels, and runner names.
- Reject: explicit user decision in UI to refuse an otherwise valid flow.
- Example: user taps reject on NBGL confirmation, or unit tests simulate that UI action.
- Naming: use
rejectonly for user-decision paths and related NBGL/mock helpers.
This distinction is required to avoid conflating security-policy/input validation failures with human confirmation refusals.
Excessive tracing strings in the debug version of the app can lead to the binary exceeding the available flash memory on the device. To mitigate this, the app uses conditional tracing guards for verbose or repetitive debug output.
To use a guard in your module:
- Define the guard at the top of your
.cfile:#ifdef TRACE_MY_MODULE #define TRACE_MODULE(...) TRACE("[module] " __VA_ARGS__) #else #define TRACE_MODULE(...) (void)0 #endif
- Use
TRACE_MODULE()for state tracking, loops, or field-level parsing. - Keep standard
TRACE()for critical errors and security-relevant information.
| Guard Name | Target Modules | Purpose |
|---|---|---|
TRACE_TX_PARSE |
src/transaction/tx_parse*.c, src/parsers/cardano_parsers.c |
Transaction component and core parsing |
TRACE_TX_HASH_BUILDER |
src/transaction/tx_hash_builder.c |
Core transaction hashing |
TRACE_HANDLERS |
src/handler/*.c |
APDU command handler flow |
TRACE_UI_DISPLAY |
src/ui/ui_display*.c, src/ui/ui_sign_msg.c, src/ui/ui_cvote.c, src/ui/ui_display_pubkey.c, src/ui/ui_display_tx.c |
UI rendering and state |
TRACE_CVOTE |
src/cvote/cvote_parser.c |
Catalyst voting data parsing |
TRACE_AUX_DATA_HASH_BUILDER |
src/cvote/aux_data_hash_builder.c |
Voting aux data hashing |
TRACE_VOTECAST_HASH_BUILDER |
src/cvote/vote_cast_hash_builder.c |
Vote cast hashing |
TRACE_NATIVE_SCRIPT_HASH_BUILDER |
src/deriveNativeScriptHash/derive_native_script_hash_builder.c |
Native script hashing |
TRACE_SWAP |
src/swap/swap_lib.c |
Swap/library-mode validation flow |
These guards apply to the device binary built via the repo-root Makefile (inside the
Ledger SDK / Docker environment). Pass extra defines via DEFINES+=:
make DEBUG=1 DEFINES+=TRACE_TX_PARSE DEFINES+=TRACE_HANDLERS -j8To verify that strings are correctly removed from the compiled binary:
# Build with guard disabled, then check count
make clean_target && make DEBUG=1 -j8
strings build/app-cardano/bin/app.elf | grep "your-string" | wc -lNone. In release builds (where HAVE_PRINTF is not defined), the TRACE macro is defined as do {} while(0) in utils/utils.h. This means all strings are removed by the preprocessor regardless of guards. These guards specifically target the debug binary size constraints.