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 machine2 = 13 { config, pkgs, ... }: { /* ... */ }; 14 # … 15 }; 16 17 testScript = 18 '' 19 Python code… 20 ''; 21} 22``` 23 24We refer to the whole test above as a test module, whereas the values 25in [`nodes.<name>`](#test-opt-nodes) are NixOS modules themselves. 26 27The option [`testScript`](#test-opt-testScript) is a piece of Python code that executes the 28test (described below). During the test, it will start one or more 29virtual machines, the configuration of which is described by 30the option [`nodes`](#test-opt-nodes). 31 32An example of a single-node test is 33[`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix). 34It only needs a single machine to test whether users can log in 35on the virtual console, whether device ownership is correctly maintained 36when switching between consoles, and so on. An interesting multi-node test is 37[`nfs/simple.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix). 38It uses two client nodes to test correct locking across server crashes. 39 40## Calling a test {#sec-calling-nixos-tests} 41 42Tests are invoked differently depending on whether the test is part of NixOS or lives in a different project. 43 44### Testing within NixOS {#sec-call-nixos-test-in-nixos} 45 46Tests 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). 47 48```nix 49{ 50 hostname = runTest ./hostname.nix; 51} 52``` 53 54Overrides can be added by defining an anonymous module in `all-tests.nix`. 55 56```nix 57{ 58 hostname = runTest { 59 imports = [ ./hostname.nix ]; 60 defaults.networking.firewall.enable = false; 61 }; 62} 63``` 64 65You can run a test with attribute name `hostname` in `nixos/tests/all-tests.nix` by invoking: 66 67```shell 68cd /my/git/clone/of/nixpkgs 69nix-build -A nixosTests.hostname 70``` 71 72### Testing outside the NixOS project {#sec-call-nixos-test-outside-nixos} 73 74Outside the `nixpkgs` repository, you can use the `runNixOSTest` function from 75`pkgs.testers`: 76 77```nix 78let pkgs = import <nixpkgs> {}; 79in 80 81pkgs.testers.runNixOSTest { 82 imports = [ ./test.nix ]; 83 defaults.services.foo.package = mypkg; 84} 85``` 86 87`runNixOSTest` returns a derivation that runs the test. 88 89## Configuring the nodes {#sec-nixos-test-nodes} 90 91There are a few special NixOS options for test VMs: 92 93`virtualisation.memorySize` 94 95: The memory of the VM in megabytes. 96 97`virtualisation.vlans` 98 99: The virtual networks to which the VM is connected. See 100 [`nat.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nat.nix) 101 for an example. 102 103`virtualisation.writableStore` 104 105: By default, the Nix store in the VM is not writable. If you enable 106 this option, a writable union file system is mounted on top of the 107 Nix store to make it appear writable. This is necessary for tests 108 that run Nix operations that modify the store. 109 110For more options, see the module 111[`qemu-vm.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix). 112 113The test script is a sequence of Python statements that perform various 114actions, such as starting VMs, executing commands in the VMs, and so on. 115Each virtual machine is represented as an object stored in the variable 116`name` if this is also the identifier of the machine in the declarative 117config. If you specified a node `nodes.machine`, the following example starts the 118machine, waits until it has finished booting, then executes a command 119and checks that the output is more-or-less correct: 120 121```py 122machine.start() 123machine.wait_for_unit("default.target") 124t.assertIn("Linux", machine.succeed("uname"), "Wrong OS") 125``` 126 127The first line is technically unnecessary; machines are implicitly started 128when you first execute an action on them (such as `wait_for_unit` or 129`succeed`). If you have multiple machines, you can speed up the test by 130starting them in parallel: 131 132```py 133start_all() 134``` 135 136Under the variable `t`, all assertions from [`unittest.TestCase`](https://docs.python.org/3/library/unittest.html) are available. 137 138If the hostname of a node contains characters that can't be used in a 139Python variable name, those characters will be replaced with 140underscores in the variable name, so `nodes.machine-a` will be exposed 141to Python as `machine_a`. 142 143## Machine objects {#ssec-machine-objects} 144 145The following methods are available on machine objects: 146 147@PYTHON_MACHINE_METHODS@ 148 149To test user units declared by `systemd.user.services` the optional 150`user` argument can be used: 151 152```py 153machine.start() 154machine.wait_for_x() 155machine.wait_for_unit("xautolock.service", "x-session-user") 156``` 157 158This applies to `systemctl`, `get_unit_info`, `wait_for_unit`, 159`start_job` and `stop_job`. 160 161For faster dev cycles it's also possible to disable the code-linters 162(this shouldn't be committed though): 163 164```nix 165{ 166 skipLint = true; 167 nodes.machine = 168 { config, pkgs, ... }: 169 { # configuration… 170 }; 171 172 testScript = 173 '' 174 Python code… 175 ''; 176} 177``` 178 179This will produce a Nix warning at evaluation time. To fully disable the 180linter, wrap the test script in comment directives to disable the Black 181linter directly (again, don't commit this within the Nixpkgs 182repository): 183 184```nix 185{ 186 testScript = 187 '' 188 # fmt: off 189 Python code… 190 # fmt: on 191 ''; 192} 193``` 194 195Similarly, the type checking of test scripts can be disabled in the following 196way: 197 198```nix 199{ 200 skipTypeCheck = true; 201 nodes.machine = 202 { config, pkgs, ... }: 203 { # configuration… 204 }; 205} 206``` 207 208## Failing tests early {#ssec-failing-tests-early} 209 210To 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: 211 212```py 213@polling_condition 214def foo_running(): 215 machine.succeed("pgrep -x foo") 216 217 218machine.succeed("foo --start") 219machine.wait_until_succeeds("pgrep -x foo") 220 221with foo_running: 222 ... # Put `foo` through its paces 223``` 224 225`polling_condition` takes the following (optional) arguments: 226 227`seconds_interval` 228 229: specifies how often the condition should be polled: 230 231```py 232@polling_condition(seconds_interval=10) 233def foo_running(): 234 machine.succeed("pgrep -x foo") 235``` 236 237`description` 238 239: 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: 240 241```py 242@polling_condition 243def foo_running(): 244 "check that foo is running" 245 machine.succeed("pgrep -x foo") 246``` 247 248```py 249@polling_condition(description="check that foo is running") 250def foo_running(): 251 machine.succeed("pgrep -x foo") 252``` 253 254## Adding Python packages to the test script {#ssec-python-packages-in-test-script} 255 256When additional Python libraries are required in the test script, they can be 257added using the parameter `extraPythonPackages`. For example, you could add 258`numpy` like this: 259 260```nix 261{ 262 extraPythonPackages = p: [ p.numpy ]; 263 264 nodes = { }; 265 266 # Type checking on extra packages doesn't work yet 267 skipTypeCheck = true; 268 269 testScript = '' 270 import numpy as np 271 assert str(np.zeros(4)) == "[0. 0. 0. 0.]" 272 ''; 273} 274``` 275 276In that case, `numpy` is chosen from the generic `python3Packages`. 277 278## Test Options Reference {#sec-test-options-reference} 279 280The following options can be used when writing tests. 281 282```{=include=} options 283id-prefix: test-opt- 284list-id: test-options-list 285source: @NIXOS_TEST_OPTIONS_JSON@ 286```