1# Writing Tests {#sec-writing-nixos-tests}
2
3A NixOS test is a module that has the following structure:
4
5```nix
6{
7
8 # One or more machines:
9 nodes = {
10 machine =
11 { config, pkgs, ... }:
12 {
13 # ...
14 };
15 machine2 =
16 { config, pkgs, ... }:
17 {
18 # ...
19 };
20 # …
21 };
22
23 testScript = ''
24 Python code…
25 '';
26}
27```
28
29We refer to the whole test above as a test module, whereas the values
30in [`nodes.<name>`](#test-opt-nodes) are NixOS modules themselves.
31
32The option [`testScript`](#test-opt-testScript) is a piece of Python code that executes the
33test (described below). During the test, it will start one or more
34virtual machines, the configuration of which is described by
35the option [`nodes`](#test-opt-nodes).
36
37An example of a single-node test is
38[`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix).
39It only needs a single machine to test whether users can log in
40on the virtual console, whether device ownership is correctly maintained
41when switching between consoles, and so on. An interesting multi-node test is
42[`nfs/simple.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix).
43It uses two client nodes to test correct locking across server crashes.
44
45## Calling a test {#sec-calling-nixos-tests}
46
47Tests are invoked differently depending on whether the test is part of NixOS or lives in a different project.
48
49### Testing within NixOS {#sec-call-nixos-test-in-nixos}
50
51Tests that are part of NixOS are added to [`nixos/tests/all-tests.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/all-tests.nix).
52
53```nix
54{ hostname = runTest ./hostname.nix; }
55```
56
57Overrides can be added by defining an anonymous module in `all-tests.nix`.
58
59```nix
60{
61 hostname = runTest {
62 imports = [ ./hostname.nix ];
63 defaults.networking.firewall.enable = false;
64 };
65}
66```
67
68You can run a test with attribute name `hostname` in `nixos/tests/all-tests.nix` by invoking:
69
70```shell
71cd /my/git/clone/of/nixpkgs
72nix-build -A nixosTests.hostname
73```
74
75### Testing outside the NixOS project {#sec-call-nixos-test-outside-nixos}
76
77Outside the `nixpkgs` repository, you can use the `runNixOSTest` function from
78`pkgs.testers`:
79
80```nix
81let
82 pkgs = import <nixpkgs> { };
83
84in
85pkgs.testers.runNixOSTest {
86 imports = [ ./test.nix ];
87 defaults.services.foo.package = mypkg;
88}
89```
90
91`runNixOSTest` returns a derivation that runs the test.
92
93## Configuring the nodes {#sec-nixos-test-nodes}
94
95There are a few special NixOS options for test VMs:
96
97`virtualisation.memorySize`
98
99: The memory of the VM in MiB (1024×1024 bytes).
100
101`virtualisation.vlans`
102
103: The virtual networks to which the VM is connected. See
104 [`nat.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nat.nix)
105 for an example.
106
107`virtualisation.writableStore`
108
109: By default, the Nix store in the VM is not writable. If you enable
110 this option, a writable union file system is mounted on top of the
111 Nix store to make it appear writable. This is necessary for tests
112 that run Nix operations that modify the store.
113
114For more options, see the module
115[`qemu-vm.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix).
116
117The test script is a sequence of Python statements that perform various
118actions, such as starting VMs, executing commands in the VMs, and so on.
119Each virtual machine is represented as an object stored in the variable
120`name` if this is also the identifier of the machine in the declarative
121config. If you specified a node `nodes.machine`, the following example starts the
122machine, waits until it has finished booting, then executes a command
123and checks that the output is more-or-less correct:
124
125```py
126machine.start()
127machine.wait_for_unit("default.target")
128t.assertIn("Linux", machine.succeed("uname"), "Wrong OS")
129```
130
131The first line is technically unnecessary; machines are implicitly started
132when you first execute an action on them (such as `wait_for_unit` or
133`succeed`). If you have multiple machines, you can speed up the test by
134starting them in parallel:
135
136```py
137start_all()
138```
139
140Under the variable `t`, all assertions from [`unittest.TestCase`](https://docs.python.org/3/library/unittest.html) are available.
141
142If the hostname of a node contains characters that can't be used in a
143Python variable name, those characters will be replaced with
144underscores in the variable name, so `nodes.machine-a` will be exposed
145to Python as `machine_a`.
146
147## Machine objects {#ssec-machine-objects}
148
149The following methods are available on machine objects:
150
151@PYTHON_MACHINE_METHODS@
152
153To test user units declared by `systemd.user.services` the optional
154`user` argument can be used:
155
156```py
157machine.start()
158machine.wait_for_x()
159machine.wait_for_unit("xautolock.service", "x-session-user")
160```
161
162This applies to `systemctl`, `get_unit_info`, `wait_for_unit`,
163`start_job` and `stop_job`.
164
165For faster dev cycles it's also possible to disable the code-linters
166(this shouldn't be committed though):
167
168```nix
169{
170 skipLint = true;
171 nodes.machine =
172 { config, pkgs, ... }:
173 {
174 # configuration…
175 };
176
177 testScript = ''
178 Python code…
179 '';
180}
181```
182
183This will produce a Nix warning at evaluation time. To fully disable the
184linter, wrap the test script in comment directives to disable the Black
185linter directly (again, don't commit this within the Nixpkgs
186repository):
187
188```nix
189{
190 testScript = ''
191 # fmt: off
192 Python code…
193 # fmt: on
194 '';
195}
196```
197
198Similarly, the type checking of test scripts can be disabled in the following
199way:
200
201```nix
202{
203 skipTypeCheck = true;
204 nodes.machine =
205 { config, pkgs, ... }:
206 {
207 # configuration…
208 };
209}
210```
211
212## Failing tests early {#ssec-failing-tests-early}
213
214To fail tests early when certain invariants are no longer met (instead of waiting for the build to time out), the decorator `polling_condition` is provided. For example, if we are testing a program `foo` that should not quit after being started, we might write the following:
215
216```py
217@polling_condition
218def foo_running():
219 machine.succeed("pgrep -x foo")
220
221
222machine.succeed("foo --start")
223machine.wait_until_succeeds("pgrep -x foo")
224
225with foo_running:
226 ... # Put `foo` through its paces
227```
228
229`polling_condition` takes the following (optional) arguments:
230
231`seconds_interval`
232
233: specifies how often the condition should be polled:
234
235```py
236@polling_condition(seconds_interval=10)
237def foo_running():
238 machine.succeed("pgrep -x foo")
239```
240
241`description`
242
243: is used in the log when the condition is checked. If this is not provided, the description is pulled from the docstring of the function. These two are therefore equivalent:
244
245```py
246@polling_condition
247def foo_running():
248 "check that foo is running"
249 machine.succeed("pgrep -x foo")
250```
251
252```py
253@polling_condition(description="check that foo is running")
254def foo_running():
255 machine.succeed("pgrep -x foo")
256```
257
258## Adding Python packages to the test script {#ssec-python-packages-in-test-script}
259
260When additional Python libraries are required in the test script, they can be
261added using the parameter `extraPythonPackages`. For example, you could add
262`numpy` like this:
263
264```nix
265{
266 extraPythonPackages = p: [ p.numpy ];
267
268 nodes = { };
269
270 # Type checking on extra packages doesn't work yet
271 skipTypeCheck = true;
272
273 testScript = ''
274 import numpy as np
275 assert str(np.zeros(4)) == "[0. 0. 0. 0.]"
276 '';
277}
278```
279
280In that case, `numpy` is chosen from the generic `python3Packages`.
281
282## Overriding a test {#sec-override-nixos-test}
283
284The NixOS test framework returns tests with multiple overriding methods.
285
286`overrideTestDerivation` *function*
287: Like applying `overrideAttrs` on the [test](#test-opt-test) derivation.
288
289 This is a convenience for `extend` with an override on the [`rawTestDerivationArg`](#test-opt-rawTestDerivationArg) option.
290
291 *function*
292 : An extension function, e.g. `finalAttrs: prevAttrs: { /* … */ }`, the result of which is passed to [`mkDerivation`](https://nixos.org/manual/nixpkgs/stable/#sec-using-stdenv).
293 Just as with `overrideAttrs`, an abbreviated form can be used, e.g. `prevAttrs: { /* … */ }` or even `{ /* … */ }`.
294 See [`lib.extends`](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.fixedPoints.extends).
295
296`extendNixOS { module = ` *module* `; specialArgs = ` *specialArgs* `; }`
297: Evaluates the test with additional NixOS modules and/or arguments.
298
299 `module`
300 : A NixOS module to add to all the nodes in the test. Sets test option [`extraBaseModules`](#test-opt-extraBaseModules).
301
302 `specialArgs`
303 : An attribute set of arguments to pass to all NixOS modules. These override the existing arguments, as well as any `_module.args.<name>` that the modules may define. Sets test option [`node.specialArgs`](#test-opt-node.specialArgs).
304
305 This is a convenience function for `extend` that overrides the aforementioned test options.
306
307 :::{.example #ex-nixos-test-extendNixOS}
308
309 # Using extendNixOS in `passthru.tests` to make `(openssh.tests.overrideAttrs f).tests.nixos` coherent
310
311 ```nix
312 mkDerivation (finalAttrs: {
313 # …
314 passthru = {
315 tests = {
316 nixos = nixosTests.openssh.extendNixOS {
317 module = {
318 services.openssh.package = finalAttrs.finalPackage;
319 };
320 };
321 };
322 };
323 })
324 ```
325 :::
326
327`extend { modules = ` *modules* `; specialArgs = ` *specialArgs* `; }`
328: Adds new `nixosTest` modules and/or module arguments to the test, which are evaluated together with the existing modules and [built-in options](#sec-test-options-reference).
329
330 If you're only looking to extend the _NixOS_ configurations of the test, and not something else about the test, you may use the `extendNixOS` convenience function instead.
331
332 `modules`
333 : A list of modules to add to the test. These are added to the existing modules and then [evaluated](https://nixos.org/manual/nixpkgs/stable/index.html#module-system-lib-evalModules) together.
334
335 `specialArgs`
336 : An attribute of arguments to pass to the test. These override the existing arguments, as well as any `_module.args.<name>` that the modules may define. See [`evalModules`/`specialArgs`](https://nixos.org/manual/nixpkgs/stable/#module-system-lib-evalModules-param-specialArgs).
337
338## Test Options Reference {#sec-test-options-reference}
339
340The following options can be used when writing tests.
341
342```{=include=} options
343id-prefix: test-opt-
344list-id: test-options-list
345source: @NIXOS_TEST_OPTIONS_JSON@
346```
347
348## Accessing VMs in the sandbox with SSH {#sec-test-sandbox-breakpoint}
349
350::: {.note}
351For debugging with SSH access into the machines, it's recommended to try using
352[the interactive driver](#sec-running-nixos-tests-interactively) with its
353[SSH backdoor](#sec-nixos-test-ssh-access) first.
354
355This feature is mostly intended to debug flaky test failures that aren't
356reproducible elsewhere.
357:::
358
359As explained in [](#sec-nixos-test-ssh-access), it's possible to configure an
360SSH backdoor based on AF_VSOCK. This can be used to SSH into a VM of a running
361build in a sandbox.
362
363This can be done when something in the test fails, e.g.
364
365```nix
366{
367 nodes.machine = { };
368
369 sshBackdoor.enable = true;
370 enableDebugHook = true;
371
372 testScript = ''
373 start_all()
374 machine.succeed("false") # this will fail
375 '';
376}
377```
378
379For the AF_VSOCK feature to work, `/dev/vhost-vsock` is needed in the sandbox
380which can be done with e.g.
381
382```
383nix-build -A nixosTests.foo --option sandbox-paths /dev/vhost-vsock
384```
385
386This will halt the test execution on a test-failure and print instructions
387on how to enter the sandbox shell of the VM test. Inside, one can log into
388e.g. `machine` with
389
390```
391ssh -F ./ssh_config vsock/3
392```
393
394As described in [](#sec-nixos-test-ssh-access), the numbers for vsock start at
395`3` instead of `1`. So the first VM in the network (sorted alphabetically) can
396be accessed with `vsock/3`.
397
398Alternatively, it's possible to explicitly set a breakpoint with
399`debug.breakpoint()`. This also has the benefit, that one can step through
400`testScript` with `pdb` like this:
401
402```
403$ sudo /nix/store/eeeee-attach <id>
404bash# telnet 127.0.0.1 4444
405pdb$ …
406```