Usage Guide¶
Understanding the JUnit Output¶
Junix maps Nix build activities to JUnit test cases:
| Nix build result | JUnit element |
|---|---|
| Build succeeded | <testcase> with <system-out> containing build logs |
| Build failed | <testcase> with <failure> containing build logs |
| Eval failed | <testcase> with <failure> and Nix error trace in <system-out> |
| Build incomplete (process crashed) | <testcase> with <error> |
| Cached or substituted (no build needed) | <testcase> with <skipped> |
| Nix error messages | <system-err> on the first failing testcase |
Example Report¶
Here is a real JUnit report from junix check on a flake with one passing and one
failing check:
<?xml version="1.0" encoding="utf-8" ?>
<testsuites>
<testsuite
name="nix-eval.x86_64-linux"
tests="2"
failures="0"
errors="0"
skipped="0"
time="0"
>
<testcase
name="nix eval .#checks.x86_64-linux.passing"
classname="/nix/store/xxx-passing-check.drv"
time="0"
>
<system-out>/nix/store/xxx-passing-check.drv</system-out>
</testcase>
<testcase
name="nix eval .#checks.x86_64-linux.failing"
classname="/nix/store/yyy-failing-check.drv"
time="0"
>
<system-out>/nix/store/yyy-failing-check.drv</system-out>
</testcase>
</testsuite>
<testsuite
name="nix-build.x86_64-linux"
tests="2"
failures="1"
errors="0"
skipped="0"
time="0"
>
<testcase
name="nix build .#checks.x86_64-linux.passing"
classname="/nix/store/xxx-passing-check.drv"
time="0"
>
<system-out>This check passes</system-out>
</testcase>
<testcase
name="nix build .#checks.x86_64-linux.failing"
classname="/nix/store/yyy-failing-check.drv"
time="0"
>
<failure message="Build failed" type="failure" />
<system-out>This check fails</system-out>
<system-err>
error: builder for '/nix/store/yyy-failing-check.drv' failed with exit
code 1
</system-err>
</testcase>
</testsuite>
</testsuites>
If a check fails to evaluate (builtins.tryEval returns success: false), the
<system-out> of the failing eval testcase contains the Nix error trace for that
check, captured by a follow-up nix eval --show-trace call. For example, a check
defined as pkgs.runCommand "eval-fail-check" {} (throw "boom") produces:
<testcase name="nix eval .#checks.x86_64-linux.eval-fail" ...>
<failure message="Build failed" type="failure" />
<system-out>error:
… while evaluating the 'drvPath' of derivation 'eval-fail-check'
at /nix/store/.../lib/customisation.nix:415:11:
414| // {
415| drvPath =
| ^
416| assert condition;
… while calling the 'throw' builtin
at ./flake.nix:23:9:
22| eval-fail = pkgs.runCommand "eval-fail-check" {} (
23| throw "This check fails to evaluate"
| ^
error: This check fails to evaluate
</system-out>
</testcase>
The report contains one <testsuite> per architecture per phase:
- Eval suites (
nix-eval.{arch}) — one testcase per check, with the derivation path in<system-out>on success, or<failure>and the Nix error trace in<system-out>if the check failed to evaluate. - Build suites (
nix-build.{arch}) — one testcase per check, with build logs in<system-out>on success,<failure>on build failure, or<skipped>when the build was cached. A check whose eval already failed also appears here as<failure>(since the build can never succeed without a derivation) carrying the same eval error trace in<system-out>.
The <testcase> name in every JUnit suite is the exact command you would run to
reproduce the work: nix eval <path>#checks.<arch>.<name> for eval suites,
nix build <path>#checks.<arch>.<name> for build suites, and nix build <attr> for the
build subcommand. This makes failures in CI reproducible by copy-paste.
Colored terminal summary¶
See the --color global switch in the CLI reference for the full
description of the three modes (auto, yes, no) and the JUNIX_COLOR precedence
rule (CLI > env > auto). When colors are enabled, the summary uses the following
scheme:
- blue for informational headers (e.g.
eval {arch} ... N checks,⬇ N path(s) downloaded) - green for passed checks
- bold red for failed checks
and the final
FAILEDline - red for the cached count line
- dim green for per-check cached entries
Example output from a mixed run:
⬇ 3 path(s) downloaded ~ 1 build(s) cached ✓ checks.x86_64-linux.passing ✗ checks.x86_64-linux.failing FAILED (1/2): - checks.x86_64-linux.failing
translate: Parsing Existing Build Output¶
Use translate when you already have Nix build output and want to convert it to JUnit
XML. This is useful when:
- You want to capture output from a complex build script
- You need to reprocess previously saved logs
- You're integrating Junix into an existing pipeline
# Stream a live build into junix
nix build --log-format internal-json -v --print-build-logs --no-link .#pkg1 2>&1 | junix translate -o report.xml
# Or capture to a file and translate later
nix build --log-format internal-json -v --print-build-logs --no-link .#pkg1 2> build.log
cat build.log | junix translate -o report.xml
junix translate reads @nix JSON lines from stdin and writes the JUnit XML report to
stdout (or to -o). While it reads, it immediately forwards non-@nix lines and Nix
log messages that match the current verbosity to its own stderr, formatted in
human-readable form. This lets you watch a long build live while still getting the
report at the end.
Tip
Make sure to use --log-format internal-json when running Nix, otherwise
Junix won't be able to parse the output.
build: Build and Report¶
Use build when you want Junix to drive the Nix build directly. This is the simplest
way to get a JUnit report.
Each <testcase> in the JUnit report is named after the exact nix build command that
would reproduce it (e.g. nix build nixpkgs#hello). A colorful summary is printed to
stderr showing downloaded, cached, passed and failed builds, ending with
PASSED (T evals, T builds) or FAILED (F/T evals, F/T builds) (F = failed, T =
total).
# Build one or more packages
junix build .#pkg1 .#pkg2 -o report.xml
# Build with a remote store
junix build .#pkg1 --store ssh-ng://eu.nixbuild.net -o report.xml
# Pass extra flags to nix build after --
junix build nixpkgs#hello -- --no-link --rebuild
The --store option sets a remote build store (e.g. nixbuild.net).
check: Run All Flake Checks¶
Use check to run all checks defined in your flake. This is ideal for CI pipelines
where you want to validate the entire project.
# Run all checks (eval all arches, build local only)
junix check -o report.xml
# Evaluate only specific architectures
junix check --eval-arch x86_64-linux --eval-arch aarch64-linux -o report.xml
# Build specific architectures (default: local only)
junix check --build-arch x86_64-linux -o report.xml
# Pass extra flags to nix build
junix check -- --no-link --rebuild
How Check Discovery Works¶
- Junix runs
nix eval --json "path#checks" --applywith a Nix expression that evaluates each check usingbuiltins.tryEval, so one eval failure does not abort the entire discovery. - The expression returns a JSON list of objects with
arch,name,drvPath, anderrorfields. - Checks whose eval succeeded (
drvPathis not null) are sent tonix buildin the build phase. - Checks whose eval failed are never sent to
nix build— they are reported as build failures with the eval error message.builtins.tryEvalswallows the Nix error trace and only returnssuccess: false, so for each failing check junix runs a secondnix eval --show-trace "path#checks.<arch>.<name>.drvPath"to capture the real trace. That trace becomes theerrorfield in the JUnit<system-out>of the failing testcase. Only failing checks pay the extra round trip; healthy flakes run one eval. - By default, only the local system checks are built. Use
-bto override.
Path handling¶
Junix passes the PATH argument to check directly to nix eval and nix build,
without rewriting it. Like Nix itself, relative paths must start with ./ or be
absolute. The default is .. If you pass a bare relative path (e.g.
tests/sample-flake), Nix rejects it with its usual error message — Junix surfaces that
error in the JUnit report as a build failure.
Summary output¶
The colorful summary on stderr has two sections: one for the eval phase (per architecture) and one for the build phase (per architecture). The final line uses separate counts for evals and builds:
eval x86_64-linux ... 2 checks ✓ nix eval .#checks.x86_64-linux.passing ✗ nix eval .#checks.x86_64-linux.failing build x86_64-linux ... 2 checks ✗ nix build .#checks.x86_64-linux.failing ✓ nix build .#checks.x86_64-linux.passing (cached) FAILED (1/2 evals, 1/2 builds)
Architecture flags¶
The -e/--eval-arch and -b/--build-arch flags are independent:
-econtrols which architectures are evaluated (default: all)-bcontrols which architectures are built (default: local only)
Passing -e aarch64-linux does NOT change the default of -b (still local). Passing
-b aarch64-linux does NOT change the default of -e (still all).
Memory-constrained flakes¶
The default junix check discovery runs one nix eval [...magic...] flake#checks
call that evaluates every check × arch in a single Nix process. That's efficient for
small flakes, but can OOM on large ones, like those building full NixOS or derivations
with hundreds of dependencies accross some arches. The symptom is a CI job that dies
without junix ever printing a line similar to eval x86_64-linux ... 2 checks.
The -1/--eval-individually flag trades that for per-check isolation: junix runs
just one nix eval to find all check names, and then evals each check individually, so
the RAM peak is bounded by the closure of the largest single check — not the sum of all
checks. The wall-clock cost is one Nix process per check (hundreds of milliseconds
each), so a flake with hundreds of checks pays noticeable overhead. Use this only when
the bulk path OOMs:
--eval-individually is orthogonal to -e and -b: it does not change which arches
are evaluated or built, only how each check is evaluated. The JUnit structure is
identical to the bulk path (per-arch nix-eval.<arch> and nix-build.<arch> suites,
same reproducer testcase names, same drv path in <system-out>). Failing checks
automatically get the real Nix error trace in their <system-out> — no second
nix eval --show-trace round trip is needed, since each per-check call surfaces the
trace directly.
Controlling Log Verbosity¶
Nix has multiple log levels. Use -v flags to control what appears in the report:
# Errors only (default)
junix build .#pkg1 -o report.xml
# Info messages included
junix build .#pkg1 -v -o report.xml
# Chatty output (build phases, progress)
junix build .#pkg1 -vv -o report.xml
# Everything (debug-level detail)
junix build .#pkg1 -vvv -o report.xml
Verbosity maps to Nix log levels as follows:
-v flag |
Nix level | Description |
|---|---|---|
| (none) | 0 (Error) | Errors only |
-v |
3 (Info) | Informational |
-vv |
5 (Chatty) | Build phases |
-vvv |
7 (Vomit) | Everything |
CI Integration¶
GitLab CI¶
Jenkins Pipeline¶
stage('Nix Build') {
steps {
sh 'junix build .#pkg1 -o report.xml'
}
post {
always {
junit 'report.xml'
}
}
}