1# Testers {#chap-testers}
2
3This chapter describes several testing builders which are available in the `testers` namespace.
4
5## `hasPkgConfigModules` {#tester-hasPkgConfigModules}
6
7<!-- Old anchor name so links still work -->
8[]{#tester-hasPkgConfigModule}
9Checks whether a package exposes a given list of `pkg-config` modules.
10If the `moduleNames` argument is omitted, `hasPkgConfigModules` will use `meta.pkgConfigModules`.
11
12:::{.example #ex-haspkgconfigmodules-defaultvalues}
13
14# Check that `pkg-config` modules are exposed using default values
15
16```nix
17{
18 passthru.tests.pkg-config = testers.hasPkgConfigModules { package = finalAttrs.finalPackage; };
19
20 meta.pkgConfigModules = [ "libfoo" ];
21}
22```
23
24:::
25
26:::{.example #ex-haspkgconfigmodules-explicitmodules}
27
28# Check that `pkg-config` modules are exposed using explicit module names
29
30```nix
31{
32 passthru.tests.pkg-config = testers.hasPkgConfigModules {
33 package = finalAttrs.finalPackage;
34 moduleNames = [ "libfoo" ];
35 };
36}
37```
38
39:::
40
41## `hasCmakeConfigModules` {#tester-hasCmakeConfigModules}
42
43Checks whether a package exposes a given list of `*config.cmake` modules.
44Note the moduleNames used in cmake find_package are case sensitive.
45
46:::{.example #ex-hascmakeconfigmodules}
47
48# Check that `*config.cmake` modules are exposed using explicit module names
49
50```nix
51{
52 passthru.tests.cmake-config = testers.hasCmakeConfigModules {
53 package = finalAttrs.finalPackage;
54 moduleNames = [ "Foo" ];
55 };
56}
57```
58
59:::
60
61## `lycheeLinkCheck` {#tester-lycheeLinkCheck}
62
63Check a packaged static site's links with the [`lychee` package](https://search.nixos.org/packages?show=lychee&type=packages&query=lychee).
64
65You may use Nix to reproducibly build static websites, such as for software documentation.
66Some packages will install documentation in their `out` or `doc` outputs, or maybe you have dedicated package where you've made your static site reproducible by running a generator, such as [Hugo](https://gohugo.io/) or [mdBook](https://rust-lang.github.io/mdBook/), in a derivation.
67
68If you have a static site that can be built with Nix, you can use `lycheeLinkCheck` to check that the hyperlinks in your site are correct, and do so as part of your Nix workflow and CI.
69
70:::{.example #ex-lycheelinkcheck}
71
72# Check hyperlinks in the `nix` documentation
73
74```nix
75testers.lycheeLinkCheck { site = nix.doc + "/share/doc/nix/manual"; }
76```
77
78:::
79
80### Return value {#tester-lycheeLinkCheck-return}
81
82This tester produces a package that does not produce useful outputs, but only succeeds if the hyperlinks in your site are correct. The build log will list the broken links.
83
84It has two modes:
85
86- Build the returned derivation; its build process will check that internal hyperlinks are correct. This runs in the sandbox, so it will not check external hyperlinks, but it is quick and reliable.
87
88- Invoke the `.online` attribute with [`nix run`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-run) ([experimental](https://nixos.org/manual/nix/stable/contributing/experimental-features#xp-feature-nix-command)). This runs outside the sandbox, and checks that both internal and external hyperlinks are correct.
89 Example:
90
91 ```shell
92 nix run nixpkgs#lychee.tests.ok.online
93 ```
94
95### Inputs {#tester-lycheeLinkCheck-inputs}
96
97`site` (path or derivation) {#tester-lycheeLinkCheck-param-site}
98
99: The path to the files to check.
100
101`remap` (attribe set, optional) {#tester-lycheeLinkCheck-param-remap}
102
103: An attribute set where the attribute names are regular expressions.
104 The values should be strings, derivations, or path values.
105
106 In the returned check's default configuration, external URLs are only checked when you run the `.online` attribute.
107
108 By adding remappings, you can check offline that URLs to external resources are correct, by providing a stand-in from the file system.
109
110 Before checking the existence of a URL, the regular expressions are matched and replaced by their corresponding values.
111
112 Example:
113
114 ```nix
115 {
116 "https://nix\\.dev/manual/nix/[a-z0-9.-]*" = "${nix.doc}/share/doc/nix/manual";
117 "https://nixos\\.org/manual/nix/(un)?stable" =
118 "${emptyDirectory}/placeholder-to-disallow-old-nix-docs-urls";
119 }
120 ```
121
122 Store paths in the attribute values are automatically prefixed with `file://`, because lychee requires this for paths in the file system.
123 If this is a problem, or if you need to control the order in which replacements are performed, use `extraConfig.remap` instead.
124
125`extraConfig` (attribute set) {#tester-lycheeLinkCheck-param-extraConfig}
126
127: Extra configuration to pass to `lychee` in its [configuration file](https://github.com/lycheeverse/lychee/blob/master/lychee.example.toml).
128 It is automatically [translated](https://nixos.org/manual/nixos/stable/index.html#sec-settings-nix-representable) to TOML.
129
130 Example: `{ "include_verbatim" = true; }`
131
132`lychee` (derivation, optional) {#tester-lycheeLinkCheck-param-lychee}
133
134: The `lychee` package to use.
135
136## `shellcheck` {#tester-shellcheck}
137
138Run files through `shellcheck`, a static analysis tool for shell scripts, failing if there are any issues.
139
140:::{.example #ex-shellcheck}
141# Run `testers.shellcheck`
142
143A single script
144
145```nix
146testers.shellcheck {
147 name = "script";
148 src = ./script.sh;
149}
150```
151
152Multiple files
153
154```nix
155let
156 inherit (lib) fileset;
157in
158testers.shellcheck {
159 name = "nixbsd-activate";
160 src = fileset.toSource {
161 root = ./.;
162 fileset = fileset.unions [
163 ./lib.sh
164 ./nixbsd-activate
165 ];
166 };
167}
168```
169
170:::
171
172### Inputs {#tester-shellcheck-inputs}
173
174`name` (string, optional)
175: The name of the test.
176 `name` will be required at a future point because it massively improves traceability of test failures, but is kept optional for now to avoid breaking existing usages.
177 Defaults to `run-shellcheck`.
178 The name of the derivation produced by the tester is `shellcheck-${name}` when `name` is supplied.
179
180`src` (path-like)
181: The path to the shell script(s) to check.
182 This can be a single file or a directory containing shell files.
183 All files in `src` will be checked, so you may want to provide `fileset`-based source instead of a whole directory.
184
185### Return value {#tester-shellcheck-return}
186
187A derivation that runs `shellcheck` on the given script(s), producing an empty output if no issues are found.
188The build will fail if `shellcheck` finds any issues.
189
190## `shfmt` {#tester-shfmt}
191
192Run files through `shfmt`, a shell script formatter, failing if any files are reformatted.
193
194:::{.example #ex-shfmt}
195# Run `testers.shfmt`
196
197A single script
198
199```nix
200testers.shfmt {
201 name = "script";
202 src = ./script.sh;
203}
204```
205
206Multiple files
207
208```nix
209let
210 inherit (lib) fileset;
211in
212testers.shfmt {
213 name = "nixbsd";
214 src = fileset.toSource {
215 root = ./.;
216 fileset = fileset.unions [
217 ./lib.sh
218 ./nixbsd-activate
219 ];
220 };
221}
222```
223
224:::
225
226### Inputs {#tester-shfmt-inputs}
227
228`name` (string)
229: The name of the test.
230 `name` is required because it massively improves traceability of test failures.
231 The name of the derivation produced by the tester is `shfmt-${name}`.
232
233`src` (path-like)
234: The path to the shell script(s) to check.
235 This can be a single file or a directory containing shell files.
236 All files in `src` will be checked, so you may want to provide `fileset`-based source instead of a whole directory.
237
238`indent` (integer, optional)
239: The number of spaces to use for indentation.
240 Defaults to `2`.
241 A value of `0` indents with tabs.
242
243### Return value {#tester-shfmt-return}
244
245A derivation that runs `shfmt` on the given script(s), producing an empty output upon success.
246The build will fail if `shfmt` reformats anything.
247
248## `testVersion` {#tester-testVersion}
249
250Checks that the output from running a command contains the specified version string in it as a whole word.
251
252NOTE: This is a check you add to `passthru.tests` which is mainly run by OfBorg, but not in Hydra. If you want a version check failure to block the build altogether, then [`versionCheckHook`](#versioncheckhook) is the tool you're looking for (and recommended for quick builds). The motivation for adding either of these checks would be:
253
254- Catch dynamic linking errors and such and missing environment variables that should be added by wrapping.
255- Probable protection against accidentally building the wrong version, for example when using an "old" hash in a fixed-output derivation.
256
257By default, the command to be run will be inferred from the given `package` attribute:
258it will check `meta.mainProgram` first, and fall back to `pname` or `name`.
259The default argument to the command is `--version`, and the version to be checked will be inferred from the given `package` attribute as well.
260
261:::{.example #ex-testversion-hello}
262
263# Check a program version using all the default values
264
265This example will run the command `hello --version`, and then check that the version of the `hello` package is in the output of the command.
266
267```nix
268{ passthru.tests.version = testers.testVersion { package = hello; }; }
269```
270
271:::
272
273:::{.example #ex-testversion-different-commandversion}
274
275# Check the program version using a specified command and expected version string
276
277This example will run the command `leetcode -V`, and then check that `leetcode 0.4.2` is in the output of the command as a whole word (separated by whitespaces).
278This means that an output like "leetcode 0.4.21" would fail the tests, and an output like "You're running leetcode 0.4.2" would pass the tests.
279
280A common usage of the `version` attribute is to specify `version = "v${version}"`.
281
282```nix
283{
284 version = "0.4.2";
285
286 passthru.tests.version = testers.testVersion {
287 package = leetcode-cli;
288 command = "leetcode -V";
289 version = "leetcode ${version}";
290 };
291}
292```
293
294:::
295
296## `testBuildFailure` {#tester-testBuildFailure}
297
298Make sure that a build does not succeed. This is useful for testing testers.
299
300This returns a derivation with an override on the builder, with the following effects:
301
302 - Fail the build when the original builder succeeds
303 - Move `$out` to `$out/result`, if it exists (assuming `out` is the default output)
304 - Save the build log to `$out/testBuildFailure.log` (same)
305
306While `testBuildFailure` is designed to keep changes to the original builder's environment to a minimum, some small changes are inevitable:
307
308 - The file `$TMPDIR/testBuildFailure.log` is present. It should not be deleted.
309 - `stdout` and `stderr` are a pipe instead of a tty. This could be improved.
310 - One or two extra processes are present in the sandbox during the original builder's execution.
311 - The derivation and output hashes are different, but not unusual.
312 - The derivation includes a dependency on `buildPackages.bash` and `expect-failure.sh`, which is built to include a transitive dependency on `buildPackages.coreutils` and possibly more.
313 These are not added to `PATH` or any other environment variable, so they should be hard to observe.
314
315:::{.example #ex-testBuildFailure-showingenvironmentchanges}
316
317# Check that a build fails, and verify the changes made during build
318
319```nix
320runCommand "example"
321 {
322 failed = testers.testBuildFailure (
323 runCommand "fail" { } ''
324 echo ok-ish >$out
325 echo failing though
326 exit 3
327 ''
328 );
329 }
330 ''
331 grep -F 'ok-ish' $failed/result
332 grep -F 'failing though' $failed/testBuildFailure.log
333 [[ 3 = $(cat $failed/testBuildFailure.exit) ]]
334 touch $out
335 ''
336```
337
338:::
339
340## `testBuildFailure'` {#tester-testBuildFailurePrime}
341
342This tester wraps the functionality provided by [`testers.testBuildFailure`](#tester-testBuildFailure) to make writing checks easier by simplifying checking the exit code of the builder and asserting the existence of entries in the builder's log.
343Additionally, users may specify a script containing additional checks, accessing the result of applying `testers.testBuildFailure` through the variable `failed`.
344
345NOTE: This tester will produce an empty output and exit with success if none of the checks fail; there is no need to `touch "$out"` in `script`.
346
347:::{.example #ex-testBuildFailurePrime-doc-example}
348
349# Check that a build fails, and verify the changes made during build
350
351Re-using the example from [`testers.testBuildFailure`](#ex-testBuildFailure-showingenvironmentchanges), we can see how common checks are made easier and remove the need for `runCommand`:
352
353```nix
354testers.testBuildFailure' {
355 drv = runCommand "doc-example" { } ''
356 echo ok-ish >"$out"
357 echo failing though
358 exit 3
359 '';
360 expectedBuilderExitCode = 3;
361 expectedBuilderLogEntries = [ "failing though" ];
362 script = ''
363 grep --silent -F 'ok-ish' "$failed/result"
364 '';
365}
366```
367
368:::
369
370### Inputs {#tester-testBuildFailurePrime-inputs}
371
372`drv` (derivation)
373
374: The failing derivation to wrap with `testBuildFailure`.
375
376`name` (string, optional)
377
378: The name of the test.
379 When not provided, this value defaults to `testBuildFailure-${(testers.testBuildFailure drv).name}`.
380
381`expectedBuilderExitCode` (integer, optional)
382
383: The expected exit code of the builder of `drv`.
384 When not provided, this value defaults to `1`.
385
386`expectedBuilderLogEntries` (array of string-like values, optional)
387
388: A list of string-like values which must be found in the builder's log by exact match.
389 When not provided, this value defaults to `[ ]`.
390
391 NOTE: Patterns and regular expressions are not supported.
392
393`script` (string, optional)
394
395: A string containing additional checks to run.
396 When not provided, this value defaults to `""`.
397 The result of `testers.testBuildFailure drv` is available through the variable `failed`.
398 As an example, the builder's log is at `"$failed/testBuildFailure.log"`.
399
400### Return value {#tester-testBuildFailurePrime-return}
401
402The tester produces an empty output and only succeeds when the checks using `expectedBuilderExitCode`, `expectedBuilderLogEntries`, and `script` succeed.
403
404## `testEqualContents` {#tester-testEqualContents}
405
406Check that two paths have the same contents.
407
408`assertion` (string)
409
410: A message that is printed before the comparison, after `Checking:`.
411
412`expected` (path or value coercible to store path)
413
414: The path to the expected [file system object] content
415
416`actual` (value coercible to store path) <!-- path value is possible, but wrong in practice, but let's not bother readers with our predictions -->
417
418: The path to the actual file system object content to check
419
420`postFailureMessage` (string)
421
422: A message that is printed last if the file system object contents at the two paths don't match exactly.
423
424:::{.example #ex-testEqualContents-toyexample}
425
426# Check that two paths have the same contents
427
428```nix
429testers.testEqualContents {
430 assertion = "sed -e performs replacement";
431 expected = writeText "expected" ''
432 foo baz baz
433 '';
434 actual =
435 runCommand "actual"
436 {
437 # not really necessary for a package that's in stdenv
438 nativeBuildInputs = [ gnused ];
439 base = writeText "base" ''
440 foo bar baz
441 '';
442 }
443 ''
444 sed -e 's/bar/baz/g' $base >$out
445 '';
446 # if applicable
447 postFailureMessage = ''
448 The bar-baz replacer produced an unexpected result.
449 If the new behavior is acceptable and validated against the bar-baz specification, run ./adopt-new-bar-baz-result.sh to adjust this test and require the new behavior.
450 '';
451}
452```
453
454:::
455
456## `testEqualArrayOrMap` {#tester-testEqualArrayOrMap}
457
458Check that bash arrays (including associative arrays, referred to as "maps") are populated correctly.
459
460This can be used to ensure setup hooks are registered in a certain order, or to write unit tests for shell functions which transform arrays.
461
462:::{.example #ex-testEqualArrayOrMap-test-function-add-cowbell}
463
464# Test a function which appends a value to an array
465
466```nix
467testers.testEqualArrayOrMap {
468 name = "test-function-add-cowbell";
469 valuesArray = [
470 "cowbell"
471 "cowbell"
472 ];
473 expectedArray = [
474 "cowbell"
475 "cowbell"
476 "cowbell"
477 ];
478 script = ''
479 addCowbell() {
480 local -rn arrayNameRef="$1"
481 arrayNameRef+=( "cowbell" )
482 }
483
484 nixLog "appending all values in valuesArray to actualArray"
485 for value in "''${valuesArray[@]}"; do
486 actualArray+=( "$value" )
487 done
488
489 nixLog "applying addCowbell"
490 addCowbell actualArray
491 '';
492}
493```
494
495:::
496
497### Inputs {#tester-testEqualArrayOrMap-inputs}
498
499NOTE: Internally, this tester uses `__structuredAttrs` to handle marshalling between Nix expressions and shell variables.
500This imposes the restriction that arrays and "maps" have values which are string-like.
501
502NOTE: At least one of `expectedArray` and `expectedMap` must be provided.
503
504`name` (string)
505
506: The name of the test.
507
508`script` (string)
509
510: The singular task of `script` is to populate `actualArray` or `actualMap` (it may populate both).
511 To do this, `script` may access the following shell variables:
512
513 - `valuesArray` (available when `valuesArray` is provided to the tester)
514 - `valuesMap` (available when `valuesMap` is provided to the tester)
515 - `actualArray` (available when `expectedArray` is provided to the tester)
516 - `actualMap` (available when `expectedMap` is provided to the tester)
517
518 While both `expectedArray` and `expectedMap` are in scope during the execution of `script`, they *must not* be accessed or modified from within `script`.
519
520`valuesArray` (array of string-like values, optional)
521
522: An array of string-like values.
523 This array may be used within `script`.
524
525`valuesMap` (attribute set of string-like values, optional)
526
527: An attribute set of string-like values.
528 This attribute set may be used within `script`.
529
530`expectedArray` (array of string-like values, optional)
531
532: An array of string-like values.
533 This array *must not* be accessed or modified from within `script`.
534 When provided, `script` is expected to populate `actualArray`.
535
536`expectedMap` (attribute set of string-like values, optional)
537
538: An attribute set of string-like values.
539 This attribute set *must not* be accessed or modified from within `script`.
540 When provided, `script` is expected to populate `actualMap`.
541
542### Return value {#tester-testEqualArrayOrMap-return}
543
544The tester produces an empty output and only succeeds when `expectedArray` and `expectedMap` match `actualArray` and `actualMap`, respectively, when non-null.
545The build log will contain differences encountered.
546
547## `testEqualDerivation` {#tester-testEqualDerivation}
548
549Checks that two packages produce the exact same build instructions.
550
551This can be used to make sure that a certain difference of configuration, such as the presence of an overlay does not cause a cache miss.
552
553When the derivations are equal, the return value is an empty file.
554Otherwise, the build log explains the difference via `nix-diff`.
555
556:::{.example #ex-testEqualDerivation-hello}
557
558# Check that two packages produce the same derivation
559
560```nix
561testers.testEqualDerivation "The hello package must stay the same when enabling checks." hello (
562 hello.overrideAttrs (o: {
563 doCheck = true;
564 })
565)
566```
567
568:::
569
570## `invalidateFetcherByDrvHash` {#tester-invalidateFetcherByDrvHash}
571
572Use the derivation hash to invalidate the output via name, for testing.
573
574Type: `(a@{ name, ... } -> Derivation) -> a -> Derivation`
575
576Normally, fixed output derivations can and should be cached by their output hash only, but for testing we want to re-fetch everytime the fetcher changes.
577
578Changes to the fetcher become apparent in the drvPath, which is a hash of how to fetch, rather than a fixed store path.
579By inserting this hash into the name, we can make sure to re-run the fetcher every time the fetcher changes.
580
581This relies on the assumption that Nix isn't clever enough to reuse its database of local store contents to optimize fetching.
582
583You might notice that the "salted" name derives from the normal invocation, not the final derivation.
584`invalidateFetcherByDrvHash` has to invoke the fetcher function twice:
585once to get a derivation hash, and again to produce the final fixed output derivation.
586
587:::{.example #ex-invalidateFetcherByDrvHash-nix}
588
589# Prevent nix from reusing the output of a fetcher
590
591```nix
592{
593 tests.fetchgit = testers.invalidateFetcherByDrvHash fetchgit {
594 name = "nix-source";
595 url = "https://github.com/NixOS/nix";
596 rev = "9d9dbe6ed05854e03811c361a3380e09183f4f4a";
597 hash = "sha256-7DszvbCNTjpzGRmpIVAWXk20P0/XTrWZ79KSOGLrUWY=";
598 };
599}
600```
601
602:::
603
604## `runCommand` {#tester-runCommand}
605
606`runCommand :: { name, script, stdenv ? stdenvNoCC, hash ? "...", ... } -> Derivation`
607
608This is a wrapper around `pkgs.runCommandWith`, which
609- produces a fixed-output derivation, enabling the command(s) to access the network ;
610- salts the derivation's name based on its inputs, ensuring the command is re-run whenever the inputs change.
611
612It accepts the following attributes:
613- the derivation's `name` ;
614- the `script` to be executed ;
615- `stdenv`, the environment to use, defaulting to `stdenvNoCC` ;
616- the derivation's output `hash`, defaulting to the empty file's.
617 The derivation's `outputHashMode` is set by default to recursive, so the `script` can output a directory as well.
618
619All other attributes are passed through to [`mkDerivation`](#sec-using-stdenv),
620including `nativeBuildInputs` to specify dependencies available to the `script`.
621
622:::{.example #ex-tester-runCommand-nix}
623
624# Run a command with network access
625
626```nix
627testers.runCommand {
628 name = "access-the-internet";
629 script = ''
630 curl -o /dev/null https://example.com
631 touch $out
632 '';
633 nativeBuildInputs = with pkgs; [
634 cacert
635 curl
636 ];
637}
638```
639
640:::
641
642## `runNixOSTest` {#tester-runNixOSTest}
643
644A helper function that behaves exactly like the NixOS `runTest`, except it also assigns this Nixpkgs package set as the `pkgs` of the test and makes the `nixpkgs.*` options read-only.
645
646If your test is part of the Nixpkgs repository, or if you need a more general entrypoint, see ["Calling a test" in the NixOS manual](https://nixos.org/manual/nixos/stable/index.html#sec-calling-nixos-tests).
647
648:::{.example #ex-runNixOSTest-hello}
649
650# Run a NixOS test using `runNixOSTest`
651
652```nix
653pkgs.testers.runNixOSTest (
654 { lib, ... }:
655 {
656 name = "hello";
657 nodes.machine =
658 { pkgs, ... }:
659 {
660 environment.systemPackages = [ pkgs.hello ];
661 };
662 testScript = ''
663 machine.succeed("hello")
664 '';
665 }
666)
667```
668
669:::
670
671## `nixosTest` {#tester-nixosTest}
672
673Run a NixOS VM network test using this evaluation of Nixpkgs.
674
675NOTE: This function is primarily for external use. NixOS itself uses `make-test-python.nix` directly. Packages defined in Nixpkgs [reuse NixOS tests via `nixosTests`, plural](#ssec-nixos-tests-linking).
676
677It is mostly equivalent to the function `import ./make-test-python.nix` from the [NixOS manual](https://nixos.org/nixos/manual/index.html#sec-nixos-tests), except that the current application of Nixpkgs (`pkgs`) will be used, instead of letting NixOS invoke Nixpkgs anew.
678
679If a test machine needs to set NixOS options under `nixpkgs`, it must set only the `nixpkgs.pkgs` option.
680
681### Parameter {#tester-nixosTest-parameter}
682
683A [NixOS VM test network](https://nixos.org/nixos/manual/index.html#sec-nixos-tests), or path to it. Example:
684
685```nix
686{
687 name = "my-test";
688 nodes = {
689 machine1 =
690 {
691 lib,
692 pkgs,
693 nodes,
694 ...
695 }:
696 {
697 environment.systemPackages = [ pkgs.hello ];
698 services.foo.enable = true;
699 };
700 # machine2 = ...;
701 };
702 testScript = ''
703 start_all()
704 machine1.wait_for_unit("foo.service")
705 machine1.succeed("hello | foo-send")
706 '';
707}
708```
709
710### Result {#tester-nixosTest-result}
711
712A derivation that runs the VM test.
713
714Notable attributes:
715
716 * `nodes`: the evaluated NixOS configurations. Useful for debugging and exploring the configuration.
717
718 * `driverInteractive`: a script that launches an interactive Python session in the context of the `testScript`.
719
720[file system object]: https://nix.dev/manual/nix/latest/store/file-system-object