Summary
The docs tell us to keep all #[test] functions in a dedicated test crate, and aztec test warns when tests are found in a contract crate. But --coverage only attributes execution to files of the crate directly under test, so a project laid out the recommended way reports 0% for every contract and library file it owns — no matter how thorough its tests are.
The two pieces of guidance are mutually exclusive today, and there's no documented way to satisfy both:
- Follow "keep tests in the test crate" → your contract/library source is never attributed → permanently 0%.
- Get a real number → tests must be co-located in the crate under test → warning, unnecessary recompilation, and test source embedded in the shipped artifact (below).
From the docs (Keep tests in the test crate):
Keep all #[test] functions in the test crate, not in the contract crate.
and errors/1:
WARNING: Found tests in contract crate(s): … Tests should be in a dedicated test crate, not in the contract crate.
Environment
- aztec
5.0.1, bundled nargo 1.0.0-beta.22 (noirc 1.0.0-beta.22+c57152f91260ecdb9faad4efc20abb14b6d2ece7)
- Command:
aztec test --coverage on a workspace of X / X_test pairs
Reproduction
A workspace laid out exactly as the docs recommend:
Nargo.toml # workspace members: common, common_test, …
common/ # type = "lib" — library under test, no #[test] fns
common_test/ # type = "lib" — deps: common; contains all #[test] fns
token_bridge_contract/ # type = "contract" — no #[test] fns
token_bridge_contract_test/ # type = "lib" — deps: token_bridge_contract; all #[test] fns
Run aztec test --coverage, then inspect target/coverage/<package>/lcov.info.
Observed:
target/coverage/common_test/lcov.info emits SF: entries only for its own common_test/src/*_test.nr files. It never emits an SF: for common/src/*.nr, although every one of those files is exercised by those tests.
target/coverage/common/lcov.info does list all 7 common/src/*.nr files — with all 155 instrumented lines at zero hits.
- Every production contract reads 0%:
token_bridge_contract/src/main.nr 0/147, token_vault_bridge_contract/src/main.nr 0/203, and so on.
This is about placement, not test quality. We took an existing test out of common_test/src/packable_bounded_vec_test.nr and put an equivalent mod test inside common/src/packable_bounded_vec.nr — same assertions, no new logic. That file went from 0% to 97% (32/33 lines).
Cause
The evaluation tracker is narrowed to the crate under test (tooling/nargo_cli/src/cli/test_cmd.rs#L690-L693):
if let Some(evaluation_tracker) = context.evaluation_tracker.as_mut() {
let crate_files = context.def_maps[&crate_id].file_ids();
evaluation_tracker.restrict_to_files(&crate_files);
}
and the baseline is built from the same set (test_cmd/coverage.rs#L22-L41):
let def_map = &context.def_maps[&crate_id];
let allowed_files = def_map.file_ids();
So a separate X_test package — the documented layout — can never contribute coverage to X's source.
Why co-locating tests isn't an acceptable workaround
Beyond the recompilation reason the docs give, co-located test source is embedded in the compiled artifact's file_map, including on a production aztec-nargo compile. functions/bytecode stay clean, so there's no circuit or ABI impact — but file_map is the largest section of the artifact (~810KB of a ~994KB contract JSON in our case), and projects that publish their contracts as an npm package ship those target/*.json files to consumers. So co-locating means shipping test source downstream.
It also wouldn't actually work for TXE tests: see #24987 — under --coverage, oracle-calling tests are skipped entirely, so co-locating them yields no coverage either.
Ask
- Attribute coverage across workspace path dependencies: when a test in crate
B executes code from A (a path dependency in the same workspace), count it toward A's source, the way cargo llvm-cov and gcov-based tooling do — ideally keyed by executed source location rather than package ownership.
- Or add an opt-in flag (
--coverage-include-deps / --coverage-workspace) that widens instrumentation to workspace path dependencies.
- Failing either, please document how a project that follows "Keep tests in the test crate" is supposed to obtain coverage for its own contract and library code. As written, the testing-layout guidance and the
--coverage feature contradict each other, and we'd rather not build a workaround around a gap you intend to close.
Note: --coverage is implemented in Noir (nargo test --coverage), so the fix may belong in noir-lang/noir. Filing here because the test-layout recommendation is Aztec's and aztec test is the documented entry point — happy to re-file upstream if you prefer.
Related: #24987 (--coverage silently skips oracle-calling tests and still reports them as passed).
Summary
The docs tell us to keep all
#[test]functions in a dedicated test crate, andaztec testwarns when tests are found in a contract crate. But--coverageonly attributes execution to files of the crate directly under test, so a project laid out the recommended way reports 0% for every contract and library file it owns — no matter how thorough its tests are.The two pieces of guidance are mutually exclusive today, and there's no documented way to satisfy both:
From the docs (Keep tests in the test crate):
and errors/1:
Environment
5.0.1, bundled nargo1.0.0-beta.22(noirc 1.0.0-beta.22+c57152f91260ecdb9faad4efc20abb14b6d2ece7)aztec test --coverageon a workspace ofX/X_testpairsReproduction
A workspace laid out exactly as the docs recommend:
Run
aztec test --coverage, then inspecttarget/coverage/<package>/lcov.info.Observed:
target/coverage/common_test/lcov.infoemitsSF:entries only for its owncommon_test/src/*_test.nrfiles. It never emits anSF:forcommon/src/*.nr, although every one of those files is exercised by those tests.target/coverage/common/lcov.infodoes list all 7common/src/*.nrfiles — with all 155 instrumented lines at zero hits.token_bridge_contract/src/main.nr0/147,token_vault_bridge_contract/src/main.nr0/203, and so on.This is about placement, not test quality. We took an existing test out of
common_test/src/packable_bounded_vec_test.nrand put an equivalentmod testinsidecommon/src/packable_bounded_vec.nr— same assertions, no new logic. That file went from 0% to 97% (32/33 lines).Cause
The evaluation tracker is narrowed to the crate under test (
tooling/nargo_cli/src/cli/test_cmd.rs#L690-L693):and the baseline is built from the same set (
test_cmd/coverage.rs#L22-L41):So a separate
X_testpackage — the documented layout — can never contribute coverage toX's source.Why co-locating tests isn't an acceptable workaround
Beyond the recompilation reason the docs give, co-located test source is embedded in the compiled artifact's
file_map, including on a productionaztec-nargo compile.functions/bytecode stay clean, so there's no circuit or ABI impact — butfile_mapis the largest section of the artifact (~810KB of a ~994KB contract JSON in our case), and projects that publish their contracts as an npm package ship thosetarget/*.jsonfiles to consumers. So co-locating means shipping test source downstream.It also wouldn't actually work for TXE tests: see #24987 — under
--coverage, oracle-calling tests are skipped entirely, so co-locating them yields no coverage either.Ask
Bexecutes code fromA(a path dependency in the same workspace), count it towardA's source, the waycargo llvm-covandgcov-based tooling do — ideally keyed by executed source location rather than package ownership.--coverage-include-deps/--coverage-workspace) that widens instrumentation to workspace path dependencies.--coveragefeature contradict each other, and we'd rather not build a workaround around a gap you intend to close.Note:
--coverageis implemented in Noir (nargo test --coverage), so the fix may belong innoir-lang/noir. Filing here because the test-layout recommendation is Aztec's andaztec testis the documented entry point — happy to re-file upstream if you prefer.Related: #24987 (
--coveragesilently skips oracle-calling tests and still reports them as passed).