Running tests
Unit-tests
The unit tests are defined using the googletest and rapidcheck frameworks.
Source and header layout
An example of some files, demonstrating much of what is described below
… ├── src │ ├── libexpr │ │ ├── … │ │ ├── value │ │ │ ├── context.cc │ │ │ └── context.hh │ … … ├── tests │ … │ └── unit │ ├── libcmd │ │ └── args.cc │ ├── libexpr │ │ ├── … │ │ └── value │ │ ├── context.cc │ │ └── print.cc │ ├── libexpr-support │ │ └── tests │ │ ├── libexpr.hh │ │ └── value │ │ ├── context.cc │ │ └── context.hh │ ├── libstore │ │ ├── common-protocol.cc │ │ ├── data │ │ │ ├── libstore │ │ │ │ ├── common-protocol │ │ │ │ │ ├── content-address.bin │ │ │ │ │ ├── drv-output.bin … … … … … …
The unit tests for each Lix library (liblixexpr
, liblixstore
, etc..) live inside a directory src/${library_shortname}/tests
within the directory for the library (src/${library_shortname}
).
The data is in tests/unit/LIBNAME/data/LIBNAME
, with one subdir per library, with the same name as where the code goes.
For example, liblixstore
code is in src/libstore
, and its test data is in tests/unit/libstore/data/libstore
.
The path to the unit test data directory is passed to the unit test executable with the environment variable _NIX_TEST_UNIT_DATA
.
Running tests
You can run the whole testsuite with just test
(see justfile for exact invocation of meson), and if you want to run just one test suite, use just test --suite installcheck functional-init
where installcheck
is the name of the test suite in this case and functional-init
is the name of the test.
To get a list of tests, use meson test -C build --list
(or just test --list
for short).
For installcheck
specifically, first run just install
before running the test suite (this is due to meson limitations that don't let us put a dependency on installing before doing the test).
Finer-grained filtering within a test suite is also possible using the --gtest_filter command-line option to a test suite executable, or the GTEST_FILTER
environment variable.
Unit test support libraries
There are headers and code which are not just used to test the library in question, but also downstream libraries.
For example, we do [property testing] with the rapidcheck library.
This requires writing Arbitrary
"instances", which are used to describe how to generate values of a given type for the sake of running property tests.
Because types contain other types, Arbitrary
"instances" for some type are not just useful for testing that type, but also any other type that contains it.
Downstream types frequently contain upstream types, so it is very important that we share arbitrary instances so that downstream libraries' property tests can also use them.
It is important that these testing libraries don't contain any actual tests themselves. On some platforms they would be run as part of every test executable that uses them, which is redundant. On other platforms they wouldn't be run at all.
Characterization testing
See below for a broader discussion of characterization testing.
Like with the functional characterization, _NIX_TEST_ACCEPT=1
is also used.
For example:
$ _NIX_TEST_ACCEPT=1 just test --suite check libstore-unit-tests
...
../tests/unit/libstore/common-protocol.cc:27: Skipped
Cannot read golden master because another test is also updating it
../tests/unit/libstore/common-protocol.cc:62: Skipped
Updating golden master
../tests/unit/libstore/common-protocol.cc:27: Skipped
Cannot read golden master because another test is also updating it
../tests/unit/libstore/common-protocol.cc:62: Skipped
Updating golden master
...
will regenerate the "golden master" expected result for the liblixstore
characterization tests.
The characterization tests will mark themselves "skipped" since they regenerated the expected result instead of actually testing anything.
Functional tests
The functional tests reside under the tests/functional
directory and are listed in tests/functional/meson.build
.
Each test is a bash script.
Running the whole test suite
Debugging failing functional tests
When a functional test fails, it usually does so somewhere in the middle of the script.
To figure out what's wrong, it is convenient to run the test regularly up to the failing nix
command, and then run that command with a debugger like GDB.
For example, if the script looks like:
foo
nix blah blub
bar
edit it like so:
foo
-nix blah blub
+gdb --args nix blah blub
bar
Then, running the test with ./mk/debug-test.sh
will drop you into GDB once the script reaches that point:
$ ./mk/debug-test.sh tests/functional/${testName}.sh
...
+ gdb blash blub
GNU gdb (GDB) 12.1
...
(gdb)
One can debug the Nix invocation in all the usual ways.
For example, enter run
to start the Nix invocation.
Characterization testing
Occasionally, Lix utilizes a technique called Characterization Testing as part of the functional tests. This technique is to include the exact output/behavior of a former version of Nix in a test in order to check that Lix continues to produce the same behavior going forward.
For example, this technique is used for the language tests, to check both the printed final value if evaluation was successful, and any errors and warnings encountered.
It is frequently useful to regenerate the expected output.
To do that, rerun the failed test(s) with _NIX_TEST_ACCEPT=1
.
For example:
_NIX_TEST_ACCEPT=1 just test --suite installcheck -v functional-lang
An interesting situation to document is the case when these tests are "overfitted". The language tests are, again, an example of this. The expected successful output of evaluation is supposed to be highly stable – we do not intend to make breaking changes to (the stable parts of) the Nix language. However, the errors and warnings during evaluation (successful or not) are not stable in this way. We are free to change how they are displayed at any time.
It may be surprising that we would test non-normative behavior like diagnostic outputs. Diagnostic outputs are indeed not a stable interface, but they still are important to users. By recording the expected output, the test suite guards against accidental changes, and ensure the result (not just the code that implements it) of the diagnostic code paths are under code review. Regressions are caught, and improvements always show up in code review.
To ensure that characterization testing doesn't make it harder to intentionally change these interfaces, there always must be an easy way to regenerate the expected output, as we do with _NIX_TEST_ACCEPT=1
.
Integration tests
The integration tests are defined in the Nix flake under the hydraJobs.tests
attribute.
These tests include everything that needs to interact with external services or run Lix in a non-trivial distributed setup.
You can run them manually with nix build .#hydraJobs.tests.{testName}
or nix-build -A hydraJobs.tests.{testName}
Installer tests section is outdated and commented out, see https://git.lix.systems/lix-project/lix/issues/33
Magic environment variables
FIXME: maybe this section should be moved elsewhere or turned partially into user docs, but I just need a complete index for now. I actually want to ban people calling getenv without writing documentation, and produce a comprehensive list of env-vars used by Lix and enforce it.
This is a non-exhaustive list of almost all environment variables, magic or not, accepted or used by various parts of the test suite as well as Lix itself. Please add more if you find them.
I looked for these in the testsuite with the following bad regexes:
rg '(?:[^A-Za-z]|^)(_[A-Z][^-\[ }/:");$(]+)' -r '$1' --no-filename --only-matching tests | sort -u > vars.txt
rg '\$\{?([A-Z][^-\[ }/:");]+)' -r '$1' --no-filename --only-matching tests | sort -u > vars.txt
I grepped src/
for get[eE]nv\("
to find the mentions in Lix code.
Used by Lix testing support code
-
_NIX_TEST_ACCEPT
(optional) - Writes out the result of a characterization test as the new expected value. Expected value: 1 -
_NIX_TEST_UNIT_DATA
- The path to the directory for the data for a given unit test suite.Expected value:
tests/unit/libstore/data/libstore
or similar
Used by Lix
-
_NIX_FORCE_HTTP
- Forces file URIs to be treated as remote ones.Used by
src/libfetchers/git.cc
,src/libstore/http-binary-cache-store.cc
,src/libstore/local-binary-cache-store.cc
. Seems to be for forcing Git clones ofgit+file://
URLs, making the HTTP binary cache store acceptfile://
URLs (presumably passing them to curl?), and unknown reasons for the local binary cache.FIXME(jade): is this obscuring a bug in https://git.lix.systems/lix-project/lix/issues/200?
Expected value: 1
-
NIX_ATTRS_SH_FILE
,NIX_ATTRS_JSON_FILE
(output) - Set by Lix builders; seestructuredAttrs
documentation. -
NIX_BIN_DIR
,NIX_STORE_DIR
(or its inconsistently-used old aliasNIX_STORE
),NIX_DATA_DIR
,NIX_LOG_DIR
,NIX_LOG_DIR
,NIX_STATE_DIR
,NIX_CONF_DIR
- Overrides compile-time configuration of various locations used by Lix. Seesrc/libstore/globals.cc
.Expected value: a directory
-
NIX_DAEMON_SOCKET_PATH
(optional) - Overrides the daemon socket path from$NIX_STATE_DIR/daemon-socket/socket
.Expected value: path to a socket
-
NIX_LOG_FD
(output) - An FD number for logs ininternal-json
format to be sent to. Used for, mostly, "setPhase" in nixpkgs setup.sh, but can also be creatively used to print verbose log messages from derivations.Provided value: number corresponding to an FD in the builder
-
NIX_PATH
- Search path for<whatever>
. Documented elsewhere in the manual.Expected value:
:
separated list of things that are not necessarily pointing to filesystem paths -
NIX_REMOTE
- The default value of the Lix settingstore
.Expected value: "daemon", usually. Could be "auto" or any other value acceptable in
store
. -
NIX_BUILD_SHELL
- Documented elsewhere; the shell to invoke withnix-shell
but notnix develop
/nix shell
. The latter ignoring it altogether seems like a bug.Expected value: the path to an executable shell
-
PRINT_PATH
- Undocumented. Used bynix-prefetch-url
as an alternative form of--print-path
. Why??? -
_NIX_IN_TEST
- If present with any value, makesfetchClosure
accept file URLs in addition to HTTP ones. Why is this not_NIX_FORCE_HTTP
??Not used anywhere else.
-
NIX_ALLOW_EVAL
- Used by eval-cache tests to block evaluation if set to0
.Expected value: 1 or 0
-
EDITOR
- Used byeditorFor()
, which has some extremely sketchy editor-detection code for jumping to line numbers. -
LISTEN_FDS
andLISTEN_PID
- Used for systemd socket activation using the systemd socket activation protocol. -
NIX_PAGER
(alternatively,PAGER
) - Used to select a pager for Lix output. Why does this not use libutilgetEnv()
? -
LESS
(output) - Sets the pager settings forless
when invoked by Lix. -
NIX_IGNORE_SYMLINK_STORE
- When set, Lix allows the store to be a symlink. Why do we support this?Apparently someone was using it enough to fix it.
-
NIX_SSL_CERT_FILE
(alternatively,SSL_CERT_FILE
) - Used to set CA certificates for libcurl.Expected value: "/etc/ssl/certs/ca-certificates.crt" or similar
-
NIX_REMOTE_SYSTEMS
- Used to setbuilders
. Can we please deprecate this? -
NIX_USER_CONF_FILES
-:
separated list of config files to load before/nix/nix.conf
under each ofXDG_CONFIG_DIRS
. -
NIX_CONFIG
- Newline separated configuration to load into Lix. -
NIX_GET_COMPLETIONS
- Returns completions. Unsure of the exact format, someone should document it; either way my shell never had any completions.Expected value: number of completions to return.
-
IN_SYSTEMD
- Used to switch the logging format so that systemd gets the correct log levels. I think. -
NIX_HELD_LOCKS
- Not used, what is this for?? We should surely remove it right after searching github? -
GC_INITIAL_HEAP_SIZE
- Used to set the initial heap size, processed by boehmgc. -
NIX_COUNT_CALLS
- Documented elsewhere; prints call counts for profiling purposes. -
NIX_SHOW_STATS
- Documented elsewhere; prints various evaluation statistics like function calls, gc info, and similar. -
NIX_SHOW_STATS_PATH
- Writes those statistics into a file at the given path instead of stdout. Undocumented. -
NIX_SHOW_SYMBOLS
- Dumps the symbol table into the show-stats json output. -
TERM
- Ifdumb
or unset, disables ANSI colour output. -
NO_COLOR
,NOCOLOR
- Disables ANSI colour output. -
_NIX_DEVELOPER_SHOW_UNKNOWN_LOCATIONS
- Highlights unknown locations in errors. -
NIX_PROFILE
- Selects which profilenix-env
will operate on. Documented elsewhere. -
NIX_SSHOPTS
- Options passed tossh(1)
when using a ssh remote store. Incorrectly documented onnix-copy-closure
which is surely not the only place they are used?? -
_NIX_TEST_GC_SYNC_1
- Path to a pipe that is used to block the GC briefly to validate invariants from the test suite. -
_NIX_TEST_GC_SYNC_2
- Path to a pipe that is used to block the GC briefly to validate invariants from the test suite. -
_NIX_TEST_FREE_SPACE_FILE
- Path to a file containing a decimal number with the free space that the GC is to believe it has. -
Various XDG vars
-
NIX_DEBUG_SQLITE_TRACES
- Dump all sqlite queries to the log atnotice
level. -
_NIX_TEST_NO_SANDBOX
- Disables actually setting up the sandbox on macOS while leaving other logic the same. Unused on other platforms. -
_NIX_TRACE_BUILT_OUTPUTS
- Dumps all the derivation paths alongside their outputs as lines into a file of the given name.
Used by the functional test framework
-
NIX_DAEMON_PACKAGE
- Runs the test suite against an alternate Nix daemon with the current client.Expected value: something like
/nix/store/...-nix-2.18.2
-
NIX_CLIENT_PACKAGE
- Runs the test suite against an alternate Nix client with the current daemon.Expected value: something like
/nix/store/...-nix-2.18.2
-
NIX_TESTS_CA_BY_DEFAULT
- Pass__contentAddressed
,outputHashMode
andoutputHashAlgo
to builds of some input-addressed derivations in the test suite.Expected value: 1
-
TEST_DATA
- Not an environment variable! This is used in repl characterization tests to refer totests/functional/repl_characterization/data
. More specifically, that path is replaced with the string$TEST_DATA
in output for reproducibility. -
TEST_HOME
(output) - Set to the temporary directory that is set as$HOME
inside the tests, underneath$TEST_ROOT
. -
TEST_ROOT
(output) - Set to the temporary directory that is created for each test to mess with. -
_NIX_TEST_DAEMON_PID
(output) - Used to track the daemon pid to be able to kill it.Provided value: Daemon pid as a base-10 integer, e.g. 2345