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 hostname = runTest ./hostname.nix; 50``` 51 52Overrides can be added by defining an anonymous module in `all-tests.nix`. 53 54```nix 55 hostname = runTest { 56 imports = [ ./hostname.nix ]; 57 defaults.networking.firewall.enable = false; 58 }; 59``` 60 61You can run a test with attribute name `hostname` in `nixos/tests/all-tests.nix` by invoking: 62 63```shell 64cd /my/git/clone/of/nixpkgs 65nix-build -A nixosTests.hostname 66``` 67 68### Testing outside the NixOS project {#sec-call-nixos-test-outside-nixos} 69 70Outside the `nixpkgs` repository, you can instantiate the test by first importing the NixOS library, 71 72```nix 73let nixos-lib = import (nixpkgs + "/nixos/lib") { }; 74in 75 76nixos-lib.runTest { 77 imports = [ ./test.nix ]; 78 hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs 79 defaults.services.foo.package = mypkg; 80} 81``` 82 83`runTest` returns a derivation that runs the test. 84 85## Configuring the nodes {#sec-nixos-test-nodes} 86 87There are a few special NixOS options for test VMs: 88 89`virtualisation.memorySize` 90 91: The memory of the VM in megabytes. 92 93`virtualisation.vlans` 94 95: The virtual networks to which the VM is connected. See 96 [`nat.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nat.nix) 97 for an example. 98 99`virtualisation.writableStore` 100 101: By default, the Nix store in the VM is not writable. If you enable 102 this option, a writable union file system is mounted on top of the 103 Nix store to make it appear writable. This is necessary for tests 104 that run Nix operations that modify the store. 105 106For more options, see the module 107[`qemu-vm.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix). 108 109The test script is a sequence of Python statements that perform various 110actions, such as starting VMs, executing commands in the VMs, and so on. 111Each virtual machine is represented as an object stored in the variable 112`name` if this is also the identifier of the machine in the declarative 113config. If you specified a node `nodes.machine`, the following example starts the 114machine, waits until it has finished booting, then executes a command 115and checks that the output is more-or-less correct: 116 117```py 118machine.start() 119machine.wait_for_unit("default.target") 120if not "Linux" in machine.succeed("uname"): 121 raise Exception("Wrong OS") 122``` 123 124The first line is technically unnecessary; machines are implicitly started 125when you first execute an action on them (such as `wait_for_unit` or 126`succeed`). If you have multiple machines, you can speed up the test by 127starting them in parallel: 128 129```py 130start_all() 131``` 132 133If the hostname of a node contains characters that can't be used in a 134Python variable name, those characters will be replaced with 135underscores in the variable name, so `nodes.machine-a` will be exposed 136to Python as `machine_a`. 137 138## Machine objects {#ssec-machine-objects} 139 140The following methods are available on machine objects: 141 142`start` 143 144: Start the virtual machine. This method is asynchronous --- it does 145 not wait for the machine to finish booting. 146 147`shutdown` 148 149: Shut down the machine, waiting for the VM to exit. 150 151`crash` 152 153: Simulate a sudden power failure, by telling the VM to exit 154 immediately. 155 156`block` 157 158: Simulate unplugging the Ethernet cable that connects the machine to 159 the other machines. 160 161`unblock` 162 163: Undo the effect of `block`. 164 165`screenshot` 166 167: Take a picture of the display of the virtual machine, in PNG format. 168 The screenshot is linked from the HTML log. 169 170`get_screen_text_variants` 171 172: Return a list of different interpretations of what is currently 173 visible on the machine's screen using optical character 174 recognition. The number and order of the interpretations is not 175 specified and is subject to change, but if no exception is raised at 176 least one will be returned. 177 178 ::: {.note} 179 This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`. 180 ::: 181 182`get_screen_text` 183 184: Return a textual representation of what is currently visible on the 185 machine's screen using optical character recognition. 186 187 ::: {.note} 188 This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`. 189 ::: 190 191`send_monitor_command` 192 193: Send a command to the QEMU monitor. This is rarely used, but allows 194 doing stuff such as attaching virtual USB disks to a running 195 machine. 196 197`send_key` 198 199: Simulate pressing keys on the virtual keyboard, e.g., 200 `send_key("ctrl-alt-delete")`. 201 202`send_chars` 203 204: Simulate typing a sequence of characters on the virtual keyboard, 205 e.g., `send_chars("foobar\n")` will type the string `foobar` 206 followed by the Enter key. 207 208`send_console` 209 210: Send keys to the kernel console. This allows interaction with the systemd 211 emergency mode, for example. Takes a string that is sent, e.g., 212 `send_console("\n\nsystemctl default\n")`. 213 214`execute` 215 216: Execute a shell command, returning a list `(status, stdout)`. 217 218 Commands are run with `set -euo pipefail` set: 219 220 - If several commands are separated by `;` and one fails, the 221 command as a whole will fail. 222 223 - For pipelines, the last non-zero exit status will be returned 224 (if there is one; otherwise zero will be returned). 225 226 - Dereferencing unset variables fails the command. 227 228 - It will wait for stdout to be closed. 229 230 If the command detaches, it must close stdout, as `execute` will wait 231 for this to consume all output reliably. This can be achieved by 232 redirecting stdout to stderr `>&2`, to `/dev/console`, `/dev/null` or 233 a file. Examples of detaching commands are `sleep 365d &`, where the 234 shell forks a new process that can write to stdout and `xclip -i`, where 235 the `xclip` command itself forks without closing stdout. 236 237 Takes an optional parameter `check_return` that defaults to `True`. 238 Setting this parameter to `False` will not check for the return code 239 and return -1 instead. This can be used for commands that shut down 240 the VM and would therefore break the pipe that would be used for 241 retrieving the return code. 242 243 A timeout for the command can be specified (in seconds) using the optional 244 `timeout` parameter, e.g., `execute(cmd, timeout=10)` or 245 `execute(cmd, timeout=None)`. The default is 900 seconds. 246 247`succeed` 248 249: Execute a shell command, raising an exception if the exit status is 250 not zero, otherwise returning the standard output. Similar to `execute`, 251 except that the timeout is `None` by default. See `execute` for details on 252 command execution. 253 254`fail` 255 256: Like `succeed`, but raising an exception if the command returns a zero 257 status. 258 259`wait_until_succeeds` 260 261: Repeat a shell command with 1-second intervals until it succeeds. 262 Has a default timeout of 900 seconds which can be modified, e.g. 263 `wait_until_succeeds(cmd, timeout=10)`. See `execute` for details on 264 command execution. 265 266`wait_until_fails` 267 268: Like `wait_until_succeeds`, but repeating the command until it fails. 269 270`wait_for_unit` 271 272: Wait until the specified systemd unit has reached the "active" 273 state. 274 275`wait_for_file` 276 277: Wait until the specified file exists. 278 279`wait_for_open_port` 280 281: Wait until a process is listening on the given TCP port and IP address 282 (default `localhost`). 283 284`wait_for_closed_port` 285 286: Wait until nobody is listening on the given TCP port and IP address 287 (default `localhost`). 288 289`wait_for_x` 290 291: Wait until the X11 server is accepting connections. 292 293`wait_for_text` 294 295: Wait until the supplied regular expressions matches the textual 296 contents of the screen by using optical character recognition (see 297 `get_screen_text` and `get_screen_text_variants`). 298 299 ::: {.note} 300 This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`. 301 ::: 302 303`wait_for_console_text` 304 305: Wait until the supplied regular expressions match a line of the 306 serial console output. This method is useful when OCR is not 307 possible or accurate enough. 308 309`wait_for_window` 310 311: Wait until an X11 window has appeared whose name matches the given 312 regular expression, e.g., `wait_for_window("Terminal")`. 313 314`copy_from_host` 315 316: Copies a file from host to machine, e.g., 317 `copy_from_host("myfile", "/etc/my/important/file")`. 318 319 The first argument is the file on the host. The file needs to be 320 accessible while building the nix derivation. The second argument is 321 the location of the file on the machine. 322 323`systemctl` 324 325: Runs `systemctl` commands with optional support for 326 `systemctl --user` 327 328 ```py 329 machine.systemctl("list-jobs --no-pager") # runs `systemctl list-jobs --no-pager` 330 machine.systemctl("list-jobs --no-pager", "any-user") # spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager` 331 ``` 332 333`shell_interact` 334 335: Allows you to directly interact with the guest shell. This should 336 only be used during test development, not in production tests. 337 Killing the interactive session with `Ctrl-d` or `Ctrl-c` also ends 338 the guest session. 339 340`console_interact` 341 342: Allows you to directly interact with QEMU's stdin. This should 343 only be used during test development, not in production tests. 344 Output from QEMU is only read line-wise. `Ctrl-c` kills QEMU and 345 `Ctrl-d` closes console and returns to the test runner. 346 347To test user units declared by `systemd.user.services` the optional 348`user` argument can be used: 349 350```py 351machine.start() 352machine.wait_for_x() 353machine.wait_for_unit("xautolock.service", "x-session-user") 354``` 355 356This applies to `systemctl`, `get_unit_info`, `wait_for_unit`, 357`start_job` and `stop_job`. 358 359For faster dev cycles it's also possible to disable the code-linters 360(this shouldn't be committed though): 361 362```nix 363{ 364 skipLint = true; 365 nodes.machine = 366 { config, pkgs, ... }: 367 { configuration… 368 }; 369 370 testScript = 371 '' 372 Python code… 373 ''; 374} 375``` 376 377This will produce a Nix warning at evaluation time. To fully disable the 378linter, wrap the test script in comment directives to disable the Black 379linter directly (again, don't commit this within the Nixpkgs 380repository): 381 382```nix 383 testScript = 384 '' 385 # fmt: off 386 Python code… 387 # fmt: on 388 ''; 389``` 390 391Similarly, the type checking of test scripts can be disabled in the following 392way: 393 394```nix 395{ 396 skipTypeCheck = true; 397 nodes.machine = 398 { config, pkgs, ... }: 399 { configuration… 400 }; 401} 402``` 403 404## Failing tests early {#ssec-failing-tests-early} 405 406To 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: 407 408```py 409@polling_condition 410def foo_running(): 411 machine.succeed("pgrep -x foo") 412 413 414machine.succeed("foo --start") 415machine.wait_until_succeeds("pgrep -x foo") 416 417with foo_running: 418 ... # Put `foo` through its paces 419``` 420 421`polling_condition` takes the following (optional) arguments: 422 423`seconds_interval` 424 425: specifies how often the condition should be polled: 426 427```py 428@polling_condition(seconds_interval=10) 429def foo_running(): 430 machine.succeed("pgrep -x foo") 431``` 432 433`description` 434 435: 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: 436 437```py 438@polling_condition 439def foo_running(): 440 "check that foo is running" 441 machine.succeed("pgrep -x foo") 442``` 443 444```py 445@polling_condition(description="check that foo is running") 446def foo_running(): 447 machine.succeed("pgrep -x foo") 448``` 449 450## Adding Python packages to the test script {#ssec-python-packages-in-test-script} 451 452When additional Python libraries are required in the test script, they can be 453added using the parameter `extraPythonPackages`. For example, you could add 454`numpy` like this: 455 456```nix 457{ 458 extraPythonPackages = p: [ p.numpy ]; 459 460 nodes = { }; 461 462 # Type checking on extra packages doesn't work yet 463 skipTypeCheck = true; 464 465 testScript = '' 466 import numpy as np 467 assert str(np.zeros(4) == "array([0., 0., 0., 0.])") 468 ''; 469} 470``` 471 472In that case, `numpy` is chosen from the generic `python3Packages`. 473 474## Test Options Reference {#sec-test-options-reference} 475 476The following options can be used when writing tests. 477 478```{=include=} options 479id-prefix: test-opt- 480list-id: test-options-list 481source: @NIXOS_TEST_OPTIONS_JSON@ 482```