summary refs log tree commit diff
path: root/nixos/tests
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
commit5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010 (patch)
treea6c0f605be6de3f372ae69905b331f9f75452da7 /nixos/tests
parent6070bc016bd2fd945b04347e25cfd3738622d2ac (diff)
downloadnixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.gz
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.bz2
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.lz
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.xz
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.zst
nixpkgs-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.zip
Move all of NixOS to nixos/ in preparation of the repository merge
Diffstat (limited to 'nixos/tests')
-rw-r--r--nixos/tests/avahi.nix55
-rw-r--r--nixos/tests/bittorrent.nix107
-rw-r--r--nixos/tests/check-filesystems.nix80
-rw-r--r--nixos/tests/common/user-account.nix11
-rw-r--r--nixos/tests/common/x11.nix12
-rw-r--r--nixos/tests/default.nix37
-rw-r--r--nixos/tests/efi-installer.nix131
-rw-r--r--nixos/tests/firefox.nix21
-rw-r--r--nixos/tests/firewall.nix48
-rw-r--r--nixos/tests/installer.nix354
-rw-r--r--nixos/tests/ipv6.nix76
-rw-r--r--nixos/tests/kde4.nix67
-rw-r--r--nixos/tests/kexec.nix18
-rw-r--r--nixos/tests/login.nix61
-rw-r--r--nixos/tests/misc.nix63
-rw-r--r--nixos/tests/mpich-example.c21
-rw-r--r--nixos/tests/mpich.nix40
-rw-r--r--nixos/tests/mysql-replication.nix57
-rw-r--r--nixos/tests/mysql.nix22
-rw-r--r--nixos/tests/nat.nix77
-rw-r--r--nixos/tests/nfs.nix85
-rw-r--r--nixos/tests/openssh.nix35
-rw-r--r--nixos/tests/partition.nix227
-rw-r--r--nixos/tests/printing.nix90
-rw-r--r--nixos/tests/proxy.nix94
-rw-r--r--nixos/tests/quake3.nix79
-rw-r--r--nixos/tests/run-in-machine.nix10
-rw-r--r--nixos/tests/simple.nix11
-rw-r--r--nixos/tests/subversion.nix117
-rwxr-xr-xnixos/tests/test-config-examples.sh14
-rw-r--r--nixos/tests/testdb.sql10
-rw-r--r--nixos/tests/tomcat.nix32
-rw-r--r--nixos/tests/trac.nix71
-rw-r--r--nixos/tests/xfce.nix32
34 files changed, 2265 insertions, 0 deletions
diff --git a/nixos/tests/avahi.nix b/nixos/tests/avahi.nix
new file mode 100644
index 00000000000..d95361dcd83
--- /dev/null
+++ b/nixos/tests/avahi.nix
@@ -0,0 +1,55 @@
+{ pkgs, ... }:
+
+with pkgs;
+
+{
+  nodes = {
+    one =
+      { config, pkgs, ... }: {
+        services.avahi.enable = true;
+        services.avahi.nssmdns = true;
+      };
+
+    two =
+      { config, pkgs, ... }: {
+        services.avahi.enable = true;
+        services.avahi.nssmdns = true;
+      };
+  };
+
+  # Test whether `avahi-daemon' and `libnss-mdns' work as expected.
+  testScript =
+    '' startAll;
+
+       # mDNS.
+       $one->waitForUnit("network.target");
+       $one->succeed("avahi-resolve-host-name one.local | tee out >&2");
+       $one->succeed("test \"`cut -f1 < out`\" = one.local");
+       $one->succeed("avahi-resolve-host-name two.local | tee out >&2");
+       $one->succeed("test \"`cut -f1 < out`\" = two.local");
+
+       $two->waitForUnit("network.target");
+       $two->succeed("avahi-resolve-host-name one.local | tee out >&2");
+       $two->succeed("test \"`cut -f1 < out`\" = one.local");
+       $two->succeed("avahi-resolve-host-name two.local | tee out >&2");
+       $two->succeed("test \"`cut -f1 < out`\" = two.local");
+
+       # Basic DNS-SD.
+       $one->succeed("avahi-browse -r -t _workstation._tcp | tee out >&2");
+       $one->succeed("test `wc -l < out` -gt 0");
+       $two->succeed("avahi-browse -r -t _workstation._tcp | tee out >&2");
+       $two->succeed("test `wc -l < out` -gt 0");
+
+       # More DNS-SD.
+       $one->execute("avahi-publish -s \"This is a test\" _test._tcp 123 one=1 &");
+       $one->sleep(5);
+       $two->succeed("avahi-browse -r -t _test._tcp | tee out >&2");
+       $two->succeed("test `wc -l < out` -gt 0");
+
+       # NSS-mDNS.
+       $one->succeed("getent hosts one.local >&2");
+       $one->succeed("getent hosts two.local >&2");
+       $two->succeed("getent hosts one.local >&2");
+       $two->succeed("getent hosts two.local >&2");
+    '';
+}
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
new file mode 100644
index 00000000000..180da8267e0
--- /dev/null
+++ b/nixos/tests/bittorrent.nix
@@ -0,0 +1,107 @@
+# This test runs a Bittorrent tracker on one machine, and verifies
+# that two client machines can download the torrent using
+# `transmission'.  The first client (behind a NAT router) downloads
+# from the initial seeder running on the tracker.  Then we kill the
+# initial seeder.  The second client downloads from the first client,
+# which only works if the first client successfully uses the UPnP-IGD
+# protocol to poke a hole in the NAT.
+
+{ pkgs, ... }:
+
+let
+
+  # Some random file to serve.
+  file = pkgs.nixUnstable.src;
+
+  miniupnpdConf = nodes: pkgs.writeText "miniupnpd.conf"
+    ''
+      ext_ifname=eth1
+      listening_ip=${nodes.router.config.networking.interfaces.eth2.ipAddress}/24
+      allow 1024-65535 192.168.2.0/24 1024-65535
+    '';
+
+in
+
+{
+
+  nodes =
+    { tracker =
+        { config, pkgs, ... }:
+        { environment.systemPackages = [ pkgs.transmission pkgs.bittorrent ];
+
+          # We need Apache on the tracker to serve the torrents.
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          services.httpd.documentRoot = "/tmp";
+        };
+
+      router =
+        { config, pkgs, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpd ];
+          virtualisation.vlans = [ 1 2 ];
+          networking.nat.enable = true;
+          networking.nat.internalIPs = "192.168.2.0/24";
+          networking.nat.externalInterface = "eth1";
+        };
+
+      client1 =
+        { config, pkgs, nodes, ... }:
+        { environment.systemPackages = [ pkgs.transmission ];
+          virtualisation.vlans = [ 2 ];
+          networking.defaultGateway =
+            nodes.router.config.networking.interfaces.eth2.ipAddress;
+        };
+
+      client2 =
+        { config, pkgs, ... }:
+        { environment.systemPackages = [ pkgs.transmission ];
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      startAll;
+
+      # Enable NAT on the router and start miniupnpd.
+      $router->waitForUnit("nat");
+      $router->succeed(
+          "iptables -t nat -N MINIUPNPD",
+          "iptables -t nat -A PREROUTING -i eth1 -j MINIUPNPD",
+          "echo 1 > /proc/sys/net/ipv4/ip_forward",
+          "miniupnpd -f ${miniupnpdConf nodes}"
+      );
+
+      # Create the torrent.
+      $tracker->succeed("mkdir /tmp/data");
+      $tracker->succeed("cp ${file} /tmp/data/test.tar.bz2");
+      $tracker->succeed("transmission-create /tmp/data/test.tar.bz2 -t http://tracker:6969/announce -o /tmp/test.torrent");
+      $tracker->succeed("chmod 644 /tmp/test.torrent");
+
+      # Start the tracker.  !!! use a less crappy tracker
+      $tracker->waitForUnit("network.target");
+      $tracker->succeed("bittorrent-tracker --port 6969 --dfile /tmp/dstate >&2 &");
+      $tracker->waitForOpenPort(6969);
+
+      # Start the initial seeder.
+      my $pid = $tracker->succeed("transmission-cli /tmp/test.torrent -M -w /tmp/data >&2 & echo \$!");
+
+      # Now we should be able to download from the client behind the NAT.
+      $tracker->waitForUnit("httpd");
+      $client1->waitForUnit("network.target");
+      $client1->succeed("transmission-cli http://tracker/test.torrent -w /tmp >&2 &");
+      $client1->waitForFile("/tmp/test.tar.bz2");
+      $client1->succeed("cmp /tmp/test.tar.bz2 ${file}");
+
+      # Bring down the initial seeder.
+      $tracker->succeed("kill -9 $pid");
+
+      # Now download from the second client.  This can only succeed if
+      # the first client created a NAT hole in the router.
+      $client2->waitForUnit("network.target");
+      $client2->succeed("transmission-cli http://tracker/test.torrent -M -w /tmp >&2 &");
+      $client2->waitForFile("/tmp/test.tar.bz2");
+      $client2->succeed("cmp /tmp/test.tar.bz2 ${file}");
+    '';
+
+}
diff --git a/nixos/tests/check-filesystems.nix b/nixos/tests/check-filesystems.nix
new file mode 100644
index 00000000000..39e8883ee59
--- /dev/null
+++ b/nixos/tests/check-filesystems.nix
@@ -0,0 +1,80 @@
+{ nixos ? ./..
+, nixpkgs ? /etc/nixos/nixpkgs
+, system ? builtins.currentSystem
+}:
+
+with import ../lib/build-vms.nix { inherit nixos nixpkgs system; };
+
+rec {
+  nodes = {
+    share = {pkgs, config, ...}: {
+      services.nfs.server.enable = true;
+      services.nfs.server.exports = ''
+        /repos1 192.168.1.0/255.255.255.0(rw,no_root_squash)
+        /repos2 192.168.1.0/255.255.255.0(rw,no_root_squash)
+      '';
+      services.nfs.server.createMountPoints = true;
+
+      jobs.checkable = {
+        startOn = [
+          config.jobs.nfs_kernel_exports.name
+          config.jobs.nfs_kernel_nfsd.name
+        ];
+        respawn = true;
+      };
+    };
+
+    fsCheck = {pkgs, config, ...}: {
+      fileSystems =
+        let
+          repos1 = {
+            mountPoint = "/repos1";
+            autocreate = true;
+            device = "share:/repos1";
+            fsType = "nfs";
+          };
+
+          repos2 = {
+            mountPoint = "/repos2";
+            autocreate = true;
+            device = "share:/repos2";
+            fsType = "nfs";
+          };
+        in pkgs.lib.mkOverrideTemplate 50 {} [
+          repos1
+          repos1 # check remount
+          repos2 # check after remount
+        ];
+
+      jobs.checkable = {
+        startOn = "stopped ${config.jobs.filesystems.name}";
+        respawn = true;
+      };
+    };
+  };
+
+  vms = buildVirtualNetwork { inherit nodes; };
+
+  test = runTests vms
+    ''
+      startAll;
+
+      $share->waitForUnit("checkable");
+      $fsCheck->waitForUnit("checkable");
+
+      # check repos1
+      $fsCheck->succeed("test -d /repos1");
+      $share->succeed("touch /repos1/test1");
+      $fsCheck->succeed("test -e /repos1/test1");
+
+      # check repos2 (check after remount)
+      $fsCheck->succeed("test -d /repos2");
+      $share->succeed("touch /repos2/test2");
+      $fsCheck->succeed("test -e /repos2/test2");
+
+      # check without network
+      $share->block();
+      $fsCheck->fail("test -e /repos1/test1");
+      $fsCheck->fail("test -e /repos2/test2");
+    '';
+}
diff --git a/nixos/tests/common/user-account.nix b/nixos/tests/common/user-account.nix
new file mode 100644
index 00000000000..8157cf8d263
--- /dev/null
+++ b/nixos/tests/common/user-account.nix
@@ -0,0 +1,11 @@
+{ pkgs, ... }:
+
+{ users.extraUsers = pkgs.lib.singleton
+    { name = "alice";
+      description = "Alice Foobar";
+      home = "/home/alice";
+      createHome = true;
+      useDefaultShell = true;
+      password = "foobar";
+    };
+}
diff --git a/nixos/tests/common/x11.nix b/nixos/tests/common/x11.nix
new file mode 100644
index 00000000000..c5a7c165d12
--- /dev/null
+++ b/nixos/tests/common/x11.nix
@@ -0,0 +1,12 @@
+{ services.xserver.enable = true;
+
+  # Automatically log in.
+  services.xserver.displayManager.auto.enable = true;
+
+  # Use IceWM as the window manager.
+  services.xserver.windowManager.default = "icewm";
+  services.xserver.windowManager.icewm.enable = true;
+
+  # Don't use a desktop manager.
+  services.xserver.desktopManager.default = "none";
+}
diff --git a/nixos/tests/default.nix b/nixos/tests/default.nix
new file mode 100644
index 00000000000..17fe6a6d045
--- /dev/null
+++ b/nixos/tests/default.nix
@@ -0,0 +1,37 @@
+{ nixpkgs ? <nixpkgs>
+, system ? builtins.currentSystem
+, minimal ? false
+}:
+
+with import ../lib/testing.nix { inherit system minimal; };
+
+{
+  avahi = makeTest (import ./avahi.nix);
+  bittorrent = makeTest (import ./bittorrent.nix);
+  firefox = makeTest (import ./firefox.nix);
+  firewall = makeTest (import ./firewall.nix);
+  installer = makeTests (import ./installer.nix);
+  efi-installer = makeTests (import ./efi-installer.nix);
+  ipv6 = makeTest (import ./ipv6.nix);
+  kde4 = makeTest (import ./kde4.nix);
+  #kexec = makeTest (import ./kexec.nix);
+  login = makeTest (import ./login.nix {});
+  latestKernel.login = makeTest (import ./login.nix ({ config, pkgs, ... }: { boot.kernelPackages = pkgs.linuxPackages_latest; }));
+  misc = makeTest (import ./misc.nix);
+  #mpich = makeTest (import ./mpich.nix);
+  mysql = makeTest (import ./mysql.nix);
+  mysql_replication = makeTest (import ./mysql-replication.nix);
+  nat = makeTest (import ./nat.nix);
+  nfs3 = makeTest (import ./nfs.nix { version = 3; });
+  #nfs4 = makeTest (import ./nfs.nix { version = 4; });
+  openssh = makeTest (import ./openssh.nix);
+  partition = makeTest (import ./partition.nix);
+  printing = makeTest (import ./printing.nix);
+  proxy = makeTest (import ./proxy.nix);
+  quake3 = makeTest (import ./quake3.nix);
+  simple = makeTest (import ./simple.nix);
+  #subversion = makeTest (import ./subversion.nix);
+  tomcat = makeTest (import ./tomcat.nix);
+  trac = makeTest (import ./trac.nix);
+  xfce = makeTest (import ./xfce.nix);
+}
diff --git a/nixos/tests/efi-installer.nix b/nixos/tests/efi-installer.nix
new file mode 100644
index 00000000000..a9c5d719030
--- /dev/null
+++ b/nixos/tests/efi-installer.nix
@@ -0,0 +1,131 @@
+# !!! Merge into normal install tests once all livecds are EFIable
+{ pkgs, system, ... }:
+
+with pkgs.lib;
+with import ../lib/qemu-flags.nix;
+
+let
+
+  # Build the ISO.  This is the regular installation CD but with test
+  # instrumentation.
+  iso =
+    (import ../lib/eval-config.nix {
+      inherit system;
+      modules =
+        [ ../modules/installer/cd-dvd/installation-cd-efi.nix
+          ../modules/testing/test-instrumentation.nix
+          { key = "serial";
+
+            # The test cannot access the network, so any sources we
+            # need must be included in the ISO.
+            isoImage.storeContents =
+              [ pkgs.glibcLocales
+                pkgs.sudo
+                pkgs.docbook5
+                pkgs.docbook5_xsl
+                pkgs.grub
+                pkgs.perlPackages.XMLLibXML
+                pkgs.unionfs-fuse
+                pkgs.gummiboot
+                pkgs.libxslt
+              ];
+          }
+        ];
+    }).config.system.build.isoImage;
+
+
+  # The config to install
+  config = builtins.toFile "configuration.nix" ''
+    { pkgs, ... }: {
+      imports = [ ./hardware.nix <nixos/modules/testing/test-instrumentation.nix> ];
+      boot.kernelPackages = pkgs.linuxPackages_3_10;
+      boot.loader.grub.enable = false;
+      boot.loader.efi.canTouchEfiVariables = true;
+      boot.loader.gummiboot.enable = true;
+      fonts.enableFontConfig = false;
+      fileSystems."/".label = "nixos";
+    }
+  '';
+
+  biosDir = pkgs.runCommand "ovmf-bios" {} ''
+    mkdir $out
+    ln -s ${pkgs.OVMF}/FV/OVMF.fd $out/bios.bin
+  '';
+
+in {
+  simple = {
+    inherit iso;
+    nodes = {};
+    testScript = ''
+      createDisk("harddisk", 4 * 1024);
+
+      my $machine = createMachine({ hda => "harddisk",
+        hdaInterface => "virtio",
+        cdrom => glob("${iso}/iso/*.iso"),
+        qemuFlags => '-L ${biosDir} ${optionalString (pkgs.stdenv.system == "x86_64-linux") "-cpu kvm64"}'});
+      $machine->start;
+
+      # Make sure that we get a login prompt etc.
+      $machine->succeed("echo hello");
+      $machine->waitForUnit("rogue");
+      $machine->waitForUnit("nixos-manual");
+      $machine->waitForUnit("dhcpcd");
+
+      # Partition the disk.
+      $machine->succeed(
+          "sgdisk -Z /dev/vda",
+          "sgdisk -n 1:0:+256M -N 2 -t 1:ef00 -t 2:8300 -c 1:boot -c 2:root /dev/vda",
+          "mkfs.vfat -n BOOT /dev/vda1",
+          "mkfs.ext3 -L nixos /dev/vda2",
+          "mount LABEL=nixos /mnt",
+          "mkdir /mnt/boot",
+          "mount LABEL=BOOT /mnt/boot",
+      );
+
+      # Create the NixOS configuration.
+      $machine->succeed(
+          "mkdir -p /mnt/etc/nixos",
+          "nixos-hardware-scan > /mnt/etc/nixos/hardware.nix",
+      );
+
+      my $cfg = $machine->succeed("cat /mnt/etc/nixos/hardware.nix");
+      print STDERR "Result of the hardware scan:\n$cfg\n";
+
+      $machine->copyFileFromHost(
+          "${config}",
+          "/mnt/etc/nixos/configuration.nix");
+
+      # Perform the installation.
+      $machine->succeed("nixos-install >&2");
+
+      # Do it again to make sure it's idempotent.
+      $machine->succeed("nixos-install >&2");
+
+      $machine->shutdown;
+
+      # Now see if we can boot the installation.
+      my $machine = createMachine({ #hda => "harddisk",
+#       hdaInterface => "virtio",
+#       !!! OVMF doesn't boot from virtio http://www.mail-archive.com/edk2-devel@lists.sourceforge.net/msg01501.html
+        qemuFlags => '-L ${biosDir} ${optionalString (pkgs.stdenv.system == "x86_64-linux") "-cpu kvm64"} -m 512 -hda ' . Cwd::abs_path('harddisk')});
+
+      # Did /boot get mounted, if appropriate?
+      $machine->waitForUnit("local-fs.target");
+      $machine->succeed("test -e /boot/efi");
+
+      $machine->succeed("nix-env -i coreutils >&2");
+      $machine->succeed("type -tP ls | tee /dev/stderr") =~ /.nix-profile/
+          or die "nix-env failed";
+
+      $machine->succeed("nixos-rebuild switch >&2");
+
+      $machine->shutdown;
+
+      my $machine = createMachine({ #hda => "harddisk",
+#       hdaInterface => "virtio",
+        qemuFlags => '-L ${biosDir} ${optionalString (pkgs.stdenv.system == "x86_64-linux") "-cpu kvm64"} -hda ' . Cwd::abs_path('harddisk')});
+      $machine->waitForUnit("network.target");
+      $machine->shutdown;
+    '';
+  };
+}
diff --git a/nixos/tests/firefox.nix b/nixos/tests/firefox.nix
new file mode 100644
index 00000000000..d6599be13c9
--- /dev/null
+++ b/nixos/tests/firefox.nix
@@ -0,0 +1,21 @@
+{ pkgs, ... }:
+
+{
+
+  machine =
+    { config, pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      environment.systemPackages = [ pkgs.firefox ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForX;
+      $machine->execute("firefox file://${pkgs.valgrind}/share/doc/valgrind/html/index.html &");
+      $machine->waitForWindow(qr/Valgrind/);
+      $machine->sleep(40); # wait until Firefox has finished loading the page
+      $machine->screenshot("screen");
+    '';
+
+}
diff --git a/nixos/tests/firewall.nix b/nixos/tests/firewall.nix
new file mode 100644
index 00000000000..de32b98e5d2
--- /dev/null
+++ b/nixos/tests/firewall.nix
@@ -0,0 +1,48 @@
+# Test the firewall module.
+
+{ pkgs, ... }:
+
+{
+
+  nodes =
+    { walled =
+        { config, pkgs, nodes, ... }:
+        { networking.firewall.enable = true;
+          networking.firewall.logRefusedPackets = true;
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+        };
+
+      attacker =
+        { config, pkgs, ... }:
+        { services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      startAll;
+
+      $walled->waitForUnit("firewall");
+      $walled->waitForUnit("httpd");
+      $attacker->waitForUnit("network.target");
+
+      # Local connections should still work.
+      $walled->succeed("curl -v http://localhost/ >&2");
+
+      # Connections to the firewalled machine should fail.
+      $attacker->fail("curl -v http://walled/ >&2");
+      $attacker->fail("ping -c 1 walled >&2");
+
+      # Outgoing connections/pings should still work.
+      $walled->succeed("curl -v http://attacker/ >&2");
+      $walled->succeed("ping -c 1 attacker >&2");
+
+      # If we stop the firewall, then connections should succeed.
+      $walled->stopJob("firewall");
+      $attacker->succeed("curl -v http://walled/ >&2");
+    '';
+
+}
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
new file mode 100644
index 00000000000..0ce78053171
--- /dev/null
+++ b/nixos/tests/installer.nix
@@ -0,0 +1,354 @@
+{ pkgs, system, ... }:
+
+with pkgs.lib;
+with import ../lib/qemu-flags.nix;
+
+let
+
+  # Build the ISO.  This is the regular installation CD but with test
+  # instrumentation.
+  iso =
+    (import ../lib/eval-config.nix {
+      inherit system;
+      modules =
+        [ ../modules/installer/cd-dvd/installation-cd-graphical.nix
+          ../modules/testing/test-instrumentation.nix
+          { key = "serial";
+            boot.loader.grub.timeout = mkOverrideTemplate 0 {} 0;
+
+            # The test cannot access the network, so any sources we
+            # need must be included in the ISO.
+            isoImage.storeContents =
+              [ pkgs.glibcLocales
+                pkgs.sudo
+                pkgs.docbook5
+                pkgs.docbook5_xsl
+                pkgs.grub
+                pkgs.perlPackages.XMLLibXML
+                pkgs.unionfs-fuse
+              ];
+          }
+        ];
+    }).config.system.build.isoImage;
+
+
+  # The configuration to install.
+  config = { fileSystems, testChannel, grubVersion, grubDevice }: pkgs.writeText "configuration.nix"
+    ''
+      { config, pkgs, modulesPath, ... }:
+
+      { imports =
+          [ ./hardware.nix
+            "''${modulesPath}/testing/test-instrumentation.nix"
+          ];
+
+        boot.loader.grub.version = ${toString grubVersion};
+        ${optionalString (grubVersion == 1) ''
+          boot.loader.grub.splashImage = null;
+        ''}
+        boot.loader.grub.device = "${grubDevice}";
+        boot.loader.grub.extraConfig = "serial; terminal_output.serial";
+        boot.initrd.kernelModules = [ "ext3" "ext4" "xfs" "virtio_console" ];
+
+        ${fileSystems}
+        swapDevices = [ { label = "swap"; } ];
+
+        environment.systemPackages = [ ${optionalString testChannel "pkgs.rlwrap"} ];
+      }
+    '';
+
+  rootFS =
+    ''
+      fileSystems."/".device = "/dev/disk/by-label/nixos";
+    '';
+
+  bootFS =
+    ''
+      fileSystems."/boot".device = "/dev/disk/by-label/boot";
+    '';
+
+
+  # Configuration of a web server that simulates the Nixpkgs channel
+  # distribution server.
+  webserver =
+    { config, pkgs, ... }:
+
+    { services.httpd.enable = true;
+      services.httpd.adminAddr = "foo@example.org";
+      services.httpd.servedDirs = singleton
+        { urlPath = "/";
+          dir = "/tmp/channel";
+        };
+
+      virtualisation.writableStore = true;
+      virtualisation.pathsInNixDB = channelContents ++ [ pkgs.hello.src ];
+      virtualisation.memorySize = 768;
+    };
+
+  channelContents = [ pkgs.rlwrap ];
+
+
+  # The test script boots the CD, installs NixOS on an empty hard
+  # disk, and then reboot from the hard disk.  It's parameterized with
+  # a test script fragment `createPartitions', which must create
+  # partitions and filesystems, and a configuration.nix fragment
+  # `fileSystems'.
+  testScriptFun = { createPartitions, fileSystems, testChannel, grubVersion, grubDevice }:
+    let iface = if grubVersion == 1 then "scsi" else "virtio"; in
+    ''
+      createDisk("harddisk", 4 * 1024);
+
+      my $machine = createMachine({ hda => "harddisk",
+        hdaInterface => "${iface}",
+        cdrom => glob("${iso}/iso/*.iso"),
+        qemuFlags => '${optionalString testChannel (toString (qemuNICFlags 1 1 2))} ${optionalString (pkgs.stdenv.system == "x86_64-linux") "-cpu kvm64"}'});
+      $machine->start;
+
+      ${optionalString testChannel ''
+        # Create a channel on the web server containing a few packages
+        # to simulate the Nixpkgs channel.
+        $webserver->start;
+        $webserver->waitForUnit("httpd");
+        $webserver->succeed(
+            "nix-push --bzip2 --dest /tmp/channel --manifest --url-prefix http://nixos.org/channels/nixos-unstable " .
+            "${toString channelContents} >&2");
+        $webserver->succeed("mkdir /tmp/channel/sha256");
+        $webserver->succeed("cp ${pkgs.hello.src} /tmp/channel/sha256/${pkgs.hello.src.outputHash}");
+      ''}
+
+      # Make sure that we get a login prompt etc.
+      $machine->succeed("echo hello");
+      #$machine->waitForUnit('getty@tty2');
+      $machine->waitForUnit("rogue");
+      $machine->waitForUnit("nixos-manual");
+      $machine->waitForUnit("dhcpcd");
+
+      ${optionalString testChannel ''
+        # Allow the machine to talk to the fake nixos.org.
+        $machine->succeed(
+            "rm /etc/hosts",
+            "echo 192.168.1.1 nixos.org cache.nixos.org tarballs.nixos.org > /etc/hosts",
+            "ifconfig eth1 up 192.168.1.2",
+        );
+
+        # Test nix-env.
+        $machine->fail("hello");
+        $machine->succeed("nix-env -i hello");
+        $machine->succeed("hello") =~ /Hello, world/
+            or die "bad `hello' output";
+      ''}
+
+      # Partition the disk.
+      ${createPartitions}
+
+      # Create the NixOS configuration.
+      $machine->succeed(
+          "mkdir -p /mnt/etc/nixos",
+          "nixos-hardware-scan > /mnt/etc/nixos/hardware.nix",
+      );
+
+      my $cfg = $machine->succeed("cat /mnt/etc/nixos/hardware.nix");
+      print STDERR "Result of the hardware scan:\n$cfg\n";
+
+      $machine->copyFileFromHost(
+          "${ config { inherit fileSystems testChannel grubVersion grubDevice; } }",
+          "/mnt/etc/nixos/configuration.nix");
+
+      # Perform the installation.
+      $machine->succeed("nixos-install >&2");
+
+      # Do it again to make sure it's idempotent.
+      $machine->succeed("nixos-install >&2");
+
+      $machine->shutdown;
+
+      # Now see if we can boot the installation.
+      my $machine = createMachine({ hda => "harddisk", hdaInterface => "${iface}" });
+
+      # Did /boot get mounted, if appropriate?
+      $machine->waitForUnit("local-fs.target");
+      $machine->succeed("test -e /boot/grub");
+
+      # Did the swap device get activated?
+      $machine->waitForUnit("swap.target");
+      $machine->succeed("cat /proc/swaps | grep -q /dev");
+
+      $machine->succeed("nix-env -i coreutils >&2");
+      $machine->succeed("type -tP ls | tee /dev/stderr") =~ /.nix-profile/
+          or die "nix-env failed";
+
+      $machine->succeed("nixos-rebuild switch >&2");
+
+      $machine->shutdown;
+
+      # And just to be sure, check that the machine still boots after
+      # "nixos-rebuild switch".
+      my $machine = createMachine({ hda => "harddisk", hdaInterface => "${iface}" });
+      $machine->waitForUnit("network.target");
+      $machine->shutdown;
+    '';
+
+
+  makeTest = { createPartitions, fileSystems, testChannel ? false, grubVersion ? 2, grubDevice ? "/dev/vda" }:
+    { inherit iso;
+      nodes = if testChannel then { inherit webserver; } else { };
+      testScript = testScriptFun {
+        inherit createPartitions fileSystems testChannel grubVersion grubDevice;
+      };
+    };
+
+
+in {
+
+  # !!! `parted mkpart' seems to silently create overlapping partitions.
+
+
+  # The (almost) simplest partitioning scheme: a swap partition and
+  # one big filesystem partition.
+  simple = makeTest
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "parted /dev/vda mklabel msdos",
+              "parted /dev/vda -- mkpart primary linux-swap 1M 1024M",
+              "parted /dev/vda -- mkpart primary ext2 1024M -1s",
+              "udevadm settle",
+              "mkswap /dev/vda1 -L swap",
+              "swapon -L swap",
+              "mkfs.ext3 -L nixos /dev/vda2",
+              "mount LABEL=nixos /mnt",
+          );
+        '';
+      fileSystems = rootFS;
+      testChannel = true;
+    };
+
+  # Same as the previous, but now with a separate /boot partition.
+  separateBoot = makeTest
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "parted /dev/vda mklabel msdos",
+              "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
+              "parted /dev/vda -- mkpart primary linux-swap 50MB 1024M",
+              "parted /dev/vda -- mkpart primary ext2 1024M -1s", # /
+              "udevadm settle",
+              "mkswap /dev/vda2 -L swap",
+              "swapon -L swap",
+              "mkfs.ext3 -L nixos /dev/vda3",
+              "mount LABEL=nixos /mnt",
+              "mkfs.ext3 -L boot /dev/vda1",
+              "mkdir /mnt/boot",
+              "mount LABEL=boot /mnt/boot",
+          );
+        '';
+      fileSystems = rootFS + bootFS;
+    };
+
+  # Create two physical LVM partitions combined into one volume group
+  # that contains the logical swap and root partitions.
+  lvm = makeTest
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "parted /dev/vda mklabel msdos",
+              "parted /dev/vda -- mkpart primary 1M 2048M", # first PV
+              "parted /dev/vda -- set 1 lvm on",
+              "parted /dev/vda -- mkpart primary 2048M -1s", # second PV
+              "parted /dev/vda -- set 2 lvm on",
+              "udevadm settle",
+              "pvcreate /dev/vda1 /dev/vda2",
+              "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
+              "lvcreate --size 1G --name swap MyVolGroup",
+              "lvcreate --size 2G --name nixos MyVolGroup",
+              "mkswap -f /dev/MyVolGroup/swap -L swap",
+              "swapon -L swap",
+              "mkfs.xfs -L nixos /dev/MyVolGroup/nixos",
+              "mount LABEL=nixos /mnt",
+          );
+        '';
+      fileSystems = rootFS;
+    };
+
+  /*
+  swraid = makeTest
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "parted /dev/vda --"
+              . " mklabel msdos"
+              . " mkpart primary ext2 1M 30MB" # /boot
+              . " mkpart extended 30M -1s"
+              . " mkpart logical 31M 1531M" # md0 (root), first device
+              . " mkpart logical 1540M 3040M" # md0 (root), second device
+              . " mkpart logical 3050M 3306M" # md1 (swap), first device
+              . " mkpart logical 3320M 3576M", # md1 (swap), second device
+              "udevadm settle",
+              "ls -l /dev/vda* >&2",
+              "cat /proc/partitions >&2",
+              "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda5 /dev/vda6",
+              "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda7 /dev/vda8",
+              "udevadm settle",
+              "mkswap -f /dev/md1 -L swap",
+              "swapon -L swap",
+              "mkfs.ext3 -L nixos /dev/md0",
+              "mount LABEL=nixos /mnt",
+              "mkfs.ext3 -L boot /dev/vda1",
+              "mkdir /mnt/boot",
+              "mount LABEL=boot /mnt/boot",
+              "udevadm settle",
+              "mdadm -W /dev/md0", # wait for sync to finish; booting off an unsynced device tends to fail
+              "mdadm -W /dev/md1",
+          );
+        '';
+      fileSystems = rootFS + bootFS;
+    };
+  */
+
+  # Test a basic install using GRUB 1.
+  grub1 = makeTest
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "parted /dev/sda mklabel msdos",
+              "parted /dev/sda -- mkpart primary linux-swap 1M 1024M",
+              "parted /dev/sda -- mkpart primary ext2 1024M -1s",
+              "udevadm settle",
+              "mkswap /dev/sda1 -L swap",
+              "swapon -L swap",
+              "mkfs.ext3 -L nixos /dev/sda2",
+              "mount LABEL=nixos /mnt",
+          );
+        '';
+      fileSystems = rootFS;
+      grubVersion = 1;
+      grubDevice = "/dev/sda";
+    };
+
+  # Rebuild the CD configuration with a little modification.
+  rebuildCD =
+    { inherit iso;
+      nodes = { };
+      testScript =
+        ''
+          my $machine = createMachine({ cdrom => glob("${iso}/iso/*.iso"), qemuFlags => '-m 768' });
+          $machine->start;
+
+          # Enable sshd service.
+          $machine->succeed(
+            "sed -i 's,^}\$,systemd.services.sshd.wantedBy = pkgs.lib.mkOverride 0 [\"multi-user.target\"]; },' /etc/nixos/configuration.nix"
+          );
+
+          $machine->succeed("cat /etc/nixos/configuration.nix >&2");
+
+          # Apply the new CD configuration.
+          $machine->succeed("nixos-rebuild test");
+
+          # Connect to it-self.
+          $machine->waitForUnit("sshd");
+          $machine->waitForOpenPort(22);
+
+          $machine->shutdown;
+        '';
+    };
+}
diff --git a/nixos/tests/ipv6.nix b/nixos/tests/ipv6.nix
new file mode 100644
index 00000000000..29d675e180a
--- /dev/null
+++ b/nixos/tests/ipv6.nix
@@ -0,0 +1,76 @@
+# Test of IPv6 functionality in NixOS, including whether router
+# solicication/advertisement using radvd works.
+
+{ pkgs, ... }:
+
+{
+
+  nodes =
+    { client = { config, pkgs, ... }: { };
+
+      server =
+        { config, pkgs, ... }:
+        { services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+        };
+
+      router =
+        { config, pkgs, ... }:
+        { services.radvd.enable = true;
+          services.radvd.config =
+            ''
+              interface eth1 {
+                AdvSendAdvert on;
+                # ULA prefix (RFC 4193).
+                prefix fd60:cc69:b537:1::/64 { };
+              };
+            '';
+        };
+    };
+
+  testScript =
+    ''
+      # Start the router first so that it respond to router solicitations.
+      $router->waitForUnit("radvd");
+
+      startAll;
+
+      $client->waitForUnit("network.target");
+      $server->waitForUnit("network.target");
+
+      # Wait until the given interface has a non-tentative address of
+      # the desired scope (i.e. has completed Duplicate Address
+      # Detection).
+      sub waitForAddress {
+          my ($machine, $iface, $scope) = @_;
+          $machine->waitUntilSucceeds("[ `ip -o -6 addr show dev $iface scope $scope | grep -v tentative | wc -l` -eq 1 ]");
+          my $ip = (split /[ \/]+/, $machine->succeed("ip -o -6 addr show dev $iface scope $scope"))[3];
+          $machine->log("$scope address on $iface is $ip");
+          return $ip;
+      }
+
+      subtest "loopback address", sub {
+          $client->succeed("ping6 -c 1 ::1 >&2");
+          $client->fail("ping6 -c 1 ::2 >&2");
+      };
+
+      subtest "local link addressing", sub {
+          my $clientIp = waitForAddress $client, "eth1", "link";
+          my $serverIp = waitForAddress $server, "eth1", "link";
+          $client->succeed("ping6 -c 1 -I eth1 $clientIp >&2");
+          $client->succeed("ping6 -c 1 -I eth1 $serverIp >&2");
+      };
+
+      subtest "global addressing", sub {
+          my $clientIp = waitForAddress $client, "eth1", "global";
+          my $serverIp = waitForAddress $server, "eth1", "global";
+          $client->succeed("ping6 -c 1 $clientIp >&2");
+          $client->succeed("ping6 -c 1 $serverIp >&2");
+          $client->succeed("curl --fail -g http://[$serverIp]");
+          $client->fail("curl --fail -g http://[$clientIp]");
+      };
+
+      # TODO: test reachability of a machine on another network.
+    '';
+
+}
diff --git a/nixos/tests/kde4.nix b/nixos/tests/kde4.nix
new file mode 100644
index 00000000000..3fb35bbab09
--- /dev/null
+++ b/nixos/tests/kde4.nix
@@ -0,0 +1,67 @@
+{ pkgs, ... }:
+
+{
+
+  machine =
+    { config, pkgs, ... }:
+
+    { imports = [ ./common/user-account.nix ];
+
+      virtualisation.memorySize = 768;
+
+      services.xserver.enable = true;
+
+      services.httpd.enable = true;
+      services.httpd.adminAddr = "foo@example.org";
+      services.httpd.documentRoot = "${pkgs.valgrind}/share/doc/valgrind/html";
+
+      services.xserver.displayManager.kdm.enable = true;
+      services.xserver.displayManager.kdm.extraConfig =
+        ''
+          [X-:0-Core]
+          AutoLoginEnable=true
+          AutoLoginUser=alice
+          AutoLoginPass=foobar
+        '';
+
+      services.xserver.desktopManager.kde4.enable = true;
+
+      # Include most of KDE. We don't really test these here, but at
+      # least they should build.
+      environment.systemPackages =
+        [ pkgs.kde4.kdemultimedia
+          pkgs.kde4.kdegraphics
+          pkgs.kde4.kdeutils
+          pkgs.kde4.kdegames
+          pkgs.kde4.kdeedu
+          pkgs.kde4.kdeaccessibility
+          pkgs.kde4.kdeadmin
+          pkgs.kde4.kdenetwork
+          pkgs.kde4.kdetoys
+          pkgs.kde4.kdewebdev
+        ];
+    };
+
+  testScript =
+    ''
+      $machine->waitUntilSucceeds("pgrep plasma-desktop");
+      $machine->waitForWindow(qr/plasma-desktop/);
+
+      # Check that logging in has given the user ownership of devices.
+      $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
+
+      $machine->execute("su - alice -c 'DISPLAY=:0.0 kwrite /var/log/messages &'");
+      $machine->waitForWindow(qr/messages.*KWrite/);
+
+      $machine->execute("su - alice -c 'DISPLAY=:0.0 konqueror http://localhost/ &'");
+      $machine->waitForWindow(qr/Valgrind.*Konqueror/);
+
+      $machine->execute("su - alice -c 'DISPLAY=:0.0 gwenview ${pkgs.kde4.kde_wallpapers}/share/wallpapers/Hanami/contents/images/1280x1024.jpg &'");
+      $machine->waitForWindow(qr/Gwenview/);
+
+      $machine->sleep(10);
+
+      $machine->screenshot("screen");
+    '';
+
+}
diff --git a/nixos/tests/kexec.nix b/nixos/tests/kexec.nix
new file mode 100644
index 00000000000..b8da332b919
--- /dev/null
+++ b/nixos/tests/kexec.nix
@@ -0,0 +1,18 @@
+# Test whether fast reboots via kexec work.
+
+{ pkgs, ... }:
+
+{
+
+  machine = { config, pkgs, ... }:
+    { virtualisation.vlans = [ ]; };
+
+  testScript =
+    ''
+      $machine->waitForUnit("multi-user.target");
+      $machine->execute("systemctl kexec &");
+      $machine->{connected} = 0;
+      $machine->waitForUnit("multi-user.target");
+    '';
+
+}
diff --git a/nixos/tests/login.nix b/nixos/tests/login.nix
new file mode 100644
index 00000000000..ed7d9786717
--- /dev/null
+++ b/nixos/tests/login.nix
@@ -0,0 +1,61 @@
+config: { pkgs, ... }:
+
+{
+
+  machine = config;
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->screenshot("postboot");
+
+      subtest "create user", sub {
+          $machine->succeed("useradd -m alice");
+          $machine->succeed("(echo foobar; echo foobar) | passwd alice");
+      };
+
+      # Check whether switching VTs works.
+      subtest "virtual console switching", sub {
+          $machine->sendKeys("alt-f2");
+          $machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
+          $machine->waitForUnit('getty@tty2.service');
+      };
+
+      # Log in as alice on a virtual console.
+      subtest "virtual console login", sub {
+          $machine->sleep(2); # urgh: wait for username prompt
+          $machine->sendChars("alice\n");
+          $machine->waitUntilSucceeds("pgrep login");
+          $machine->sleep(2); # urgh: wait for `Password:'
+          $machine->sendChars("foobar\n");
+          $machine->waitUntilSucceeds("pgrep -u alice bash");
+          $machine->sendChars("touch done\n");
+          $machine->waitForFile("/home/alice/done");
+      };
+
+      # Check whether systemd gives and removes device ownership as
+      # needed.
+      subtest "device permissions", sub {
+          $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
+          $machine->sendKeys("alt-f1");
+          $machine->waitUntilSucceeds("[ \$(fgconsole) = 1 ]");
+          $machine->fail("getfacl /dev/snd/timer | grep -q alice");
+          $machine->succeed("chvt 2");
+          $machine->waitUntilSucceeds("getfacl /dev/snd/timer | grep -q alice");
+      };
+
+      # Log out.
+      subtest "virtual console logout", sub {
+          $machine->sendChars("exit\n");
+          $machine->waitUntilFails("pgrep -u alice bash");
+          $machine->screenshot("mingetty");
+      };
+
+      # Check whether ctrl-alt-delete works.
+      subtest "ctrl-alt-delete", sub {
+          $machine->sendKeys("ctrl-alt-delete");
+          $machine->waitForShutdown;
+      };
+    '';
+
+}
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
new file mode 100644
index 00000000000..e18a208fe43
--- /dev/null
+++ b/nixos/tests/misc.nix
@@ -0,0 +1,63 @@
+# Miscellaneous small tests that don't warrant their own VM run.
+
+{ pkgs, ... }:
+
+{
+
+  machine =
+    { config, pkgs, ... }:
+    { swapDevices = pkgs.lib.mkOverride 0
+        [ { device = "/root/swapfile"; size = 128; } ];
+      environment.variables.EDITOR = pkgs.lib.mkOverride 0 "emacs";
+    };
+
+  testScript =
+    ''
+      subtest "nixos-version", sub {
+          $machine->succeed("[ `nixos-version | wc -w` = 2 ]");
+      };
+
+      subtest "nixos-rebuild", sub {
+          $machine->succeed("nixos-rebuild --help | grep 'Usage:'");
+      };
+
+      # Sanity check for uid/gid assignment.
+      subtest "users-groups", sub {
+          $machine->succeed("[ `id -u messagebus` = 4 ]");
+          $machine->succeed("[ `id -g messagebus` = 4 ]");
+          $machine->succeed("[ `getent group users` = 'users:x:100:' ]");
+      };
+
+      # Regression test for GMP aborts on QEMU.
+      subtest "gmp", sub {
+          $machine->succeed("expr 1 + 2");
+      };
+
+      # Test that the swap file got created.
+      subtest "swapfile", sub {
+          $machine->waitForUnit("root-swapfile.swap");
+          $machine->succeed("ls -l /root/swapfile | grep 134217728");
+      };
+
+      # Test whether kernel.poweroff_cmd is set.
+      subtest "poweroff_cmd", sub {
+          $machine->succeed("[ -x \"\$(cat /proc/sys/kernel/poweroff_cmd)\" ]")
+      };
+
+      # Test whether the blkio controller is properly enabled.
+      subtest "blkio-cgroup", sub {
+          $machine->succeed("[ -n \"\$(cat /sys/fs/cgroup/blkio/blkio.sectors)\" ]")
+      };
+
+      # Test whether we have a reboot record in wtmp.
+      subtest "reboot-wtmp", sub {
+          $machine->succeed("last | grep reboot >&2");
+      };
+
+      # Test whether we can override environment variables.
+      subtest "override-env-var", sub {
+          $machine->succeed('[ "$EDITOR" = emacs ]');
+      };
+    '';
+
+}
diff --git a/nixos/tests/mpich-example.c b/nixos/tests/mpich-example.c
new file mode 100644
index 00000000000..c48e3c45b72
--- /dev/null
+++ b/nixos/tests/mpich-example.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <mpi.h>
+
+int
+main (int argc, char *argv[])
+{
+  int rank, size, length;
+  char name[BUFSIZ];
+
+  MPI_Init (&argc, &argv);
+  MPI_Comm_rank (MPI_COMM_WORLD, &rank);
+  MPI_Comm_size (MPI_COMM_WORLD, &size);
+  MPI_Get_processor_name (name, &length);
+
+  printf ("%s: hello world from process %d of %d\n", name, rank, size);
+
+  MPI_Finalize ();
+
+  return EXIT_SUCCESS;
+}
diff --git a/nixos/tests/mpich.nix b/nixos/tests/mpich.nix
new file mode 100644
index 00000000000..d57512ebdfe
--- /dev/null
+++ b/nixos/tests/mpich.nix
@@ -0,0 +1,40 @@
+# Simple example to showcase distributed tests using NixOS VMs.
+
+{ pkgs, ... }:
+
+with pkgs;
+
+{
+  nodes = {
+    master =
+      { config, pkgs, ... }: {
+        environment.systemPackages = [ gcc mpich2 ];
+        #boot.kernelPackages = pkgs.kernelPackages_2_6_29;
+      };
+
+    slave =
+      { config, pkgs, ... }: {
+        environment.systemPackages = [ gcc mpich2 ];
+      };
+  };
+
+  # Start master/slave MPI daemons and compile/run a program that uses both
+  # nodes.
+  testScript =
+    ''
+       startAll;
+
+       $master->succeed("echo 'MPD_SECRETWORD=secret' > /etc/mpd.conf");
+       $master->succeed("chmod 600 /etc/mpd.conf");
+       $master->succeed("mpd --daemon --ifhn=master --listenport=4444");
+
+       $slave->succeed("echo 'MPD_SECRETWORD=secret' > /etc/mpd.conf");
+       $slave->succeed("chmod 600 /etc/mpd.conf");
+       $slave->succeed("mpd --daemon --host=master --port=4444");
+
+       $master->succeed("mpicc -o example -Wall ${./mpich-example.c}");
+       $slave->succeed("mpicc -o example -Wall ${./mpich-example.c}");
+
+       $master->succeed("mpiexec -n 2 ./example >&2");
+    '';
+}
diff --git a/nixos/tests/mysql-replication.nix b/nixos/tests/mysql-replication.nix
new file mode 100644
index 00000000000..28a1187dd18
--- /dev/null
+++ b/nixos/tests/mysql-replication.nix
@@ -0,0 +1,57 @@
+{ pkgs, ... }:
+
+let
+  replicateUser = "replicate";
+  replicatePassword = "secret";
+in
+{
+  nodes = {
+    master =
+      { pkgs, config, ... }:
+
+      {
+        services.mysql.enable = true;
+	services.mysql.replication.role = "master";
+	services.mysql.initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+	services.mysql.initialScript = pkgs.writeText "initmysql"
+        ''
+	  create user '${replicateUser}'@'%' identified by '${replicatePassword}';
+          grant replication slave on *.* to '${replicateUser}'@'%';
+        '';
+      };
+
+    slave1 =
+      { pkgs, config, nodes, ... }:
+
+      {
+        services.mysql.enable = true;
+	services.mysql.replication.role = "slave";
+	services.mysql.replication.serverId = 2;
+	services.mysql.replication.masterHost = nodes.master.config.networking.hostName;
+	services.mysql.replication.masterUser = replicateUser;
+	services.mysql.replication.masterPassword = replicatePassword;
+      };
+
+    slave2 =
+      { pkgs, config, nodes, ... }:
+
+      {
+        services.mysql.enable = true;
+	services.mysql.replication.role = "slave";
+	services.mysql.replication.serverId = 3;
+	services.mysql.replication.masterHost = nodes.master.config.networking.hostName;
+	services.mysql.replication.masterUser = replicateUser;
+	services.mysql.replication.masterPassword = replicatePassword;
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $master->waitForUnit("mysql");
+    $master->waitForUnit("mysql");
+    $slave2->waitForUnit("mysql");
+    $slave2->sleep(100); # Hopefully this is long enough!!
+    $slave2->succeed("echo 'use testdb; select * from tests' | mysql -u root -N | grep 4");
+  '';
+}
diff --git a/nixos/tests/mysql.nix b/nixos/tests/mysql.nix
new file mode 100644
index 00000000000..b48850738b7
--- /dev/null
+++ b/nixos/tests/mysql.nix
@@ -0,0 +1,22 @@
+{ pkgs, ... }:
+
+{
+  nodes = {
+    master =
+      { pkgs, config, ... }:
+
+      {
+        services.mysql.enable = true;
+	services.mysql.replication.role = "master";
+	services.mysql.initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $master->waitForUnit("mysql");
+    $master->sleep(10); # Hopefully this is long enough!!
+    $master->succeed("echo 'use testdb; select * from tests' | mysql -u root -N | grep 4");
+  '';
+}
diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix
new file mode 100644
index 00000000000..55d87ed4fa1
--- /dev/null
+++ b/nixos/tests/nat.nix
@@ -0,0 +1,77 @@
+# This is a simple distributed test involving a topology with two
+# separate virtual networks - the "inside" and the "outside" - with a
+# client on the inside network, a server on the outside network, and a
+# router connected to both that performs Network Address Translation
+# for the client.
+
+{ pkgs, ... }:
+
+{
+
+  nodes =
+    { client =
+        { config, pkgs, nodes, ... }:
+        { virtualisation.vlans = [ 1 ];
+          networking.defaultGateway =
+            nodes.router.config.networking.interfaces.eth2.ipAddress;
+        };
+
+      router =
+        { config, pkgs, ... }:
+        { virtualisation.vlans = [ 2 1 ];
+          networking.nat.enable = true;
+          networking.nat.internalIPs = "192.168.1.0/24";
+          networking.nat.externalInterface = "eth1";
+        };
+
+      server =
+        { config, pkgs, ... }:
+        { virtualisation.vlans = [ 2 ];
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          services.vsftpd.enable = true;
+          services.vsftpd.anonymousUser = true;
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      startAll;
+
+      # The router should have access to the server.
+      $server->waitForUnit("network.target");
+      $server->waitForUnit("httpd");
+      $router->waitForUnit("network.target");
+      $router->succeed("curl --fail http://server/ >&2");
+
+      # The client should be also able to connect via the NAT router.
+      $router->waitForUnit("nat");
+      $client->waitForUnit("network.target");
+      $client->succeed("curl --fail http://server/ >&2");
+      $client->succeed("ping -c 1 server >&2");
+
+      # Test whether passive FTP works.
+      $server->waitForUnit("vsftpd");
+      $server->succeed("echo Hello World > /home/ftp/foo.txt");
+      $client->succeed("curl -v ftp://server/foo.txt >&2");
+
+      # Test whether active FTP works.
+      $client->succeed("curl -v -P - ftp://server/foo.txt >&2");
+
+      # Test ICMP.
+      $client->succeed("ping -c 1 router >&2");
+      $router->succeed("ping -c 1 client >&2");
+
+      # If we turn off NAT, the client shouldn't be able to reach the server.
+      $router->stopJob("nat");
+      $client->fail("curl --fail --connect-timeout 5 http://server/ >&2");
+      $client->fail("ping -c 1 server >&2");
+
+      # And make sure that restarting the NAT job works.
+      $router->succeed("systemctl start nat");
+      $client->succeed("curl --fail http://server/ >&2");
+      $client->succeed("ping -c 1 server >&2");
+    '';
+
+}
diff --git a/nixos/tests/nfs.nix b/nixos/tests/nfs.nix
new file mode 100644
index 00000000000..ee65c298dd0
--- /dev/null
+++ b/nixos/tests/nfs.nix
@@ -0,0 +1,85 @@
+{ version }:
+
+{ pkgs, ... }:
+
+let
+
+  client =
+    { config, pkgs, ... }:
+    { fileSystems = pkgs.lib.mkOverride 50
+        [ { mountPoint = "/data";
+            device = "server:${if version == 4 then "/" else "/data"}";
+            fsType = "nfs";
+            options = "vers=${toString version}";
+          }
+        ];
+    };
+
+in
+
+{
+
+  nodes =
+    { client1 = client;
+      client2 = client;
+
+      server =
+        { config, pkgs, ... }:
+        { services.nfs.server.enable = true;
+          services.nfs.server.exports =
+            ''
+              /data 192.168.1.0/255.255.255.0(rw,no_root_squash,no_subtree_check,fsid=0)
+            '';
+          services.nfs.server.createMountPoints = true;
+        };
+    };
+
+  testScript =
+    ''
+      $server->waitForUnit("nfsd");
+      $server->waitForUnit("network.target");
+
+      startAll;
+
+      $client1->waitForUnit("data.mount");
+      $client1->succeed("echo bla > /data/foo");
+      $server->succeed("test -e /data/foo");
+
+      $client2->waitForUnit("data.mount");
+      $client2->succeed("echo bla > /data/bar");
+      $server->succeed("test -e /data/bar");
+
+      # Test whether restarting ‘nfsd’ works correctly.
+      $server->succeed("systemctl restart nfsd");
+      $client2->succeed("echo bla >> /data/bar"); # will take 90 seconds due to the NFS grace period
+
+      # Test whether we can get a lock.
+      $client2->succeed("time flock -n -s /data/lock true");
+
+      # Test locking: client 1 acquires an exclusive lock, so client 2
+      # should then fail to acquire a shared lock.
+      $client1->succeed("flock -x /data/lock -c 'touch locked; sleep 100000' &");
+      $client1->waitForFile("locked");
+      $client2->fail("flock -n -s /data/lock true");
+
+      # Test whether client 2 obtains the lock if we reset client 1.
+      $client2->succeed("flock -x /data/lock -c 'echo acquired; touch locked; sleep 100000' >&2 &");
+      $client1->crash;
+      $client1->start;
+      $client2->waitForFile("locked");
+
+      # Test whether locks survive a reboot of the server.
+      $client1->waitForUnit("data.mount");
+      $server->shutdown;
+      $server->start;
+      $client1->succeed("touch /data/xyzzy");
+      $client1->fail("time flock -n -s /data/lock true");
+
+      # Test whether unmounting during shutdown happens quickly.
+      my $t1 = time;
+      $client1->shutdown;
+      my $duration = time - $t1;
+      die "shutdown took too long ($duration seconds)" if $duration > 30;
+    '';
+
+}
diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix
new file mode 100644
index 00000000000..16757cf9098
--- /dev/null
+++ b/nixos/tests/openssh.nix
@@ -0,0 +1,35 @@
+{ pkgs, ... }:
+
+{
+  nodes = {
+
+    server =
+      { config, pkgs, ... }:
+
+      {
+        services.openssh.enable = true;
+      };
+
+    client =
+      { config, pkgs, ... }: { };
+
+  };
+
+  testScript = ''
+    startAll;
+
+    my $key=`${pkgs.openssh}/bin/ssh-keygen -t dsa -f key -N ""`;
+
+    $server->waitForUnit("sshd");
+
+    $server->succeed("mkdir -m 700 /root/.ssh");
+    $server->copyFileFromHost("key.pub", "/root/.ssh/authorized_keys");
+
+    $client->succeed("mkdir -m 700 /root/.ssh");
+    $client->copyFileFromHost("key", "/root/.ssh/id_dsa");
+    $client->succeed("chmod 600 /root/.ssh/id_dsa");
+
+    $client->waitForUnit("network.target");
+    $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2");
+  '';
+}
diff --git a/nixos/tests/partition.nix b/nixos/tests/partition.nix
new file mode 100644
index 00000000000..7126e7255ef
--- /dev/null
+++ b/nixos/tests/partition.nix
@@ -0,0 +1,227 @@
+{ pkgs, system, ... }:
+
+with pkgs.lib;
+
+let
+  ksExt = pkgs.writeText "ks-ext4" ''
+    clearpart --all --initlabel --drives=vdb
+
+    part /boot --recommended --label=boot --fstype=ext2 --ondisk=vdb
+    part swap --recommended --label=swap --fstype=swap --ondisk=vdb
+    part /nix --size=500 --label=nix --fstype=ext3 --ondisk=vdb
+    part / --recommended --label=root --fstype=ext4 --ondisk=vdb
+  '';
+
+  ksBtrfs = pkgs.writeText "ks-btrfs" ''
+    clearpart --all --initlabel --drives=vdb,vdc
+
+    part swap1 --recommended --label=swap1 --fstype=swap --ondisk=vdb
+    part swap2 --recommended --label=swap2 --fstype=swap --ondisk=vdc
+
+    part btrfs.1 --grow --ondisk=vdb
+    part btrfs.2 --grow --ondisk=vdc
+
+    btrfs / --data=0 --metadata=1 --label=root btrfs.1 btrfs.2
+  '';
+
+  ksRaid = pkgs.writeText "ks-raid" ''
+    clearpart --all --initlabel --drives=vdb,vdc
+
+    part raid.01 --size=200 --ondisk=vdb
+    part raid.02 --size=200 --ondisk=vdc
+
+    part swap1 --size=500 --label=swap1 --fstype=swap --ondisk=vdb
+    part swap2 --size=500 --label=swap2 --fstype=swap --ondisk=vdc
+
+    part raid.11 --grow --ondisk=vdb
+    part raid.12 --grow --ondisk=vdc
+
+    raid /boot --level=1 --fstype=ext3 --device=md0 raid.01 raid.02
+    raid / --level=1 --fstype=xfs --device=md1 raid.11 raid.12
+  '';
+
+  ksRaidLvmCrypt = pkgs.writeText "ks-lvm-crypt" ''
+    clearpart --all --initlabel --drives=vdb,vdc
+
+    part raid.1 --grow --ondisk=vdb
+    part raid.2 --grow --ondisk=vdc
+
+    raid pv.0 --level=1 --encrypted --passphrase=x --device=md0 raid.1 raid.2
+
+    volgroup nixos pv.0
+
+    logvol /boot --size=200 --fstype=ext3 --name=boot --vgname=nixos
+    logvol swap --size=500 --fstype=swap --name=swap --vgname=nixos
+    logvol / --size=1000 --grow --fstype=ext4 --name=root --vgname=nixos
+  '';
+in {
+  machine = { config, pkgs, ... }: {
+    environment.systemPackages = [
+      pkgs.pythonPackages.nixpart
+      pkgs.file pkgs.btrfsProgs pkgs.xfsprogs pkgs.lvm2
+    ];
+    virtualisation.emptyDiskImages = [ 4096 4096 ];
+  };
+
+  testScript = ''
+    my $diskStart;
+    my @mtab;
+
+    sub getMtab {
+      my $mounts = $machine->succeed("cat /proc/mounts");
+      chomp $mounts;
+      return map [split], split /\n/, $mounts;
+    }
+
+    sub parttest {
+      my ($desc, $code) = @_;
+      $machine->start;
+      $machine->waitForUnit("default.target");
+
+      # Gather mounts and superblock
+      @mtab = getMtab;
+      $diskStart = $machine->succeed("dd if=/dev/vda bs=512 count=1");
+
+      subtest($desc, $code);
+      $machine->shutdown;
+    }
+
+    sub ensureSanity {
+      # Check whether the filesystem in /dev/vda is still intact
+      my $newDiskStart = $machine->succeed("dd if=/dev/vda bs=512 count=1");
+      if ($diskStart ne $newDiskStart) {
+        $machine->log("Something went wrong, the partitioner wrote " .
+                      "something into the first 512 bytes of /dev/vda!");
+        die;
+      }
+
+      # Check whether nixpart has unmounted anything
+      my @currentMtab = getMtab;
+      for my $mount (@mtab) {
+        my $path = $mount->[1];
+        unless (grep { $_->[1] eq $path } @currentMtab) {
+          $machine->log("The partitioner seems to have unmounted $path.");
+          die;
+        }
+      }
+    }
+
+    sub checkMount {
+      my $mounts = $machine->succeed("cat /proc/mounts");
+
+    }
+
+    sub kickstart {
+      $machine->copyFileFromHost($_[0], "/kickstart");
+      $machine->succeed("nixpart -v /kickstart");
+      ensureSanity;
+    }
+
+    sub ensurePartition {
+      my ($name, $match) = @_;
+      my $path = $name =~ /^\// ? $name : "/dev/disk/by-label/$name";
+      my $out = $machine->succeed("file -Ls $path");
+      my @matches = grep(/^$path: .*$match/i, $out);
+      if (!@matches) {
+        $machine->log("Partition on $path was expected to have a " .
+                      "file system that matches $match, but instead has: $out");
+        die;
+      }
+    }
+
+    sub ensureNoPartition {
+      $machine->succeed("test ! -e /dev/$_[0]");
+    }
+
+    sub ensureMountPoint {
+      $machine->succeed("mountpoint $_[0]");
+    }
+
+    sub remountAndCheck {
+      $machine->nest("Remounting partitions:", sub {
+        # XXX: "findmnt -ARunl -oTARGET /mnt" seems to NOT print all mounts!
+        my $getmounts_cmd = "cat /proc/mounts | cut -d' ' -f2 | grep '^/mnt'";
+        # Insert canaries first
+        my $canaries = $machine->succeed($getmounts_cmd . " | while read p;" .
+                                         " do touch \"\$p/canary\";" .
+                                         " echo \"\$p/canary\"; done");
+        # Now unmount manually
+        $machine->succeed($getmounts_cmd . " | tac | xargs -r umount");
+        # /mnt should be empty or non-existing
+        my $found = $machine->succeed("find /mnt -mindepth 1");
+        chomp $found;
+        if ($found) {
+          $machine->log("Cruft found in /mnt:\n$found");
+          die;
+        }
+        # Try to remount with nixpart
+        $machine->succeed("nixpart -vm /kickstart");
+        ensureMountPoint("/mnt");
+        # Check if our beloved canaries are dead
+        chomp $canaries;
+        $machine->nest("Checking canaries:", sub {
+          for my $canary (split /\n/, $canaries) {
+            $machine->succeed("test -e '$canary'");
+          }
+        });
+      });
+    }
+
+    parttest "ext2, ext3 and ext4 filesystems", sub {
+      kickstart("${ksExt}");
+      ensurePartition("boot", "ext2");
+      ensurePartition("swap", "swap");
+      ensurePartition("nix", "ext3");
+      ensurePartition("root", "ext4");
+      ensurePartition("/dev/vdb4", "boot sector");
+      ensureNoPartition("vdb6");
+      ensureNoPartition("vdc1");
+      remountAndCheck;
+      ensureMountPoint("/mnt/boot");
+      ensureMountPoint("/mnt/nix");
+    };
+
+    parttest "btrfs filesystem", sub {
+      $machine->succeed("modprobe btrfs");
+      kickstart("${ksBtrfs}");
+      ensurePartition("swap1", "swap");
+      ensurePartition("swap2", "swap");
+      ensurePartition("/dev/vdb2", "btrfs");
+      ensurePartition("/dev/vdc2", "btrfs");
+      ensureNoPartition("vdb3");
+      ensureNoPartition("vdc3");
+      remountAndCheck;
+    };
+
+    parttest "RAID1 with XFS", sub {
+      kickstart("${ksRaid}");
+      ensurePartition("swap1", "swap");
+      ensurePartition("swap2", "swap");
+      ensurePartition("/dev/md0", "ext3");
+      ensurePartition("/dev/md1", "xfs");
+      ensureNoPartition("vdb4");
+      ensureNoPartition("vdc4");
+      ensureNoPartition("md2");
+      remountAndCheck;
+      ensureMountPoint("/mnt/boot");
+    };
+
+    parttest "RAID1 with LUKS and LVM", sub {
+      kickstart("${ksRaidLvmCrypt}");
+      ensurePartition("/dev/vdb1", "data");
+      ensureNoPartition("vdb2");
+      ensurePartition("/dev/vdc1", "data");
+      ensureNoPartition("vdc2");
+
+      ensurePartition("/dev/md0", "luks");
+      ensureNoPartition("md1");
+
+      ensurePartition("/dev/nixos/boot", "ext3");
+      ensurePartition("/dev/nixos/swap", "swap");
+      ensurePartition("/dev/nixos/root", "ext4");
+
+      remountAndCheck;
+      ensureMountPoint("/mnt/boot");
+    };
+  '';
+}
diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix
new file mode 100644
index 00000000000..13cc3115d50
--- /dev/null
+++ b/nixos/tests/printing.nix
@@ -0,0 +1,90 @@
+# Test printing via CUPS.
+
+{ pkgs, ... }:
+
+{
+
+  nodes = {
+
+    server =
+      { config, pkgs, ... }:
+      { services.printing.enable = true;
+        services.printing.cupsdConf =
+          ''
+            Listen server:631
+            <Location />
+              Order allow,deny
+              Allow from all
+            </Location>
+          '';
+      };
+
+    client =
+      { config, pkgs, nodes, ... }:
+      { services.printing.enable = true;
+      };
+
+  };
+
+  testScript =
+    ''
+      startAll;
+
+      # Make sure that cups is up on both sides.
+      $server->waitForUnit("cupsd.service");
+      $client->waitForUnit("cupsd.service");
+      $client->succeed("lpstat -r") =~ /scheduler is running/ or die;
+      $client->succeed("lpstat -H") =~ "/var/run/cups/cups.sock" or die;
+      $client->succeed("curl --fail http://localhost:631/");
+      $client->succeed("curl --fail http://server:631/");
+      $server->fail("curl --fail http://client:631/");
+
+      # Add a HP Deskjet printer connected via USB to the server.
+      $server->succeed("lpadmin -p DeskjetLocal -v usb://HP/Deskjet%205400%20series?serial=TH93I152S123XY -m 'drv:///sample.drv/deskjet.ppd' -E");
+
+      # Add it to the client as well via IPP.
+      $client->succeed("lpadmin -p DeskjetRemote -v ipp://server/printers/DeskjetLocal -m 'drv:///sample.drv/deskjet.ppd' -E");
+      $client->succeed("lpadmin -d DeskjetRemote");
+
+      # Do some status checks.
+      $client->succeed("lpstat -a") =~ /DeskjetRemote accepting requests/ or die;
+      $client->succeed("lpstat -h server -a") =~ /DeskjetLocal accepting requests/ or die;
+      $client->succeed("cupsdisable DeskjetRemote");
+      $client->succeed("lpq") =~ /DeskjetRemote is not ready.*no entries/s or die;
+      $client->succeed("cupsenable DeskjetRemote");
+      $client->succeed("lpq") =~ /DeskjetRemote is ready.*no entries/s or die;
+
+      # Test printing various file types.
+      foreach my $file ("${pkgs.groff}/share/doc/*/examples/mom/typesetting.pdf",
+                        "${pkgs.groff}/share/doc/*/meref.ps",
+                        "${pkgs.cups}/share/doc/cups/images/cups.png",
+                        "${pkgs.xz}/share/doc/xz/faq.txt")
+      {
+          $file =~ /([^\/]*)$/; my $fn = $1;
+
+          subtest "print $fn", sub {
+
+              # Print the file on the client.
+              $client->succeed("lp $file");
+              $client->succeed("lpq") =~ /active.*root.*$fn/ or die;
+
+              # Ensure that a raw PCL file appeared in the server's queue
+              # (showing that the right filters have been applied).  Of
+              # course, since there is no actual USB printer attached, the
+              # file will stay in the queue forever.
+              $server->waitForFile("/var/spool/cups/d*-*");
+              $server->succeed("lpq -a") =~ /remroot.*$fn/ or die;
+              $server->succeed("hexdump -C -n2 /var/spool/cups/d*-*") =~ /1b 45/ or die; # 1b 45 = printer reset
+
+              # Delete the job on the client.  It should disappear on the
+              # server as well.
+              $client->succeed("lprm");
+              $client->succeed("lpq -a") =~ /no entries/;
+              Machine::retry sub {
+                return 1 if $server->succeed("lpq -a") =~ /no entries/;
+              };
+          };
+      }
+    '';
+
+}
diff --git a/nixos/tests/proxy.nix b/nixos/tests/proxy.nix
new file mode 100644
index 00000000000..3b79c16ea2c
--- /dev/null
+++ b/nixos/tests/proxy.nix
@@ -0,0 +1,94 @@
+{ pkgs, ... }:
+
+let
+
+  backend =
+    { config, pkgs, ... }:
+
+    {
+      services.openssh.enable = true;
+
+      services.httpd.enable = true;
+      services.httpd.adminAddr = "foo@example.org";
+      services.httpd.documentRoot = "${pkgs.valgrind}/share/doc/valgrind/html";
+    };
+
+in
+
+{
+
+  nodes =
+    { proxy =
+        { config, pkgs, nodes, ... }:
+
+        {
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "bar@example.org";
+          services.httpd.extraModules = ["proxy_balancer"];
+
+          services.httpd.extraConfig =
+            ''
+              ExtendedStatus on
+
+              <Location /server-status>
+                Order deny,allow
+                Allow from all
+                SetHandler server-status
+              </Location>
+
+              <Proxy balancer://cluster>
+                Allow from all
+                BalancerMember http://${nodes.backend1.config.networking.hostName} retry=0
+                BalancerMember http://${nodes.backend2.config.networking.hostName} retry=0
+              </Proxy>
+
+              ProxyStatus       full
+              ProxyPass         /server-status !
+              ProxyPass         /       balancer://cluster/
+              ProxyPassReverse  /       balancer://cluster/
+
+              # For testing; don't want to wait forever for dead backend servers.
+              ProxyTimeout      5
+            '';
+        };
+
+      backend1 = backend;
+      backend2 = backend;
+
+      client = { config, pkgs, ... }: { };
+    };
+
+  testScript =
+    ''
+      startAll;
+
+      $proxy->waitForUnit("httpd");
+      $backend1->waitForUnit("httpd");
+      $backend2->waitForUnit("httpd");
+
+      # With the back-ends up, the proxy should work.
+      $client->succeed("curl --fail http://proxy/");
+
+      $client->succeed("curl --fail http://proxy/server-status");
+
+      # Block the first back-end.
+      $backend1->block;
+
+      # The proxy should still work.
+      $client->succeed("curl --fail http://proxy/");
+
+      $client->succeed("curl --fail http://proxy/");
+
+      # Block the second back-end.
+      $backend2->block;
+
+      # Now the proxy should fail as well.
+      $client->fail("curl --fail http://proxy/");
+
+      # But if the second back-end comes back, the proxy should start
+      # working again.
+      $backend2->unblock;
+      $client->succeed("curl --fail http://proxy/");
+    '';
+
+}
diff --git a/nixos/tests/quake3.nix b/nixos/tests/quake3.nix
new file mode 100644
index 00000000000..92501107780
--- /dev/null
+++ b/nixos/tests/quake3.nix
@@ -0,0 +1,79 @@
+{ pkgs, ... }:
+
+let
+
+  # Build Quake with coverage instrumentation.
+  overrides = pkgs:
+    rec {
+      quake3game = pkgs.quake3game.override (args: {
+        stdenv = pkgs.stdenvAdapters.addCoverageInstrumentation args.stdenv;
+      });
+    };
+
+in
+
+rec {
+
+  client =
+    { config, pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      services.xserver.driSupport = true;
+      services.xserver.defaultDepth = pkgs.lib.mkOverride 0 16;
+      environment.systemPackages = [ pkgs.quake3demo ];
+      nixpkgs.config.packageOverrides = overrides;
+    };
+
+  nodes =
+    { server =
+        { config, pkgs, ... }:
+
+        { jobs."quake3-server" =
+            { startOn = "startup";
+              exec =
+                "${pkgs.quake3demo}/bin/quake3-server '+set g_gametype 0' " +
+                "'+map q3dm7' '+addbot grunt' '+addbot daemia' 2> /tmp/log";
+            };
+          nixpkgs.config.packageOverrides = overrides;
+        };
+
+      client1 = client;
+      client2 = client;
+    };
+
+  testScript =
+    ''
+      startAll;
+
+      $server->waitForUnit("quake3-server");
+      $client1->waitForX;
+      $client2->waitForX;
+
+      $client1->execute("quake3 '+set r_fullscreen 0' '+set name Foo' '+connect server' &");
+      $client2->execute("quake3 '+set r_fullscreen 0' '+set name Bar' '+connect server' &");
+
+      $server->waitUntilSucceeds("grep -q 'Foo.*entered the game' /tmp/log");
+      $server->waitUntilSucceeds("grep -q 'Bar.*entered the game' /tmp/log");
+
+      $server->sleep(10); # wait for a while to get a nice screenshot
+
+      $client1->block();
+
+      $server->sleep(20);
+
+      $client1->screenshot("screen1");
+      $client2->screenshot("screen2");
+
+      $client1->unblock();
+
+      $server->sleep(10);
+
+      $client1->screenshot("screen3");
+      $client2->screenshot("screen4");
+
+      $client1->shutdown();
+      $client2->shutdown();
+      $server->stopJob("quake3-server");
+    '';
+
+}
diff --git a/nixos/tests/run-in-machine.nix b/nixos/tests/run-in-machine.nix
new file mode 100644
index 00000000000..75bd161ec9e
--- /dev/null
+++ b/nixos/tests/run-in-machine.nix
@@ -0,0 +1,10 @@
+{ nixpkgs ? <nixpkgs>
+, system ? builtins.currentSystem
+}:
+
+with import ../lib/testing.nix { inherit system; };
+
+runInMachine {
+  drv = (import nixpkgs { inherit system; }).aterm;
+  machine = { config, pkgs, ... }: { services.sshd.enable = true; };
+}
diff --git a/nixos/tests/simple.nix b/nixos/tests/simple.nix
new file mode 100644
index 00000000000..eee13a10133
--- /dev/null
+++ b/nixos/tests/simple.nix
@@ -0,0 +1,11 @@
+{ pkgs, ... }:
+
+{
+  machine = { config, pkgs, ... }: { };
+
+  testScript =
+    ''
+      startAll;
+      $machine->shutdown;
+    '';
+}
diff --git a/nixos/tests/subversion.nix b/nixos/tests/subversion.nix
new file mode 100644
index 00000000000..309da90c5df
--- /dev/null
+++ b/nixos/tests/subversion.nix
@@ -0,0 +1,117 @@
+{ pkgs, ... }:
+
+let
+
+  # Build some packages with coverage instrumentation.
+  overrides = pkgs:
+    with pkgs.stdenvAdapters;
+    let
+      do = pkg: pkg.override (args: {
+        stdenv = addCoverageInstrumentation args.stdenv;
+      });
+    in
+      rec {
+        apr = do pkgs.apr;
+        aprutil = do pkgs.aprutil;
+        apacheHttpd = do pkgs.apacheHttpd;
+        mod_python = do pkgs.mod_python;
+        subversion = do pkgs.subversion;
+
+        # To build the kernel with coverage instrumentation, we need a
+        # special patch to make coverage data available under /proc.
+        linux = pkgs.linux.override (orig: {
+          stdenv = cleanupBuildTree (keepBuildTree orig.stdenv);
+          extraConfig =
+            ''
+              GCOV_KERNEL y
+              GCOV_PROFILE_ALL y
+            '';
+        });
+      };
+
+in
+
+{
+
+  nodes =
+    { webserver =
+        { config, pkgs, ... }:
+
+        {
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "e.dolstra@tudelft.nl";
+          services.httpd.extraSubservices =
+            [ { function = import <services/subversion>;
+                urlPrefix = "";
+                dataDir = "/data/subversion";
+                userCreationDomain = "192.168.0.0/16";
+              }
+            ];
+          nixpkgs.config.packageOverrides = overrides;
+        };
+
+      client =
+        { config, pkgs, ... }:
+
+        {
+          environment.systemPackages = [ pkgs.subversion ];
+          nixpkgs.config.packageOverrides = overrides;
+        };
+
+    };
+
+  testScript =
+    ''
+      startAll;
+
+      $webserver->waitForOpenPort(80);
+
+      print STDERR $client->succeed("svn --version");
+
+      print STDERR $client->succeed("curl --fail http://webserver/");
+
+      # Create a new user through the web interface.
+      $client->succeed("curl --fail -F username=alice -F fullname='Alice Lastname' -F address=alice\@example.org -F password=foobar -F password_again=foobar http://webserver/repoman/adduser");
+
+      # Let Alice create a new repository.
+      $client->succeed("curl --fail -u alice:foobar --form repo=xyzzy --form description=Xyzzy http://webserver/repoman/create");
+
+      $client->succeed("curl --fail http://webserver/") =~ /alice/ or die;
+
+      # Let Alice do a checkout.
+      my $svnFlags = "--non-interactive --username alice --password foobar";
+      $client->succeed("svn co $svnFlags http://webserver/repos/xyzzy wc");
+      $client->succeed("echo hello > wc/world");
+      $client->succeed("svn add wc/world");
+      $client->succeed("svn ci $svnFlags -m 'Added world.' wc/world");
+
+      # Create a new user on the server through the create-user.pl script.
+      $webserver->execute("svn-server-create-user.pl bob bob\@example.org Bob");
+      $webserver->succeed("svn-server-resetpw.pl bob fnord");
+      $client->succeed("curl --fail http://webserver/") =~ /bob/ or die;
+
+      # Bob should not have access to the repo.
+      my $svnFlagsBob = "--non-interactive --username bob --password fnord";
+      $client->fail("svn co $svnFlagsBob http://webserver/repos/xyzzy wc2");
+
+      # Bob should not be able change the ACLs of the repo.
+      # !!! Repoman should really return a 403 here.
+      $client->succeed("curl --fail -u bob:fnord -F description=Xyzzy -F readers=alice,bob -F writers=alice -F watchers= -F tardirs= http://webserver/repoman/update/xyzzy")
+          =~ /not authorised/ or die;
+
+      # Give Bob access.
+      $client->succeed("curl --fail -u alice:foobar -F description=Xyzzy -F readers=alice,bob -F writers=alice -F watchers= -F tardirs= http://webserver/repoman/update/xyzzy");
+
+      # So now his checkout should succeed.
+      $client->succeed("svn co $svnFlagsBob http://webserver/repos/xyzzy wc2");
+
+      # Test ViewVC and WebSVN
+      $client->succeed("curl --fail -u alice:foobar http://webserver/viewvc/xyzzy");
+      $client->succeed("curl --fail -u alice:foobar http://webserver/websvn/xyzzy");
+      $client->succeed("curl --fail -u alice:foobar http://webserver/repos-xml/xyzzy");
+
+      # Stop Apache to gather all the coverage data.
+      $webserver->stopJob("httpd");
+    '';
+
+}
diff --git a/nixos/tests/test-config-examples.sh b/nixos/tests/test-config-examples.sh
new file mode 100755
index 00000000000..1ba2f841c41
--- /dev/null
+++ b/nixos/tests/test-config-examples.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# This script try to evaluate all configurations which are stored in
+# doc/config-examples.  This script is useful to ensure that examples are
+# working with the current system.
+
+pwd=$(pwd)
+set -xe
+for i in ../doc/config-examples/*.nix; do
+  NIXOS_CONFIG="$pwd/$i" nix-instantiate \
+      --eval-only --xml --strict > /dev/null 2>&1 \
+      ../default.nix -A system
+done
+set +xe
diff --git a/nixos/tests/testdb.sql b/nixos/tests/testdb.sql
new file mode 100644
index 00000000000..4fb28fea3df
--- /dev/null
+++ b/nixos/tests/testdb.sql
@@ -0,0 +1,10 @@
+create table tests
+( Id   INTEGER      NOT NULL,
+  Name VARCHAR(255) NOT NULL,
+  primary key(Id)
+);
+
+insert into tests values (1, 'a');
+insert into tests values (2, 'b');
+insert into tests values (3, 'c');
+insert into tests values (4, 'd');
diff --git a/nixos/tests/tomcat.nix b/nixos/tests/tomcat.nix
new file mode 100644
index 00000000000..c25276aa424
--- /dev/null
+++ b/nixos/tests/tomcat.nix
@@ -0,0 +1,32 @@
+{ pkgs, ... }:
+
+{
+  nodes = {
+    server =
+      { pkgs, config, ... }:
+
+      {
+        services.tomcat.enable = true;
+        services.httpd.enable = true;
+        services.httpd.adminAddr = "foo@bar.com";
+        services.httpd.extraSubservices = [
+          { serviceType = "tomcat-connector";
+            stateDir = "/var/run/httpd";
+            logDir = "/var/log/httpd";
+          }
+        ];
+      };
+
+    client = { };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("tomcat");
+    $server->sleep(30); # Dirty, but it takes a while before Tomcat handles to requests properly
+    $client->waitForUnit("network.target");
+    $client->succeed("curl --fail http://server/examples/servlets/servlet/HelloWorldExample");
+    $client->succeed("curl --fail http://server/examples/jsp/jsp2/simpletag/hello.jsp");
+  '';
+}
diff --git a/nixos/tests/trac.nix b/nixos/tests/trac.nix
new file mode 100644
index 00000000000..72442c885ac
--- /dev/null
+++ b/nixos/tests/trac.nix
@@ -0,0 +1,71 @@
+{ pkgs, ... }:
+
+{
+  nodes = {
+    storage =
+      { config, pkgs, ... }:
+      { services.nfs.server.enable = true;
+        services.nfs.server.exports = ''
+          /repos 192.168.1.0/255.255.255.0(rw,no_root_squash)
+        '';
+        services.nfs.server.createMountPoints = true;
+      };
+
+    postgresql =
+      { config, pkgs, ... }:
+      { services.postgresql.enable = true;
+        services.postgresql.package = pkgs.postgresql92;
+        services.postgresql.enableTCPIP = true;
+        services.postgresql.authentication = ''
+          # Generated file; do not edit!
+          local all all                trust
+          host  all all 127.0.0.1/32   trust
+          host  all all ::1/128        trust
+          host  all all 192.168.1.0/24 trust
+        '';
+      };
+
+    webserver =
+      { config, pkgs, ... }:
+      { fileSystems = pkgs.lib.mkOverride 50
+          [ { mountPoint = "/repos";
+              device = "storage:/repos";
+              fsType = "nfs";
+            }
+          ];
+        services.httpd.enable = true;
+        services.httpd.adminAddr = "root@localhost";
+        services.httpd.extraSubservices = [ { serviceType = "trac"; } ];
+        environment.systemPackages = [ pkgs.pythonPackages.trac pkgs.subversion ];
+      };
+
+    client =
+      { config, pkgs, ... }:
+      { imports = [ ./common/x11.nix ];
+        services.xserver.desktopManager.kde4.enable = true;
+      };
+  };
+
+  testScript =
+    ''
+      startAll;
+
+      $postgresql->waitForUnit("postgresql");
+      $postgresql->succeed("createdb trac");
+
+      $webserver->succeed("mkdir -p /repos/trac");
+      $webserver->succeed("svnadmin create /repos/trac");
+
+      $webserver->waitForUnit("httpd");
+      $webserver->waitForFile("/var/trac");
+      $webserver->succeed("mkdir -p /var/trac/projects/test");
+      $webserver->succeed("PYTHONPATH=${pkgs.pythonPackages.psycopg2}/lib/${pkgs.python.libPrefix}/site-packages trac-admin /var/trac/projects/test initenv Test postgres://root\@postgresql/trac svn /repos/trac");
+
+      $client->waitForX;
+      $client->execute("konqueror http://webserver/projects/test &");
+      $client->waitForWindow(qr/Test.*Konqueror/);
+      $client->sleep(30); # loading takes a long time
+
+      $client->screenshot("screen");
+    '';
+}
diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix
new file mode 100644
index 00000000000..9f9692f8a01
--- /dev/null
+++ b/nixos/tests/xfce.nix
@@ -0,0 +1,32 @@
+{ pkgs, ... }:
+
+{
+
+  machine =
+    { config, pkgs, ... }:
+
+    { imports = [ ./common/user-account.nix ];
+
+      services.xserver.enable = true;
+
+      services.xserver.displayManager.auto.enable = true;
+      services.xserver.displayManager.auto.user = "alice";
+
+      services.xserver.desktopManager.xfce.enable = true;
+    };
+
+  testScript =
+    ''
+      $machine->waitForWindow(qr/xfce4-panel/);
+      $machine->sleep(10);
+
+      # Check that logging in has given the user ownership of devices.
+      $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
+
+      $machine->succeed("su - alice -c 'DISPLAY=:0.0 xfce4-terminal &'");
+      $machine->waitForWindow(qr/Terminal/);
+      $machine->sleep(10);
+      $machine->screenshot("screen");
+    '';
+
+}