about summary refs log tree commit diff
path: root/nixos/doc/manual/development/writing-nixos-tests.xml
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/doc/manual/development/writing-nixos-tests.xml')
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.xml251
1 files changed, 251 insertions, 0 deletions
diff --git a/nixos/doc/manual/development/writing-nixos-tests.xml b/nixos/doc/manual/development/writing-nixos-tests.xml
new file mode 100644
index 000000000000..bbb655eed2a6
--- /dev/null
+++ b/nixos/doc/manual/development/writing-nixos-tests.xml
@@ -0,0 +1,251 @@
+<section xmlns="http://docbook.org/ns/docbook"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        xmlns:xi="http://www.w3.org/2001/XInclude"
+        version="5.0"
+        xml:id="sec-writing-nixos-tests">
+
+<title>Writing Tests</title>
+
+<para>A NixOS test is a Nix expression that has the following structure:
+
+<programlisting>
+import ./make-test.nix {
+
+  # Either the configuration of a single machine:
+  machine =
+    { config, pkgs, ... }:
+    { <replaceable>configuration…</replaceable>
+    };
+
+  # Or a set of machines:
+  nodes =
+    { <replaceable>machine1</replaceable> =
+        { config, pkgs, ... }: { <replaceable>…</replaceable> };
+      <replaceable>machine2</replaceable> =
+        { config, pkgs, ... }: { <replaceable>…</replaceable> };
+      …
+    };
+
+  testScript =
+    ''
+      <replaceable>Perl code…</replaceable>
+    '';
+}
+</programlisting>
+
+The attribute <literal>testScript</literal> is a bit of Perl code that
+executes the test (described below). During the test, it will start
+one or more virtual machines, the configuration of which is described
+by the attribute <literal>machine</literal> (if you need only one
+machine in your test) or by the attribute <literal>nodes</literal> (if
+you need multiple machines). For instance, <filename
+xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix">login.nix</filename>
+only needs a single machine to test whether users can log in on the
+virtual console, whether device ownership is correctly maintained when
+switching between consoles, and so on. On the other hand, <filename
+xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs.nix">nfs.nix</filename>,
+which tests NFS client and server functionality in the Linux kernel
+(including whether locks are maintained across server crashes),
+requires three machines: a server and two clients.</para>
+
+<para>There are a few special NixOS configuration options for test
+VMs:
+
+<!-- FIXME: would be nice to generate this automatically. -->
+
+<variablelist>
+
+  <varlistentry>
+    <term><option>virtualisation.memorySize</option></term>
+    <listitem><para>The memory of the VM in
+    megabytes.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><option>virtualisation.vlans</option></term>
+    <listitem><para>The virtual networks to which the VM is
+    connected. See <filename
+    xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nat.nix">nat.nix</filename>
+    for an example.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><option>virtualisation.writableStore</option></term>
+    <listitem><para>By default, the Nix store in the VM is not
+    writable. If you enable this option, a writable union file system
+    is mounted on top of the Nix store to make it appear
+    writable. This is necessary for tests that run Nix operations that
+    modify the store.</para></listitem>
+  </varlistentry>
+
+</variablelist>
+
+For more options, see the module <filename
+xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix">qemu-vm.nix</filename>.</para>
+
+<para>The test script is a sequence of Perl statements that perform
+various actions, such as starting VMs, executing commands in the VMs,
+and so on. Each virtual machine is represented as an object stored in
+the variable <literal>$<replaceable>name</replaceable></literal>,
+where <replaceable>name</replaceable> is the identifier of the machine
+(which is just <literal>machine</literal> if you didn’t specify
+multiple machines using the <literal>nodes</literal> attribute). For
+instance, the following starts the machine, waits until it has
+finished booting, then executes a command and checks that the output
+is more-or-less correct:
+
+<programlisting>
+$machine->start;
+$machine->waitForUnit("default.target");
+$machine->succeed("uname") =~ /Linux/;
+</programlisting>
+
+The first line is actually unnecessary; machines are implicitly
+started when you first execute an action on them (such as
+<literal>waitForUnit</literal> or <literal>succeed</literal>). If you
+have multiple machines, you can speed up the test by starting them in
+parallel:
+
+<programlisting>
+startAll;
+</programlisting>
+
+</para>
+
+<para>The following methods are available on machine objects:
+
+<variablelist>
+
+  <varlistentry>
+    <term><methodname>start</methodname></term>
+    <listitem><para>Start the virtual machine. This method is
+    asynchronous — it does not wait for the machine to finish
+    booting.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>shutdown</methodname></term>
+    <listitem><para>Shut down the machine, waiting for the VM to
+    exit.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>crash</methodname></term>
+    <listitem><para>Simulate a sudden power failure, by telling the VM
+    to exit immediately.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>block</methodname></term>
+    <listitem><para>Simulate unplugging the Ethernet cable that
+    connects the machine to the other machines.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>unblock</methodname></term>
+    <listitem><para>Undo the effect of
+    <methodname>block</methodname>.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>screenshot</methodname></term>
+    <listitem><para>Take a picture of the display of the virtual
+    machine, in PNG format. The screenshot is linked from the HTML
+    log.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>sendMonitorCommand</methodname></term>
+    <listitem><para>Send a command to the QEMU monitor. This is rarely
+    used, but allows doing stuff such as attaching virtual USB disks
+    to a running machine.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>sendKeys</methodname></term>
+    <listitem><para>Simulate pressing keys on the virtual keyboard,
+    e.g., <literal>sendKeys("ctrl-alt-delete")</literal>.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>sendChars</methodname></term>
+    <listitem><para>Simulate typing a sequence of characters on the
+    virtual keyboard, e.g., <literal>sendKeys("foobar\n")</literal>
+    will type the string <literal>foobar</literal> followed by the
+    Enter key.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>execute</methodname></term>
+    <listitem><para>Execute a shell command, returning a list
+    <literal>(<replaceable>status</replaceable>,
+    <replaceable>stdout</replaceable>)</literal>.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>succeed</methodname></term>
+    <listitem><para>Execute a shell command, raising an exception if
+    the exit status is not zero, otherwise returning the standard
+    output.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>fail</methodname></term>
+    <listitem><para>Like <methodname>succeed</methodname>, but raising
+    an exception if the command returns a zero status.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>waitUntilSucceeds</methodname></term>
+    <listitem><para>Repeat a shell command with 1-second intervals
+    until it succeeds.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>waitUntilFails</methodname></term>
+    <listitem><para>Repeat a shell command with 1-second intervals
+    until it fails.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>waitForUnit</methodname></term>
+    <listitem><para>Wait until the specified systemd unit has reached
+    the “active” state.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>waitForFile</methodname></term>
+    <listitem><para>Wait until the specified file
+    exists.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>waitForOpenPort</methodname></term>
+    <listitem><para>Wait until a process is listening on the given TCP
+    port (on <literal>localhost</literal>, at least).</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>waitForClosedPort</methodname></term>
+    <listitem><para>Wait until nobody is listening on the given TCP
+    port.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>waitForX</methodname></term>
+    <listitem><para>Wait until the X11 server is accepting
+    connections.</para></listitem>
+  </varlistentry>
+
+  <varlistentry>
+    <term><methodname>waitForWindow</methodname></term>
+    <listitem><para>Wait until an X11 window has appeared whose name
+    matches the given regular expression, e.g.,
+    <literal>waitForWindow(qr/Terminal/)</literal>.</para></listitem>
+  </varlistentry>
+
+</variablelist>
+
+</para>
+
+</section>
\ No newline at end of file