Skip to content

Add the nix-unit integration - #157

Merged
clhodapp merged 3 commits into
mainfrom
feat/nix-unit-integration
Nov 14, 2026
Merged

clhodapp merged 3 commits into
mainfrom
feat/nix-unit-integration

Conversation

@clhodapp

@clhodapp clhodapp commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Step 5 of the checks work: the nix-unit integration.

The integration

A nix-unit configuration names a file of unit tests and the nix-unit program that runs them:

# configs/flake/checks/default.nix, a flake-parts configuration
caisson.nix-unit.configurations.lib = lib.caisson.nix-unit.mkConfiguration { };

# configs/nixUnit/lib/default.nix, the module registered under that name
{ ... }:
{ ... }:
{
  tests = ../../../tests/unit;
}

The file, or the default.nix of a directory, is a function of { lib, manifest, dependencies } that returns nix-unit's suite. lib is the library that a module of the configuration that declares the nix-unit configuration receives. manifest is the manifest of that library, and dependencies are the pinned dependencies of the nix-unit configuration. package defaults to nix-unit of the package set the configuration runs on.

A nix-unit configuration is evaluated per system, once for each system its parent evaluates its children for, and is published in no output attribute set. Its default check unit, of the category unit, is published as <names>-unit, so the configuration above gives the check lib-unit.

How the check runs nix-unit

The derivation runs nix-unit in file mode over a generated file, with a local store under the build directory as nix-unit's flake-parts module sets it up. The file imports lib-overlays/nix-unit/sandbox.nix with a JSON record written at evaluation:

  • the store path of every source that the library and the configuration reach (dependencies, projects and the inputs of each flake, recursively), with the source information of each;
  • the file every registry entry of the library was registered from (lib overlays, modules, configs, package overlays), and the lib overlays the library applied, in order;
  • the name, systems, root and defaultEcosystemSrc names of the library.

In the sandbox, sandbox.nix evaluates each flake again with caisson's callFlake over its inputs. It then calls mkLib of the caisson-core that composed the library, over that record, and calls the test file. No pin reader runs in the sandbox.

Limits of the composition in the sandbox

  • The record holds no declaration of a package config, so the library composed in the sandbox keeps the names of its package configs, and a test that reads a package config fails with a message that says so.
  • A registry entry given as a value, with no file, and a file outside the Nix store make the check fail at evaluation, with a message that names the entry. A caisson top evaluated as a flake has every file in the store.

caisson's unit tests

They stay on the nix-unit flake-parts module. The published name of a check of this integration is <configuration>-unit, so keeping the name nix-unit would mean naming the configuration nix. The suite also reads a library that tests/unit/flake.nix composes from caisson's source with no name, configs or root, and the input parent of that flake, caisson's source. The library composed in the sandbox would be the library of the caisson top of caisson. Porting therefore changes the name and what the cases evaluate against.

Deviations from the design

  • The design hands the integration a file through caisson.checks.<name>.tests. This PR makes the file an option of a nix-unit configuration, caisson.nix-unit.configurations.<name>, as the nixos-test integration does for VM tests.
  • Configurations of the class are registered under configs/nixUnit/<name>, because the registry is still keyed by class.
  • Package configs are not composed again in the sandbox (above).

Checks

nix-unit-integration (tests/nix-unit) evaluates a flake-parts caisson top for x86_64-linux and aarch64-linux with the nix-unit configurations passing and failing. It asserts that the check record holds passing-unit and failing-unit with the category unit for both systems. It builds passing-unit, whose cases read the library composed in the sandbox, its manifest and a dependency flake. It builds failing-unit with testers.testBuildFailure and finds in its log that the failing case failed and the passing case passed.

Eval weight

A flake that uses caisson composes the nix-unit overlay, and every module evaluation declares caisson.nix-unit.configurations. On main 3b28e96 the caisson-overhead gate measures 5055440 allocated bytes, and on this branch 5114064, above the ceiling of 5084142 (10% over the baseline of 4618224). The last commit records the values measured on this branch as the baseline. The integration adds 58624 bytes (1.2%); the rest of the growth since the previous baseline came from earlier integrations.

Results

Built locally before the rebase onto 3b28e96: every check of checks.x86_64-linux, the VM checks and nix-unit (232/232) among them. After the rebase: nix-unit, nix-unit-integration, pinned-world, registries, eval-weight and treefmt. The check darwin-system of checks.aarch64-darwin evaluates; building it needs a macOS builder, which the darwin job of the workflow has.

🤖 Generated with Claude Code

@clhodapp clhodapp self-assigned this Nov 12, 2026
clhodapp and others added 3 commits November 13, 2026 09:47
A nix-unit configuration, declared with
`caisson.nix-unit.configurations.<name> = lib.caisson.nix-unit.mkConfiguration { … }`,
names a file of unit tests in `tests` and the nix-unit program in
`package`, by default `nix-unit` of the package set the configuration
runs on. The file is a function of `{ lib, manifest, dependencies }`
that returns nix-unit's suite. The configuration is evaluated per
system and is published in no output attribute set. Its default check
`unit`, of the category `unit`, runs nix-unit over the file in a
derivation.

nix-unit evaluates the file inside the build sandbox, where Nix can
fetch nothing. The check writes a JSON record of the library that the
configuration is evaluated with: the store path of every source the
library and the configuration reach, with the inputs of each flake, and
the file every registry entry was registered from. In the sandbox,
lib-overlays/nix-unit/sandbox.nix evaluates each flake again with
caisson's `callFlake` and calls mkLib of the same caisson-core over
that record. No pin reader runs in the sandbox. Reading a package
config of the library composed in the sandbox fails, because the
record holds no declaration of a package config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check `nix-unit-integration` evaluates a flake-parts caisson top
for x86_64-linux and aarch64-linux that declares the nix-unit
configurations `passing` and `failing` (tests/nix-unit). It asserts
that the check record holds the check `unit` of each, of the category
`unit`, for both systems. It builds the check of `passing`, whose cases
read the library composed in the sandbox, its manifest and a
dependency. It builds the check of `failing` with
`testers.testBuildFailure` and reads in the log that nix-unit reported
the failing case as failed and the passing case as passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A flake that uses caisson composes every lib overlay caisson exports,
the nix-unit overlay among them. Every module evaluation also declares
the option `caisson.nix-unit.configurations`. With the integration, the
`caisson-overhead` gate measures 5114064 allocated bytes. That exceeds
the ceiling of 5084142, which is 10% above the committed baseline of
4618224. main measures 5055440 bytes, so the nix-unit integration adds
58624 bytes (1.2%), and the rest of the growth since the last baseline
came from earlier integrations. The baseline holds the gate values
measured at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@clhodapp
clhodapp force-pushed the feat/nix-unit-integration branch from 086cda4 to 41e5a67 Compare November 13, 2026 17:59
@clhodapp
clhodapp merged commit e22972c into main Nov 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant