1 # Writing Tests {#sec-writing-nixos-tests}
3 A NixOS test is a module that has the following structure:
8 # One or more machines:
11 { config, pkgs, ... }: { /* ... */ };
13 { config, pkgs, ... }: { /* ... */ };
24 We refer to the whole test above as a test module, whereas the values
25 in [`nodes.<name>`](#test-opt-nodes) are NixOS modules themselves.
27 The option [`testScript`](#test-opt-testScript) is a piece of Python code that executes the
28 test (described below). During the test, it will start one or more
29 virtual machines, the configuration of which is described by
30 the option [`nodes`](#test-opt-nodes).
32 An example of a single-node test is
33 [`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix).
34 It only needs a single machine to test whether users can log in
35 on the virtual console, whether device ownership is correctly maintained
36 when 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).
38 It uses two client nodes to test correct locking across server crashes.
40 ## Calling a test {#sec-calling-nixos-tests}
42 Tests are invoked differently depending on whether the test is part of NixOS or lives in a different project.
44 ### Testing within NixOS {#sec-call-nixos-test-in-nixos}
46 Tests 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).
50 hostname = runTest ./hostname.nix;
54 Overrides can be added by defining an anonymous module in `all-tests.nix`.
59 imports = [ ./hostname.nix ];
60 defaults.networking.firewall.enable = false;
65 You can run a test with attribute name `hostname` in `nixos/tests/all-tests.nix` by invoking:
68 cd /my/git/clone/of/nixpkgs
69 nix-build -A nixosTests.hostname
72 ### Testing outside the NixOS project {#sec-call-nixos-test-outside-nixos}
74 Outside the `nixpkgs` repository, you can instantiate the test by first importing the NixOS library,
77 let nixos-lib = import (nixpkgs + "/nixos/lib") { };
81 imports = [ ./test.nix ];
82 hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs
83 defaults.services.foo.package = mypkg;
87 `runTest` returns a derivation that runs the test.
89 ## Configuring the nodes {#sec-nixos-test-nodes}
91 There are a few special NixOS options for test VMs:
93 `virtualisation.memorySize`
95 : The memory of the VM in megabytes.
97 `virtualisation.vlans`
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)
103 `virtualisation.writableStore`
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.
110 For more options, see the module
111 [`qemu-vm.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix).
113 The test script is a sequence of Python statements that perform various
114 actions, such as starting VMs, executing commands in the VMs, and so on.
115 Each 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
117 config. If you specified a node `nodes.machine`, the following example starts the
118 machine, waits until it has finished booting, then executes a command
119 and checks that the output is more-or-less correct:
123 machine.wait_for_unit("default.target")
124 if not "Linux" in machine.succeed("uname"):
125 raise Exception("Wrong OS")
128 The first line is technically unnecessary; machines are implicitly started
129 when you first execute an action on them (such as `wait_for_unit` or
130 `succeed`). If you have multiple machines, you can speed up the test by
131 starting them in parallel:
137 If the hostname of a node contains characters that can't be used in a
138 Python variable name, those characters will be replaced with
139 underscores in the variable name, so `nodes.machine-a` will be exposed
140 to Python as `machine_a`.
142 ## Machine objects {#ssec-machine-objects}
144 The following methods are available on machine objects:
146 @PYTHON_MACHINE_METHODS@
148 To test user units declared by `systemd.user.services` the optional
149 `user` argument can be used:
154 machine.wait_for_unit("xautolock.service", "x-session-user")
157 This applies to `systemctl`, `get_unit_info`, `wait_for_unit`,
158 `start_job` and `stop_job`.
160 For faster dev cycles it's also possible to disable the code-linters
161 (this shouldn't be committed though):
167 { config, pkgs, ... }:
178 This will produce a Nix warning at evaluation time. To fully disable the
179 linter, wrap the test script in comment directives to disable the Black
180 linter directly (again, don't commit this within the Nixpkgs
194 Similarly, the type checking of test scripts can be disabled in the following
199 skipTypeCheck = true;
201 { config, pkgs, ... }:
207 ## Failing tests early {#ssec-failing-tests-early}
209 To 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:
214 machine.succeed("pgrep -x foo")
217 machine.succeed("foo --start")
218 machine.wait_until_succeeds("pgrep -x foo")
221 ... # Put `foo` through its paces
224 `polling_condition` takes the following (optional) arguments:
228 : specifies how often the condition should be polled:
231 @polling_condition(seconds_interval=10)
233 machine.succeed("pgrep -x foo")
238 : 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:
243 "check that foo is running"
244 machine.succeed("pgrep -x foo")
248 @polling_condition(description="check that foo is running")
250 machine.succeed("pgrep -x foo")
253 ## Adding Python packages to the test script {#ssec-python-packages-in-test-script}
255 When additional Python libraries are required in the test script, they can be
256 added using the parameter `extraPythonPackages`. For example, you could add
261 extraPythonPackages = p: [ p.numpy ];
265 # Type checking on extra packages doesn't work yet
266 skipTypeCheck = true;
270 assert str(np.zeros(4)) == "[0. 0. 0. 0.]"
275 In that case, `numpy` is chosen from the generic `python3Packages`.
277 ## Test Options Reference {#sec-test-options-reference}
279 The following options can be used when writing tests.
281 ```{=include=} options
283 list-id: test-options-list
284 source: @NIXOS_TEST_OPTIONS_JSON@