1# Writing Tests {#sec-writing-nixos-tests} 2 3A NixOS test is a Nix expression that has the following structure: 4 5```nix 6import ./make-test-python.nix { 7 8 # Either the configuration of a single machine: 9 machine = 10 { config, pkgs, ... }: 11 { configuration 12 }; 13 14 # Or a set of machines: 15 nodes = 16 { machine1 = 17 { config, pkgs, ... }: { }; 18 machine2 = 19 { config, pkgs, ... }: { }; 20 21 }; 22 23 testScript = 24 '' 25 Python code… 26 ''; 27} 28``` 29 30The attribute `testScript` is a bit of Python code that executes the 31test (described below). During the test, it will start one or more 32virtual machines, the configuration of which is described by the 33attribute `machine` (if you need only one machine in your test) or by 34the attribute `nodes` (if you need multiple machines). For instance, 35[`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix) 36only needs a single machine to test whether users can log in 37on the virtual console, whether device ownership is correctly maintained 38when switching between consoles, and so on. On the other hand, 39[`nfs/simple.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix), 40which tests NFS client and server functionality in the 41Linux kernel (including whether locks are maintained across server 42crashes), requires three machines: a server and two clients. 43 44There are a few special NixOS configuration options for test VMs: 45 46`virtualisation.memorySize` 47 48: The memory of the VM in megabytes. 49 50`virtualisation.vlans` 51 52: The virtual networks to which the VM is connected. See 53 [`nat.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nat.nix) 54 for an example. 55 56`virtualisation.writableStore` 57 58: By default, the Nix store in the VM is not writable. If you enable 59 this option, a writable union file system is mounted on top of the 60 Nix store to make it appear writable. This is necessary for tests 61 that run Nix operations that modify the store. 62 63For more options, see the module 64[`qemu-vm.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix). 65 66The test script is a sequence of Python statements that perform various 67actions, such as starting VMs, executing commands in the VMs, and so on. 68Each virtual machine is represented as an object stored in the variable 69`name` if this is also the identifier of the machine in the declarative 70config. If you didn\'t specify multiple machines using the `nodes` 71attribute, it is just `machine`. The following example starts the 72machine, waits until it has finished booting, then executes a command 73and checks that the output is more-or-less correct: 74 75```py 76machine.start() 77machine.wait_for_unit("default.target") 78if not "Linux" in machine.succeed("uname"): 79 raise Exception("Wrong OS") 80``` 81 82The first line is actually unnecessary; machines are implicitly started 83when you first execute an action on them (such as `wait_for_unit` or 84`succeed`). If you have multiple machines, you can speed up the test by 85starting them in parallel: 86 87```py 88start_all() 89``` 90 91The following methods are available on machine objects: 92 93`start` 94 95: Start the virtual machine. This method is asynchronous --- it does 96 not wait for the machine to finish booting. 97 98`shutdown` 99 100: Shut down the machine, waiting for the VM to exit. 101 102`crash` 103 104: Simulate a sudden power failure, by telling the VM to exit 105 immediately. 106 107`block` 108 109: Simulate unplugging the Ethernet cable that connects the machine to 110 the other machines. 111 112`unblock` 113 114: Undo the effect of `block`. 115 116`screenshot` 117 118: Take a picture of the display of the virtual machine, in PNG format. 119 The screenshot is linked from the HTML log. 120 121`get_screen_text_variants` 122 123: Return a list of different interpretations of what is currently 124 visible on the machine\'s screen using optical character 125 recognition. The number and order of the interpretations is not 126 specified and is subject to change, but if no exception is raised at 127 least one will be returned. 128 129 ::: {.note} 130 This requires passing `enableOCR` to the test attribute set. 131 ::: 132 133`get_screen_text` 134 135: Return a textual representation of what is currently visible on the 136 machine\'s screen using optical character recognition. 137 138 ::: {.note} 139 This requires passing `enableOCR` to the test attribute set. 140 ::: 141 142`send_monitor_command` 143 144: Send a command to the QEMU monitor. This is rarely used, but allows 145 doing stuff such as attaching virtual USB disks to a running 146 machine. 147 148`send_key` 149 150: Simulate pressing keys on the virtual keyboard, e.g., 151 `send_key("ctrl-alt-delete")`. 152 153`send_chars` 154 155: Simulate typing a sequence of characters on the virtual keyboard, 156 e.g., `send_chars("foobar\n")` will type the string `foobar` 157 followed by the Enter key. 158 159`execute` 160 161: Execute a shell command, returning a list `(status, stdout)`. 162 If the command detaches, it must close stdout, as `execute` will wait 163 for this to consume all output reliably. This can be achieved by 164 redirecting stdout to stderr `>&2`, to `/dev/console`, `/dev/null` or 165 a file. Examples of detaching commands are `sleep 365d &`, where the 166 shell forks a new process that can write to stdout and `xclip -i`, where 167 the `xclip` command itself forks without closing stdout. 168 Takes an optional parameter `check_return` that defaults to `True`. 169 Setting this parameter to `False` will not check for the return code 170 and return -1 instead. This can be used for commands that shut down 171 the VM and would therefore break the pipe that would be used for 172 retrieving the return code. 173 174`succeed` 175 176: Execute a shell command, raising an exception if the exit status is 177 not zero, otherwise returning the standard output. Commands are run 178 with `set -euo pipefail` set: 179 180 - If several commands are separated by `;` and one fails, the 181 command as a whole will fail. 182 183 - For pipelines, the last non-zero exit status will be returned 184 (if there is one, zero will be returned otherwise). 185 186 - Dereferencing unset variables fail the command. 187 188 - It will wait for stdout to be closed. See `execute` for the 189 implications. 190 191`fail` 192 193: Like `succeed`, but raising an exception if the command returns a zero 194 status. 195 196`wait_until_succeeds` 197 198: Repeat a shell command with 1-second intervals until it succeeds. 199 200`wait_until_fails` 201 202: Repeat a shell command with 1-second intervals until it fails. 203 204`wait_for_unit` 205 206: Wait until the specified systemd unit has reached the "active" 207 state. 208 209`wait_for_file` 210 211: Wait until the specified file exists. 212 213`wait_for_open_port` 214 215: Wait until a process is listening on the given TCP port (on 216 `localhost`, at least). 217 218`wait_for_closed_port` 219 220: Wait until nobody is listening on the given TCP port. 221 222`wait_for_x` 223 224: Wait until the X11 server is accepting connections. 225 226`wait_for_text` 227 228: Wait until the supplied regular expressions matches the textual 229 contents of the screen by using optical character recognition (see 230 `get_screen_text` and `get_screen_text_variants`). 231 232 ::: {.note} 233 This requires passing `enableOCR` to the test attribute set. 234 ::: 235 236`wait_for_console_text` 237 238: Wait until the supplied regular expressions match a line of the 239 serial console output. This method is useful when OCR is not 240 possibile or accurate enough. 241 242`wait_for_window` 243 244: Wait until an X11 window has appeared whose name matches the given 245 regular expression, e.g., `wait_for_window("Terminal")`. 246 247`copy_from_host` 248 249: Copies a file from host to machine, e.g., 250 `copy_from_host("myfile", "/etc/my/important/file")`. 251 252 The first argument is the file on the host. The file needs to be 253 accessible while building the nix derivation. The second argument is 254 the location of the file on the machine. 255 256`systemctl` 257 258: Runs `systemctl` commands with optional support for 259 `systemctl --user` 260 261 ```py 262 machine.systemctl("list-jobs --no-pager") # runs `systemctl list-jobs --no-pager` 263 machine.systemctl("list-jobs --no-pager", "any-user") # spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager` 264 ``` 265 266`shell_interact` 267 268: Allows you to directly interact with the guest shell. This should 269 only be used during test development, not in production tests. 270 Killing the interactive session with `Ctrl-d` or `Ctrl-c` also ends 271 the guest session. 272 273To test user units declared by `systemd.user.services` the optional 274`user` argument can be used: 275 276```py 277machine.start() 278machine.wait_for_x() 279machine.wait_for_unit("xautolock.service", "x-session-user") 280``` 281 282This applies to `systemctl`, `get_unit_info`, `wait_for_unit`, 283`start_job` and `stop_job`. 284 285For faster dev cycles it\'s also possible to disable the code-linters 286(this shouldn\'t be commited though): 287 288```nix 289import ./make-test-python.nix { 290 skipLint = true; 291 machine = 292 { config, pkgs, ... }: 293 { configuration… 294 }; 295 296 testScript = 297 '' 298 Python code… 299 ''; 300} 301``` 302 303This will produce a Nix warning at evaluation time. To fully disable the 304linter, wrap the test script in comment directives to disable the Black 305linter directly (again, don\'t commit this within the Nixpkgs 306repository): 307 308```nix 309 testScript = 310 '' 311 # fmt: off 312 Python code… 313 # fmt: on 314 ''; 315```