summary refs log tree commit diff
path: root/nixos/doc/manual/from_md/development
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/doc/manual/from_md/development')
-rw-r--r--nixos/doc/manual/from_md/development/assertions.section.xml58
-rw-r--r--nixos/doc/manual/from_md/development/building-nixos.chapter.xml33
-rw-r--r--nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml50
-rw-r--r--nixos/doc/manual/from_md/development/running-nixos-tests.section.xml34
-rw-r--r--nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml526
5 files changed, 701 insertions, 0 deletions
diff --git a/nixos/doc/manual/from_md/development/assertions.section.xml b/nixos/doc/manual/from_md/development/assertions.section.xml
new file mode 100644
index 00000000000..0844d484d60
--- /dev/null
+++ b/nixos/doc/manual/from_md/development/assertions.section.xml
@@ -0,0 +1,58 @@
+<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-assertions">
+  <title>Warnings and Assertions</title>
+  <para>
+    When configuration problems are detectable in a module, it is a good
+    idea to write an assertion or warning. Doing so provides clear
+    feedback to the user and prevents errors after the build.
+  </para>
+  <para>
+    Although Nix has the <literal>abort</literal> and
+    <literal>builtins.trace</literal>
+    <link xlink:href="https://nixos.org/nix/manual/#ssec-builtins">functions</link>
+    to perform such tasks, they are not ideally suited for NixOS
+    modules. Instead of these functions, you can declare your warnings
+    and assertions using the NixOS module system.
+  </para>
+  <section xml:id="sec-assertions-warnings">
+    <title>Warnings</title>
+    <para>
+      This is an example of using <literal>warnings</literal>.
+    </para>
+    <programlisting language="bash">
+{ config, lib, ... }:
+{
+  config = lib.mkIf config.services.foo.enable {
+    warnings =
+      if config.services.foo.bar
+      then [ ''You have enabled the bar feature of the foo service.
+               This is known to cause some specific problems in certain situations.
+               '' ]
+      else [];
+  }
+}
+</programlisting>
+  </section>
+  <section xml:id="sec-assertions-assetions">
+    <title>Assertions</title>
+    <para>
+      This example, extracted from the
+      <link xlink:href="https://github.com/NixOS/nixpkgs/blob/release-17.09/nixos/modules/services/logging/syslogd.nix"><literal>syslogd</literal>
+      module</link> shows how to use <literal>assertions</literal>.
+      Since there can only be one active syslog daemon at a time, an
+      assertion is useful to prevent such a broken system from being
+      built.
+    </para>
+    <programlisting language="bash">
+{ config, lib, ... }:
+{
+  config = lib.mkIf config.services.syslogd.enable {
+    assertions =
+      [ { assertion = !config.services.rsyslogd.enable;
+          message = &quot;rsyslogd conflicts with syslogd&quot;;
+        }
+      ];
+  }
+}
+</programlisting>
+  </section>
+</section>
diff --git a/nixos/doc/manual/from_md/development/building-nixos.chapter.xml b/nixos/doc/manual/from_md/development/building-nixos.chapter.xml
new file mode 100644
index 00000000000..ceb744447da
--- /dev/null
+++ b/nixos/doc/manual/from_md/development/building-nixos.chapter.xml
@@ -0,0 +1,33 @@
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-building-cd">
+  <title>Building Your Own NixOS CD</title>
+  <para>
+    Building a NixOS CD is as easy as configuring your own computer. The
+    idea is to use another module which will replace your
+    <literal>configuration.nix</literal> to configure the system that
+    would be installed on the CD.
+  </para>
+  <para>
+    Default CD/DVD configurations are available inside
+    <literal>nixos/modules/installer/cd-dvd</literal>
+  </para>
+  <programlisting>
+$ git clone https://github.com/NixOS/nixpkgs.git
+$ cd nixpkgs/nixos
+$ nix-build -A config.system.build.isoImage -I nixos-config=modules/installer/cd-dvd/installation-cd-minimal.nix default.nix
+</programlisting>
+  <para>
+    Before burning your CD/DVD, you can check the content of the image
+    by mounting anywhere like suggested by the following command:
+  </para>
+  <programlisting>
+# mount -o loop -t iso9660 ./result/iso/cd.iso /mnt/iso&lt;/screen&gt;
+</programlisting>
+  <para>
+    If you want to customize your NixOS CD in more detail, or generate
+    other kinds of images, you might want to check out
+    <link xlink:href="https://github.com/nix-community/nixos-generators">nixos-generators</link>.
+    This can also be a good starting point when you want to use Nix to
+    build a <quote>minimal</quote> image that doesn’t include a NixOS
+    installation.
+  </para>
+</chapter>
diff --git a/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml b/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml
new file mode 100644
index 00000000000..a2030e9c073
--- /dev/null
+++ b/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml
@@ -0,0 +1,50 @@
+<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-running-nixos-tests-interactively">
+  <title>Running Tests interactively</title>
+  <para>
+    The test itself can be run interactively. This is particularly
+    useful when developing or debugging a test:
+  </para>
+  <programlisting>
+$ nix-build nixos/tests/login.nix -A driverInteractive
+$ ./result/bin/nixos-test-driver
+starting VDE switch for network 1
+&gt;
+</programlisting>
+  <para>
+    You can then take any Python statement, e.g.
+  </para>
+  <programlisting language="python">
+&gt; start_all()
+&gt; test_script()
+&gt; machine.succeed(&quot;touch /tmp/foo&quot;)
+&gt; print(machine.succeed(&quot;pwd&quot;)) # Show stdout of command
+</programlisting>
+  <para>
+    The function <literal>test_script</literal> executes the entire test
+    script and drops you back into the test driver command line upon its
+    completion. This allows you to inspect the state of the VMs after
+    the test (e.g. to debug the test script).
+  </para>
+  <para>
+    To just start and experiment with the VMs, run:
+  </para>
+  <programlisting>
+$ nix-build nixos/tests/login.nix -A driverInteractive
+$ ./result/bin/nixos-run-vms
+</programlisting>
+  <para>
+    The script <literal>nixos-run-vms</literal> starts the virtual
+    machines defined by test.
+  </para>
+  <para>
+    You can re-use the VM states coming from a previous run by setting
+    the <literal>--keep-vm-state</literal> flag.
+  </para>
+  <programlisting>
+$ ./result/bin/nixos-run-vms --keep-vm-state
+</programlisting>
+  <para>
+    The machine state is stored in the
+    <literal>$TMPDIR/vm-state-machinename</literal> directory.
+  </para>
+</section>
diff --git a/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml
new file mode 100644
index 00000000000..7159b95b22b
--- /dev/null
+++ b/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml
@@ -0,0 +1,34 @@
+<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-running-nixos-tests">
+  <title>Running Tests</title>
+  <para>
+    You can run tests using <literal>nix-build</literal>. For example,
+    to run the test
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix"><literal>login.nix</literal></link>,
+    you just do:
+  </para>
+  <programlisting>
+$ nix-build '&lt;nixpkgs/nixos/tests/login.nix&gt;'
+</programlisting>
+  <para>
+    or, if you don’t want to rely on <literal>NIX_PATH</literal>:
+  </para>
+  <programlisting>
+$ cd /my/nixpkgs/nixos/tests
+$ nix-build login.nix
+…
+running the VM test script
+machine: QEMU running (pid 8841)
+…
+6 out of 6 tests succeeded
+</programlisting>
+  <para>
+    After building/downloading all required dependencies, this will
+    perform a build that starts a QEMU/KVM virtual machine containing a
+    NixOS system. The virtual machine mounts the Nix store of the host;
+    this makes VM creation very fast, as no disk image needs to be
+    created. Afterwards, you can view a pretty-printed log of the test:
+  </para>
+  <programlisting>
+$ firefox result/log.html
+</programlisting>
+</section>
diff --git a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
new file mode 100644
index 00000000000..83a96d5bb22
--- /dev/null
+++ b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
@@ -0,0 +1,526 @@
+<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-writing-nixos-tests">
+  <title>Writing Tests</title>
+  <para>
+    A NixOS test is a Nix expression that has the following structure:
+  </para>
+  <programlisting language="bash">
+import ./make-test-python.nix {
+
+  # Either the configuration of a single machine:
+  machine =
+    { config, pkgs, ... }:
+    { configuration…
+    };
+
+  # Or a set of machines:
+  nodes =
+    { machine1 =
+        { config, pkgs, ... }: { … };
+      machine2 =
+        { config, pkgs, ... }: { … };
+      …
+    };
+
+  testScript =
+    ''
+      Python code…
+    '';
+}
+</programlisting>
+  <para>
+    The attribute <literal>testScript</literal> is a bit of Python 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,
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix"><literal>login.nix</literal></link>
+    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,
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix"><literal>nfs/simple.nix</literal></link>,
+    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:
+  </para>
+  <variablelist>
+    <varlistentry>
+      <term>
+        <literal>virtualisation.memorySize</literal>
+      </term>
+      <listitem>
+        <para>
+          The memory of the VM in megabytes.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>virtualisation.vlans</literal>
+      </term>
+      <listitem>
+        <para>
+          The virtual networks to which the VM is connected. See
+          <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nat.nix"><literal>nat.nix</literal></link>
+          for an example.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>virtualisation.writableStore</literal>
+      </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>
+  <para>
+    For more options, see the module
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix"><literal>qemu-vm.nix</literal></link>.
+  </para>
+  <para>
+    The test script is a sequence of Python 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>name</literal> if this is also the
+    identifier of the machine in the declarative config. If you didn't
+    specify multiple machines using the <literal>nodes</literal>
+    attribute, it is just <literal>machine</literal>. The following
+    example starts the machine, waits until it has finished booting,
+    then executes a command and checks that the output is more-or-less
+    correct:
+  </para>
+  <programlisting language="python">
+machine.start()
+machine.wait_for_unit(&quot;default.target&quot;)
+if not &quot;Linux&quot; in machine.succeed(&quot;uname&quot;):
+  raise Exception(&quot;Wrong OS&quot;)
+</programlisting>
+  <para>
+    The first line is actually unnecessary; machines are implicitly
+    started when you first execute an action on them (such as
+    <literal>wait_for_unit</literal> or <literal>succeed</literal>). If
+    you have multiple machines, you can speed up the test by starting
+    them in parallel:
+  </para>
+  <programlisting language="python">
+start_all()
+</programlisting>
+  <para>
+    The following methods are available on machine objects:
+  </para>
+  <variablelist>
+    <varlistentry>
+      <term>
+        <literal>start</literal>
+      </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>
+        <literal>shutdown</literal>
+      </term>
+      <listitem>
+        <para>
+          Shut down the machine, waiting for the VM to exit.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>crash</literal>
+      </term>
+      <listitem>
+        <para>
+          Simulate a sudden power failure, by telling the VM to exit
+          immediately.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>block</literal>
+      </term>
+      <listitem>
+        <para>
+          Simulate unplugging the Ethernet cable that connects the
+          machine to the other machines.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>unblock</literal>
+      </term>
+      <listitem>
+        <para>
+          Undo the effect of <literal>block</literal>.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>screenshot</literal>
+      </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>
+        <literal>get_screen_text_variants</literal>
+      </term>
+      <listitem>
+        <para>
+          Return a list of different interpretations of what is
+          currently visible on the machine's screen using optical
+          character recognition. The number and order of the
+          interpretations is not specified and is subject to change, but
+          if no exception is raised at least one will be returned.
+        </para>
+        <note>
+          <para>
+            This requires passing <literal>enableOCR</literal> to the
+            test attribute set.
+          </para>
+        </note>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>get_screen_text</literal>
+      </term>
+      <listitem>
+        <para>
+          Return a textual representation of what is currently visible
+          on the machine's screen using optical character recognition.
+        </para>
+        <note>
+          <para>
+            This requires passing <literal>enableOCR</literal> to the
+            test attribute set.
+          </para>
+        </note>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>send_monitor_command</literal>
+      </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>
+        <literal>send_key</literal>
+      </term>
+      <listitem>
+        <para>
+          Simulate pressing keys on the virtual keyboard, e.g.,
+          <literal>send_key(&quot;ctrl-alt-delete&quot;)</literal>.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>send_chars</literal>
+      </term>
+      <listitem>
+        <para>
+          Simulate typing a sequence of characters on the virtual
+          keyboard, e.g.,
+          <literal>send_chars(&quot;foobar\n&quot;)</literal> will type
+          the string <literal>foobar</literal> followed by the Enter
+          key.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>execute</literal>
+      </term>
+      <listitem>
+        <para>
+          Execute a shell command, returning a list
+          <literal>(status, stdout)</literal>.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>succeed</literal>
+      </term>
+      <listitem>
+        <para>
+          Execute a shell command, raising an exception if the exit
+          status is not zero, otherwise returning the standard output.
+          Commands are run with <literal>set -euo pipefail</literal>
+          set:
+        </para>
+        <itemizedlist>
+          <listitem>
+            <para>
+              If several commands are separated by <literal>;</literal>
+              and one fails, the command as a whole will fail.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              For pipelines, the last non-zero exit status will be
+              returned (if there is one, zero will be returned
+              otherwise).
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Dereferencing unset variables fail the command.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>fail</literal>
+      </term>
+      <listitem>
+        <para>
+          Like <literal>succeed</literal>, but raising an exception if
+          the command returns a zero status.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_until_succeeds</literal>
+      </term>
+      <listitem>
+        <para>
+          Repeat a shell command with 1-second intervals until it
+          succeeds.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_until_fails</literal>
+      </term>
+      <listitem>
+        <para>
+          Repeat a shell command with 1-second intervals until it fails.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_for_unit</literal>
+      </term>
+      <listitem>
+        <para>
+          Wait until the specified systemd unit has reached the
+          <quote>active</quote> state.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_for_file</literal>
+      </term>
+      <listitem>
+        <para>
+          Wait until the specified file exists.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_for_open_port</literal>
+      </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>
+        <literal>wait_for_closed_port</literal>
+      </term>
+      <listitem>
+        <para>
+          Wait until nobody is listening on the given TCP port.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_for_x</literal>
+      </term>
+      <listitem>
+        <para>
+          Wait until the X11 server is accepting connections.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_for_text</literal>
+      </term>
+      <listitem>
+        <para>
+          Wait until the supplied regular expressions matches the
+          textual contents of the screen by using optical character
+          recognition (see <literal>get_screen_text</literal> and
+          <literal>get_screen_text_variants</literal>).
+        </para>
+        <note>
+          <para>
+            This requires passing <literal>enableOCR</literal> to the
+            test attribute set.
+          </para>
+        </note>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_for_console_text</literal>
+      </term>
+      <listitem>
+        <para>
+          Wait until the supplied regular expressions match a line of
+          the serial console output. This method is useful when OCR is
+          not possibile or accurate enough.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>wait_for_window</literal>
+      </term>
+      <listitem>
+        <para>
+          Wait until an X11 window has appeared whose name matches the
+          given regular expression, e.g.,
+          <literal>wait_for_window(&quot;Terminal&quot;)</literal>.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>copy_from_host</literal>
+      </term>
+      <listitem>
+        <para>
+          Copies a file from host to machine, e.g.,
+          <literal>copy_from_host(&quot;myfile&quot;, &quot;/etc/my/important/file&quot;)</literal>.
+        </para>
+        <para>
+          The first argument is the file on the host. The file needs to
+          be accessible while building the nix derivation. The second
+          argument is the location of the file on the machine.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>systemctl</literal>
+      </term>
+      <listitem>
+        <para>
+          Runs <literal>systemctl</literal> commands with optional
+          support for <literal>systemctl --user</literal>
+        </para>
+        <programlisting language="python">
+machine.systemctl(&quot;list-jobs --no-pager&quot;) # runs `systemctl list-jobs --no-pager`
+machine.systemctl(&quot;list-jobs --no-pager&quot;, &quot;any-user&quot;) # spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager`
+</programlisting>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
+        <literal>shell_interact</literal>
+      </term>
+      <listitem>
+        <para>
+          Allows you to directly interact with the guest shell. This
+          should only be used during test development, not in production
+          tests. Killing the interactive session with
+          <literal>Ctrl-d</literal> or <literal>Ctrl-c</literal> also
+          ends the guest session.
+        </para>
+      </listitem>
+    </varlistentry>
+  </variablelist>
+  <para>
+    To test user units declared by
+    <literal>systemd.user.services</literal> the optional
+    <literal>user</literal> argument can be used:
+  </para>
+  <programlisting language="python">
+machine.start()
+machine.wait_for_x()
+machine.wait_for_unit(&quot;xautolock.service&quot;, &quot;x-session-user&quot;)
+</programlisting>
+  <para>
+    This applies to <literal>systemctl</literal>,
+    <literal>get_unit_info</literal>, <literal>wait_for_unit</literal>,
+    <literal>start_job</literal> and <literal>stop_job</literal>.
+  </para>
+  <para>
+    For faster dev cycles it's also possible to disable the code-linters
+    (this shouldn't be commited though):
+  </para>
+  <programlisting language="bash">
+import ./make-test-python.nix {
+  skipLint = true;
+  machine =
+    { config, pkgs, ... }:
+    { configuration…
+    };
+
+  testScript =
+    ''
+      Python code…
+    '';
+}
+</programlisting>
+  <para>
+    This will produce a Nix warning at evaluation time. To fully disable
+    the linter, wrap the test script in comment directives to disable
+    the Black linter directly (again, don't commit this within the
+    Nixpkgs repository):
+  </para>
+  <programlisting language="bash">
+  testScript =
+    ''
+      # fmt: off
+      Python code…
+      # fmt: on
+    '';
+</programlisting>
+</section>