about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/tests')
-rw-r--r--nixpkgs/nixos/tests/acme.nix39
-rw-r--r--nixpkgs/nixos/tests/all-tests.nix229
-rw-r--r--nixpkgs/nixos/tests/ammonite.nix20
-rw-r--r--nixpkgs/nixos/tests/atd.nix31
-rw-r--r--nixpkgs/nixos/tests/avahi.nix60
-rw-r--r--nixpkgs/nixos/tests/bcachefs.nix38
-rw-r--r--nixpkgs/nixos/tests/beegfs.nix115
-rw-r--r--nixpkgs/nixos/tests/bees.nix55
-rw-r--r--nixpkgs/nixos/tests/bind.nix27
-rw-r--r--nixpkgs/nixos/tests/bittorrent.nix149
-rw-r--r--nixpkgs/nixos/tests/blivet.nix87
-rw-r--r--nixpkgs/nixos/tests/boot-stage1.nix162
-rw-r--r--nixpkgs/nixos/tests/boot.nix99
-rw-r--r--nixpkgs/nixos/tests/borgbackup.nix165
-rw-r--r--nixpkgs/nixos/tests/buildbot.nix120
-rw-r--r--nixpkgs/nixos/tests/cadvisor.nix35
-rw-r--r--nixpkgs/nixos/tests/cassandra.nix71
-rw-r--r--nixpkgs/nixos/tests/ceph.nix139
-rw-r--r--nixpkgs/nixos/tests/certmgr.nix151
-rw-r--r--nixpkgs/nixos/tests/cfssl.nix67
-rw-r--r--nixpkgs/nixos/tests/chromium.nix201
-rw-r--r--nixpkgs/nixos/tests/cjdns.nix119
-rw-r--r--nixpkgs/nixos/tests/clickhouse.nix25
-rw-r--r--nixpkgs/nixos/tests/cloud-init.nix49
-rw-r--r--nixpkgs/nixos/tests/cockroachdb.nix126
-rw-r--r--nixpkgs/nixos/tests/codimd.nix54
-rw-r--r--nixpkgs/nixos/tests/common/letsencrypt/common.nix27
-rw-r--r--nixpkgs/nixos/tests/common/letsencrypt/default.nix445
-rw-r--r--nixpkgs/nixos/tests/common/letsencrypt/mkcerts.nix69
-rwxr-xr-xnixpkgs/nixos/tests/common/letsencrypt/mkcerts.sh6
-rw-r--r--nixpkgs/nixos/tests/common/letsencrypt/snakeoil-certs.nix253
-rw-r--r--nixpkgs/nixos/tests/common/resolver.nix141
-rw-r--r--nixpkgs/nixos/tests/common/user-account.nix14
-rw-r--r--nixpkgs/nixos/tests/common/webroot/news-rss.xml15
-rw-r--r--nixpkgs/nixos/tests/common/x11.nix12
-rw-r--r--nixpkgs/nixos/tests/containers-bridge.nix103
-rw-r--r--nixpkgs/nixos/tests/containers-extra_veth.nix103
-rw-r--r--nixpkgs/nixos/tests/containers-hosts.nix52
-rw-r--r--nixpkgs/nixos/tests/containers-imperative.nix111
-rw-r--r--nixpkgs/nixos/tests/containers-ipv4.nix55
-rw-r--r--nixpkgs/nixos/tests/containers-ipv6.nix60
-rw-r--r--nixpkgs/nixos/tests/containers-macvlans.nix82
-rw-r--r--nixpkgs/nixos/tests/containers-physical_interfaces.nix133
-rw-r--r--nixpkgs/nixos/tests/containers-portforward.nix62
-rw-r--r--nixpkgs/nixos/tests/containers-reloadable.nix65
-rw-r--r--nixpkgs/nixos/tests/containers-restart_networking.nix113
-rw-r--r--nixpkgs/nixos/tests/containers-tmpfs.nix79
-rw-r--r--nixpkgs/nixos/tests/couchdb.nix56
-rw-r--r--nixpkgs/nixos/tests/deluge.nix29
-rw-r--r--nixpkgs/nixos/tests/dhparams.nix144
-rw-r--r--nixpkgs/nixos/tests/dnscrypt-proxy.nix33
-rw-r--r--nixpkgs/nixos/tests/docker-edge.nix47
-rw-r--r--nixpkgs/nixos/tests/docker-preloader.nix27
-rw-r--r--nixpkgs/nixos/tests/docker-registry.nix63
-rw-r--r--nixpkgs/nixos/tests/docker-tools-overlay.nix32
-rw-r--r--nixpkgs/nixos/tests/docker-tools.nix71
-rw-r--r--nixpkgs/nixos/tests/docker.nix47
-rw-r--r--nixpkgs/nixos/tests/dovecot.nix77
-rw-r--r--nixpkgs/nixos/tests/ec2.nix178
-rw-r--r--nixpkgs/nixos/tests/ecryptfs.nix84
-rw-r--r--nixpkgs/nixos/tests/elk.nix152
-rw-r--r--nixpkgs/nixos/tests/emacs-daemon.nix45
-rw-r--r--nixpkgs/nixos/tests/env.nix35
-rw-r--r--nixpkgs/nixos/tests/etcd-cluster.nix157
-rw-r--r--nixpkgs/nixos/tests/etcd.nix27
-rw-r--r--nixpkgs/nixos/tests/ferm.nix74
-rw-r--r--nixpkgs/nixos/tests/firefox.nix29
-rw-r--r--nixpkgs/nixos/tests/firewall.nix65
-rw-r--r--nixpkgs/nixos/tests/flannel.nix55
-rw-r--r--nixpkgs/nixos/tests/flatpak.nix26
-rw-r--r--nixpkgs/nixos/tests/fsck.nix29
-rw-r--r--nixpkgs/nixos/tests/fwupd.nix21
-rw-r--r--nixpkgs/nixos/tests/gdk-pixbuf.nix21
-rw-r--r--nixpkgs/nixos/tests/gitea.nix79
-rw-r--r--nixpkgs/nixos/tests/gitlab.nix83
-rw-r--r--nixpkgs/nixos/tests/gitolite.nix139
-rw-r--r--nixpkgs/nixos/tests/gjs.nix19
-rw-r--r--nixpkgs/nixos/tests/gnome3-gdm.nix63
-rw-r--r--nixpkgs/nixos/tests/gnome3.nix41
-rw-r--r--nixpkgs/nixos/tests/gocd-agent.nix40
-rw-r--r--nixpkgs/nixos/tests/gocd-server.nix28
-rw-r--r--nixpkgs/nixos/tests/google-oslogin/default.nix52
-rw-r--r--nixpkgs/nixos/tests/google-oslogin/server.nix29
-rw-r--r--nixpkgs/nixos/tests/google-oslogin/server.py96
-rw-r--r--nixpkgs/nixos/tests/grafana.nix25
-rw-r--r--nixpkgs/nixos/tests/graphite.nix41
-rw-r--r--nixpkgs/nixos/tests/hadoop/hdfs.nix54
-rw-r--r--nixpkgs/nixos/tests/hadoop/yarn.nix46
-rw-r--r--nixpkgs/nixos/tests/haka.nix24
-rw-r--r--nixpkgs/nixos/tests/handbrake.nix25
-rw-r--r--nixpkgs/nixos/tests/haproxy.nix41
-rw-r--r--nixpkgs/nixos/tests/hardened.nix87
-rw-r--r--nixpkgs/nixos/tests/hibernate.nix43
-rw-r--r--nixpkgs/nixos/tests/hitch/default.nix33
-rw-r--r--nixpkgs/nixos/tests/hitch/example.pem53
-rw-r--r--nixpkgs/nixos/tests/hitch/example/index.txt1
-rw-r--r--nixpkgs/nixos/tests/hocker-fetchdocker/default.nix15
-rw-r--r--nixpkgs/nixos/tests/hocker-fetchdocker/hello-world-container.nix19
-rw-r--r--nixpkgs/nixos/tests/hocker-fetchdocker/machine.nix26
-rw-r--r--nixpkgs/nixos/tests/home-assistant.nix102
-rw-r--r--nixpkgs/nixos/tests/hound.nix58
-rwxr-xr-xnixpkgs/nixos/tests/hydra/create-trivial-project.sh57
-rw-r--r--nixpkgs/nixos/tests/hydra/default.nix77
-rw-r--r--nixpkgs/nixos/tests/i3wm.nix35
-rw-r--r--nixpkgs/nixos/tests/iftop.nix34
-rw-r--r--nixpkgs/nixos/tests/incron.nix52
-rw-r--r--nixpkgs/nixos/tests/influxdb.nix33
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/default.nix59
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/dropbear.privbin0 -> 1573 bytes
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/dropbear.pub1
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/generate-keys.nix12
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/openssh.priv51
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/openssh.pub1
-rw-r--r--nixpkgs/nixos/tests/initrd-network.nix22
-rw-r--r--nixpkgs/nixos/tests/installer.nix677
-rw-r--r--nixpkgs/nixos/tests/ipfs.nix55
-rw-r--r--nixpkgs/nixos/tests/ipv6.nix79
-rw-r--r--nixpkgs/nixos/tests/jackett.nix18
-rw-r--r--nixpkgs/nixos/tests/jenkins.nix50
-rw-r--r--nixpkgs/nixos/tests/kafka.nix75
-rw-r--r--nixpkgs/nixos/tests/kerberos/default.nix7
-rw-r--r--nixpkgs/nixos/tests/kerberos/heimdal.nix53
-rw-r--r--nixpkgs/nixos/tests/kerberos/mit.nix45
-rw-r--r--nixpkgs/nixos/tests/kernel-latest.nix17
-rw-r--r--nixpkgs/nixos/tests/kernel-lts.nix17
-rw-r--r--nixpkgs/nixos/tests/kexec.nix19
-rw-r--r--nixpkgs/nixos/tests/keymap.nix157
-rw-r--r--nixpkgs/nixos/tests/krb5/default.nix5
-rw-r--r--nixpkgs/nixos/tests/krb5/deprecated-config.nix48
-rw-r--r--nixpkgs/nixos/tests/krb5/example-config.nix106
-rw-r--r--nixpkgs/nixos/tests/kubernetes/base.nix115
-rw-r--r--nixpkgs/nixos/tests/kubernetes/certs.nix219
-rw-r--r--nixpkgs/nixos/tests/kubernetes/default.nix7
-rw-r--r--nixpkgs/nixos/tests/kubernetes/dns.nix127
-rw-r--r--nixpkgs/nixos/tests/kubernetes/e2e.nix40
-rw-r--r--nixpkgs/nixos/tests/kubernetes/kubernetes-common.nix57
-rw-r--r--nixpkgs/nixos/tests/kubernetes/rbac.nix137
-rw-r--r--nixpkgs/nixos/tests/ldap.nix396
-rw-r--r--nixpkgs/nixos/tests/leaps.nix30
-rw-r--r--nixpkgs/nixos/tests/lidarr.nix18
-rw-r--r--nixpkgs/nixos/tests/lightdm.nix29
-rw-r--r--nixpkgs/nixos/tests/login.nix72
-rw-r--r--nixpkgs/nixos/tests/make-test.nix9
-rw-r--r--nixpkgs/nixos/tests/mathics.nix20
-rw-r--r--nixpkgs/nixos/tests/matrix-synapse.nix31
-rw-r--r--nixpkgs/nixos/tests/memcached.nix28
-rw-r--r--nixpkgs/nixos/tests/mesos.nix92
-rw-r--r--nixpkgs/nixos/tests/mesos_test.py72
-rw-r--r--nixpkgs/nixos/tests/minio.nix34
-rw-r--r--nixpkgs/nixos/tests/misc.nix142
-rw-r--r--nixpkgs/nixos/tests/mongodb.nix34
-rw-r--r--nixpkgs/nixos/tests/morty.nix32
-rw-r--r--nixpkgs/nixos/tests/mpd.nix119
-rw-r--r--nixpkgs/nixos/tests/mpich-example.c21
-rw-r--r--nixpkgs/nixos/tests/mumble.nix73
-rw-r--r--nixpkgs/nixos/tests/munin.nix44
-rw-r--r--nixpkgs/nixos/tests/mutable-users.nix39
-rw-r--r--nixpkgs/nixos/tests/mxisd.nix21
-rw-r--r--nixpkgs/nixos/tests/mysql-backup.nix50
-rw-r--r--nixpkgs/nixos/tests/mysql-replication.nix81
-rw-r--r--nixpkgs/nixos/tests/mysql.nix24
-rw-r--r--nixpkgs/nixos/tests/nat.nix117
-rw-r--r--nixpkgs/nixos/tests/ndppd.nix61
-rw-r--r--nixpkgs/nixos/tests/neo4j.nix20
-rw-r--r--nixpkgs/nixos/tests/netdata.nix38
-rw-r--r--nixpkgs/nixos/tests/networking-proxy.nix111
-rw-r--r--nixpkgs/nixos/tests/networking.nix609
-rw-r--r--nixpkgs/nixos/tests/nextcloud/basic.nix56
-rw-r--r--nixpkgs/nixos/tests/nextcloud/default.nix9
-rw-r--r--nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix97
-rw-r--r--nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix130
-rw-r--r--nixpkgs/nixos/tests/nexus.nix32
-rw-r--r--nixpkgs/nixos/tests/nfs.nix90
-rw-r--r--nixpkgs/nixos/tests/nghttpx.nix61
-rw-r--r--nixpkgs/nixos/tests/nginx-sso.nix44
-rw-r--r--nixpkgs/nixos/tests/nginx.nix42
-rw-r--r--nixpkgs/nixos/tests/nix-ssh-serve.nix39
-rw-r--r--nixpkgs/nixos/tests/novacomd.nix34
-rw-r--r--nixpkgs/nixos/tests/nsd.nix101
-rw-r--r--nixpkgs/nixos/tests/openldap.nix35
-rw-r--r--nixpkgs/nixos/tests/opensmtpd.nix125
-rw-r--r--nixpkgs/nixos/tests/openssh.nix81
-rw-r--r--nixpkgs/nixos/tests/osquery.nix28
-rw-r--r--nixpkgs/nixos/tests/ostree.nix21
-rw-r--r--nixpkgs/nixos/tests/pam-oath-login.nix124
-rw-r--r--nixpkgs/nixos/tests/pam-u2f.nix23
-rw-r--r--nixpkgs/nixos/tests/partition.nix247
-rw-r--r--nixpkgs/nixos/tests/peerflix.nix23
-rw-r--r--nixpkgs/nixos/tests/pgjwt.nix37
-rw-r--r--nixpkgs/nixos/tests/pgmanage.nix39
-rw-r--r--nixpkgs/nixos/tests/phabricator.nix77
-rw-r--r--nixpkgs/nixos/tests/php-pcre.nix44
-rw-r--r--nixpkgs/nixos/tests/plasma5.nix64
-rw-r--r--nixpkgs/nixos/tests/plotinus.nix27
-rw-r--r--nixpkgs/nixos/tests/postgis.nix29
-rw-r--r--nixpkgs/nixos/tests/postgresql.nix73
-rw-r--r--nixpkgs/nixos/tests/powerdns.nix12
-rw-r--r--nixpkgs/nixos/tests/predictable-interface-names.nix27
-rw-r--r--nixpkgs/nixos/tests/printing.nix101
-rw-r--r--nixpkgs/nixos/tests/prometheus-exporters.nix335
-rw-r--r--nixpkgs/nixos/tests/prometheus.nix48
-rw-r--r--nixpkgs/nixos/tests/prosody.nix78
-rw-r--r--nixpkgs/nixos/tests/proxy.nix96
-rw-r--r--nixpkgs/nixos/tests/quagga.nix96
-rw-r--r--nixpkgs/nixos/tests/quake3.nix95
-rw-r--r--nixpkgs/nixos/tests/rabbitmq.nix21
-rw-r--r--nixpkgs/nixos/tests/radarr.nix18
-rw-r--r--nixpkgs/nixos/tests/radicale.nix106
-rw-r--r--nixpkgs/nixos/tests/redmine.nix58
-rw-r--r--nixpkgs/nixos/tests/riak.nix21
-rw-r--r--nixpkgs/nixos/tests/roundcube.nix28
-rw-r--r--nixpkgs/nixos/tests/rspamd.nix255
-rw-r--r--nixpkgs/nixos/tests/rss2email.nix66
-rw-r--r--nixpkgs/nixos/tests/rsyslogd.nix42
-rw-r--r--nixpkgs/nixos/tests/run-in-machine.nix23
-rw-r--r--nixpkgs/nixos/tests/rxe.nix53
-rw-r--r--nixpkgs/nixos/tests/samba.nix47
-rw-r--r--nixpkgs/nixos/tests/sddm.nix69
-rw-r--r--nixpkgs/nixos/tests/simple.nix17
-rw-r--r--nixpkgs/nixos/tests/slim.nix66
-rw-r--r--nixpkgs/nixos/tests/slurm.nix139
-rw-r--r--nixpkgs/nixos/tests/smokeping.nix33
-rw-r--r--nixpkgs/nixos/tests/snapper.nix43
-rw-r--r--nixpkgs/nixos/tests/solr.nix47
-rw-r--r--nixpkgs/nixos/tests/sonarr.nix18
-rw-r--r--nixpkgs/nixos/tests/ssh-keys.nix15
-rw-r--r--nixpkgs/nixos/tests/strongswan-swanctl.nix148
-rw-r--r--nixpkgs/nixos/tests/subversion.nix121
-rw-r--r--nixpkgs/nixos/tests/sudo.nix93
-rw-r--r--nixpkgs/nixos/tests/switch-test.nix25
-rw-r--r--nixpkgs/nixos/tests/syncthing-relay.nix22
-rw-r--r--nixpkgs/nixos/tests/systemd.nix73
-rw-r--r--nixpkgs/nixos/tests/taskserver.nix290
-rw-r--r--nixpkgs/nixos/tests/telegraf.nix30
-rw-r--r--nixpkgs/nixos/tests/testdb.sql11
-rw-r--r--nixpkgs/nixos/tests/timezone.nix45
-rw-r--r--nixpkgs/nixos/tests/tomcat.nix30
-rw-r--r--nixpkgs/nixos/tests/tor.nix28
-rw-r--r--nixpkgs/nixos/tests/trac.nix74
-rw-r--r--nixpkgs/nixos/tests/transmission.nix21
-rw-r--r--nixpkgs/nixos/tests/udisks2.nix61
-rw-r--r--nixpkgs/nixos/tests/upnp.nix94
-rw-r--r--nixpkgs/nixos/tests/vault.nix23
-rw-r--r--nixpkgs/nixos/tests/virtualbox.nix512
-rw-r--r--nixpkgs/nixos/tests/wordpress.nix53
-rw-r--r--nixpkgs/nixos/tests/xautolock.nix24
-rw-r--r--nixpkgs/nixos/tests/xdg-desktop-portal.nix17
-rw-r--r--nixpkgs/nixos/tests/xfce.nix42
-rw-r--r--nixpkgs/nixos/tests/xmonad.nix29
-rw-r--r--nixpkgs/nixos/tests/xrdp.nix45
-rw-r--r--nixpkgs/nixos/tests/xss-lock.nix24
-rw-r--r--nixpkgs/nixos/tests/yabar.nix33
-rw-r--r--nixpkgs/nixos/tests/zfs.nix88
-rw-r--r--nixpkgs/nixos/tests/zookeeper.nix28
254 files changed, 18439 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/acme.nix b/nixpkgs/nixos/tests/acme.nix
new file mode 100644
index 000000000000..4669a092433e
--- /dev/null
+++ b/nixpkgs/nixos/tests/acme.nix
@@ -0,0 +1,39 @@
+let
+  commonConfig = ./common/letsencrypt/common.nix;
+in import ./make-test.nix {
+  name = "acme";
+
+  nodes = {
+    letsencrypt = ./common/letsencrypt;
+
+    webserver = { config, pkgs, ... }: {
+      imports = [ commonConfig ];
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      networking.extraHosts = ''
+        ${config.networking.primaryIPAddress} example.com
+      '';
+
+      services.nginx.enable = true;
+      services.nginx.virtualHosts."example.com" = {
+        enableACME = true;
+        forceSSL = true;
+        locations."/".root = pkgs.runCommand "docroot" {} ''
+          mkdir -p "$out"
+          echo hello world > "$out/index.html"
+        '';
+      };
+    };
+
+    client = commonConfig;
+  };
+
+  testScript = ''
+    $letsencrypt->waitForUnit("default.target");
+    $letsencrypt->waitForUnit("boulder.service");
+    $webserver->waitForUnit("default.target");
+    $webserver->waitForUnit("acme-certificates.target");
+    $client->waitForUnit("default.target");
+    $client->succeed('curl https://example.com/ | grep -qF "hello world"');
+  '';
+}
diff --git a/nixpkgs/nixos/tests/all-tests.nix b/nixpkgs/nixos/tests/all-tests.nix
new file mode 100644
index 000000000000..4450cafd2800
--- /dev/null
+++ b/nixpkgs/nixos/tests/all-tests.nix
@@ -0,0 +1,229 @@
+{ system, pkgs, callTest }:
+# The return value of this function will be an attrset with arbitrary depth and
+# the `anything` returned by callTest at its test leafs.
+# The tests not supported by `system` will be replaced with `{}`, so that
+# `passthru.tests` can contain links to those without breaking on architectures
+# where said tests are unsupported.
+# Example callTest that just extracts the derivation from the test:
+#   callTest = t: t.test;
+
+with pkgs.lib;
+
+let
+  discoverTests = val:
+    if !isAttrs val then val
+    else if hasAttr "test" val then callTest val
+    else mapAttrs (n: s: discoverTests s) val;
+  handleTest = path: args:
+    discoverTests (import path ({ inherit system pkgs; } // args));
+  handleTestOn = systems: path: args:
+    if elem system systems then handleTest path args
+    else {};
+in
+{
+  acme = handleTestOn ["x86_64-linux"] ./acme.nix {};
+  atd = handleTest ./atd.nix {};
+  avahi = handleTest ./avahi.nix {};
+  bcachefs = handleTestOn ["x86_64-linux"] ./bcachefs.nix {}; # linux-4.18.2018.10.12 is unsupported on aarch64
+  beegfs = handleTestOn ["x86_64-linux"] ./beegfs.nix {}; # beegfs is unsupported on aarch64
+  bind = handleTest ./bind.nix {};
+  bittorrent = handleTest ./bittorrent.nix {};
+  #blivet = handleTest ./blivet.nix {};   # broken since 2017-07024
+  boot = handleTestOn ["x86_64-linux"] ./boot.nix {}; # syslinux is unsupported on aarch64
+  boot-stage1 = handleTest ./boot-stage1.nix {};
+  borgbackup = handleTest ./borgbackup.nix {};
+  buildbot = handleTest ./buildbot.nix {};
+  cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {};
+  ceph = handleTestOn ["x86_64-linux"] ./ceph.nix {};
+  certmgr = handleTest ./certmgr.nix {};
+  cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {};
+  chromium = (handleTestOn ["x86_64-linux"] ./chromium.nix {}).stable or {};
+  cjdns = handleTest ./cjdns.nix {};
+  clickhouse = handleTest ./clickhouse.nix {};
+  cloud-init = handleTest ./cloud-init.nix {};
+  codimd = handleTest ./codimd.nix {};
+  containers-bridge = handleTest ./containers-bridge.nix {};
+  containers-extra_veth = handleTest ./containers-extra_veth.nix {};
+  containers-hosts = handleTest ./containers-hosts.nix {};
+  containers-imperative = handleTest ./containers-imperative.nix {};
+  containers-ipv4 = handleTest ./containers-ipv4.nix {};
+  containers-ipv6 = handleTest ./containers-ipv6.nix {};
+  containers-macvlans = handleTest ./containers-macvlans.nix {};
+  containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {};
+  containers-restart_networking = handleTest ./containers-restart_networking.nix {};
+  containers-tmpfs = handleTest ./containers-tmpfs.nix {};
+  #couchdb = handleTest ./couchdb.nix {}; # spidermonkey-1.8.5 is marked as broken
+  deluge = handleTest ./deluge.nix {};
+  dhparams = handleTest ./dhparams.nix {};
+  dnscrypt-proxy = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy.nix {};
+  docker = handleTestOn ["x86_64-linux"] ./docker.nix {};
+  docker-edge = handleTestOn ["x86_64-linux"] ./docker-edge.nix {};
+  docker-preloader = handleTestOn ["x86_64-linux"] ./docker-preloader.nix {};
+  docker-registry = handleTest ./docker-registry.nix {};
+  docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {};
+  docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {};
+  dovecot = handleTest ./dovecot.nix {};
+  # ec2-config doesn't work in a sandbox as the simulated ec2 instance needs network access
+  #ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
+  ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
+  ecryptfs = handleTest ./ecryptfs.nix {};
+  elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
+  env = handleTest ./env.nix {};
+  etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
+  ferm = handleTest ./ferm.nix {};
+  firefox = handleTest ./firefox.nix {};
+  firewall = handleTest ./firewall.nix {};
+  flatpak = handleTest ./flatpak.nix {};
+  fsck = handleTest ./fsck.nix {};
+  fwupd = handleTestOn ["x86_64-linux"] ./fwupd.nix {}; # libsmbios is unsupported on aarch64
+  gdk-pixbuf = handleTest ./gdk-pixbuf.nix {};
+  gitea = handleTest ./gitea.nix {};
+  gitlab = handleTest ./gitlab.nix {};
+  gitolite = handleTest ./gitolite.nix {};
+  gjs = handleTest ./gjs.nix {};
+  google-oslogin = handleTest ./google-oslogin {};
+  gnome3 = handleTestOn ["x86_64-linux"] ./gnome3.nix {}; # libsmbios is unsupported on aarch64
+  gnome3-gdm = handleTestOn ["x86_64-linux"] ./gnome3-gdm.nix {}; # libsmbios is unsupported on aarch64
+  gocd-agent = handleTest ./gocd-agent.nix {};
+  gocd-server = handleTest ./gocd-server.nix {};
+  grafana = handleTest ./grafana.nix {};
+  graphite = handleTest ./graphite.nix {};
+  hadoop.hdfs = handleTestOn [ "x86_64-linux" ] ./hadoop/hdfs.nix {};
+  hadoop.yarn = handleTestOn [ "x86_64-linux" ] ./hadoop/yarn.nix {};
+  handbrake = handleTestOn ["x86_64-linux"] ./handbrake.nix {};
+  haproxy = handleTest ./haproxy.nix {};
+  hardened = handleTest ./hardened.nix {};
+  hibernate = handleTest ./hibernate.nix {};
+  hitch = handleTest ./hitch {};
+  hocker-fetchdocker = handleTest ./hocker-fetchdocker {};
+  home-assistant = handleTest ./home-assistant.nix {};
+  hound = handleTest ./hound.nix {};
+  hydra = handleTest ./hydra {};
+  i3wm = handleTest ./i3wm.nix {};
+  iftop = handleTest ./iftop.nix {};
+  incron = handleTest ./incron.nix {};
+  influxdb = handleTest ./influxdb.nix {};
+  initrd-network-ssh = handleTest ./initrd-network-ssh {};
+  initrdNetwork = handleTest ./initrd-network.nix {};
+  installer = handleTest ./installer.nix {};
+  ipv6 = handleTest ./ipv6.nix {};
+  jackett = handleTest ./jackett.nix {};
+  jenkins = handleTest ./jenkins.nix {};
+  #kafka = handleTest ./kafka.nix {}; # broken since openjdk: 8u181 -> 8u192
+  kerberos = handleTest ./kerberos/default.nix {};
+  kernel-latest = handleTest ./kernel-latest.nix {};
+  kernel-lts = handleTest ./kernel-lts.nix {};
+  keymap = handleTest ./keymap.nix {};
+  kubernetes.dns = handleTestOn ["x86_64-linux"] ./kubernetes/dns.nix {};
+  # kubernetes.e2e should eventually replace kubernetes.rbac when it works
+  #kubernetes.e2e = handleTestOn ["x86_64-linux"] ./kubernetes/e2e.nix {};
+  kubernetes.rbac = handleTestOn ["x86_64-linux"] ./kubernetes/rbac.nix {};
+  latestKernel.login = handleTest ./login.nix { latestKernel = true; };
+  ldap = handleTest ./ldap.nix {};
+  leaps = handleTest ./leaps.nix {};
+  lidarr = handleTest ./lidarr.nix {};
+  #lightdm = handleTest ./lightdm.nix {};
+  login = handleTest ./login.nix {};
+  #logstash = handleTest ./logstash.nix {};
+  mathics = handleTest ./mathics.nix {};
+  matrix-synapse = handleTest ./matrix-synapse.nix {};
+  memcached = handleTest ./memcached.nix {};
+  mesos = handleTest ./mesos.nix {};
+  minio = handleTest ./minio.nix {};
+  misc = handleTest ./misc.nix {};
+  mongodb = handleTest ./mongodb.nix {};
+  morty = handleTest ./morty.nix {};
+  mpd = handleTest ./mpd.nix {};
+  mumble = handleTest ./mumble.nix {};
+  munin = handleTest ./munin.nix {};
+  mutableUsers = handleTest ./mutable-users.nix {};
+  mysql = handleTest ./mysql.nix {};
+  mysqlBackup = handleTest ./mysql-backup.nix {};
+  mysqlReplication = handleTest ./mysql-replication.nix {};
+  nat.firewall = handleTest ./nat.nix { withFirewall = true; };
+  nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
+  nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+  ndppd = handleTest ./ndppd.nix {};
+  neo4j = handleTest ./neo4j.nix {};
+  netdata = handleTest ./netdata.nix {};
+  networking.networkd = handleTest ./networking.nix { networkd = true; };
+  networking.scripted = handleTest ./networking.nix { networkd = false; };
+  # TODO: put in networking.nix after the test becomes more complete
+  networkingProxy = handleTest ./networking-proxy.nix {};
+  nextcloud = handleTest ./nextcloud {};
+  nexus = handleTest ./nexus.nix {};
+  nfs3 = handleTest ./nfs.nix { version = 3; };
+  nfs4 = handleTest ./nfs.nix { version = 4; };
+  nghttpx = handleTest ./nghttpx.nix {};
+  nginx = handleTest ./nginx.nix {};
+  nginx-sso = handleTest ./nginx-sso.nix {};
+  nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
+  novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
+  nsd = handleTest ./nsd.nix {};
+  openldap = handleTest ./openldap.nix {};
+  opensmtpd = handleTest ./opensmtpd.nix {};
+  openssh = handleTest ./openssh.nix {};
+  osquery = handleTest ./osquery.nix {};
+  ostree = handleTest ./ostree.nix {};
+  pam-oath-login = handleTest ./pam-oath-login.nix {};
+  pam-u2f = handleTest ./pam-u2f.nix {};
+  peerflix = handleTest ./peerflix.nix {};
+  pgjwt = handleTest ./pgjwt.nix {};
+  pgmanage = handleTest ./pgmanage.nix {};
+  php-pcre = handleTest ./php-pcre.nix {};
+  plasma5 = handleTest ./plasma5.nix {};
+  plotinus = handleTest ./plotinus.nix {};
+  postgis = handleTest ./postgis.nix {};
+  postgresql = handleTest ./postgresql.nix {};
+  powerdns = handleTest ./powerdns.nix {};
+  predictable-interface-names = handleTest ./predictable-interface-names.nix {};
+  printing = handleTest ./printing.nix {};
+  prometheus = handleTest ./prometheus.nix {};
+  prometheus-exporters = handleTest ./prometheus-exporters.nix {};
+  prosody = handleTest ./prosody.nix {};
+  proxy = handleTest ./proxy.nix {};
+  quagga = handleTest ./quagga.nix {};
+  quake3 = handleTest ./quake3.nix {};
+  rabbitmq = handleTest ./rabbitmq.nix {};
+  radarr = handleTest ./radarr.nix {};
+  radicale = handleTest ./radicale.nix {};
+  redmine = handleTest ./redmine.nix {};
+  roundcube = handleTest ./roundcube.nix {};
+  rspamd = handleTest ./rspamd.nix {};
+  rss2email = handleTest ./rss2email.nix {};
+  rsyslogd = handleTest ./rsyslogd.nix {};
+  runInMachine = handleTest ./run-in-machine.nix {};
+  rxe = handleTest ./rxe.nix {};
+  samba = handleTest ./samba.nix {};
+  sddm = handleTest ./sddm.nix {};
+  simple = handleTest ./simple.nix {};
+  slim = handleTest ./slim.nix {};
+  slurm = handleTest ./slurm.nix {};
+  smokeping = handleTest ./smokeping.nix {};
+  snapper = handleTest ./snapper.nix {};
+  solr = handleTest ./solr.nix {};
+  sonarr = handleTest ./sonarr.nix {};
+  strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
+  sudo = handleTest ./sudo.nix {};
+  switchTest = handleTest ./switch-test.nix {};
+  syncthing-relay = handleTest ./syncthing-relay.nix {};
+  systemd = handleTest ./systemd.nix {};
+  taskserver = handleTest ./taskserver.nix {};
+  telegraf = handleTest ./telegraf.nix {};
+  tomcat = handleTest ./tomcat.nix {};
+  tor = handleTest ./tor.nix {};
+  transmission = handleTest ./transmission.nix {};
+  udisks2 = handleTest ./udisks2.nix {};
+  upnp = handleTest ./upnp.nix {};
+  vault = handleTest ./vault.nix {};
+  virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
+  wordpress = handleTest ./wordpress.nix {};
+  xautolock = handleTest ./xautolock.nix {};
+  xdg-desktop-portal = handleTest ./xdg-desktop-portal.nix {};
+  xfce = handleTest ./xfce.nix {};
+  xmonad = handleTest ./xmonad.nix {};
+  xrdp = handleTest ./xrdp.nix {};
+  xss-lock = handleTest ./xss-lock.nix {};
+  yabar = handleTest ./yabar.nix {};
+  zookeeper = handleTest ./zookeeper.nix {};
+}
diff --git a/nixpkgs/nixos/tests/ammonite.nix b/nixpkgs/nixos/tests/ammonite.nix
new file mode 100644
index 000000000000..fedfde233e8d
--- /dev/null
+++ b/nixpkgs/nixos/tests/ammonite.nix
@@ -0,0 +1,20 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "ammonite";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  nodes = {
+    amm =
+      { pkgs, ... }:
+        {
+          environment.systemPackages = [ pkgs.ammonite ];
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $amm->succeed("amm -c 'val foo = 21; println(foo * 2)' | grep 42")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/atd.nix b/nixpkgs/nixos/tests/atd.nix
new file mode 100644
index 000000000000..25db72799241
--- /dev/null
+++ b/nixpkgs/nixos/tests/atd.nix
@@ -0,0 +1,31 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "atd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bjornfor ];
+  };
+
+  machine =
+    { ... }:
+    { services.atd.enable = true;
+      users.users.alice = { isNormalUser = true; };
+    };
+
+  # "at" has a resolution of 1 minute
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit('atd.service'); # wait for atd to start
+    $machine->fail("test -f ~root/at-1");
+    $machine->fail("test -f ~alice/at-1");
+
+    $machine->succeed("echo 'touch ~root/at-1' | at now+1min");
+    $machine->succeed("su - alice -c \"echo 'touch at-1' | at now+1min\"");
+
+    $machine->succeed("sleep 1.5m");
+
+    $machine->succeed("test -f ~root/at-1");
+    $machine->succeed("test -f ~alice/at-1");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/avahi.nix b/nixpkgs/nixos/tests/avahi.nix
new file mode 100644
index 000000000000..dfb60998941b
--- /dev/null
+++ b/nixpkgs/nixos/tests/avahi.nix
@@ -0,0 +1,60 @@
+# Test whether `avahi-daemon' and `libnss-mdns' work as expected.
+import ./make-test.nix ({ pkgs, ... } : {
+  name = "avahi";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  nodes = let
+    cfg = { ... }: {
+      services.avahi = {
+        enable = true;
+        nssmdns = true;
+        publish.addresses = true;
+        publish.domain = true;
+        publish.enable = true;
+        publish.userServices = true;
+        publish.workstation = true;
+      };
+    };
+  in {
+    one = cfg;
+    two = cfg;
+  };
+
+  testScript =
+    '' startAll;
+
+       # mDNS.
+       $one->waitForUnit("network.target");
+       $two->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->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/nixpkgs/nixos/tests/bcachefs.nix b/nixpkgs/nixos/tests/bcachefs.nix
new file mode 100644
index 000000000000..658676ef0ab9
--- /dev/null
+++ b/nixpkgs/nixos/tests/bcachefs.nix
@@ -0,0 +1,38 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "bcachefs";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ chiiruno ];
+
+  machine = { pkgs, ... }: {
+    virtualisation.emptyDiskImages = [ 4096 ];
+    networking.hostId = "deadbeef";
+    boot.supportedFilesystems = [ "bcachefs" ];
+    environment.systemPackages = with pkgs; [ parted ];
+  };
+
+  testScript = ''
+    $machine->succeed("modprobe bcachefs");
+    $machine->succeed("bcachefs version");
+    $machine->succeed("ls /dev");
+    
+    $machine->succeed(
+      "mkdir /tmp/mnt",
+
+      "udevadm settle",
+      "parted --script /dev/vdb mklabel msdos",
+      "parted --script /dev/vdb -- mkpart primary 1024M -1s",
+      "udevadm settle",
+
+      # Due to #32279, we cannot use encryption for this test yet
+      # "echo password | bcachefs format --encrypted /dev/vdb1",
+      # "echo password | bcachefs unlock /dev/vdb1",
+      "bcachefs format /dev/vdb1",
+      "mount -t bcachefs /dev/vdb1 /tmp/mnt",
+      "udevadm settle",
+
+      "bcachefs fs usage /tmp/mnt",
+
+      "umount /tmp/mnt",
+      "udevadm settle"
+    );
+  '';
+})
diff --git a/nixpkgs/nixos/tests/beegfs.nix b/nixpkgs/nixos/tests/beegfs.nix
new file mode 100644
index 000000000000..9c241fd2301a
--- /dev/null
+++ b/nixpkgs/nixos/tests/beegfs.nix
@@ -0,0 +1,115 @@
+import ./make-test.nix ({ ... } :
+
+let
+  connAuthFile="beegfs/auth-def.key";
+
+  client = { pkgs, ... } : {
+    networking.firewall.enable = false;
+    services.beegfsEnable = true;
+    services.beegfs.default = {
+      mgmtdHost = "mgmt";
+      connAuthFile = "/etc/${connAuthFile}";
+      client = {
+        mount = false;
+        enable = true;
+      };
+    };
+
+    fileSystems = pkgs.lib.mkVMOverride # FIXME: this should be creatd by the module
+      [ { mountPoint = "/beegfs";
+          device = "default";
+          fsType = "beegfs";
+          options = [ "cfgFile=/etc/beegfs/client-default.conf" "_netdev" ];
+        }
+      ];
+
+    environment.etc."${connAuthFile}" = {
+      enable = true;
+      text = "ThisIsALousySecret";
+      mode = "0600";
+    };
+  };
+
+
+  server = service : { pkgs, ... } : {
+    networking.firewall.enable = false;
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb
+    '';
+
+    virtualisation.emptyDiskImages = [ 4096 ];
+
+    fileSystems = pkgs.lib.mkVMOverride
+      [ { mountPoint = "/data";
+          device = "/dev/disk/by-label/data";
+          fsType = "ext4";
+        }
+      ];
+
+    environment.systemPackages = with pkgs; [ beegfs ];
+    environment.etc."${connAuthFile}" = {
+      enable = true;
+      text = "ThisIsALousySecret";
+      mode = "0600";
+    };
+
+    services.beegfsEnable = true;
+    services.beegfs.default = {
+      mgmtdHost = "mgmt";
+      connAuthFile = "/etc/${connAuthFile}";
+      "${service}" = {
+        enable = true;
+        storeDir = "/data";
+      };
+    };
+  };
+
+in
+{
+  name = "beegfs";
+
+  nodes = {
+    meta = server "meta";
+    mgmt = server "mgmtd";
+    storage1 = server "storage";
+    storage2 = server "storage";
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = ''
+    # Initalize the data directories
+    $mgmt->waitForUnit("default.target");
+    $mgmt->succeed("beegfs-setup-mgmtd -C -f -p /data");
+    $mgmt->succeed("systemctl start beegfs-mgmtd-default");
+
+    $meta->waitForUnit("default.target");
+    $meta->succeed("beegfs-setup-meta -C -f -s 1 -p /data");
+    $meta->succeed("systemctl start beegfs-meta-default");
+
+    $storage1->waitForUnit("default.target");
+    $storage1->succeed("beegfs-setup-storage -C -f -s 1 -i 1 -p /data");
+    $storage1->succeed("systemctl start beegfs-storage-default");
+
+    $storage2->waitForUnit("default.target");
+    $storage2->succeed("beegfs-setup-storage -C -f -s 2 -i 2 -p /data");
+    $storage2->succeed("systemctl start beegfs-storage-default");
+
+    #
+
+    # Basic test
+    $client1->waitForUnit("beegfs.mount");
+    $client1->succeed("beegfs-check-servers-default");
+    $client1->succeed("echo test > /beegfs/test");
+    $client2->waitForUnit("beegfs.mount");
+    $client2->succeed("test -e /beegfs/test");
+    $client2->succeed("cat /beegfs/test | grep test");
+
+    # test raid0/stripping
+    $client1->succeed("dd if=/dev/urandom bs=1M count=10 of=/beegfs/striped");
+    $client2->succeed("cat /beegfs/striped > /dev/null");
+
+    # check if fs is still healthy
+    $client1->succeed("beegfs-fsck-default --checkfs");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bees.nix b/nixpkgs/nixos/tests/bees.nix
new file mode 100644
index 000000000000..6f68c2f834f1
--- /dev/null
+++ b/nixpkgs/nixos/tests/bees.nix
@@ -0,0 +1,55 @@
+import ./make-test.nix ({ lib, ... }:
+{
+  name = "bees";
+
+  machine = { config, pkgs, ... }: {
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux1 /dev/vdb
+      ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux2 /dev/vdc
+    '';
+    virtualisation.emptyDiskImages = [ 4096 4096 ];
+    fileSystems = lib.mkVMOverride {
+      "/aux1" = { # filesystem configured to be deduplicated
+        device = "/dev/disk/by-label/aux1";
+        fsType = "btrfs";
+      };
+      "/aux2" = { # filesystem not configured to be deduplicated
+        device = "/dev/disk/by-label/aux2";
+        fsType = "btrfs";
+      };
+    };
+    services.beesd.filesystems = {
+      aux1 = {
+        spec = "LABEL=aux1";
+        hashTableSizeMB = 16;
+        verbosity = "debug";
+      };
+    };
+  };
+
+  testScript =
+  let
+    withRetry = content: maxTests: sleepTime: ''
+      max_tests=${lib.escapeShellArg maxTests}; sleep_time=${lib.escapeShellArg sleepTime}; for ((i=0; i<max_tests; i++)); do ${content} && exit 0; sleep "$sleep_time"; done; exit 1;
+    '';
+    someContentIsShared = loc: ''[[ $(btrfs fi du -s --raw ${lib.escapeShellArg loc}/dedup-me-{1,2} | awk 'BEGIN { count=0; } NR>1 && $3 == 0 { count++ } END { print count }') -eq 0 ]]'';
+  in ''
+    # shut down the instance started by systemd at boot, so we can test our test procedure
+    $machine->succeed("systemctl stop beesd\@aux1.service");
+
+    $machine->succeed("dd if=/dev/urandom of=/aux1/dedup-me-1 bs=1M count=8");
+    $machine->succeed("cp --reflink=never /aux1/dedup-me-1 /aux1/dedup-me-2");
+    $machine->succeed("cp --reflink=never /aux1/* /aux2/");
+    $machine->succeed("sync");
+    $machine->fail(q(${someContentIsShared "/aux1"}));
+    $machine->fail(q(${someContentIsShared "/aux2"}));
+    $machine->succeed("systemctl start beesd\@aux1.service");
+
+    # assert that "Set Shared" column is nonzero
+    $machine->succeed(q(${withRetry (someContentIsShared "/aux1") 20 2}));
+    $machine->fail(q(${someContentIsShared "/aux2"}));
+
+    # assert that 16MB hash table size requested was honored
+    $machine->succeed(q([[ $(stat -c %s /aux1/.beeshome/beeshash.dat) = $(( 16 * 1024 * 1024)) ]]))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bind.nix b/nixpkgs/nixos/tests/bind.nix
new file mode 100644
index 000000000000..1f8c1dc7be40
--- /dev/null
+++ b/nixpkgs/nixos/tests/bind.nix
@@ -0,0 +1,27 @@
+import ./make-test.nix {
+  name = "bind";
+
+  machine = { pkgs, lib, ... }: {
+    services.bind.enable = true;
+    services.bind.extraOptions = "empty-zones-enable no;";
+    services.bind.zones = lib.singleton {
+      name = ".";
+      file = pkgs.writeText "root.zone" ''
+        $TTL 3600
+        . IN SOA ns.example.org. admin.example.org. ( 1 3h 1h 1w 1d )
+        . IN NS ns.example.org.
+
+        ns.example.org. IN A    192.168.0.1
+        ns.example.org. IN AAAA abcd::1
+
+        1.0.168.192.in-addr.arpa IN PTR ns.example.org.
+      '';
+    };
+  };
+
+  testScript = ''
+    $machine->waitForUnit('bind.service');
+    $machine->waitForOpenPort(53);
+    $machine->succeed('host 192.168.0.1 127.0.0.1 | grep -qF ns.example.org');
+  '';
+}
diff --git a/nixpkgs/nixos/tests/bittorrent.nix b/nixpkgs/nixos/tests/bittorrent.nix
new file mode 100644
index 000000000000..27871f72b4e5
--- /dev/null
+++ b/nixpkgs/nixos/tests/bittorrent.nix
@@ -0,0 +1,149 @@
+# 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.
+
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+
+  # Some random file to serve.
+  file = pkgs.hello.src;
+
+  internalRouterAddress = "192.168.3.1";
+  internalClient1Address = "192.168.3.2";
+  externalRouterAddress = "80.100.100.1";
+  externalClient2Address = "80.100.100.2";
+  externalTrackerAddress = "80.100.100.3";
+in
+
+{
+  name = "bittorrent";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ domenkozar eelco chaoflow rob bobvanderlinden ];
+  };
+
+  nodes =
+    { tracker =
+        { pkgs, ... }:
+        { environment.systemPackages = [ pkgs.transmission ];
+
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalTrackerAddress; prefixLength = 24; }
+          ];
+
+          # We need Apache on the tracker to serve the torrents.
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          services.httpd.documentRoot = "/tmp";
+
+          networking.firewall.enable = false;
+
+          services.opentracker.enable = true;
+
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.port-forwaring-enabled = false;
+        };
+
+      router =
+        { pkgs, nodes, ... }:
+        { virtualisation.vlans = [ 1 2 ];
+          networking.nat.enable = true;
+          networking.nat.internalInterfaces = [ "eth2" ];
+          networking.nat.externalInterface = "eth1";
+          networking.firewall.enable = true;
+          networking.firewall.trustedInterfaces = [ "eth2" ];
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalRouterAddress; prefixLength = 24; }
+          ];
+          networking.interfaces.eth2.ipv4.addresses = [
+            { address = internalRouterAddress; prefixLength = 24; }
+          ];
+          services.miniupnpd = {
+            enable = true;
+            externalInterface = "eth1";
+            internalIPs = [ "eth2" ];
+            appendConfig = ''
+              ext_ip=${externalRouterAddress}
+            '';
+          };
+        };
+
+      client1 =
+        { pkgs, nodes, ... }:
+        { environment.systemPackages = [ pkgs.transmission pkgs.miniupnpc ];
+          virtualisation.vlans = [ 2 ];
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = internalClient1Address; prefixLength = 24; }
+          ];
+          networking.defaultGateway = internalRouterAddress;
+          networking.firewall.enable = false;
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.message-level = 3;
+        };
+
+      client2 =
+        { pkgs, ... }:
+        { environment.systemPackages = [ pkgs.transmission ];
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalClient2Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.port-forwaring-enabled = false;
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      startAll;
+
+      # Wait for network and miniupnpd.
+      $router->waitForUnit("network-online.target");
+      $router->waitForUnit("miniupnpd");
+
+      # 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 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent");
+      $tracker->succeed("chmod 644 /tmp/test.torrent");
+
+      # Start the tracker.  !!! use a less crappy tracker
+      $tracker->waitForUnit("network-online.target");
+      $tracker->waitForUnit("opentracker.service");
+      $tracker->waitForOpenPort(6969);
+
+      # Start the initial seeder.
+      $tracker->succeed("transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data");
+
+      # Now we should be able to download from the client behind the NAT.
+      $tracker->waitForUnit("httpd");
+      $client1->waitForUnit("network-online.target");
+      $client1->succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &");
+      $client1->waitForFile("/tmp/test.tar.bz2");
+      $client1->succeed("cmp /tmp/test.tar.bz2 ${file}");
+
+      # Bring down the initial seeder.
+      # $tracker->stopJob("transmission");
+
+      # Now download from the second client.  This can only succeed if
+      # the first client created a NAT hole in the router.
+      $client2->waitForUnit("network-online.target");
+      $client2->succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &");
+      $client2->waitForFile("/tmp/test.tar.bz2");
+      $client2->succeed("cmp /tmp/test.tar.bz2 ${file}");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/blivet.nix b/nixpkgs/nixos/tests/blivet.nix
new file mode 100644
index 000000000000..2adc2ee1eeea
--- /dev/null
+++ b/nixpkgs/nixos/tests/blivet.nix
@@ -0,0 +1,87 @@
+import ./make-test.nix ({ pkgs, ... }: with pkgs.python2Packages; rec {
+  name = "blivet";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  machine = {
+    environment.systemPackages = [ pkgs.python blivet mock ];
+    boot.supportedFilesystems = [ "btrfs" "jfs" "reiserfs" "xfs" ];
+    virtualisation.memorySize = 768;
+  };
+
+  debugBlivet       = false;
+  debugProgramCalls = false;
+
+  pythonTestRunner = pkgs.writeText "run-blivet-tests.py" ''
+    import sys
+    import logging
+
+    from unittest import TestLoader
+    from unittest.runner import TextTestRunner
+
+    ${pkgs.lib.optionalString debugProgramCalls ''
+      blivet_program_log = logging.getLogger("program")
+      blivet_program_log.setLevel(logging.DEBUG)
+      blivet_program_log.addHandler(logging.StreamHandler(sys.stderr))
+    ''}
+
+    ${pkgs.lib.optionalString debugBlivet ''
+      blivet_log = logging.getLogger("blivet")
+      blivet_log.setLevel(logging.DEBUG)
+      blivet_log.addHandler(logging.StreamHandler(sys.stderr))
+    ''}
+
+    runner = TextTestRunner(verbosity=2, failfast=False, buffer=False)
+    result = runner.run(TestLoader().discover('tests/', pattern='*_test.py'))
+    sys.exit(not result.wasSuccessful())
+  '';
+
+  blivetTest = pkgs.writeScript "blivet-test.sh" ''
+    #!${pkgs.stdenv.shell} -e
+
+    # Use the hosts temporary directory, because we have a tmpfs within the VM
+    # and we don't want to increase the memory size of the VM for no reason.
+    mkdir -p /tmp/xchg/bigtmp
+    TMPDIR=/tmp/xchg/bigtmp
+    export TMPDIR
+
+    cp -Rd "${blivet.src}/tests" .
+
+    # Skip SELinux tests
+    rm -f tests/formats_test/selinux_test.py
+
+    # Race conditions in growing/shrinking during resync
+    rm -f tests/devicelibs_test/mdraid_*
+
+    # Deactivate small BTRFS device test, because it fails with newer btrfsprogs
+    sed -i -e '/^class *BTRFSAsRootTestCase3(/,/^[^ ]/ {
+      /^class *BTRFSAsRootTestCase3(/d
+      /^$/d
+      /^ /d
+    }' tests/devicelibs_test/btrfs_test.py
+
+    # How on earth can these tests ever work even upstream? O_o
+    sed -i -e '/def testDiskChunk[12]/,/^ *[^ ]/{n; s/^ */&return # /}' \
+      tests/partitioning_test.py
+
+    # fix hardcoded temporary directory
+    sed -i \
+      -e '1i import tempfile' \
+      -e 's|_STORE_FILE_PATH = .*|_STORE_FILE_PATH = tempfile.gettempdir()|' \
+      -e 's|DEFAULT_STORE_SIZE = .*|DEFAULT_STORE_SIZE = 409600|' \
+      tests/loopbackedtestcase.py
+
+    PYTHONPATH=".:$(< "${pkgs.stdenv.mkDerivation {
+      name = "blivet-pythonpath";
+      buildInputs = [ blivet mock ];
+      buildCommand = "echo \"$PYTHONPATH\" > \"$out\"";
+    }}")" python "${pythonTestRunner}"
+  '';
+
+  testScript = ''
+    $machine->waitForUnit("multi-user.target");
+    $machine->succeed("${blivetTest}");
+    $machine->execute("rm -rf /tmp/xchg/bigtmp");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/boot-stage1.nix b/nixpkgs/nixos/tests/boot-stage1.nix
new file mode 100644
index 000000000000..b2e74bff6fcd
--- /dev/null
+++ b/nixpkgs/nixos/tests/boot-stage1.nix
@@ -0,0 +1,162 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "boot-stage1";
+
+  machine = { config, pkgs, lib, ... }: {
+    boot.extraModulePackages = let
+      compileKernelModule = name: source: pkgs.runCommandCC name rec {
+        inherit source;
+        kdev = config.boot.kernelPackages.kernel.dev;
+        kver = config.boot.kernelPackages.kernel.modDirVersion;
+        ksrc = "${kdev}/lib/modules/${kver}/build";
+        hardeningDisable = [ "pic" ];
+        nativeBuildInputs = kdev.moduleBuildDependencies;
+      } ''
+        echo "obj-m += $name.o" > Makefile
+        echo "$source" > "$name.c"
+        make -C "$ksrc" M=$(pwd) modules
+        install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko"
+      '';
+
+      # This spawns a kthread which just waits until it gets a signal and
+      # terminates if that is the case. We want to make sure that nothing during
+      # the boot process kills any kthread by accident, like what happened in
+      # issue #15226.
+      kcanary = compileKernelModule "kcanary" ''
+        #include <linux/version.h>
+        #include <linux/init.h>
+        #include <linux/module.h>
+        #include <linux/kernel.h>
+        #include <linux/kthread.h>
+        #include <linux/sched.h>
+        #include <linux/signal.h>
+        #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+        #include <linux/sched/signal.h>
+        #endif
+
+        struct task_struct *canaryTask;
+
+        static int kcanary(void *nothing)
+        {
+          allow_signal(SIGINT);
+          allow_signal(SIGTERM);
+          allow_signal(SIGKILL);
+          while (!kthread_should_stop()) {
+            set_current_state(TASK_INTERRUPTIBLE);
+            schedule_timeout_interruptible(msecs_to_jiffies(100));
+            if (signal_pending(current)) break;
+          }
+          return 0;
+        }
+
+        static int kcanaryInit(void)
+        {
+          kthread_run(&kcanary, NULL, "kcanary");
+          return 0;
+        }
+
+        static void kcanaryExit(void)
+        {
+          kthread_stop(canaryTask);
+        }
+
+        module_init(kcanaryInit);
+        module_exit(kcanaryExit);
+      '';
+
+    in lib.singleton kcanary;
+
+    boot.initrd.kernelModules = [ "kcanary" ];
+
+    boot.initrd.extraUtilsCommands = let
+      compile = name: source: pkgs.runCommandCC name { inherit source; } ''
+        mkdir -p "$out/bin"
+        echo "$source" | gcc -Wall -o "$out/bin/$name" -xc -
+      '';
+
+      daemonize = name: source: compile name ''
+        #include <stdio.h>
+        #include <unistd.h>
+
+        void runSource(void) {
+        ${source}
+        }
+
+        int main(void) {
+          if (fork() > 0) return 0;
+          setsid();
+          runSource();
+          return 1;
+        }
+      '';
+
+      mkCmdlineCanary = { name, cmdline ? "", source ? "" }: (daemonize name ''
+        char *argv[] = {"${cmdline}", NULL};
+        execvp("${name}-child", argv);
+      '') // {
+        child = compile "${name}-child" ''
+          #include <stdio.h>
+          #include <unistd.h>
+
+          int main(void) {
+            ${source}
+            while (1) sleep(1);
+            return 1;
+          }
+        '';
+      };
+
+      copyCanaries = with lib; concatMapStrings (canary: ''
+        ${optionalString (canary ? child) ''
+          copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
+        ''}
+        copy_bin_and_libs "${canary}/bin/${canary.name}"
+      '');
+
+    in copyCanaries [
+      # Simple canary process which just sleeps forever and should be killed by
+      # stage 2.
+      (daemonize "canary1" "while (1) sleep(1);")
+
+      # We want this canary process to try mimicking a kthread using a cmdline
+      # with a zero length so we can make sure that the process is properly
+      # killed in stage 1.
+      (mkCmdlineCanary {
+        name = "canary2";
+        source = ''
+          FILE *f;
+          f = fopen("/run/canary2.pid", "w");
+          fprintf(f, "%d\n", getpid());
+          fclose(f);
+        '';
+      })
+
+      # This canary process mimicks a storage daemon, which we do NOT want to be
+      # killed before going into stage 2. For more on root storage daemons, see:
+      # https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
+      (mkCmdlineCanary {
+        name = "canary3";
+        cmdline = "@canary3";
+      })
+    ];
+
+    boot.initrd.postMountCommands = ''
+      canary1
+      canary2
+      canary3
+      # Make sure the pidfile of canary 2 is created so that we still can get
+      # its former pid after the killing spree starts next within stage 1.
+      while [ ! -s /run/canary2.pid ]; do sleep 0.1; done
+    '';
+  };
+
+  testScript = ''
+    $machine->waitForUnit("multi-user.target");
+    $machine->succeed('test -s /run/canary2.pid');
+    $machine->fail('pgrep -a canary1');
+    $machine->fail('kill -0 $(< /run/canary2.pid)');
+    $machine->succeed('pgrep -a -f \'^@canary3$\''');
+    $machine->succeed('pgrep -a -f \'^kcanary$\''');
+  '';
+
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ aszlig ];
+})
diff --git a/nixpkgs/nixos/tests/boot.nix b/nixpkgs/nixos/tests/boot.nix
new file mode 100644
index 000000000000..c9bb1e77c6d0
--- /dev/null
+++ b/nixpkgs/nixos/tests/boot.nix
@@ -0,0 +1,99 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+
+  iso =
+    (import ../lib/eval-config.nix {
+      inherit system;
+      modules =
+        [ ../modules/installer/cd-dvd/installation-cd-minimal.nix
+          ../modules/testing/test-instrumentation.nix
+        ];
+    }).config.system.build.isoImage;
+
+  makeBootTest = name: machineConfig:
+    makeTest {
+      inherit iso;
+      name = "boot-" + name;
+      nodes = { };
+      testScript =
+        ''
+          my $machine = createMachine({ ${machineConfig}, qemuFlags => '-m 768' });
+          $machine->start;
+          $machine->waitForUnit("multi-user.target");
+          $machine->succeed("nix verify -r --no-trust /run/current-system");
+
+          # Test whether the channel got installed correctly.
+          $machine->succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello");
+          $machine->succeed("nix-env --dry-run -iA nixos.procps");
+
+          $machine->shutdown;
+        '';
+    };
+in {
+
+    biosCdrom = makeBootTest "bios-cdrom" ''
+        cdrom => glob("${iso}/iso/*.iso")
+      '';
+
+    biosUsb = makeBootTest "bios-usb" ''
+        usb => glob("${iso}/iso/*.iso")
+      '';
+
+    uefiCdrom = makeBootTest "uefi-cdrom" ''
+        cdrom => glob("${iso}/iso/*.iso"),
+        bios => '${pkgs.OVMF.fd}/FV/OVMF.fd'
+      '';
+
+    uefiUsb = makeBootTest "uefi-usb" ''
+        usb => glob("${iso}/iso/*.iso"),
+        bios => '${pkgs.OVMF.fd}/FV/OVMF.fd'
+      '';
+
+    netboot = let
+      config = (import ../lib/eval-config.nix {
+          inherit system;
+          modules =
+            [ ../modules/installer/netboot/netboot.nix
+              ../modules/testing/test-instrumentation.nix
+              { key = "serial"; }
+            ];
+        }).config;
+      ipxeScriptDir = pkgs.writeTextFile {
+        name = "ipxeScriptDir";
+        text = ''
+          #!ipxe
+          dhcp
+          kernel bzImage init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} console=ttyS0
+          initrd initrd
+          boot
+        '';
+        destination = "/boot.ipxe";
+      };
+      ipxeBootDir = pkgs.symlinkJoin {
+        name = "ipxeBootDir";
+        paths = [
+          config.system.build.netbootRamdisk
+          config.system.build.kernel
+          ipxeScriptDir
+        ];
+      };
+    in
+      makeTest {
+        name = "boot-netboot";
+        nodes = { };
+        testScript =
+          ''
+            my $machine = createMachine({ qemuFlags => '-boot order=n -net nic,model=e1000 -net user,tftp=${ipxeBootDir}/,bootfile=boot.ipxe -m 2000M' });
+            $machine->start;
+            $machine->waitForUnit("multi-user.target");
+            $machine->shutdown;
+          '';
+      };
+}
diff --git a/nixpkgs/nixos/tests/borgbackup.nix b/nixpkgs/nixos/tests/borgbackup.nix
new file mode 100644
index 000000000000..fdb87dbea438
--- /dev/null
+++ b/nixpkgs/nixos/tests/borgbackup.nix
@@ -0,0 +1,165 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+  passphrase = "supersecret";
+  dataDir = "/ran:dom/data";
+  excludeFile = "not_this_file";
+  keepFile = "important_file";
+  keepFileData = "important_data";
+  localRepo = "/root/back:up";
+  archiveName = "my_archive";
+  remoteRepo = "borg@server:."; # No need to specify path
+  privateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
+    RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
+    AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
+    9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+  publicKey = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
+  '';
+  privateKeyAppendOnly = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8
+    cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw
+    AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8
+    IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+  publicKeyAppendOnly = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
+  '';
+
+in {
+  name = "borgbackup";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ dotlambda ];
+  };
+
+  nodes = {
+    client = { ... }: {
+      services.borgbackup.jobs = {
+        
+        local = rec {
+          paths = dataDir;
+          repo = localRepo;
+          preHook = ''
+            # Don't append a timestamp
+            archiveName="${archiveName}"
+          '';
+          encryption = {
+            mode = "repokey";
+            inherit passphrase;
+          };
+          compression = "auto,zlib,9";
+          prune.keep = {
+            within = "1y";
+            yearly = 5;
+          };
+          exclude = [ "*/${excludeFile}" ];
+          postHook = "echo post";
+          startAt = [ ]; # Do not run automatically
+        };
+
+        remote = {
+          paths = dataDir;
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
+        remoteAppendOnly = {
+          paths = dataDir;
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
+        };
+
+      };
+    };
+
+    server = { ... }: {
+      services.openssh = {
+        enable = true;
+        passwordAuthentication = false;
+        challengeResponseAuthentication = false;
+      };
+
+      services.borgbackup.repos.repo1 = {
+        authorizedKeys = [ publicKey ];
+        path = "/data/borgbackup";
+      };
+
+      # Second repo to make sure the authorizedKeys options are merged correctly
+      services.borgbackup.repos.repo2 = {
+        authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
+        path = "/data/borgbackup";
+        quota = ".5G";
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $client->fail('test -d "${remoteRepo}"');
+
+    $client->succeed("cp ${privateKey} /root/id_ed25519");
+    $client->succeed("chmod 0600 /root/id_ed25519");
+    $client->succeed("cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly");
+    $client->succeed("chmod 0600 /root/id_ed25519.appendOnly");
+
+    $client->succeed("mkdir -p ${dataDir}");
+    $client->succeed("touch ${dataDir}/${excludeFile}");
+    $client->succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}");
+
+    subtest "local", sub {
+      my $borg = "BORG_PASSPHRASE='${passphrase}' borg";
+      $client->systemctl("start --wait borgbackup-job-local");
+      $client->fail("systemctl is-failed borgbackup-job-local");
+      # Make sure exactly one archive has been created
+      $client->succeed("c=\$($borg list '${localRepo}' | wc -l) && [[ \$c == '1' ]]");
+      # Make sure excludeFile has been excluded
+      $client->fail("$borg list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'");
+      # Make sure keepFile has the correct content
+      $client->succeed("$borg extract '${localRepo}::${archiveName}'");
+      $client->succeed('c=$(cat ${dataDir}/${keepFile}) && [[ "$c" == "${keepFileData}" ]]');
+      # Make sure the same is true when using `borg mount`
+      $client->succeed("mkdir -p /mnt/borg && $borg mount '${localRepo}::${archiveName}' /mnt/borg");
+      $client->succeed('c=$(cat /mnt/borg/${dataDir}/${keepFile}) && [[ "$c" == "${keepFileData}" ]]');
+    };
+
+    subtest "remote", sub {
+      my $borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg";
+      $server->waitForUnit("sshd.service");
+      $client->waitForUnit("network.target");
+      $client->systemctl("start --wait borgbackup-job-remote");
+      $client->fail("systemctl is-failed borgbackup-job-remote");
+
+      # Make sure we can't access repos other than the specified one
+      $client->fail("$borg list borg\@server:wrong");
+
+      #TODO: Make sure that data is actually deleted
+    };
+
+    subtest "remoteAppendOnly", sub {
+      my $borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg";
+      $server->waitForUnit("sshd.service");
+      $client->waitForUnit("network.target");
+      $client->systemctl("start --wait borgbackup-job-remoteAppendOnly");
+      $client->fail("systemctl is-failed borgbackup-job-remoteAppendOnly");
+
+      # Make sure we can't access repos other than the specified one
+      $client->fail("$borg list borg\@server:wrong");
+
+      #TODO: Make sure that data is not actually deleted
+    };
+
+  '';
+})
diff --git a/nixpkgs/nixos/tests/buildbot.nix b/nixpkgs/nixos/tests/buildbot.nix
new file mode 100644
index 000000000000..210ad8e91df7
--- /dev/null
+++ b/nixpkgs/nixos/tests/buildbot.nix
@@ -0,0 +1,120 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+
+let
+  # Test ensures buildbot master comes up correctly and workers can connect
+  mkBuildbotTest = python: makeTest {
+    name = "buildbot";
+
+    nodes = {
+      bbmaster = { pkgs, ... }: {
+        services.buildbot-master = {
+          enable = true;
+          package = python.pkgs.buildbot-full;
+
+          # NOTE: use fake repo due to no internet in hydra ci
+          factorySteps = [
+            "steps.Git(repourl='git://gitrepo/fakerepo.git', mode='incremental')"
+            "steps.ShellCommand(command=['bash', 'fakerepo.sh'])"
+          ];
+          changeSource = [
+            "changes.GitPoller('git://gitrepo/fakerepo.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
+          ];
+        };
+        networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
+        environment.systemPackages = with pkgs; [ git python.pkgs.buildbot-full ];
+      };
+
+      bbworker = { pkgs, ... }: {
+        services.buildbot-worker = {
+          enable = true;
+          masterUrl = "bbmaster:9989";
+        };
+        environment.systemPackages = with pkgs; [ git python.pkgs.buildbot-worker ];
+      };
+
+      gitrepo = { pkgs, ... }: {
+        services.openssh.enable = true;
+        networking.firewall.allowedTCPPorts = [ 22 9418 ];
+        environment.systemPackages = with pkgs; [ git ];
+      };
+    };
+
+    testScript = ''
+      #Start up and populate fake repo
+      $gitrepo->waitForUnit("multi-user.target");
+      print($gitrepo->execute(" \
+        git config --global user.name 'Nobody Fakeuser' && \
+        git config --global user.email 'nobody\@fakerepo.com' && \
+        rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo && \
+        mkdir -pv /srv/repos/fakerepo ~/.ssh && \
+        ssh-keyscan -H gitrepo > ~/.ssh/known_hosts && \
+        cat ~/.ssh/known_hosts && \
+        cd /srv/repos/fakerepo && \
+        git init && \
+        echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh && \
+        cat fakerepo.sh && \
+        touch .git/git-daemon-export-ok && \
+        git add fakerepo.sh .git/git-daemon-export-ok && \
+        git commit -m fakerepo && \
+        git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr & \
+      "));
+
+      # Test gitrepo
+      $bbmaster->waitForUnit("network-online.target");
+      #$bbmaster->execute("nc -z gitrepo 9418");
+      print($bbmaster->execute(" \
+        rm -rfv /tmp/fakerepo && \
+        git clone git://gitrepo/fakerepo /tmp/fakerepo && \
+        pwd && \
+        ls -la && \
+        ls -la /tmp/fakerepo \
+      "));
+
+      # Test start master and connect worker
+      $bbmaster->waitForUnit("buildbot-master.service");
+      $bbmaster->waitUntilSucceeds("curl -s --head http://bbmaster:8010") =~ /200 OK/;
+      $bbworker->waitForUnit("network-online.target");
+      $bbworker->execute("nc -z bbmaster 8010");
+      $bbworker->execute("nc -z bbmaster 9989");
+      $bbworker->waitForUnit("buildbot-worker.service");
+      print($bbworker->execute("ls -la /home/bbworker/worker"));
+
+
+      # Test stop buildbot master and worker
+      print($bbmaster->execute(" \
+        systemctl -l --no-pager status buildbot-master && \
+        systemctl stop buildbot-master \
+      "));
+      $bbworker->fail("nc -z bbmaster 8010");
+      $bbworker->fail("nc -z bbmaster 9989");
+      print($bbworker->execute(" \
+        systemctl -l --no-pager status buildbot-worker && \
+        systemctl stop buildbot-worker && \
+        ls -la /home/bbworker/worker \
+      "));
+
+
+      # Test buildbot daemon mode
+      $bbmaster->execute("buildbot create-master /tmp");
+      $bbmaster->execute("mv -fv /tmp/master.cfg.sample /tmp/master.cfg");
+      $bbmaster->execute("sed -i 's/8010/8011/' /tmp/master.cfg");
+      $bbmaster->execute("buildbot start /tmp");
+      $bbworker->execute("nc -z bbmaster 8011");
+      $bbworker->waitUntilSucceeds("curl -s --head http://bbmaster:8011") =~ /200 OK/;
+      $bbmaster->execute("buildbot stop /tmp");
+      $bbworker->fail("nc -z bbmaster 8011");
+
+    '';
+
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ nand0p ];
+
+  };
+in {
+  python2 = mkBuildbotTest pkgs.python2;
+  python3 = mkBuildbotTest pkgs.python3;
+}
diff --git a/nixpkgs/nixos/tests/cadvisor.nix b/nixpkgs/nixos/tests/cadvisor.nix
new file mode 100644
index 000000000000..e60bae4b7003
--- /dev/null
+++ b/nixpkgs/nixos/tests/cadvisor.nix
@@ -0,0 +1,35 @@
+import ./make-test.nix ({ pkgs, ... } : {
+  name = "cadvisor";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.cadvisor.enable = true;
+    };
+
+    influxdb = { lib, ... }: with lib; {
+      services.cadvisor.enable = true;
+      services.cadvisor.storageDriver = "influxdb";
+      services.influxdb.enable = true;
+    };
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("cadvisor.service");
+      $machine->succeed("curl http://localhost:8080/containers/");
+
+      $influxdb->waitForUnit("influxdb.service");
+
+      # create influxdb database
+      $influxdb->succeed(q~
+        curl -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE root"
+      ~);
+
+      $influxdb->waitForUnit("cadvisor.service");
+      $influxdb->succeed("curl http://localhost:8080/containers/");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/cassandra.nix b/nixpkgs/nixos/tests/cassandra.nix
new file mode 100644
index 000000000000..60d0c6d76068
--- /dev/null
+++ b/nixpkgs/nixos/tests/cassandra.nix
@@ -0,0 +1,71 @@
+import ./make-test.nix ({ pkgs, ...}:
+let
+  # Change this to test a different version of Cassandra:
+  testPackage = pkgs.cassandra;
+  cassandraCfg = 
+    { enable = true;
+      listenAddress = null;
+      listenInterface = "eth1";
+      rpcAddress = null;
+      rpcInterface = "eth1";
+      extraConfig =
+        { start_native_transport = true;
+          seed_provider =
+            [{ class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
+               parameters = [ { seeds = "cass0"; } ];
+            }];
+        };
+      package = testPackage;
+    };
+  nodeCfg = extra: {pkgs, config, ...}:
+    { environment.systemPackages = [ testPackage ];
+      networking.firewall.enable = false;
+      services.cassandra = cassandraCfg // extra;
+      virtualisation.memorySize = 1024;
+    };
+in
+{
+  name = "cassandra-ci";
+
+  nodes = {
+    cass0 = nodeCfg {};
+    cass1 = nodeCfg {};
+    cass2 = nodeCfg { jvmOpts = [ "-Dcassandra.replace_address=cass1" ]; };
+  };
+
+  testScript = ''
+    subtest "timers exist", sub {
+      $cass0->succeed("systemctl list-timers | grep cassandra-full-repair.timer");
+      $cass0->succeed("systemctl list-timers | grep cassandra-incremental-repair.timer");
+    };
+    subtest "can connect via cqlsh", sub {
+      $cass0->waitForUnit("cassandra.service");
+      $cass0->waitUntilSucceeds("nc -z cass0 9042");
+      $cass0->succeed("echo 'show version;' | cqlsh cass0");
+    };
+    subtest "nodetool is operational", sub {
+      $cass0->waitForUnit("cassandra.service");
+      $cass0->waitUntilSucceeds("nc -z localhost 7199");
+      $cass0->succeed("nodetool status --resolve-ip | egrep '^UN[[:space:]]+cass0'");
+    };
+    subtest "bring up cluster", sub {
+      $cass1->waitForUnit("cassandra.service");
+      $cass1->waitUntilSucceeds("nodetool status | egrep -c '^UN' | grep 2");
+      $cass0->succeed("nodetool status --resolve-ip | egrep '^UN[[:space:]]+cass1'");
+    };
+    subtest "break and fix node", sub {
+      $cass1->block;
+      $cass0->waitUntilSucceeds("nodetool status --resolve-ip | egrep -c '^DN[[:space:]]+cass1'");
+      $cass0->succeed("nodetool status | egrep -c '^UN'  | grep 1");
+      $cass1->unblock;
+      $cass1->waitUntilSucceeds("nodetool status | egrep -c '^UN'  | grep 2");
+      $cass0->succeed("nodetool status | egrep -c '^UN'  | grep 2");
+    };
+    subtest "replace crashed node", sub {
+      $cass1->crash;
+      $cass2->waitForUnit("cassandra.service");
+      $cass0->waitUntilFails("nodetool status --resolve-ip | egrep '^UN[[:space:]]+cass1'");
+      $cass0->waitUntilSucceeds("nodetool status --resolve-ip | egrep '^UN[[:space:]]+cass2'");
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ceph.nix b/nixpkgs/nixos/tests/ceph.nix
new file mode 100644
index 000000000000..7408029c460e
--- /dev/null
+++ b/nixpkgs/nixos/tests/ceph.nix
@@ -0,0 +1,139 @@
+import ./make-test.nix ({pkgs, ...}: rec {
+  name = "All-in-one-basic-ceph-cluster";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lejonet ];
+  };
+
+  nodes = {
+    aio = { pkgs, ... }: {
+      virtualisation = {
+        emptyDiskImages = [ 20480 20480 ];
+        vlans = [ 1 ];
+      };
+
+      networking = {
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+
+      environment.systemPackages = with pkgs; [
+        bash
+        sudo
+        ceph
+        xfsprogs
+      ];
+      nixpkgs.config.packageOverrides = super: {
+        ceph = super.ceph.override({ nss = super.nss; libxfs = super.libxfs; libaio = super.libaio; jemalloc = super.jemalloc; });
+      };
+
+      boot.kernelModules = [ "xfs" ];
+
+      services.ceph.enable = true;
+      services.ceph.global = {
+        fsid = "066ae264-2a5d-4729-8001-6ad265f50b03";
+        monInitialMembers = "aio";
+        monHost = "192.168.1.1";
+      };
+
+      services.ceph.mon = {
+        enable = true;
+        daemons = [ "aio" ];
+      };
+
+      services.ceph.mgr = {
+        enable = true;
+        daemons = [ "aio" ];
+      };
+
+      services.ceph.osd = {
+        enable = true;
+        daemons = [ "0" "1" ];
+      };
+    };
+  };
+
+  testScript = { ... }: ''
+    startAll;
+
+    $aio->waitForUnit("network.target");
+
+    # Create the ceph-related directories
+    $aio->mustSucceed(
+      "mkdir -p /var/lib/ceph/mgr/ceph-aio/",
+      "mkdir -p /var/lib/ceph/mon/ceph-aio/",
+      "mkdir -p /var/lib/ceph/osd/ceph-{0..1}/",
+      "chown ceph:ceph -R /var/lib/ceph/"
+    );
+
+    # Bootstrap ceph-mon daemon
+    $aio->mustSucceed(
+      "mkdir -p /var/lib/ceph/bootstrap-osd && chown ceph:ceph /var/lib/ceph/bootstrap-osd",
+      "sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
+      "ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --set-uid=0 --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
+      "ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
+            "monmaptool --create --add aio 192.168.1.1 --fsid 066ae264-2a5d-4729-8001-6ad265f50b03 /tmp/monmap",
+      "sudo -u ceph ceph-mon --mkfs -i aio --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
+      "touch /var/lib/ceph/mon/ceph-aio/done",
+      "systemctl start ceph-mon-aio"
+    );
+    $aio->waitForUnit("ceph-mon-aio");
+
+    # Can't check ceph status until a mon is up
+    $aio->succeed("ceph -s | grep 'mon: 1 daemons'");
+
+    # Start the ceph-mgr daemon, it has no deps and hardly any setup
+    $aio->mustSucceed(
+      "ceph auth get-or-create mgr.aio mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-aio/keyring",
+      "systemctl start ceph-mgr-aio"
+    );
+    $aio->waitForUnit("ceph-mgr-aio");
+    $aio->waitUntilSucceeds("ceph -s | grep 'quorum aio'");
+
+    # Bootstrap both OSDs
+    $aio->mustSucceed(
+      "mkfs.xfs /dev/vdb",
+      "mkfs.xfs /dev/vdc",
+      "mount /dev/vdb /var/lib/ceph/osd/ceph-0",
+      "mount /dev/vdc /var/lib/ceph/osd/ceph-1",
+      "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-0/keyring --name osd.0 --add-key AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==",
+      "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-1/keyring --name osd.1 --add-key AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==",
+      "echo '{\"cephx_secret\": \"AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==\"}' | ceph osd new 55ba2294-3e24-478f-bee0-9dca4c231dd9 -i -",
+      "echo '{\"cephx_secret\": \"AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==\"}' | ceph osd new 5e97a838-85b6-43b0-8950-cb56d554d1e5 -i -"
+    );
+
+    # Initialize the OSDs with regular filestore
+    $aio->mustSucceed(
+      "ceph-osd -i 0 --mkfs --osd-uuid 55ba2294-3e24-478f-bee0-9dca4c231dd9",
+      "ceph-osd -i 1 --mkfs --osd-uuid 5e97a838-85b6-43b0-8950-cb56d554d1e5",
+      "chown -R ceph:ceph /var/lib/ceph/osd",
+      "systemctl start ceph-osd-0",
+      "systemctl start ceph-osd-1"
+    );
+
+    $aio->waitUntilSucceeds("ceph osd stat | grep '2 osds: 2 up, 2 in'");
+    $aio->waitUntilSucceeds("ceph -s | grep 'mgr: aio(active)'");
+    $aio->waitUntilSucceeds("ceph -s | grep 'HEALTH_OK'");
+
+    $aio->mustSucceed(
+      "ceph osd pool create aio-test 100 100",
+      "ceph osd pool ls | grep 'aio-test'",
+      "ceph osd pool rename aio-test aio-other-test",
+      "ceph osd pool ls | grep 'aio-other-test'",
+      "ceph -s | grep '1 pools, 100 pgs'",
+      "ceph osd getcrushmap -o crush",
+      "crushtool -d crush -o decrushed",
+      "sed 's/step chooseleaf firstn 0 type host/step chooseleaf firstn 0 type osd/' decrushed > modcrush",
+      "crushtool -c modcrush -o recrushed",
+      "ceph osd setcrushmap -i recrushed",
+      "ceph osd pool set aio-other-test size 2"
+    );
+    $aio->waitUntilSucceeds("ceph -s | grep 'HEALTH_OK'");
+    $aio->waitUntilSucceeds("ceph -s | grep '100 active+clean'");
+    $aio->mustFail(
+      "ceph osd pool ls | grep 'aio-test'",
+      "ceph osd pool delete aio-other-test aio-other-test --yes-i-really-really-mean-it"
+    );
+  '';
+})
diff --git a/nixpkgs/nixos/tests/certmgr.nix b/nixpkgs/nixos/tests/certmgr.nix
new file mode 100644
index 000000000000..fe67833808ce
--- /dev/null
+++ b/nixpkgs/nixos/tests/certmgr.nix
@@ -0,0 +1,151 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+let
+  mkSpec = { host, service ? null, action }: {
+    inherit action;
+    authority = {
+      file = {
+        group = "nobody";
+        owner = "nobody";
+        path = "/tmp/${host}-ca.pem";
+      };
+      label = "www_ca";
+      profile = "three-month";
+      remote = "localhost:8888";
+    };
+    certificate = {
+      group = "nobody";
+      owner = "nobody";
+      path = "/tmp/${host}-cert.pem";
+    };
+    private_key = {
+      group = "nobody";
+      mode = "0600";
+      owner = "nobody";
+      path = "/tmp/${host}-key.pem";
+    };
+    request = {
+      CN = host;
+      hosts = [ host "www.${host}" ];
+      key = {
+        algo = "rsa";
+        size = 2048;
+      };
+      names = [
+        {
+          C = "US";
+          L = "San Francisco";
+          O = "Example, LLC";
+          ST = "CA";
+        }
+      ];
+    };
+    inherit service;
+  };
+
+  mkCertmgrTest = { svcManager, specs, testScript }: makeTest {
+    name = "certmgr-" + svcManager;
+    nodes = {
+      machine = { config, lib, pkgs, ... }: {
+        networking.firewall.allowedTCPPorts = with config.services; [ cfssl.port certmgr.metricsPort ];
+        networking.extraHosts = "127.0.0.1 imp.example.org decl.example.org";
+
+        services.cfssl.enable = true;
+        systemd.services.cfssl.after = [ "cfssl-init.service" "networking.target" ];
+
+        systemd.services.cfssl-init = {
+          description = "Initialize the cfssl CA";
+          wantedBy    = [ "multi-user.target" ];
+          serviceConfig = {
+            User             = "cfssl";
+            Type             = "oneshot";
+            WorkingDirectory = config.services.cfssl.dataDir;
+          };
+          script = ''
+            ${pkgs.cfssl}/bin/cfssl genkey -initca ${pkgs.writeText "ca.json" (builtins.toJSON {
+              hosts = [ "ca.example.com" ];
+              key = {
+                algo = "rsa"; size = 4096; };
+                names = [
+                  {
+                    C = "US";
+                    L = "San Francisco";
+                    O = "Internet Widgets, LLC";
+                    OU = "Certificate Authority";
+                    ST = "California";
+                  }
+                ];
+            })} | ${pkgs.cfssl}/bin/cfssljson -bare ca
+          '';
+        };
+
+        services.nginx = {
+          enable = true;
+          virtualHosts = lib.mkMerge (map (host: {
+            ${host} = {
+              sslCertificate = "/tmp/${host}-cert.pem";
+              sslCertificateKey = "/tmp/${host}-key.pem";
+              extraConfig = ''
+                ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+              '';
+              onlySSL = true;
+              serverName = host;
+              root = pkgs.writeTextDir "index.html" "It works!";
+            };
+          }) [ "imp.example.org" "decl.example.org" ]);
+        };
+
+        systemd.services.nginx.wantedBy = lib.mkForce [];
+
+        systemd.services.certmgr.after = [ "cfssl.service" ];
+        services.certmgr = {
+          enable = true;
+          inherit svcManager;
+          inherit specs;
+        };
+
+      };
+    };
+    inherit testScript;
+  };
+in
+{
+  systemd = mkCertmgrTest {
+    svcManager = "systemd";
+    specs = {
+      decl = mkSpec { host = "decl.example.org"; service = "nginx"; action ="restart"; };
+      imp = toString (pkgs.writeText "test.json" (builtins.toJSON (
+        mkSpec { host = "imp.example.org"; service = "nginx"; action = "restart"; }
+      )));
+    };
+    testScript = ''
+      $machine->waitForUnit('cfssl.service');
+      $machine->waitUntilSucceeds('ls /tmp/decl.example.org-ca.pem');
+      $machine->waitUntilSucceeds('ls /tmp/decl.example.org-key.pem');
+      $machine->waitUntilSucceeds('ls /tmp/decl.example.org-cert.pem');
+      $machine->waitUntilSucceeds('ls /tmp/imp.example.org-ca.pem');
+      $machine->waitUntilSucceeds('ls /tmp/imp.example.org-key.pem');
+      $machine->waitUntilSucceeds('ls /tmp/imp.example.org-cert.pem');
+      $machine->waitForUnit('nginx.service');
+      $machine->succeed('[ "1" -lt "$(journalctl -u nginx | grep "Starting Nginx" | wc -l)" ]');
+      $machine->succeed('curl --cacert /tmp/imp.example.org-ca.pem https://imp.example.org');
+      $machine->succeed('curl --cacert /tmp/decl.example.org-ca.pem https://decl.example.org');
+    '';
+  };
+
+  command = mkCertmgrTest {
+    svcManager = "command";
+    specs = {
+      test = mkSpec { host = "command.example.org"; action = "touch /tmp/command.executed"; };
+    };
+    testScript = ''
+      $machine->waitForUnit('cfssl.service');
+      $machine->waitUntilSucceeds('stat /tmp/command.executed');
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/tests/cfssl.nix b/nixpkgs/nixos/tests/cfssl.nix
new file mode 100644
index 000000000000..513ed8c45741
--- /dev/null
+++ b/nixpkgs/nixos/tests/cfssl.nix
@@ -0,0 +1,67 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "cfssl";
+
+  machine = { config, lib, pkgs, ... }:
+  {
+    networking.firewall.allowedTCPPorts = [ config.services.cfssl.port ];
+
+    services.cfssl.enable = true;
+    systemd.services.cfssl.after = [ "cfssl-init.service" ];
+
+    systemd.services.cfssl-init = {
+      description = "Initialize the cfssl CA";
+      wantedBy    = [ "multi-user.target" ];
+      serviceConfig = {
+        User             = "cfssl";
+        Type             = "oneshot";
+        WorkingDirectory = config.services.cfssl.dataDir;
+      };
+      script = with pkgs; ''
+        ${cfssl}/bin/cfssl genkey -initca ${pkgs.writeText "ca.json" (builtins.toJSON {
+          hosts = [ "ca.example.com" ];
+          key = {
+            algo = "rsa"; size = 4096; };
+            names = [
+              {
+                C = "US";
+                L = "San Francisco";
+                O = "Internet Widgets, LLC";
+                OU = "Certificate Authority";
+                ST = "California";
+              }
+            ];
+        })} | ${cfssl}/bin/cfssljson -bare ca
+      '';
+    };
+  };
+
+  testScript =
+  let
+    cfsslrequest = with pkgs; writeScript "cfsslrequest" ''
+      curl -X POST -H "Content-Type: application/json" -d @${csr} \
+        http://localhost:8888/api/v1/cfssl/newkey | ${cfssl}/bin/cfssljson /tmp/certificate
+    '';
+    csr = pkgs.writeText "csr.json" (builtins.toJSON {
+      CN = "www.example.com";
+      hosts = [ "example.com" "www.example.com" ];
+      key = {
+        algo = "rsa";
+        size = 2048;
+      };
+      names = [
+        {
+          C = "US";
+          L = "San Francisco";
+          O = "Example Company, LLC";
+          OU = "Operations";
+          ST = "California";
+        }
+      ];
+    });
+  in
+    ''
+      $machine->waitForUnit('cfssl.service');
+      $machine->waitUntilSucceeds('${cfsslrequest}');
+      $machine->succeed('ls /tmp/certificate-key.pem');
+    '';
+})
diff --git a/nixpkgs/nixos/tests/chromium.nix b/nixpkgs/nixos/tests/chromium.nix
new file mode 100644
index 000000000000..af5db2a3dbe1
--- /dev/null
+++ b/nixpkgs/nixos/tests/chromium.nix
@@ -0,0 +1,201 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, channelMap ? {
+    stable = pkgs.chromium;
+    beta   = pkgs.chromiumBeta;
+    dev    = pkgs.chromiumDev;
+  }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+mapAttrs (channel: chromiumPkg: makeTest rec {
+  name = "chromium-${channel}";
+  meta = {
+    maintainers = with maintainers; [ aszlig ];
+    # https://github.com/NixOS/hydra/issues/591#issuecomment-435125621
+    inherit (chromiumPkg.meta) timeout;
+  };
+
+  enableOCR = true;
+
+  machine.imports = [ ./common/user-account.nix ./common/x11.nix ];
+  machine.virtualisation.memorySize = 2047;
+  machine.services.xserver.displayManager.auto.user = "alice";
+  machine.environment.systemPackages = [ chromiumPkg ];
+
+  startupHTML = pkgs.writeText "chromium-startup.html" ''
+    <!DOCTYPE html>
+    <html>
+    <head>
+    <meta charset="UTF-8">
+    <title>Chromium startup notifier</title>
+    </head>
+    <body onload="javascript:document.title='startup done'">
+      <img src="file://${pkgs.fetchurl {
+        url = "http://nixos.org/logo/nixos-hex.svg";
+        sha256 = "0wxpp65npdw2cg8m0cxc9qff1sb3b478cxpg1741d8951g948rg8";
+      }}" />
+    </body>
+    </html>
+  '';
+
+  testScript = let
+    xdo = name: text: let
+      xdoScript = pkgs.writeText "${name}.xdo" text;
+    in "${pkgs.xdotool}/bin/xdotool '${xdoScript}'";
+  in ''
+    # Run as user alice
+    sub ru ($) {
+      my $esc = $_[0] =~ s/'/'\\${"'"}'/gr;
+      return "su - alice -c '$esc'";
+    }
+
+    sub createNewWin {
+      $machine->nest("creating a new Chromium window", sub {
+        $machine->execute(ru "${xdo "new-window" ''
+          search --onlyvisible --name "startup done"
+          windowfocus --sync
+          windowactivate --sync
+        ''}");
+        $machine->execute(ru "${xdo "new-window" ''
+          key Ctrl+n
+        ''}");
+      });
+    }
+
+    sub closeWin {
+      Machine::retry sub {
+        $machine->execute(ru "${xdo "close-window" ''
+          search --onlyvisible --name "new tab"
+          windowfocus --sync
+          windowactivate --sync
+        ''}");
+        $machine->execute(ru "${xdo "close-window" ''
+          key Ctrl+w
+        ''}");
+        for (1..20) {
+          my ($status, $out) = $machine->execute(ru "${xdo "wait-for-close" ''
+            search --onlyvisible --name "new tab"
+          ''}");
+          return 1 if $status != 0;
+          $machine->sleep(1);
+        }
+      }
+    }
+
+    sub waitForNewWin {
+      my $ret = 0;
+      $machine->nest("waiting for new Chromium window to appear", sub {
+        for (1..20) {
+          my ($status, $out) = $machine->execute(ru "${xdo "wait-for-window" ''
+            search --onlyvisible --name "new tab"
+            windowfocus --sync
+            windowactivate --sync
+          ''}");
+          if ($status == 0) {
+            $ret = 1;
+
+            # XXX: Somehow Chromium is not accepting keystrokes for a few
+            # seconds after a new window has appeared, so let's wait a while.
+            $machine->sleep(10);
+
+            last;
+          }
+          $machine->sleep(1);
+        }
+      });
+      return $ret;
+    }
+
+    sub createAndWaitForNewWin {
+      for (1..3) {
+        createNewWin;
+        return 1 if waitForNewWin;
+      }
+      die "new window didn't appear within 60 seconds";
+    }
+
+    sub testNewWin {
+      my ($desc, $code) = @_;
+      createAndWaitForNewWin;
+      subtest($desc, $code);
+      closeWin;
+    }
+
+    $machine->waitForX;
+
+    my $url = "file://${startupHTML}";
+    $machine->execute(ru "ulimit -c unlimited; chromium \"$url\" & disown");
+    $machine->waitForText(qr/startup done/);
+    $machine->waitUntilSucceeds(ru "${xdo "check-startup" ''
+      search --sync --onlyvisible --name "startup done"
+      # close first start help popup
+      key -delay 1000 Escape
+      windowfocus --sync
+      windowactivate --sync
+    ''}");
+
+    createAndWaitForNewWin;
+    $machine->screenshot("empty_windows");
+    closeWin;
+
+    $machine->screenshot("startup_done");
+
+    testNewWin "check sandbox", sub {
+      $machine->succeed(ru "${xdo "type-url" ''
+        search --sync --onlyvisible --name "new tab"
+        windowfocus --sync
+        type --delay 1000 "chrome://sandbox"
+      ''}");
+
+      $machine->succeed(ru "${xdo "submit-url" ''
+        search --sync --onlyvisible --name "new tab"
+        windowfocus --sync
+        key --delay 1000 Return
+      ''}");
+
+      $machine->screenshot("sandbox_info");
+
+      $machine->succeed(ru "${xdo "find-window" ''
+        search --sync --onlyvisible --name "sandbox status"
+        windowfocus --sync
+      ''}");
+      $machine->succeed(ru "${xdo "copy-sandbox-info" ''
+        key --delay 1000 Ctrl+a Ctrl+c
+      ''}");
+
+      my $clipboard = $machine->succeed(ru "${pkgs.xclip}/bin/xclip -o");
+      die "sandbox not working properly: $clipboard"
+      unless $clipboard =~ /layer 1 sandbox.*namespace/mi
+          && $clipboard =~ /pid namespaces.*yes/mi
+          && $clipboard =~ /network namespaces.*yes/mi
+          && $clipboard =~ /seccomp.*sandbox.*yes/mi
+          && $clipboard =~ /you are adequately sandboxed/mi;
+
+      $machine->sleep(1);
+      $machine->succeed(ru "${xdo "find-window-after-copy" ''
+        search --onlyvisible --name "sandbox status"
+      ''}");
+
+      my $clipboard = $machine->succeed(ru "echo void | ${pkgs.xclip}/bin/xclip -i");
+      $machine->succeed(ru "${xdo "copy-sandbox-info" ''
+        key --delay 1000 Ctrl+a Ctrl+c
+      ''}");
+
+      my $clipboard = $machine->succeed(ru "${pkgs.xclip}/bin/xclip -o");
+      die "copying twice in a row does not work properly: $clipboard"
+      unless $clipboard =~ /layer 1 sandbox.*namespace/mi
+          && $clipboard =~ /pid namespaces.*yes/mi
+          && $clipboard =~ /network namespaces.*yes/mi
+          && $clipboard =~ /seccomp.*sandbox.*yes/mi
+          && $clipboard =~ /you are adequately sandboxed/mi;
+
+      $machine->screenshot("afer_copy_from_chromium");
+    };
+
+    $machine->shutdown;
+  '';
+}) channelMap
diff --git a/nixpkgs/nixos/tests/cjdns.nix b/nixpkgs/nixos/tests/cjdns.nix
new file mode 100644
index 000000000000..e03bb9882540
--- /dev/null
+++ b/nixpkgs/nixos/tests/cjdns.nix
@@ -0,0 +1,119 @@
+let
+  carolKey = "2d2a338b46f8e4a8c462f0c385b481292a05f678e19a2b82755258cf0f0af7e2";
+  carolPubKey = "n932l3pjvmhtxxcdrqq2qpw5zc58f01vvjx01h4dtd1bb0nnu2h0.k";
+  carolPassword = "678287829ce4c67bc8b227e56d94422ee1b85fa11618157b2f591de6c6322b52";
+
+  basicConfig =
+    { ... }:
+    { services.cjdns.enable = true;
+
+      # Turning off DHCP isn't very realistic but makes
+      # the sequence of address assignment less stochastic.
+      networking.useDHCP = false;
+
+      # CJDNS output is incompatible with the XML log.
+      systemd.services.cjdns.serviceConfig.StandardOutput = "null";
+    };
+
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "cjdns";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ehmry ];
+  };
+
+  nodes = rec
+    { # Alice finds peers over over ETHInterface.
+      alice =
+        { ... }:
+        { imports = [ basicConfig ];
+
+          services.cjdns.ETHInterface.bind = "eth1";
+
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          networking.firewall.allowedTCPPorts = [ 80 ];
+        };
+
+      # Bob explicitly connects to Carol over UDPInterface.
+      bob =
+        { ... }:
+
+        { imports = [ basicConfig ];
+
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = "192.168.0.2"; prefixLength = 24; }
+          ];
+
+          services.cjdns =
+            { UDPInterface =
+                { bind = "0.0.0.0:1024";
+                  connectTo."192.168.0.1:1024" =
+                    { password = carolPassword;
+                      publicKey = carolPubKey;
+                    };
+                };
+            };
+        };
+
+      # Carol listens on ETHInterface and UDPInterface,
+      # but knows neither Alice or Bob.
+      carol =
+        { ... }:
+        { imports = [ basicConfig ];
+
+          environment.etc."cjdns.keys".text = ''
+            CJDNS_PRIVATE_KEY=${carolKey}
+            CJDNS_ADMIN_PASSWORD=FOOBAR
+          '';
+
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = "192.168.0.1"; prefixLength = 24; }
+          ];
+
+          services.cjdns =
+            { authorizedPasswords = [ carolPassword ];
+              ETHInterface.bind = "eth1";
+              UDPInterface.bind = "192.168.0.1:1024";
+            };
+          networking.firewall.allowedUDPPorts = [ 1024 ];
+        };
+
+    };
+
+  testScript =
+    ''
+      startAll;
+
+      $alice->waitForUnit("cjdns.service");
+      $bob->waitForUnit("cjdns.service");
+      $carol->waitForUnit("cjdns.service");
+
+      sub cjdnsIp {
+          my ($machine) = @_;
+          my $ip = (split /[ \/]+/, $machine->succeed("ip -o -6 addr show dev tun0"))[3];
+          $machine->log("has ip $ip");
+          return $ip;
+      }
+
+      my $aliceIp6 = cjdnsIp $alice;
+      my $bobIp6   = cjdnsIp $bob;
+      my $carolIp6 = cjdnsIp $carol;
+
+      # ping a few times each to let the routing table establish itself
+
+      $alice->succeed("ping -c 4 $carolIp6");
+      $bob->succeed("ping -c 4 $carolIp6");
+
+      $carol->succeed("ping -c 4 $aliceIp6");
+      $carol->succeed("ping -c 4 $bobIp6");
+
+      $alice->succeed("ping -c 4 $bobIp6");
+      $bob->succeed("ping -c 4 $aliceIp6");
+
+      $alice->waitForUnit("httpd.service");
+
+      $bob->succeed("curl --fail -g http://[$aliceIp6]");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/clickhouse.nix b/nixpkgs/nixos/tests/clickhouse.nix
new file mode 100644
index 000000000000..7d835069ec4d
--- /dev/null
+++ b/nixpkgs/nixos/tests/clickhouse.nix
@@ -0,0 +1,25 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "clickhouse";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ];
+
+  machine = {
+    services.clickhouse.enable = true;
+  };
+
+  testScript =
+    let
+      # work around quote/substitution complexity by Nix, Perl, bash and SQL.
+      tableDDL = pkgs.writeText "ddl.sql" "CREATE TABLE `demo` (`value` FixedString(10)) engine = MergeTree PARTITION BY value ORDER BY tuple();";
+      insertQuery = pkgs.writeText "insert.sql" "INSERT INTO `demo` (`value`) VALUES ('foo');";
+      selectQuery = pkgs.writeText "select.sql" "SELECT * from `demo`";
+    in
+      ''
+        $machine->start();
+        $machine->waitForUnit("clickhouse.service");
+        $machine->waitForOpenPort(9000);
+
+        $machine->succeed("cat ${tableDDL} | clickhouse-client");
+        $machine->succeed("cat ${insertQuery} | clickhouse-client");
+        $machine->succeed("cat ${selectQuery} | clickhouse-client | grep foo");
+      '';
+})
diff --git a/nixpkgs/nixos/tests/cloud-init.nix b/nixpkgs/nixos/tests/cloud-init.nix
new file mode 100644
index 000000000000..516d29c9036b
--- /dev/null
+++ b/nixpkgs/nixos/tests/cloud-init.nix
@@ -0,0 +1,49 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  metadataDrive = pkgs.stdenv.mkDerivation {
+    name = "metadata";
+    buildCommand = ''
+      mkdir -p $out/iso
+
+      cat << EOF > $out/iso/user-data
+      #cloud-config
+      write_files:
+      -   content: |
+                cloudinit
+          path: /tmp/cloudinit-write-file
+      EOF
+
+      cat << EOF > $out/iso/meta-data
+      instance-id: iid-local01
+      local-hostname: "test"
+      public-keys:
+          - "should be a key!"
+      EOF
+      ${pkgs.cdrkit}/bin/genisoimage -volid cidata -joliet -rock -o $out/metadata.iso $out/iso
+      '';
+  };
+in makeTest {
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lewo ];
+  };
+  machine =
+    { ... }:
+    {
+      virtualisation.qemu.options = [ "-cdrom" "${metadataDrive}/metadata.iso" ];
+      services.cloud-init.enable = true;
+    };
+  testScript = ''
+     $machine->start;
+     $machine->waitForUnit("cloud-init.service");
+     $machine->succeed("cat /tmp/cloudinit-write-file | grep -q 'cloudinit'");
+
+     $machine->waitUntilSucceeds("cat /root/.ssh/authorized_keys | grep -q 'should be a key!'");
+  '';
+}
diff --git a/nixpkgs/nixos/tests/cockroachdb.nix b/nixpkgs/nixos/tests/cockroachdb.nix
new file mode 100644
index 000000000000..56c624d8cf2f
--- /dev/null
+++ b/nixpkgs/nixos/tests/cockroachdb.nix
@@ -0,0 +1,126 @@
+# This performs a full 'end-to-end' test of a multi-node CockroachDB cluster
+# using the built-in 'cockroach workload' command, to simulate a semi-realistic
+# test load. It generally takes anywhere from 3-5 minutes to run and 1-2GB of
+# RAM (though each of 3 workers gets 1GB allocated)
+#
+# CockroachDB requires synchronized system clocks within a small error window
+# (~500ms by default) on each node in order to maintain a multi-node cluster.
+# Cluster joins that are outside this window will fail, and nodes that skew
+# outside the window after joining will promptly get kicked out.
+#
+# To accomodate this, we use QEMU/virtio infrastructure and load the 'ptp_kvm'
+# driver inside a guest. This driver allows the host machine to pass its clock
+# through to the guest as a hardware clock that appears as a Precision Time
+# Protocol (PTP) Clock device, generally /dev/ptp0. PTP devices can be measured
+# and used as hardware reference clocks (similar to an on-board GPS clock) by
+# NTP software. In our case, we use Chrony to synchronize to the reference
+# clock.
+#
+# This test is currently NOT enabled as a continuously-checked NixOS test.
+# Ideally, this test would be run by Hydra and Borg on all relevant changes,
+# except:
+#
+#   - Not every build machine is compatible with the ptp_kvm driver.
+#     Virtualized EC2 instances, for example, do not support loading the ptp_kvm
+#     driver into guests. However, bare metal builders (e.g. Packet) do seem to
+#     work just fine. In practice, this means x86_64-linux builds would fail
+#     randomly, depending on which build machine got the job. (This is probably
+#     worth some investigation; I imagine it's based on ptp_kvm's usage of paravirt
+#     support which may not be available in 'nested' environments.)
+#
+#   - ptp_kvm is not supported on aarch64, otherwise it seems likely Cockroach
+#     could be tested there, as well. This seems to be due to the usage of
+#     the TSC in ptp_kvm, which isn't supported (easily) on AArch64. (And:
+#     testing stuff, not just making sure it builds, is important to ensure
+#     aarch64 support remains viable.)
+#
+# For future developers who are reading this message, are daring and would want
+# to fix this, some options are:
+#
+#   - Just test a single node cluster instead (boring and less thorough).
+#   - Move all CI to bare metal packet builders, and we can at least do x86_64-linux.
+#   - Get virtualized clocking working in aarch64, somehow.
+#   - Add a 4th node that acts as an NTP service and uses no PTP clocks for
+#     references, at the client level. This bloats the node and memory
+#     requirements, but would probably allow both aarch64/x86_64 to work.
+#
+
+let
+
+  # Creates a node. If 'joinNode' parameter, a string containing an IP address,
+  # is non-null, then the CockroachDB server will attempt to join/connect to
+  # the cluster node specified at that address.
+  makeNode = locality: myAddr: joinNode:
+    { nodes, pkgs, lib, config, ... }:
+
+    {
+      # Bank/TPC-C benchmarks take some memory to complete
+      virtualisation.memorySize = 1024;
+
+      # Install the KVM PTP "Virtualized Clock" driver. This allows a /dev/ptp0
+      # device to appear as a reference clock, synchronized to the host clock.
+      # Because CockroachDB *requires* a time-synchronization mechanism for
+      # the system time in a cluster scenario, this is necessary to work.
+      boot.kernelModules = [ "ptp_kvm" ];
+
+      # Enable and configure Chrony, using the given virtualized clock passed
+      # through by KVM.
+      services.chrony.enable = true;
+      services.chrony.servers = lib.mkForce [ ];
+      services.chrony.extraConfig = ''
+        refclock PHC /dev/ptp0 poll 2 prefer require refid KVM
+        makestep 0.1 3
+      '';
+
+      # Enable CockroachDB. In order to ensure that Chrony has performed its
+      # first synchronization at boot-time (which may take ~10 seconds) before
+      # starting CockroachDB, we block the ExecStartPre directive using the
+      # 'waitsync' command. This ensures Cockroach doesn't have its system time
+      # leap forward out of nowhere during startup/execution.
+      #
+      # Note that the default threshold for NTP-based skew in CockroachDB is
+      # ~500ms by default, so making sure it's started *after* accurate time
+      # synchronization is extremely important.
+      services.cockroachdb.enable = true;
+      services.cockroachdb.insecure = true;
+      services.cockroachdb.openPorts = true;
+      services.cockroachdb.locality = locality;
+      services.cockroachdb.listen.address = myAddr;
+      services.cockroachdb.join = lib.mkIf (joinNode != null) joinNode;
+
+      # Hold startup until Chrony has performed its first measurement (which
+      # will probably result in a full timeskip, thanks to makestep)
+      systemd.services.cockroachdb.preStart = ''
+        ${pkgs.chrony}/bin/chronyc waitsync
+      '';
+    };
+
+in import ./make-test.nix ({ pkgs, ...} : {
+  name = "cockroachdb";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers;
+    [ thoughtpolice ];
+
+  nodes = rec {
+    node1 = makeNode "country=us,region=east,dc=1"  "192.168.1.1" null;
+    node2 = makeNode "country=us,region=west,dc=2b" "192.168.1.2" "192.168.1.1";
+    node3 = makeNode "country=eu,region=west,dc=2"  "192.168.1.3" "192.168.1.1";
+  };
+
+  # NOTE: All the nodes must start in order and you must NOT use startAll, because
+  # there's otherwise no way to guarantee that node1 will start before the others try
+  # to join it.
+  testScript = ''
+    $node1->start;
+    $node1->waitForUnit("cockroachdb");
+
+    $node2->start;
+    $node2->waitForUnit("cockroachdb");
+
+    $node3->start;
+    $node3->waitForUnit("cockroachdb");
+
+    $node1->mustSucceed("cockroach sql --host=192.168.1.1 --insecure -e 'SHOW ALL CLUSTER SETTINGS' 2>&1");
+    $node1->mustSucceed("cockroach workload init bank 'postgresql://root\@192.168.1.1:26257?sslmode=disable'");
+    $node1->mustSucceed("cockroach workload run bank --duration=1m 'postgresql://root\@192.168.1.1:26257?sslmode=disable'");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/codimd.nix b/nixpkgs/nixos/tests/codimd.nix
new file mode 100644
index 000000000000..562f6f24f999
--- /dev/null
+++ b/nixpkgs/nixos/tests/codimd.nix
@@ -0,0 +1,54 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+{
+  name = "codimd";
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  nodes = {
+    codimdSqlite = { ... }: {
+      services = {
+        codimd = {
+          enable = true;
+          configuration.dbURL = "sqlite:///var/lib/codimd/codimd.db";
+        };
+      };
+    };
+
+    codimdPostgres = { ... }: {
+      systemd.services.codimd.after = [ "postgresql.service" ];
+      services = {
+        codimd = {
+          enable = true;
+          configuration.dbURL = "postgres://codimd:snakeoilpassword@localhost:5432/codimddb";
+        };
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script.sql" ''
+            CREATE ROLE codimd LOGIN PASSWORD 'snakeoilpassword';
+            CREATE DATABASE codimddb OWNER codimd;
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll();
+
+    subtest "CodiMD sqlite", sub {
+      $codimdSqlite->waitForUnit("codimd.service");
+      $codimdSqlite->waitForOpenPort(3000);
+      $codimdSqlite->waitUntilSucceeds("curl -sSf http://localhost:3000/new");
+    };
+
+    subtest "CodiMD postgres", sub {
+      $codimdPostgres->waitForUnit("postgresql.service");
+      $codimdPostgres->waitForUnit("codimd.service");
+      $codimdPostgres->waitForOpenPort(5432);
+      $codimdPostgres->waitForOpenPort(3000);
+      $codimdPostgres->waitUntilSucceeds("curl -sSf http://localhost:3000/new");
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/common/letsencrypt/common.nix b/nixpkgs/nixos/tests/common/letsencrypt/common.nix
new file mode 100644
index 000000000000..798a749f7f9b
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/letsencrypt/common.nix
@@ -0,0 +1,27 @@
+{ lib, nodes, ... }: {
+  networking.nameservers = [
+    nodes.letsencrypt.config.networking.primaryIPAddress
+  ];
+
+  nixpkgs.overlays = lib.singleton (self: super: {
+    cacert = super.cacert.overrideDerivation (drv: {
+      installPhase = (drv.installPhase or "") + ''
+        cat "${nodes.letsencrypt.config.test-support.letsencrypt.caCert}" \
+          >> "$out/etc/ssl/certs/ca-bundle.crt"
+      '';
+    });
+
+    # Override certifi so that it accepts fake certificate for Let's Encrypt
+    # Need to override the attribute used by simp_le, which is python3Packages
+    python3Packages = (super.python3.override {
+      packageOverrides = lib.const (pysuper: {
+        certifi = pysuper.certifi.overridePythonAttrs (attrs: {
+          postPatch = (attrs.postPatch or "") + ''
+            cat "${self.cacert}/etc/ssl/certs/ca-bundle.crt" \
+              > certifi/cacert.pem
+          '';
+        });
+      });
+    }).pkgs;
+  });
+}
diff --git a/nixpkgs/nixos/tests/common/letsencrypt/default.nix b/nixpkgs/nixos/tests/common/letsencrypt/default.nix
new file mode 100644
index 000000000000..73aac51a0126
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/letsencrypt/default.nix
@@ -0,0 +1,445 @@
+# Fully pluggable module to have Letsencrypt's Boulder ACME service running in
+# a test environment.
+#
+# The certificate for the ACME service is exported as:
+#
+#   config.test-support.letsencrypt.caCert
+#
+# This value can be used inside the configuration of other test nodes to inject
+# the snakeoil certificate into security.pki.certificateFiles or into package
+# overlays.
+#
+# Another value that's needed if you don't use a custom resolver (see below for
+# notes on that) is to add the letsencrypt node as a nameserver to every node
+# that needs to acquire certificates using ACME, because otherwise the API host
+# for letsencrypt.org can't be resolved.
+#
+# A configuration example of a full node setup using this would be this:
+#
+# {
+#   letsencrypt = import ./common/letsencrypt;
+#
+#   example = { nodes, ... }: {
+#     networking.nameservers = [
+#       nodes.letsencrypt.config.networking.primaryIPAddress
+#     ];
+#     security.pki.certificateFiles = [
+#       nodes.letsencrypt.config.test-support.letsencrypt.caCert
+#     ];
+#   };
+# }
+#
+# By default, this module runs a local resolver, generated using resolver.nix
+# from the parent directory to automatically discover all zones in the network.
+#
+# If you do not want this and want to use your own resolver, you can just
+# override networking.nameservers like this:
+#
+# {
+#   letsencrypt = { nodes, ... }: {
+#     imports = [ ./common/letsencrypt ];
+#     networking.nameservers = [
+#       nodes.myresolver.config.networking.primaryIPAddress
+#     ];
+#   };
+#
+#   myresolver = ...;
+# }
+#
+# Keep in mind, that currently only _one_ resolver is supported, if you have
+# more than one resolver in networking.nameservers only the first one will be
+# used.
+#
+# Also make sure that whenever you use a resolver from a different test node
+# that it has to be started _before_ the ACME service.
+{ config, pkgs, lib, ... }:
+
+let
+  softhsm = pkgs.stdenv.mkDerivation rec {
+    name = "softhsm-${version}";
+    version = "1.3.8";
+
+    src = pkgs.fetchurl {
+      url = "https://dist.opendnssec.org/source/${name}.tar.gz";
+      sha256 = "0flmnpkgp65ym7w3qyg78d3fbmvq3aznmi66rgd420n33shf7aif";
+    };
+
+    configureFlags = [ "--with-botan=${pkgs.botan}" ];
+    buildInputs = [ pkgs.sqlite ];
+  };
+
+  pkcs11-proxy = pkgs.stdenv.mkDerivation {
+    name = "pkcs11-proxy";
+
+    src = pkgs.fetchFromGitHub {
+      owner = "SUNET";
+      repo = "pkcs11-proxy";
+      rev = "944684f78bca0c8da6cabe3fa273fed3db44a890";
+      sha256 = "1nxgd29y9wmifm11pjcdpd2y293p0dgi0x5ycis55miy97n0f5zy";
+    };
+
+    postPatch = "patchShebangs mksyscalls.sh";
+
+    nativeBuildInputs = [ pkgs.cmake ];
+    buildInputs = [ pkgs.openssl pkgs.libseccomp ];
+  };
+
+  mkGoDep = { goPackagePath, url ? "https://${goPackagePath}", rev, sha256 }: {
+    inherit goPackagePath;
+    src = pkgs.fetchgit { inherit url rev sha256; };
+  };
+
+  goose = let
+    owner = "liamstask";
+    repo = "goose";
+    rev = "8488cc47d90c8a502b1c41a462a6d9cc8ee0a895";
+    version = "20150116";
+
+  in pkgs.buildGoPackage rec {
+    name = "${repo}-${version}";
+
+    src = pkgs.fetchFromBitbucket {
+      name = "${name}-src";
+      inherit rev owner repo;
+      sha256 = "1jy0pscxjnxjdg3hj111w21g8079rq9ah2ix5ycxxhbbi3f0wdhs";
+    };
+
+    goPackagePath = "bitbucket.org/${owner}/${repo}";
+    subPackages = [ "cmd/goose" ];
+    extraSrcs = map mkGoDep [
+      { goPackagePath = "github.com/go-sql-driver/mysql";
+        rev = "2e00b5cd70399450106cec6431c2e2ce3cae5034";
+        sha256 = "085g48jq9hzmlcxg122n0c4pi41sc1nn2qpx1vrl2jfa8crsppa5";
+      }
+      { goPackagePath = "github.com/kylelemons/go-gypsy";
+        rev = "08cad365cd28a7fba23bb1e57aa43c5e18ad8bb8";
+        sha256 = "1djv7nii3hy451n5jlslk0dblqzb1hia1cbqpdwhnps1g8hqjy8q";
+      }
+      { goPackagePath = "github.com/lib/pq";
+        rev = "ba5d4f7a35561e22fbdf7a39aa0070f4d460cfc0";
+        sha256 = "1mfbqw9g00bk24bfmf53wri5c2wqmgl0qh4sh1qv2da13a7cwwg3";
+      }
+      { goPackagePath = "github.com/mattn/go-sqlite3";
+        rev = "2acfafad5870400156f6fceb12852c281cbba4d5";
+        sha256 = "1rpgil3w4hh1cibidskv1js898hwz83ps06gh0hm3mym7ki8d5h7";
+      }
+      { goPackagePath = "github.com/ziutek/mymysql";
+        rev = "0582bcf675f52c0c2045c027fd135bd726048f45";
+        sha256 = "0bkc9x8sgqbzgdimsmsnhb0qrzlzfv33fgajmmjxl4hcb21qz3rf";
+      }
+      { goPackagePath = "golang.org/x/net";
+        url = "https://go.googlesource.com/net";
+        rev = "10c134ea0df15f7e34d789338c7a2d76cc7a3ab9";
+        sha256 = "14cbr2shl08gyg85n5gj7nbjhrhhgrd52h073qd14j97qcxsakcz";
+      }
+    ];
+  };
+
+  boulder = let
+    owner = "letsencrypt";
+    repo = "boulder";
+    rev = "9c6a1f2adc4c26d925588f5ae366cfd4efb7813a";
+    version = "20180129";
+
+  in pkgs.buildGoPackage rec {
+    name = "${repo}-${version}";
+
+    src = pkgs.fetchFromGitHub {
+      name = "${name}-src";
+      inherit rev owner repo;
+      sha256 = "09kszswrifm9rc6idfaq0p1mz5w21as2qbc8gd5pphrq9cf9pn55";
+    };
+
+    postPatch = ''
+      # compat for go < 1.8
+      sed -i -e 's/time\.Until(\([^)]\+\))/\1.Sub(time.Now())/' \
+        test/ocsp/helper/helper.go
+
+      find test -type f -exec sed -i -e '/libpkcs11-proxy.so/ {
+        s,/usr/local,${pkcs11-proxy},
+      }' {} +
+
+      sed -i -r \
+        -e '/^def +install/a \    return True' \
+        -e 's,exec \./bin/,,' \
+        test/startservers.py
+
+      cat ${lib.escapeShellArg snakeOilCerts.ca.key} > test/test-ca.key
+      cat ${lib.escapeShellArg snakeOilCerts.ca.cert} > test/test-ca.pem
+    '';
+
+    # Until vendored pkcs11 is go 1.9 compatible
+    preBuild = ''
+      rm -r go/src/github.com/letsencrypt/boulder/vendor/github.com/miekg/pkcs11
+    '';
+
+    # XXX: Temporarily brought back putting the source code in the output,
+    # since e95f17e2720e67e2eabd59d7754c814d3e27a0b2 was removing that from
+    # buildGoPackage.
+    preInstall = ''
+      mkdir -p $out
+      pushd "$NIX_BUILD_TOP/go"
+      while read f; do
+        echo "$f" | grep -q '^./\(src\|pkg/[^/]*\)/${goPackagePath}' \
+          || continue
+        mkdir -p "$(dirname "$out/share/go/$f")"
+        cp "$NIX_BUILD_TOP/go/$f" "$out/share/go/$f"
+      done < <(find . -type f)
+      popd
+    '';
+
+    extraSrcs = map mkGoDep [
+      { goPackagePath = "github.com/miekg/pkcs11";
+        rev           = "6dbd569b952ec150d1425722dbbe80f2c6193f83";
+        sha256        = "1m8g6fx7df6hf6q6zsbyw1icjmm52dmsx28rgb0h930wagvngfwb";
+      }
+    ];
+
+    goPackagePath = "github.com/${owner}/${repo}";
+    buildInputs = [ pkgs.libtool ];
+  };
+
+  boulderSource = "${boulder.out}/share/go/src/${boulder.goPackagePath}";
+
+  softHsmConf = pkgs.writeText "softhsm.conf" ''
+    0:/var/lib/softhsm/slot0.db
+    1:/var/lib/softhsm/slot1.db
+  '';
+
+  snakeOilCerts = import ./snakeoil-certs.nix;
+
+  wfeDomain = "acme-v01.api.letsencrypt.org";
+  wfeCertFile = snakeOilCerts.${wfeDomain}.cert;
+  wfeKeyFile = snakeOilCerts.${wfeDomain}.key;
+
+  siteDomain = "letsencrypt.org";
+  siteCertFile = snakeOilCerts.${siteDomain}.cert;
+  siteKeyFile = snakeOilCerts.${siteDomain}.key;
+
+  # Retrieved via:
+  # curl -s -I https://acme-v01.api.letsencrypt.org/terms \
+  #   | sed -ne 's/^[Ll]ocation: *//p'
+  tosUrl = "https://letsencrypt.org/documents/2017.11.15-LE-SA-v1.2.pdf";
+  tosPath = builtins.head (builtins.match "https?://[^/]+(.*)" tosUrl);
+
+  tosFile = pkgs.fetchurl {
+    url = tosUrl;
+    sha256 = "0yvyckqzj0b1xi61sypcha82nanizzlm8yqy828h2jbza7cxi26c";
+  };
+
+  resolver = let
+    message = "You need to define a resolver for the letsencrypt test module.";
+    firstNS = lib.head config.networking.nameservers;
+  in if config.networking.nameservers == [] then throw message else firstNS;
+
+  cfgDir = pkgs.stdenv.mkDerivation {
+    name = "boulder-config";
+    src = "${boulderSource}/test/config";
+    nativeBuildInputs = [ pkgs.jq ];
+    phases = [ "unpackPhase" "patchPhase" "installPhase" ];
+    postPatch = ''
+      sed -i -e 's/5002/80/' -e 's/5002/443/' va.json
+      sed -i -e '/listenAddress/s/:4000/:80/' wfe.json
+      sed -i -r \
+        -e ${lib.escapeShellArg "s,http://boulder:4000/terms/v1,${tosUrl},g"} \
+        -e 's,http://(boulder|127\.0\.0\.1):4000,https://${wfeDomain},g' \
+        -e '/dnsResolver/s/127\.0\.0\.1:8053/${resolver}:53/' \
+        *.json
+      if grep 4000 *.json; then exit 1; fi
+
+      # Change all ports from 1909X to 909X, because the 1909X range of ports is
+      # allocated by startservers.py in order to intercept gRPC communication.
+      sed -i -e 's/\<1\(909[0-9]\)\>/\1/' *.json
+
+      # Patch out all additional issuer certs
+      jq '. + {ca: (.ca + {Issuers:
+        [.ca.Issuers[] | select(.CertFile == "test/test-ca.pem")]
+      })}' ca.json > tmp
+      mv tmp ca.json
+    '';
+    installPhase = "cp -r . \"$out\"";
+  };
+
+  components = {
+    gsb-test-srv.args = "-apikey my-voice-is-my-passport";
+    gsb-test-srv.waitForPort = 6000;
+    gsb-test-srv.first = true;
+    boulder-sa.args = "--config ${cfgDir}/sa.json";
+    boulder-wfe.args = "--config ${cfgDir}/wfe.json";
+    boulder-ra.args = "--config ${cfgDir}/ra.json";
+    boulder-ca.args = "--config ${cfgDir}/ca.json";
+    boulder-va.args = "--config ${cfgDir}/va.json";
+    boulder-publisher.args = "--config ${cfgDir}/publisher.json";
+    boulder-publisher.waitForPort = 9091;
+    ocsp-updater.args = "--config ${cfgDir}/ocsp-updater.json";
+    ocsp-updater.after = [ "boulder-publisher" ];
+    ocsp-responder.args = "--config ${cfgDir}/ocsp-responder.json";
+    ct-test-srv = {};
+    mail-test-srv.args = let
+      key = "${boulderSource}/test/mail-test-srv/minica-key.pem";
+      crt = "${boulderSource}/test/mail-test-srv/minica.pem";
+     in
+      "--closeFirst 5 --cert ${crt} --key ${key}";
+  };
+
+  commonPath = [ softhsm pkgs.mariadb goose boulder ];
+
+  mkServices = a: b: with lib; listToAttrs (concatLists (mapAttrsToList a b));
+
+  componentServices = mkServices (name: attrs: let
+    mkSrvName = n: "boulder-${n}.service";
+    firsts = lib.filterAttrs (lib.const (c: c.first or false)) components;
+    firstServices = map mkSrvName (lib.attrNames firsts);
+    firstServicesNoSelf = lib.remove "boulder-${name}.service" firstServices;
+    additionalAfter = firstServicesNoSelf ++ map mkSrvName (attrs.after or []);
+    needsPort = attrs ? waitForPort;
+    inits = map (n: "boulder-init-${n}.service") [ "mysql" "softhsm" ];
+    portWaiter = {
+      name = "boulder-${name}";
+      value = {
+        description = "Wait For Port ${toString attrs.waitForPort} (${name})";
+        after = [ "boulder-real-${name}.service" "bind.service" ];
+        requires = [ "boulder-real-${name}.service" ];
+        requiredBy = [ "boulder.service" ];
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        script = let
+          netcat = "${pkgs.libressl.nc}/bin/nc";
+          portCheck = "${netcat} -z 127.0.0.1 ${toString attrs.waitForPort}";
+        in "while ! ${portCheck}; do :; done";
+      };
+    };
+  in lib.optional needsPort portWaiter ++ lib.singleton {
+    name = if needsPort then "boulder-real-${name}" else "boulder-${name}";
+    value = {
+      description = "Boulder ACME Component (${name})";
+      after = inits ++ additionalAfter;
+      requires = inits;
+      requiredBy = [ "boulder.service" ];
+      path = commonPath;
+      environment.GORACE = "halt_on_error=1";
+      environment.SOFTHSM_CONF = softHsmConf;
+      environment.PKCS11_PROXY_SOCKET = "tcp://127.0.0.1:5657";
+      serviceConfig.WorkingDirectory = boulderSource;
+      serviceConfig.ExecStart = "${boulder}/bin/${name} ${attrs.args or ""}";
+      serviceConfig.Restart = "on-failure";
+    };
+  }) components;
+
+in {
+  imports = [ ../resolver.nix ];
+
+  options.test-support.letsencrypt.caCert = lib.mkOption {
+    type = lib.types.path;
+    description = ''
+      A certificate file to use with the <literal>nodes</literal> attribute to
+      inject the snakeoil CA certificate used in the ACME server into
+      <option>security.pki.certificateFiles</option>.
+    '';
+  };
+
+  config = {
+    test-support = {
+      resolver.enable = let
+        isLocalResolver = config.networking.nameservers == [ "127.0.0.1" ];
+      in lib.mkOverride 900 isLocalResolver;
+      letsencrypt.caCert = snakeOilCerts.ca.cert;
+    };
+
+    # This has priority 140, because modules/testing/test-instrumentation.nix
+    # already overrides this with priority 150.
+    networking.nameservers = lib.mkOverride 140 [ "127.0.0.1" ];
+    networking.firewall.enable = false;
+
+    networking.extraHosts = ''
+      127.0.0.1 ${toString [
+        "sa.boulder" "ra.boulder" "wfe.boulder" "ca.boulder" "va.boulder"
+        "publisher.boulder" "ocsp-updater.boulder" "admin-revoker.boulder"
+        "boulder" "boulder-mysql" wfeDomain
+      ]}
+      ${config.networking.primaryIPAddress} ${wfeDomain} ${siteDomain}
+    '';
+
+    services.mysql.enable = true;
+    services.mysql.package = pkgs.mariadb;
+
+    services.nginx.enable = true;
+    services.nginx.recommendedProxySettings = true;
+    # This fixes the test on i686
+    services.nginx.commonHttpConfig = ''
+      server_names_hash_bucket_size 64;
+    '';
+    services.nginx.virtualHosts.${wfeDomain} = {
+      onlySSL = true;
+      enableACME = false;
+      sslCertificate = wfeCertFile;
+      sslCertificateKey = wfeKeyFile;
+      locations."/".proxyPass = "http://127.0.0.1:80";
+    };
+    services.nginx.virtualHosts.${siteDomain} = {
+      onlySSL = true;
+      enableACME = false;
+      sslCertificate = siteCertFile;
+      sslCertificateKey = siteKeyFile;
+      locations.${tosPath}.extraConfig = "alias ${tosFile};";
+    };
+
+    systemd.services = {
+      pkcs11-daemon = {
+        description = "PKCS11 Daemon";
+        after = [ "boulder-init-softhsm.service" ];
+        before = map (n: "${n}.service") (lib.attrNames componentServices);
+        wantedBy = [ "multi-user.target" ];
+        environment.SOFTHSM_CONF = softHsmConf;
+        environment.PKCS11_DAEMON_SOCKET = "tcp://127.0.0.1:5657";
+        serviceConfig.ExecStart = let
+          softhsmLib = "${softhsm}/lib/softhsm/libsofthsm.so";
+        in "${pkcs11-proxy}/bin/pkcs11-daemon ${softhsmLib}";
+      };
+
+      boulder-init-mysql = {
+        description = "Boulder ACME Init (MySQL)";
+        after = [ "mysql.service" ];
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        serviceConfig.WorkingDirectory = boulderSource;
+        path = commonPath;
+        script = "${pkgs.bash}/bin/sh test/create_db.sh";
+      };
+
+      boulder-init-softhsm = {
+        description = "Boulder ACME Init (SoftHSM)";
+        environment.SOFTHSM_CONF = softHsmConf;
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        serviceConfig.WorkingDirectory = boulderSource;
+        preStart = "mkdir -p /var/lib/softhsm";
+        path = commonPath;
+        script = ''
+          softhsm --slot 0 --init-token \
+            --label intermediate --pin 5678 --so-pin 1234
+          softhsm --slot 0 --import test/test-ca.key \
+            --label intermediate_key --pin 5678 --id FB
+          softhsm --slot 1 --init-token \
+            --label root --pin 5678 --so-pin 1234
+          softhsm --slot 1 --import test/test-root.key \
+            --label root_key --pin 5678 --id FA
+        '';
+      };
+
+      boulder = {
+        description = "Boulder ACME Server";
+        after = map (n: "${n}.service") (lib.attrNames componentServices);
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        script = let
+          ports = lib.range 8000 8005 ++ lib.singleton 80;
+          netcat = "${pkgs.libressl.nc}/bin/nc";
+          mkPortCheck = port: "${netcat} -z 127.0.0.1 ${toString port}";
+          checks = "(${lib.concatMapStringsSep " && " mkPortCheck ports})";
+        in "while ! ${checks}; do :; done";
+      };
+    } // componentServices;
+  };
+}
diff --git a/nixpkgs/nixos/tests/common/letsencrypt/mkcerts.nix b/nixpkgs/nixos/tests/common/letsencrypt/mkcerts.nix
new file mode 100644
index 000000000000..3b4a589e4142
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/letsencrypt/mkcerts.nix
@@ -0,0 +1,69 @@
+{ pkgs ? import <nixpkgs> {}
+, lib ? pkgs.lib
+
+, domains ? [ "acme-v01.api.letsencrypt.org" "letsencrypt.org" ]
+}:
+
+pkgs.runCommand "letsencrypt-snakeoil-ca" {
+  nativeBuildInputs = [ pkgs.openssl ];
+} ''
+  addpem() {
+    local file="$1"; shift
+    local storeFileName="$(IFS=.; echo "$*")"
+
+    echo -n "  " >> "$out"
+
+    # Every following argument is an attribute, so let's recurse and check
+    # every attribute whether it must be quoted and write it into $out.
+    while [ -n "$1" ]; do
+      if expr match "$1" '^[a-zA-Z][a-zA-Z0-9]*$' > /dev/null; then
+        echo -n "$1" >> "$out"
+      else
+        echo -n '"' >> "$out"
+        echo -n "$1" | sed -e 's/["$]/\\&/g' >> "$out"
+        echo -n '"' >> "$out"
+      fi
+      shift
+      [ -z "$1" ] || echo -n . >> "$out"
+    done
+
+    echo " = builtins.toFile \"$storeFileName\" '''" >> "$out"
+    sed -e 's/^/    /' "$file" >> "$out"
+
+    echo "  ''';" >> "$out"
+  }
+
+  echo '# Generated via mkcert.sh in the same directory.' > "$out"
+  echo '{' >> "$out"
+
+  openssl req -newkey rsa:4096 -x509 -sha256 -days 36500 \
+    -subj '/CN=Snakeoil CA' -nodes -out ca.pem -keyout ca.key
+
+  addpem ca.key ca key
+  addpem ca.pem ca cert
+
+  ${lib.concatMapStrings (fqdn: let
+    opensslConfig = pkgs.writeText "snakeoil.cnf" ''
+      [req]
+      default_bits = 4096
+      prompt = no
+      default_md = sha256
+      req_extensions = req_ext
+      distinguished_name = dn
+      [dn]
+      CN = ${fqdn}
+      [req_ext]
+      subjectAltName = DNS:${fqdn}
+    '';
+  in ''
+    export OPENSSL_CONF=${lib.escapeShellArg opensslConfig}
+    openssl genrsa -out snakeoil.key 4096
+    openssl req -new -key snakeoil.key -out snakeoil.csr
+    openssl x509 -req -in snakeoil.csr -sha256 -set_serial 666 \
+      -CA ca.pem -CAkey ca.key -out snakeoil.pem -days 36500
+    addpem snakeoil.key ${lib.escapeShellArg fqdn} key
+    addpem snakeoil.pem ${lib.escapeShellArg fqdn} cert
+  '') domains}
+
+  echo '}' >> "$out"
+''
diff --git a/nixpkgs/nixos/tests/common/letsencrypt/mkcerts.sh b/nixpkgs/nixos/tests/common/letsencrypt/mkcerts.sh
new file mode 100755
index 000000000000..cc7f8ca650dd
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/letsencrypt/mkcerts.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env nix-shell
+#!nix-shell -p nix bash -i bash
+set -e
+cd "$(dirname "$0")"
+storepath="$(nix-build --no-out-link mkcerts.nix)"
+cat "$storepath" > snakeoil-certs.nix
diff --git a/nixpkgs/nixos/tests/common/letsencrypt/snakeoil-certs.nix b/nixpkgs/nixos/tests/common/letsencrypt/snakeoil-certs.nix
new file mode 100644
index 000000000000..c3d29ab8f163
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/letsencrypt/snakeoil-certs.nix
@@ -0,0 +1,253 @@
+# Generated via mkcert.sh in the same directory.
+{
+  ca.key = builtins.toFile "ca.key" ''
+    -----BEGIN PRIVATE KEY-----
+    MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDfdVxC/4HwhuzD
+    9or9CDDu3TBQE5lirJI5KYmfMZtfgdzEjgOzmR9AVSkn2rQeCqzM5m+YCzPO+2y7
+    0Fdk7vDORi1OdhYfUQIW6/TZ27xEjx4t82j9i705yUqTJZKjMbD830geXImJ6VGj
+    Nv/WisTHmwBspWKefYQPN68ZvYNCn0d5rYJg9uROZPJHSI0MYj9iERWIPN+xhZoS
+    xN74ILJ0rEOQfx2GHDhTr99vZYAFqbAIfh35fYulRWarUSekI+rDxa83FD8q9cMg
+    OP84KkLep2dRXXTbUWErGUOpHP55M9M7ws0RVNdl9PUSbDgChl7yYlHCde3261q/
+    zGp5dMV/t/jXXNUgRurvXc4gUKKjS4Sffvg0XVnPs3sMlZ4JNmycK9klgISVmbTK
+    VcjRRJv8Bva2NQVsJ9TIryV0QEk94DucgsC3LbhQfQdmnWVcEdzwrZHNpk9az5mn
+    w42RuvZW9L19T7xpIrdLSHaOis4VEquZjkWIhfIz0DVMeXtYEQmwqFG23Ww0utcp
+    mCW4FPvpyYs5GAPmGWfrlMxsLD/7eteot3AheC+56ZBoVBnI8FFvIX2qci+gfVDu
+    CjvDmbyS/0NvxLGqvSC1GUPmWP3TR5Fb1H8Rp+39zJHRmH+qYWlhcv6p7FlY2/6d
+    9Rkw8WKRTSCB7yeUdNNPiPopk6N4NwIDAQABAoICAQCzV0ei5dntpvwjEp3eElLj
+    glYiDnjOPt5kTjgLsg6XCmyau7ewzrXMNgz/1YE1ky+4i0EI8AS2nAdafQ2HDlXp
+    11zJWfDLVYKtztYGe1qQU6TPEEo1I4/M7waRLliP7XO0n6cL5wzjyIQi0CNolprz
+    8CzZBasutGHmrLQ1nmnYcGk2+NBo7f2yBUaFe27of3mLRVbYrrKBkU5kveiNkABp
+    r0/SipKxbbivQbm7d+TVpqiHSGDaOa54CEksOcfs7n6efOvw8qj326KtG9GJzDE6
+    7XP4U19UHe40XuR0t7Zso/FmRyO6QzNUutJt5LjXHezZ75razTcdMyr0QCU8MUHH
+    jXZxQCsbt+9AmdxUMBm1SMNVBdHYM8oiNHynlgsEj9eM6jxDEss/Uc3FeKoHl+XL
+    L6m28guIB8NivqjVzZcwhxvdiQCzYxjyqMC+/eX7aaK4NIlX2QRMoDL6mJ58Bz/8
+    V2Qxp2UNVwKJFWAmpgXC+sq6XV/TP3HkOvd0OK82Nid2QxEvfE/EmOhU63qAjgUR
+    QnteLEcJ3MkGGurs05pYBDE7ejKVz6uu2tHahFMOv+yanGP2gfivnT9a323/nTqH
+    oR5ffMEI1u/ufpWU7sWXZfL/mH1L47x87k+9wwXHCPeSigcy+hFI7t1+rYsdCmz9
+    V6QtmxZHMLanwzh5R0ipcQKCAQEA8kuZIz9JyYP6L+5qmIUxiWESihVlRCSKIqLB
+    fJ5sQ06aDBV2sqS4XnoWsHuJWUd39rulks8cg8WIQu8oJwVkFI9EpARt/+a1fRP0
+    Ncc9qiBdP6VctQGgKfe5KyOfMzIBUl3zj2cAmU6q+CW1OgdhnEl4QhgBe5XQGquZ
+    Alrd2P2jhJbMO3sNFgzTy7xPEr3KqUy+L4gtRnGOegKIh8EllmsyMRO4eIrZV2z3
+    XI+S2ZLyUn3WHYkaJqvUFrbfekgBBmbk5Ead6ImlsLsBla6MolKrVYV1kN6KT+Y+
+    plcxNpWY8bnWfw5058OWPLPa9LPfReu9rxAeGT2ZLmAhSkjGxQKCAQEA7BkBzT3m
+    SIzop9RKl5VzYbVysCYDjFU9KYMW5kBIw5ghSMnRmU7kXIZUkc6C1L/v9cTNFFLw
+    ZSF4vCHLdYLmDysW2d4DU8fS4qdlDlco5A00g8T1FS7nD9CzdkVN/oix6ujw7RuI
+    7pE1K3JELUYFBc8AZ7mIGGbddeCwnM+NdPIlhWzk5s4x4/r31cdk0gzor0kE4e+d
+    5m0s1T4O/Iak6rc0MGDeTejZQg04p1eAJFYQ6OY23tJhH/kO8CMYnQ4fidfCkf8v
+    85v4EC1MCorFR7J65uSj8MiaL7LTXPvLAkgFls1c3ijQ2tJ8qXvqmfo0by33T1OF
+    ZGyaOP9/1WQSywKCAQB47m6CfyYO5EZNAgxGD8SHsuGT9dXTSwF/BAjacB/NAEA2
+    48eYpko3LWyBrUcCPn+LsGCVg7XRtxepgMBjqXcoI9G4o1VbsgTHZtwus0D91qV0
+    DM7WsPcFu1S6SU8+OCkcuTPFUT2lRvRiYj+vtNttK+ZP5rdmvYFermLyH/Q2R3ID
+    zVgmH+aKKODVASneSsgJ8/nAs5EVZbwc/YKzbx2Zk+s7P4KE95g+4G4dzrMW0RcN
+    QS1LFJDu2DhFFgU4fRO15Ek9/lj2JS2DpfLGiJY8tlI5nyDsq4YRFvQSBdbUTZpG
+    m+CJDegffSlRJtuT4ur/dQf5hmvfYTVBRk2XS/eZAoIBAB143a22PWnvFRfmO02C
+    3X1j/iYZCLZa6aCl+ZTSj4LDGdyRPPXrUDxwlFwDMHfIYfcHEyanV9T4Aa9SdKh9
+    p6RbF6YovbeWqS+b/9RzcupM77JHQuTbDwL9ZXmtGxhcDgGqBHFEz6ogPEfpIrOY
+    GwZnmcBY+7E4HgsZ+lII4rqng6GNP2HEeZvg91Eba+2AqQdAkTh3Bfn+xOr1rT8+
+    u5WFOyGS5g1JtN0280yIcrmWeNPp8Q2Nq4wnNgMqDmeEnNFDOsmo1l6NqMC0NtrW
+    CdxyXj82aXSkRgMQSqw/zk7BmNkDV8VvyOqX/fHWQynnfuYmEco4Pd2UZQgadOW5
+    cVMCggEBANGz1fC+QQaangUzsVNOJwg2+CsUFYlAKYA3pRKZPIyMob2CBXk3Oln/
+    YqOq6j373kG2AX74EZT07JFn28F27JF3r+zpyS/TYrfZyO1lz/5ZejPtDTmqBiVd
+    qa2coaPKwCOz64s77A9KSPyvpvyuTfRVa8UoArHcrQsPXMHgEhnFRsbxgmdP582A
+    kfYfoJBSse6dQtS9ZnREJtyWJlBNIBvsuKwzicuIgtE3oCBcIUZpEa6rBSN7Om2d
+    ex8ejCcS7qpHeULYspXbm5ZcwE4glKlQbJDTKaJ9mjiMdvuNFUZnv1BdMQ3Tb8zf
+    Gvfq54FbDuB10XP8JdLrsy9Z6GEsmoE=
+    -----END PRIVATE KEY-----
+  '';
+  ca.cert = builtins.toFile "ca.cert" ''
+    -----BEGIN CERTIFICATE-----
+    MIIFATCCAumgAwIBAgIJANydi4uFZr0LMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
+    BAMMC1NuYWtlb2lsIENBMCAXDTE4MDcxMjAwMjIxNloYDzIxMTgwNjE4MDAyMjE2
+    WjAWMRQwEgYDVQQDDAtTbmFrZW9pbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+    ADCCAgoCggIBAN91XEL/gfCG7MP2iv0IMO7dMFATmWKskjkpiZ8xm1+B3MSOA7OZ
+    H0BVKSfatB4KrMzmb5gLM877bLvQV2Tu8M5GLU52Fh9RAhbr9NnbvESPHi3zaP2L
+    vTnJSpMlkqMxsPzfSB5ciYnpUaM2/9aKxMebAGylYp59hA83rxm9g0KfR3mtgmD2
+    5E5k8kdIjQxiP2IRFYg837GFmhLE3vggsnSsQ5B/HYYcOFOv329lgAWpsAh+Hfl9
+    i6VFZqtRJ6Qj6sPFrzcUPyr1wyA4/zgqQt6nZ1FddNtRYSsZQ6kc/nkz0zvCzRFU
+    12X09RJsOAKGXvJiUcJ17fbrWr/Manl0xX+3+Ndc1SBG6u9dziBQoqNLhJ9++DRd
+    Wc+zewyVngk2bJwr2SWAhJWZtMpVyNFEm/wG9rY1BWwn1MivJXRAST3gO5yCwLct
+    uFB9B2adZVwR3PCtkc2mT1rPmafDjZG69lb0vX1PvGkit0tIdo6KzhUSq5mORYiF
+    8jPQNUx5e1gRCbCoUbbdbDS61ymYJbgU++nJizkYA+YZZ+uUzGwsP/t616i3cCF4
+    L7npkGhUGcjwUW8hfapyL6B9UO4KO8OZvJL/Q2/Esaq9ILUZQ+ZY/dNHkVvUfxGn
+    7f3MkdGYf6phaWFy/qnsWVjb/p31GTDxYpFNIIHvJ5R000+I+imTo3g3AgMBAAGj
+    UDBOMB0GA1UdDgQWBBQ3vPWzjLmu5krbSpfhBAht9KL3czAfBgNVHSMEGDAWgBQ3
+    vPWzjLmu5krbSpfhBAht9KL3czAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA
+    A4ICAQDF9HyC1ZFN3Ob+JA9Dj5+Rcobi7JIA5F8uW3Q92LfPoVaUGEkBrwJSiTFX
+    47zvP/ySBJIpZ9rzHMbJ+1L+eJgczF1uQ91inthCKo1THTPo5TgBrpJj0YAIunsj
+    9eH1tBnfWFYdVIDZoTSiwPtgIvglpyuK/eJXEe+FRzubhtdc9w1Hlzox1sd0TQuy
+    Pl9KFHg7BlFZfCPig1mkB8pfwjBDgVhv5DKJ9cJXh3R5zSoiyuS2b+qYSvw8YTHq
+    0WNKWUthb7BVAYE3OmcbOHgUAUjtJ6EIGIB9z/SoLe90CofXLXFR5dppuVLKCMBA
+    kgL4luBIu7t8mcnN2yzobvcGHy8RVY6F5abCCy6gackLzjOzvH1SYOxP8yN74aKB
+    ANgcqdWspb8JYoU8lEbA8dhBVrsgBf7XeJlrZvMdcUENlJ2PI0JWr9WvlRAM9rYY
+    EY1alJqBCp6530Ggd6/f0V64cEqptejUdmN9L0zboxKjQf4LjpUNraGvg8tw/xkY
+    4dT1U2HlVnhOyBVkx/tE6zIK/RU16oMqwpjCdfbK/TuWCNc/emJz5PMlp81zm83+
+    dExpWwuV4rt6OQbZ/GSatNLJXOw+pkLjaEhnHgrsgI+HqAUXg3ByKol+1e76wN51
+    k1ZKpB6mk4kejySGPYBHiJwED0IyXu9gUfalSczXFO4ySAvhCg==
+    -----END CERTIFICATE-----
+  '';
+  "acme-v01.api.letsencrypt.org".key = builtins.toFile "acme-v01.api.letsencrypt.org.key" ''
+    -----BEGIN RSA PRIVATE KEY-----
+    MIIJKQIBAAKCAgEAvG+sL4q0VkgSClBTn4NkPiUrtXx5oLyZ+CCM1jrQx/xotUt5
+    X2S4/7vMnAK/yRLsR7R2PhXO8CZPqJ7B6OfAgaDTgvipJkZYPZQSMP3KOinM3WJL
+    ssqKh7/HOxZIf0iyUXewrnX5eTAo/CLsUnhBjBD7E99nmQz/leLWSl82sSYDkO3n
+    Uk3/1qJZA8iddb4uH0IEQWcNKev3WoQQzwiVrXBiftlRQOJy5JJXm5m8229MCpMA
+    1AUWmpdu6sl3/gFFdsDhUFq/a7LFrVyaUCMRIHg9szAB7ZFkixr9umQs8jKwuo98
+    3JHB11h2SirwgfIzHHmyhaWhCt22ucTwEXGhq63LtrzZvLsfP8Ql5S+AuqGTH0v8
+    meuc784leAjulBZjkpuIFwDnVv9+YeUEbqJeo1hSHrILddora3nkH4E2dJWmLpqp
+    iPr++GRi+BNgYKW/BQLTJ7C6v+vUs+kdPgYJH5z7oP6f0YZkT0Wkubp/UEz7UV2d
+    fjz57d77DYx5rFWGYzJriWR/xltgL1zDpjwjwG1FDpRqwlyYbBFpjQhxI+X0aT98
+    m6fCzBDQHDb/+JgvsjTHh6OZatahFAwzFIEfrceDv1BG8sBWIaZGhLzYiWQxafl8
+    oXbWv1T6I1jpsTlCdCSkWzaJb4ZjxI9Ga1ynVu8F16+GR2a71wKWu7UbZQsCAwEA
+    AQKCAgBYvrs4FLoD3KNqahRIDqhaQEVKjtn1Yn2dBy9tAXwsg2qI34fE7nnWLwsY
+    +o56U0gmKQ57BOhV36Uqg8JNP0BBjI2wpA19simCrsa2fgAMznzmUpHWHV+KuT5K
+    TJ9OGt2oUpdKQtOASLc0r/neiTZNkf29iTyQLzf7zj4f/qGSYpXRXsnP0F5KJmGH
+    z6agujWckQnSB4eCk9gFsCb+akubyE8K8Kw8w6lajrVl2czBB7SnUj5UnCTeH62k
+    M8goP08Is6QppON8BFDm6bLfRPSe9yIPzu9JhGz2unp+mwkz872Zz1P9yUOieM4U
+    9g4ZFQkPQx1ZpfynUm3pJZ/uhzadBabnIvMe/1qwDAEDifh/WzEM76/2kBpQkHtS
+    qcjwjAElfWnP8aBr1Pj42/cVJy3dbDqb0OawFHx/8xSO2CkY4Gq2h3OYv1XpPv3g
+    S9qqKhvuaT+aD0YjKhP4FYc2vvQSJwdZL8vqOyma8JGmc+r7jakIPCyOx3oPVqnS
+    L2P7DuJ1FcGIZyYOU3UUSzKndDU9fVC8YoLWvHDlwm4RK9UPtdsBY8mEu6BlaAwL
+    zEQG+fbcFnEkHPiJeAohYUCHiqCihLt0pqGwZi+QrudPQE6C47YijGZWJu4VVLjB
+    B2L9iDQKsN4FnBJ9egJIwWBLX3XXQfjC43UGm1A5sBvD+ScsCQKCAQEA7GxU7/SW
+    4YJ+wBXrp7Z3vzlc5mTT5U4L2muWZLhIjT/jmpHpZ4c9a5DY/K9OYcu8XJ+7kx2B
+    N40cU3ZkT2ZbB5/BUCEmi3Wzy3R/KZshHDzvvSZHcXJqVBtv+HGJgR5ssFqAw8c6
+    gJtDls+JE9Sz+nhLk0ZZ4658vbTQfG1lmtzrbC3Kz2xK8RPTdOU5Or7fayeaEKEW
+    ECBJPE41ME2UTdB/E85vyYoee0MBijjAs19QKqvoNbyrsZ5bihcIDYsrvjCmkdW1
+    20IUrSF3ZYJ9bb+CxHeRyNqwvRxPYSkzdMjZHx+xEAvJgw51QqmIi2QQf/qB+ych
+    cSbE/0Jhx4QbDQKCAQEAzAoenEOgmZvUegFUu8C6gWeibMjl3Y9SikQ4CoQO/zWr
+    aoCr5BpbzbtOffwnPfgk9wCGvXf6smOdrLUP1K2QAhBr/vJh7ih2MonvpYr5HPP7
+    maVARR66IgtxXP2ER2I9+9p2OQdecGRP2fUn2KCDQIASHSSY/VjBb8LLJgryC/DS
+    r2b0+m1e2qXfNWt/BYTQZhD/8B/jl/2pl/jI2ne3rkeiwEm7lqZaDt3Q8gC+qoP5
+    /IdG1Gob7UTMCbICWy1aGuzRYUmbpg0Vq4DAV1RtgBySB5oNq5PMBHYpOxedM2nM
+    NxHvf0u6wsxVULwQ4IfWUqUTspjxDmIgogSzmOGadwKCAQEA558if4tynjBImUtg
+    egirvG4oc5doeQhDWJN63eYlPizPgUleD41RQSbBTp04/1qoiV38WJ7ZT2Ex1Rry
+    H0+58vgyXZx8tLh1kufpBQv0HkQc44SzDZP4U7olspMZEaSK+yNPb36p9AEo8IEW
+    XJVQVhywffK4cfUqRHj2oFBU8KlrA6rBPQFtUk4IJkfED6ecHtDHgW8vvFDFLw23
+    0kDPAIU5WmAu6JYmUsBMq+v57kF8urF8Z9kVpIfuSpVR0GL+UfA74DgtWEefFhbp
+    cEutMm4jYPN7ofmOmVc49Yl13f4/qNxVjdDedUUe4FZTbax09cyotzOY8c/3w9R3
+    Ew57qQKCAQAa5jqi30eM+L5KV2KUXhQ4ezEupk2np/15vQSmXkKb4rd2kwAWUmNH
+    /Cmc8mE6CjzVU3xv/iFO41MmMbikkT0rCH80XUAL5cmvX//4ExpEduX0m5SdiC+B
+    zYBkggeuYYVKbsKnQhFxP8hHM8rNBFxJZJj+vpRs0gaudT/TBB5k9JrSBQDHAyQ+
+    Lx/+Ku3UDG5tBlC3l3ypzQdOwb25D49nqooKT64rbkLxMs0ZGoAIet26LRtpZZPI
+    9AjyPkWRP6lhY1c3PD0I5zC0K4Uv/jFxclLOLcEfnZyH+gv1fmd7H7eMixDH93Pn
+    uoiE3EZdU4st2hV+tisRel5S/cuvnA6BAoIBAQDJISK8H0hwYp+J4/WUv/WLtrm4
+    Mhmn8ItdEPAyCljycU6oLHJy4fgmmfRHeoO1i3jb87ks2GghegFBbJNzugfoGxIM
+    dLWIV+uFXWs24fMJ/J6lqN1JtAj7HjvqkXp061X+MdIJ0DsACygzFfJOjv+Ij77Q
+    Q1OBTSPfb0EWFNOuIJr9i2TwdN9eW/2ZMo1bPuwe4ttPEIBssfIC02dn2KD1RTqM
+    1l+L97vVFk7CoSJZf5rLeysLVyUeGdDcoEcRA6fKhfB/55h+iqrZNvySX1HrR6on
+    PQcxDRPJD7f9rMsTzVl3DOxzvXAU3lIcZtPZps97IwXceAAh2e1kZNNv/cxj
+    -----END RSA PRIVATE KEY-----
+  '';
+  "acme-v01.api.letsencrypt.org".cert = builtins.toFile "acme-v01.api.letsencrypt.org.cert" ''
+    -----BEGIN CERTIFICATE-----
+    MIIEtDCCApwCAgKaMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC1NuYWtlb2ls
+    IENBMCAXDTE4MDcxMjAwMjIxN1oYDzIxMTgwNjE4MDAyMjE3WjAnMSUwIwYDVQQD
+    DBxhY21lLXYwMS5hcGkubGV0c2VuY3J5cHQub3JnMIICIjANBgkqhkiG9w0BAQEF
+    AAOCAg8AMIICCgKCAgEAvG+sL4q0VkgSClBTn4NkPiUrtXx5oLyZ+CCM1jrQx/xo
+    tUt5X2S4/7vMnAK/yRLsR7R2PhXO8CZPqJ7B6OfAgaDTgvipJkZYPZQSMP3KOinM
+    3WJLssqKh7/HOxZIf0iyUXewrnX5eTAo/CLsUnhBjBD7E99nmQz/leLWSl82sSYD
+    kO3nUk3/1qJZA8iddb4uH0IEQWcNKev3WoQQzwiVrXBiftlRQOJy5JJXm5m8229M
+    CpMA1AUWmpdu6sl3/gFFdsDhUFq/a7LFrVyaUCMRIHg9szAB7ZFkixr9umQs8jKw
+    uo983JHB11h2SirwgfIzHHmyhaWhCt22ucTwEXGhq63LtrzZvLsfP8Ql5S+AuqGT
+    H0v8meuc784leAjulBZjkpuIFwDnVv9+YeUEbqJeo1hSHrILddora3nkH4E2dJWm
+    LpqpiPr++GRi+BNgYKW/BQLTJ7C6v+vUs+kdPgYJH5z7oP6f0YZkT0Wkubp/UEz7
+    UV2dfjz57d77DYx5rFWGYzJriWR/xltgL1zDpjwjwG1FDpRqwlyYbBFpjQhxI+X0
+    aT98m6fCzBDQHDb/+JgvsjTHh6OZatahFAwzFIEfrceDv1BG8sBWIaZGhLzYiWQx
+    afl8oXbWv1T6I1jpsTlCdCSkWzaJb4ZjxI9Ga1ynVu8F16+GR2a71wKWu7UbZQsC
+    AwEAATANBgkqhkiG9w0BAQsFAAOCAgEAzeGlFMz1Bo+bbpZDQ60HLdw7qDp3SPJi
+    x5LYG860yzbh9ghvyc59MIm5E6vB140LRJAs+Xo6VdVSTC4jUA2kI9k1BQsbZKds
+    XT0RqA7HkqcLS3t3JWFkkKbCshMGZTSZ//hpbaUG1qEAfUfmZw1lAxqSa0kqavbP
+    awf7k8qHbqcj7WORCdH7fjKAjntEQwIpl1GEkAdCSghOJz2/o9aWmiGZt27OM/sG
+    MLSrcmL3QBElCjOxg14P8rnsmZ+VEp6MO93otoJ4dJL7fN7vTIh5ThbS384at/4l
+    4KK/y7XctUzAtWzhnodjk/NSgrrGX2kseOGOWEM1sZc9xtinHH2tpOMqtLVOkgHD
+    Lul+TArqgqeoOdEM/9OL64kgOrO/JzxBq+egLUi4wgAul2wmtecKZK1dkwYZHeqW
+    74i55yeBp+TTomnPr0ZBns6xKFYldJVzC34OB+2YVDxe8y9XtWtuQOxFw0LQHhNb
+    zy5aBverWzZFwiIIjJoVHTQq848uKBJec0YILfMinS1Wjif4xqW/IMfi+GFS0oka
+    sKCGNE/8ur9u/Jm6cbto3f2dtV8/vkhiITQgwzM2jalyuVJ9jyPxG7EvbTvZORgw
+    pRvBRTd4/eE7I1L+UDe6x8EjR/MrqfF9FWVGOZo4vPTyNbrSWYBh6s9kYy56ds1l
+    IRxst1BXEfI=
+    -----END CERTIFICATE-----
+  '';
+  "letsencrypt.org".key = builtins.toFile "letsencrypt.org.key" ''
+    -----BEGIN RSA PRIVATE KEY-----
+    MIIJKAIBAAKCAgEAwPvhlwemgPi6919sSD7Pz6l6CRfU1G/fDc0AvsMN/nTmiGND
+    pqn9ef1CA+RtLtOuPc1LLyEovcfu75/V+6KSgO4k19E2CrFCFwjEOWDGF4DgclT3
+    751WGmFJgzPEfZfhbOrmQfQau86KxAtNZVp9FxcKbuLyQ/sNNxfNMB+7IHbVhwvz
+    VcndHpYZEP6kdnwvNLP22bouX5q3avxWStln01uZ0BfUm4XwxaUNIU7t0Dv56FK9
+    C9hW9AZae0do0BJBWRF7xSwLeDJqn9uZz+sX0X/tIaaSQSBuZySj0He5ZKzdUO0t
+    px2xTS2Brl3Y2BOJaOE98HubWvdKoslLt4X2rVrMxGa86SmFzcyDL1RSowcP/ruy
+    y555l7pepL5s4cmMgRBBXj5tXhqUTVOn5WO+JClLk+rtvtAT4rogJmMqEKmMw2t7
+    LNy1W9ri/378QG/i3AGaLIL/7GsPbuRO51Sdti4QMVe2zNFze72mzNmj1SXokWy7
+    +ZvjUMp55oEjRRsTPUZdNOEHJWy6Os2znuqL7ZpIHCxBG8FKnkCViXRJqAA8bzcE
+    hR+pLamLIOHlv4kdzJ6phHkSvK68qvbRReUmOjJgSupVBI9jhK+fHay/UWR4zfJQ
+    ed99H8ZOoiXlrLCVs+VPDynUUKrzF1nYyolNzi/NS4e4AbnfWgyC5JKRpjUCAwEA
+    AQKCAgB0fNYL+zM3MGxy+2d6KGf6GnuuV3NBlBGY3ACyJT0iNmAdPYXNaVi2tPeP
+    L+fz1xSa+3uBhEt6Wt/QRrO8g8JZDuawWvl69MpG6yS+2bpY35MbkExkl50sqULd
+    bncRtIb+3r+EWht099RtR8E9B6TwNhk3G8hO3pB4i+ZwQQcMLo7vSHhmdUYCu2mA
+    B6UwW/+GmYbMoARz8wj6DDzuS1LPksBCis/r3KqcMue9Dk6gXkOYR7ETIFBEVj1x
+    ooYS6qIFaHdEajS2JgCUY9LxXR/wdn6lzE0GANSDb+tt34bJzUp+Gdxvvo2SX4Ci
+    xsUokIpmA2gG7CW3gAPORSFuMu/VYZtvt+owNYlODXRPuGi/eLDknFRB/S4Nx0J0
+    WZZq5uTgJdQainyKYtDZALia5X4cc5I2hNetCorG9jNZIsSunbIAG+htx2FI3eqK
+    jwOUiHE8SCZ6YdXoDQjg2w+g8jeB23eqkPyzunpZphYiKay7VFeLwQEMC2a791ln
+    +MbHhhpRAc1uAoU2reB2fxKyaPlOfAWVMgUOGlgpVOuEVeMoc1CwjajaFztGG7fI
+    8EHNoyAftCdXnTaLZk2KZnnIDHHzFXR62TE1GJFD1fdI1pHAloCbgA4h+Dtwm1Uu
+    iAEEfvVU/E5wbtAzv6pY32+OKX5kyHAbM5/e918B8ZxmHG1J9QKCAQEA6FwxsRG3
+    526NnZak540yboht5kV12BNBChjmARv/XgZ7o1VsfwjaosErMvasUBcHDEYOC/oE
+    ZgPAyrMVsYm0xe/5FSIFLJVeYXTr0rmCNhVtBCHx3IS94BCXreNnz0qoEWnb5E09
+    Z1O42D0yGcLXklg6QaJfb7EdHh03F3dSVMHyDR3JlAQHRINeuP6LlQpbvRD3adH5
+    QWr2M3k+Stuq2OJdG7eUS1dreCxRShLuDjDhiZekdl/TB3LM0prOaWrKBrryN2g6
+    mjiasH6I5zRD3LQP5zg57Thb8afHqA4Fb85Frt6ltfFlPTIoxXZ5drVhmRWfXXnQ
+    POnj8T+w4zVjvwKCAQEA1J4ivyFkCL0JTSY3/PtwAQvBBj3GazzU6P+urWeH74Vh
+    WK17Ae40iOUHGyy80Db/fVY4VLQTpxvAeG91Gj5Nd/AucXJgOrisabcEz6N/xUs5
+    sjJNgXuNKTAgjYBu0bqLXxgZj43zT8JhA6KW7RuYU0PtHMRragz4RbK9NWDaVvJb
+    xSR5QoVLS00PerUa0SfupEYKCrlSTP6FOM5YNkCuSMt7X6/m9cR0WwVINKvUQBiT
+    ObrN+KeBmF9awpQQnQOq/GbCl3kf6VyPQqYFhdrWSg52w33c2tBVYrtHJpeXGcin
+    akw4KKcj4rdU2qxMuuRiD5paagshbLdGsYMTbSzjCwKCAQEAh89DGAyUIcfDLAWd
+    st0bSfGh0oJsw3NVg3JUFPfpRWqiny/Rr1pcd95RwoLc6h7bdrgHg8aJBZtR9ue/
+    WTp0l3CQdGKjBZD0TiAJqevViIjzZAP3Gn3XgPwRu4f75/Pp0eu+o2zl49vSYUk7
+    XEU+vIGm4y/leiHaM/y9c5DBZVrKgBIV/NZx7QCfv56/tMgOIK6m/YnFlw/OgP1v
+    hE9qR0PfSdD98x9QaDf290WjMFYvrL0eWjXd4S+fOcVTude55z8jTXE1N2i4OUpr
+    +D7bH0d7OBjr+pQDYXZAQyCW2ueEYRYvYu2Jz7/ehrOdgN25AsHZmMgXB1NpcFta
+    pyJQfwKCAQByoPMwworRH0GVg4Zp8RFYrwKZH9MK29gZ6kc9m/Sw0OND0PvhdZCD
+    QZ8MKpl9VDl4VHS4TgHOdWrWQ5kJ1g8kG6yeY0C4R/pEYHTKkWaAcucfSHl61qar
+    TxQt1dFpZz5evXqCZ9CG7tApCo5+NQNx2MxMVyVmHqn3wb66uYXdnHqXlet+Tqji
+    ZyByUpOrsfC6RjyBvZo+gnZGwxDR5xtPiczxML+/PvRQYk+kfgNHrzgoxqrnZT+8
+    a6ReBT/TtzeHLsu4qIfo44slLqcJnIstkBC9ouzgV7PBMCDTEKVZNFH2QDOCz2HM
+    iHTKFFyl4h1wNhKK24dguor1hyqBENMzAoIBAAQvQHwRWIVlfCMRI170Ls8AXB9Z
+    MMdZJ37bh6kmJpkV3+HB1ZkKwofHKR9h/3xLt5iYXzqT+/zA4EAsFFs1A93+tkzh
+    yPrN5iTSJicophZSlA4ObX1hMkgshvl7ZB1fRM5WyiszBOfm8W7eAxaK8nY2oAoP
+    tI7rioo6CFBNMCGbOl4gEX6YJ4OsVSm+efCRSDDw+3HW8H2YgqufBzAULk1Jcj5t
+    ZvraXpC5qZ92VtsH0cGA1ovNDAmoOV4AAvtZVpLQsXwaphad/Fbn/ItGrrluvvFC
+    HuldRzYtl/AQtoirK86LTY3aAmcwVFuiYvDQMzjzkJvVMmRCFZBcUIaz2oI=
+    -----END RSA PRIVATE KEY-----
+  '';
+  "letsencrypt.org".cert = builtins.toFile "letsencrypt.org.cert" ''
+    -----BEGIN CERTIFICATE-----
+    MIIEpzCCAo8CAgKaMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC1NuYWtlb2ls
+    IENBMCAXDTE4MDcxMjAwMjIxOVoYDzIxMTgwNjE4MDAyMjE5WjAaMRgwFgYDVQQD
+    DA9sZXRzZW5jcnlwdC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+    AQDA++GXB6aA+Lr3X2xIPs/PqXoJF9TUb98NzQC+ww3+dOaIY0Omqf15/UID5G0u
+    0649zUsvISi9x+7vn9X7opKA7iTX0TYKsUIXCMQ5YMYXgOByVPfvnVYaYUmDM8R9
+    l+Fs6uZB9Bq7zorEC01lWn0XFwpu4vJD+w03F80wH7sgdtWHC/NVyd0elhkQ/qR2
+    fC80s/bZui5fmrdq/FZK2WfTW5nQF9SbhfDFpQ0hTu3QO/noUr0L2Fb0Blp7R2jQ
+    EkFZEXvFLAt4Mmqf25nP6xfRf+0hppJBIG5nJKPQd7lkrN1Q7S2nHbFNLYGuXdjY
+    E4lo4T3we5ta90qiyUu3hfatWszEZrzpKYXNzIMvVFKjBw/+u7LLnnmXul6kvmzh
+    yYyBEEFePm1eGpRNU6flY74kKUuT6u2+0BPiuiAmYyoQqYzDa3ss3LVb2uL/fvxA
+    b+LcAZosgv/saw9u5E7nVJ22LhAxV7bM0XN7vabM2aPVJeiRbLv5m+NQynnmgSNF
+    GxM9Rl004QclbLo6zbOe6ovtmkgcLEEbwUqeQJWJdEmoADxvNwSFH6ktqYsg4eW/
+    iR3MnqmEeRK8rryq9tFF5SY6MmBK6lUEj2OEr58drL9RZHjN8lB5330fxk6iJeWs
+    sJWz5U8PKdRQqvMXWdjKiU3OL81Lh7gBud9aDILkkpGmNQIDAQABMA0GCSqGSIb3
+    DQEBCwUAA4ICAQAkx3jcryukAuYP7PQxMy3LElOl65ZFVqxDtTDlr7DvAkWJzVCb
+    g08L6Tu+K0rKh2RbG/PqS0+8/jBgc4IwSOPfDDAX+sinfj0kwXG34WMzB0G3fQzU
+    2BMplJDOaBcNqHG8pLP1BG+9HAtR/RHe9p2Jw8LG2qmZs6uemPT/nCTNoyIL4oxh
+    UncjETV4ayCHDKD1XA7/icgddYsnfLQHWuIMuCrmQCHo0uQAd7qVHfUWZ+gcsZx0
+    jTNCcaI8OTS2S65Bjaq2HaM7GMcUYNUD2vSyNQeQbha4ZeyZ9bPyFzznPMmrPXQe
+    MJdkbJ009RQIG9As79En4m+l+/6zrdx4DNdROqaL6YNiSebWMnuFHpMW/rCnhrT/
+    HYadijHOiJJGj9tWSdC4XJs7fvZW3crMPUYxpOvl01xW2ZlgaekILi1FAjSMQVoV
+    NhWstdGCKJdthJqLL5MtNdfgihKcmgkJqKFXTkPv7sgAQCopu6X+S+srCgn856Lv
+    21haRWZa8Ml+E0L/ticT8Fd8Luysc6K9TJ4mT8ENC5ywvgDlEkwBD3yvINXm5lg1
+    xOIxv/Ye5gFk1knuM7OzpUFBrXUHdVVxflCUqNAhFPbcXwjgEQ+A+S5B0vI6Ohue
+    ZnR/wuiou6Y+Yzh8XfqL/3H18mGDdjyMXI1B6l4Judk000UVyr46cnI7mw==
+    -----END CERTIFICATE-----
+  '';
+}
diff --git a/nixpkgs/nixos/tests/common/resolver.nix b/nixpkgs/nixos/tests/common/resolver.nix
new file mode 100644
index 000000000000..6be8d1d18e62
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/resolver.nix
@@ -0,0 +1,141 @@
+# This module automatically discovers zones in BIND and NSD NixOS
+# configurations and creates zones for all definitions of networking.extraHosts
+# (except those that point to 127.0.0.1 or ::1) within the current test network
+# and delegates these zones using a fake root zone served by a BIND recursive
+# name server.
+{ config, nodes, pkgs, lib, ... }:
+
+{
+  options.test-support.resolver.enable = lib.mkOption {
+    type = lib.types.bool;
+    default = true;
+    internal = true;
+    description = ''
+      Whether to enable the resolver that automatically discovers zone in the
+      test network.
+
+      This option is <literal>true</literal> by default, because the module
+      defining this option needs to be explicitly imported.
+
+      The reason this option exists is for the
+      <filename>nixos/tests/common/letsencrypt</filename> module, which
+      needs that option to disable the resolver once the user has set its own
+      resolver.
+    '';
+  };
+
+  config = lib.mkIf config.test-support.resolver.enable {
+    networking.firewall.enable = false;
+    services.bind.enable = true;
+    services.bind.cacheNetworks = lib.mkForce [ "any" ];
+    services.bind.forwarders = lib.mkForce [];
+    services.bind.zones = lib.singleton {
+      name = ".";
+      file = let
+        addDot = zone: zone + lib.optionalString (!lib.hasSuffix "." zone) ".";
+        mkNsdZoneNames = zones: map addDot (lib.attrNames zones);
+        mkBindZoneNames = zones: map (zone: addDot zone.name) zones;
+        getZones = cfg: mkNsdZoneNames cfg.services.nsd.zones
+                     ++ mkBindZoneNames cfg.services.bind.zones;
+
+        getZonesForNode = attrs: {
+          ip = attrs.config.networking.primaryIPAddress;
+          zones = lib.filter (zone: zone != ".") (getZones attrs.config);
+        };
+
+        zoneInfo = lib.mapAttrsToList (lib.const getZonesForNode) nodes;
+
+        # A and AAAA resource records for all the definitions of
+        # networking.extraHosts except those for 127.0.0.1 or ::1.
+        #
+        # The result is an attribute set with keys being the host name and the
+        # values are either { ipv4 = ADDR; } or { ipv6 = ADDR; } where ADDR is
+        # the IP address for the corresponding key.
+        recordsFromExtraHosts = let
+          getHostsForNode = lib.const (n: n.config.networking.extraHosts);
+          allHostsList = lib.mapAttrsToList getHostsForNode nodes;
+          allHosts = lib.concatStringsSep "\n" allHostsList;
+
+          reIp = "[a-fA-F0-9.:]+";
+          reHost = "[a-zA-Z0-9.-]+";
+
+          matchAliases = str: let
+            matched = builtins.match "[ \t]+(${reHost})(.*)" str;
+            continue = lib.singleton (lib.head matched)
+                    ++ matchAliases (lib.last matched);
+          in if matched == null then [] else continue;
+
+          matchLine = str: let
+            result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str;
+          in if result == null then null else {
+            ipAddr = lib.head result;
+            hosts = lib.singleton (lib.elemAt result 1)
+                 ++ matchAliases (lib.last result);
+          };
+
+          skipLine = str: let
+            rest = builtins.match "[^\n]*\n(.*)" str;
+          in if rest == null then "" else lib.head rest;
+
+          getEntries = str: acc: let
+            result = matchLine str;
+            next = getEntries (skipLine str);
+            newEntry = acc ++ lib.singleton result;
+            continue = if result == null then next acc else next newEntry;
+          in if str == "" then acc else continue;
+
+          isIPv6 = str: builtins.match ".*:.*" str != null;
+          loopbackIps = [ "127.0.0.1" "::1" ];
+          filterLoopback = lib.filter (e: !lib.elem e.ipAddr loopbackIps);
+
+          allEntries = lib.concatMap (entry: map (host: {
+            inherit host;
+            ${if isIPv6 entry.ipAddr then "ipv6" else "ipv4"} = entry.ipAddr;
+          }) entry.hosts) (filterLoopback (getEntries (allHosts + "\n") []));
+
+          mkRecords = entry: let
+            records = lib.optional (entry ? ipv6) "AAAA ${entry.ipv6}"
+                   ++ lib.optional (entry ? ipv4) "A ${entry.ipv4}";
+            mkRecord = typeAndData: "${entry.host}. IN ${typeAndData}";
+          in lib.concatMapStringsSep "\n" mkRecord records;
+
+        in lib.concatMapStringsSep "\n" mkRecords allEntries;
+
+        # All of the zones that are subdomains of existing zones.
+        # For example if there is only "example.com" the following zones would
+        # be 'subZones':
+        #
+        #  * foo.example.com.
+        #  * bar.example.com.
+        #
+        # While the following would *not* be 'subZones':
+        #
+        #  * example.com.
+        #  * com.
+        #
+        subZones = let
+          allZones = lib.concatMap (zi: zi.zones) zoneInfo;
+          isSubZoneOf = z1: z2: lib.hasSuffix z2 z1 && z1 != z2;
+        in lib.filter (z: lib.any (isSubZoneOf z) allZones) allZones;
+
+        # All the zones without 'subZones'.
+        filteredZoneInfo = map (zi: zi // {
+          zones = lib.filter (x: !lib.elem x subZones) zi.zones;
+        }) zoneInfo;
+
+      in pkgs.writeText "fake-root.zone" ''
+        $TTL 3600
+        . IN SOA ns.fakedns. admin.fakedns. ( 1 3h 1h 1w 1d )
+        ns.fakedns. IN A ${config.networking.primaryIPAddress}
+        . IN NS ns.fakedns.
+        ${lib.concatImapStrings (num: { ip, zones }: ''
+          ns${toString num}.fakedns. IN A ${ip}
+          ${lib.concatMapStrings (zone: ''
+          ${zone} IN NS ns${toString num}.fakedns.
+          '') zones}
+        '') (lib.filter (zi: zi.zones != []) filteredZoneInfo)}
+        ${recordsFromExtraHosts}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/common/user-account.nix b/nixpkgs/nixos/tests/common/user-account.nix
new file mode 100644
index 000000000000..9cd531a1f96c
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/user-account.nix
@@ -0,0 +1,14 @@
+{ ... }:
+
+{ users.users.alice =
+    { isNormalUser = true;
+      description = "Alice Foobar";
+      password = "foobar";
+    };
+
+  users.users.bob =
+    { isNormalUser = true;
+      description = "Bob Foobar";
+      password = "foobar";
+    };
+}
diff --git a/nixpkgs/nixos/tests/common/webroot/news-rss.xml b/nixpkgs/nixos/tests/common/webroot/news-rss.xml
new file mode 100644
index 000000000000..28e6fa7da1f3
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/webroot/news-rss.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss xmlns:blogChannel="http://backend.userland.com/blogChannelModule" version="2.0"><channel><title>NixOS News</title><link>https://nixos.org</link><description>News for NixOS, the purely functional Linux distribution.</description><image><title>NixOS</title><url>https://nixos.org/logo/nixos-logo-only-hires.png</url><link>https://nixos.org/</link></image><item><title>
+      NixOS 18.09 released
+    </title><link>https://nixos.org/news.html</link><description>
+      <a href="https://github.com/NixOS/nixos-artwork/blob/master/releases/18.09-jellyfish/jellyfish.png">
+        <img class="inline" src="logo/nixos-logo-18.09-jellyfish-lores.png" alt="18.09 Jellyfish logo" with="100" height="87"/>
+      </a>
+      NixOS 18.09 “Jellyfish” has been released, the tenth stable release branch.
+      See the <a href="/nixos/manual/release-notes.html#sec-release-18.09">release notes</a>
+      for details. You can get NixOS 18.09 ISOs and VirtualBox appliances
+      from the <a href="nixos/download.html">download page</a>.
+      For information on how to upgrade from older release branches
+      to 18.09, check out the
+      <a href="/nixos/manual/index.html#sec-upgrading">manual section on upgrading</a>.
+    </description><pubDate>Sat Oct 06 2018 00:00:00 GMT</pubDate></item></channel></rss>
diff --git a/nixpkgs/nixos/tests/common/x11.nix b/nixpkgs/nixos/tests/common/x11.nix
new file mode 100644
index 000000000000..c5a7c165d126
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-bridge.nix b/nixpkgs/nixos/tests/containers-bridge.nix
new file mode 100644
index 000000000000..0eae51433d20
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-bridge.nix
@@ -0,0 +1,103 @@
+# Test for NixOS' container support.
+
+let
+  hostIp = "192.168.0.1";
+  containerIp = "192.168.0.100/24";
+  hostIp6 = "fc00::1";
+  containerIp6 = "fc00::2/7";
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-bridge";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ];
+  };
+
+  machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      networking.bridges = {
+        br0 = {
+          interfaces = [];
+        };
+      };
+      networking.interfaces = {
+        br0 = {
+          ipv4.addresses = [{ address = hostIp; prefixLength = 24; }];
+          ipv6.addresses = [{ address = hostIp6; prefixLength = 7; }];
+        };
+      };
+
+      containers.webserver =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          localAddress = containerIp;
+          localAddress6 = containerIp6;
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+      containers.web-noip =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Start the webserver container.
+      $machine->succeed("nixos-container status webserver") =~ /up/ or die;
+
+      # Check if bridges exist inside containers
+      $machine->succeed("nixos-container run webserver -- ip link show eth0");
+      $machine->succeed("nixos-container run web-noip -- ip link show eth0");
+
+      "${containerIp}" =~ /([^\/]+)\/([0-9+])/;
+      my $ip = $1;
+      chomp $ip;
+      $machine->succeed("ping -n -c 1 $ip");
+      $machine->succeed("curl --fail http://$ip/ > /dev/null");
+
+      "${containerIp6}" =~ /([^\/]+)\/([0-9+])/;
+      my $ip6 = $1;
+      chomp $ip6;
+      $machine->succeed("ping -n -c 1 $ip6");
+      $machine->succeed("curl --fail http://[$ip6]/ > /dev/null");
+
+      # Check that nixos-container show-ip works in case of an ipv4 address with
+      # subnetmask in CIDR notation.
+      my $result = $machine->succeed("nixos-container show-ip webserver");
+      chomp $result;
+      $result eq $ip or die;
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("curl --fail --connect-timeout 2 http://$ip/ > /dev/null");
+      $machine->fail("curl --fail --connect-timeout 2 http://[$ip6]/ > /dev/null");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-extra_veth.nix b/nixpkgs/nixos/tests/containers-extra_veth.nix
new file mode 100644
index 000000000000..b3d3bce87579
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-extra_veth.nix
@@ -0,0 +1,103 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-bridge";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ kampfschlaefer ];
+  };
+
+  machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+      virtualisation.vlans = [];
+
+      networking.useDHCP = false;
+      networking.bridges = {
+        br0 = {
+          interfaces = [];
+        };
+        br1 = { interfaces = []; };
+      };
+      networking.interfaces = {
+        br0 = {
+          ipv4.addresses = [{ address = "192.168.0.1"; prefixLength = 24; }];
+          ipv6.addresses = [{ address = "fc00::1"; prefixLength = 7; }];
+        };
+        br1 = {
+          ipv4.addresses = [{ address = "192.168.1.1"; prefixLength = 24; }];
+        };
+      };
+
+      containers.webserver =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          localAddress = "192.168.0.100/24";
+          localAddress6 = "fc00::2/7";
+          extraVeths = {
+            veth1 = { hostBridge = "br1"; localAddress = "192.168.1.100/24"; };
+            veth2 = { hostAddress = "192.168.2.1"; localAddress = "192.168.2.100"; };
+          };
+          config =
+            {
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Status of the webserver container.
+      $machine->succeed("nixos-container status webserver") =~ /up/ or die;
+
+      # Debug
+      #$machine->succeed("nixos-container run webserver -- ip link >&2");
+
+      # Ensure that the veths are inside the container
+      $machine->succeed("nixos-container run webserver -- ip link show veth1") =~ /state UP/ or die;
+      $machine->succeed("nixos-container run webserver -- ip link show veth2") =~ /state UP/ or die;
+
+      # Debug
+      #$machine->succeed("ip link >&2");
+
+      # Ensure the presence of the extra veths
+      $machine->succeed("ip link show veth1") =~ /state UP/ or die;
+      $machine->succeed("ip link show veth2") =~ /state UP/ or die;
+
+      # Ensure the veth1 is part of br1 on the host
+      $machine->succeed("ip link show veth1") =~ /master br1/ or die;
+
+      # Debug
+      #$machine->succeed("ip -4 a >&2");
+      #$machine->succeed("ip -4 r >&2");
+      #$machine->succeed("nixos-container run webserver -- ip link >&2");
+      #$machine->succeed("nixos-container run webserver -- ip -4 a >&2");
+      #$machine->succeed("nixos-container run webserver -- ip -4 r >&2");
+
+      # Ping on main veth
+      $machine->succeed("ping -n -c 1 192.168.0.100");
+      $machine->succeed("ping -n -c 1 fc00::2");
+
+      # Ping on the first extra veth
+      $machine->succeed("ping -n -c 1 192.168.1.100 >&2");
+
+      # Ping on the second extra veth
+      $machine->succeed("ping -n -c 1 192.168.2.100 >&2");
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("ping -n -c 1 192.168.1.100 >&2");
+      $machine->fail("ping -n -c 1 192.168.2.100 >&2");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/containers-hosts.nix b/nixpkgs/nixos/tests/containers-hosts.nix
new file mode 100644
index 000000000000..8cf298c62258
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-hosts.nix
@@ -0,0 +1,52 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-hosts";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ montag451 ];
+  };
+
+  machine =
+    { lib, ... }:
+    {
+      virtualisation.memorySize = 256;
+      virtualisation.vlans = [];
+
+      networking.bridges.br0.interfaces = [];
+      networking.interfaces.br0.ipv4.addresses = [
+        { address = "10.11.0.254"; prefixLength = 24; }
+      ];
+
+      # Force /etc/hosts to be the only source for host name resolution
+      environment.etc."nsswitch.conf".text = lib.mkForce ''
+        hosts: files
+      '';
+
+      containers.simple = {
+        autoStart = true;
+        privateNetwork = true;
+        localAddress = "10.10.0.1";
+        hostAddress = "10.10.0.254";
+
+        config = {};
+      };
+
+      containers.netmask = {
+        autoStart = true;
+        privateNetwork = true;
+        hostBridge = "br0";
+        localAddress = "10.11.0.1/24";
+
+        config = {};
+      };
+    };
+
+  testScript = ''
+    startAll;
+    $machine->waitForUnit("default.target");
+
+    # Ping the containers using the entries added in /etc/hosts
+    $machine->succeed("ping -n -c 1 simple.containers");
+    $machine->succeed("ping -n -c 1 netmask.containers");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-imperative.nix b/nixpkgs/nixos/tests/containers-imperative.nix
new file mode 100644
index 000000000000..782095a09dad
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-imperative.nix
@@ -0,0 +1,111 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-imperative";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ];
+  };
+
+  machine =
+    { config, pkgs, lib, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+
+      # XXX: Sandbox setup fails while trying to hardlink files from the host's
+      #      store file system into the prepared chroot directory.
+      nix.useSandbox = false;
+      nix.binaryCaches = []; # don't try to access cache.nixos.org
+
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 1024;
+      # Make sure we always have all the required dependencies for creating a
+      # container available within the VM, because we don't have network access.
+      virtualisation.pathsInNixDB = let
+        emptyContainer = import ../lib/eval-config.nix {
+          inherit (config.nixpkgs.localSystem) system;
+          modules = lib.singleton {
+            containers.foo.config = {
+              system.stateVersion = "18.03";
+            };
+          };
+        };
+      in with pkgs; [
+        stdenv stdenvNoCC emptyContainer.config.containers.foo.path
+        libxslt desktop-file-utils texinfo docbook5 libxml2
+        docbook_xsl_ns xorg.lndir documentation-highlighter
+      ];
+    };
+
+  testScript =
+    ''
+      # Make sure we have a NixOS tree (required by ‘nixos-container create’).
+      $machine->succeed("PAGER=cat nix-env -qa -A nixos.hello >&2");
+
+      # Create some containers imperatively.
+      my $id1 = $machine->succeed("nixos-container create foo --ensure-unique-name");
+      chomp $id1;
+      $machine->log("created container $id1");
+
+      my $id2 = $machine->succeed("nixos-container create foo --ensure-unique-name");
+      chomp $id2;
+      $machine->log("created container $id2");
+
+      die if $id1 eq $id2;
+
+      # Put the root of $id2 into a bind mount.
+      $machine->succeed(
+        "mv /var/lib/containers/$id2 /id2-bindmount",
+        "mount --bind /id2-bindmount /var/lib/containers/$id1"
+      );
+
+      my $ip1 = $machine->succeed("nixos-container show-ip $id1");
+      chomp $ip1;
+      my $ip2 = $machine->succeed("nixos-container show-ip $id2");
+      chomp $ip2;
+      die if $ip1 eq $ip2;
+
+      # Create a directory and a file we can later check if it still exists
+      # after destruction of the container.
+      $machine->succeed(
+        "mkdir /nested-bindmount",
+        "echo important data > /nested-bindmount/dummy",
+      );
+
+      # Create a directory with a dummy file and bind-mount it into both
+      # containers.
+      foreach ($id1, $id2) {
+        my $importantPath = "/var/lib/containers/$_/very/important/data";
+        $machine->succeed(
+          "mkdir -p $importantPath",
+          "mount --bind /nested-bindmount $importantPath"
+        );
+      }
+
+      # Start one of them.
+      $machine->succeed("nixos-container start $id1");
+
+      # Execute commands via the root shell.
+      $machine->succeed("nixos-container run $id1 -- uname") =~ /Linux/ or die;
+
+      # Execute a nix command via the root shell. (regression test for #40355)
+      $machine->succeed("nixos-container run $id1 -- nix-instantiate -E 'derivation { name = \"empty\"; builder = \"false\"; system = \"false\"; }'");
+
+      # Stop and start (regression test for #4989)
+      $machine->succeed("nixos-container stop $id1");
+      $machine->succeed("nixos-container start $id1");
+
+      # Execute commands via the root shell.
+      $machine->succeed("nixos-container run $id1 -- uname") =~ /Linux/ or die;
+
+      # Destroy the containers.
+      $machine->succeed("nixos-container destroy $id1");
+      $machine->succeed("nixos-container destroy $id2");
+
+      $machine->succeed(
+        # Check whether destruction of any container has killed important data
+        "grep -qF 'important data' /nested-bindmount/dummy",
+        # Ensure that the container path is gone
+        "test ! -e /var/lib/containers/$id1"
+      );
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-ipv4.nix b/nixpkgs/nixos/tests/containers-ipv4.nix
new file mode 100644
index 000000000000..5f83a33b1079
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-ipv4.nix
@@ -0,0 +1,55 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-ipv4";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ];
+  };
+
+  machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      containers.webserver =
+        { privateNetwork = true;
+          hostAddress = "10.231.136.1";
+          localAddress = "10.231.136.2";
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+              system.stateVersion = "18.03";
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Start the webserver container.
+      $machine->succeed("nixos-container start webserver");
+
+      # wait two seconds for the container to start and the network to be up
+      sleep 2;
+
+      # Since "start" returns after the container has reached
+      # multi-user.target, we should now be able to access it.
+      my $ip = $machine->succeed("nixos-container show-ip webserver");
+      chomp $ip;
+      $machine->succeed("ping -n -c1 $ip");
+      $machine->succeed("curl --fail http://$ip/ > /dev/null");
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("curl --fail --connect-timeout 2 http://$ip/ > /dev/null");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-ipv6.nix b/nixpkgs/nixos/tests/containers-ipv6.nix
new file mode 100644
index 000000000000..5866e51b731d
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-ipv6.nix
@@ -0,0 +1,60 @@
+# Test for NixOS' container support.
+
+let
+  hostIp = "fc00::2";
+  localIp = "fc00::1";
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-ipv6";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ];
+  };
+
+  machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      containers.webserver =
+        { privateNetwork = true;
+          hostAddress6 = hostIp;
+          localAddress6 = localIp;
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Start the webserver container.
+      $machine->succeed("nixos-container start webserver");
+
+      # wait two seconds for the container to start and the network to be up
+      sleep 2;
+
+      # Since "start" returns after the container has reached
+      # multi-user.target, we should now be able to access it.
+      my $ip = "${localIp}";
+      chomp $ip;
+      $machine->succeed("ping -n -c 1 $ip");
+      $machine->succeed("curl --fail http://[$ip]/ > /dev/null");
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("curl --fail --connect-timeout 2 http://[$ip]/ > /dev/null");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-macvlans.nix b/nixpkgs/nixos/tests/containers-macvlans.nix
new file mode 100644
index 000000000000..2bdb926a8e2b
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-macvlans.nix
@@ -0,0 +1,82 @@
+# Test for NixOS' container support.
+
+let
+  # containers IP on VLAN 1
+  containerIp1 = "192.168.1.253";
+  containerIp2 = "192.168.1.254";
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-macvlans";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ montag451 ];
+  };
+
+  nodes = {
+
+    machine1 =
+      { lib, ... }:
+      {
+        virtualisation.memorySize = 256;
+        virtualisation.vlans = [ 1 ];
+
+        # To be able to ping containers from the host, it is necessary
+        # to create a macvlan on the host on the VLAN 1 network.
+        networking.macvlans.mv-eth1-host = {
+          interface = "eth1";
+          mode = "bridge";
+        };
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [];
+        networking.interfaces.mv-eth1-host = {
+          ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+        };
+
+        containers.test1 = {
+          autoStart = true;
+          macvlans = [ "eth1" ];
+
+          config = {
+            networking.interfaces.mv-eth1 = {
+              ipv4.addresses = [ { address = containerIp1; prefixLength = 24; } ];
+            };
+          };
+        };
+
+        containers.test2 = {
+          autoStart = true;
+          macvlans = [ "eth1" ];
+
+          config = {
+            networking.interfaces.mv-eth1 = {
+              ipv4.addresses = [ { address = containerIp2; prefixLength = 24; } ];
+            };
+          };
+        };
+      };
+
+    machine2 =
+      { ... }:
+      {
+        virtualisation.memorySize = 256;
+        virtualisation.vlans = [ 1 ];
+      };
+
+  };
+
+  testScript = ''
+    startAll;
+    $machine1->waitForUnit("default.target");
+    $machine2->waitForUnit("default.target");
+
+    # Ping between containers to check that macvlans are created in bridge mode
+    $machine1->succeed("nixos-container run test1 -- ping -n -c 1 ${containerIp2}");
+
+    # Ping containers from the host (machine1)
+    $machine1->succeed("ping -n -c 1 ${containerIp1}");
+    $machine1->succeed("ping -n -c 1 ${containerIp2}");
+
+    # Ping containers from the second machine to check that containers are reachable from the outside
+    $machine2->succeed("ping -n -c 1 ${containerIp1}");
+    $machine2->succeed("ping -n -c 1 ${containerIp2}");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-physical_interfaces.nix b/nixpkgs/nixos/tests/containers-physical_interfaces.nix
new file mode 100644
index 000000000000..1e312f59f437
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-physical_interfaces.nix
@@ -0,0 +1,133 @@
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-physical_interfaces";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ kampfschlaefer ];
+  };
+
+  nodes = {
+    server = { ... }:
+      {
+        virtualisation.memorySize = 256;
+        virtualisation.vlans = [ 1 ];
+
+        containers.server = {
+          privateNetwork = true;
+          interfaces = [ "eth1" ];
+
+          config = {
+            networking.interfaces.eth1.ipv4.addresses = [
+              { address = "10.10.0.1"; prefixLength = 24; }
+            ];
+            networking.firewall.enable = false;
+          };
+        };
+      };
+    bridged = { ... }: {
+      virtualisation.memorySize = 128;
+      virtualisation.vlans = [ 1 ];
+
+      containers.bridged = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bridges.br0.interfaces = [ "eth1" ];
+          networking.interfaces.br0.ipv4.addresses = [
+            { address = "10.10.0.2"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+
+    bonded = { ... }: {
+      virtualisation.memorySize = 128;
+      virtualisation.vlans = [ 1 ];
+
+      containers.bonded = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bonds.bond0 = {
+            interfaces = [ "eth1" ];
+            driverOptions.mode = "active-backup";
+          };
+          networking.interfaces.bond0.ipv4.addresses = [
+            { address = "10.10.0.3"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+
+    bridgedbond = { ... }: {
+      virtualisation.memorySize = 128;
+      virtualisation.vlans = [ 1 ];
+
+      containers.bridgedbond = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bonds.bond0 = {
+            interfaces = [ "eth1" ];
+            driverOptions.mode = "active-backup";
+          };
+          networking.bridges.br0.interfaces = [ "bond0" ];
+          networking.interfaces.br0.ipv4.addresses = [
+            { address = "10.10.0.4"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    subtest "prepare server", sub {
+      $server->waitForUnit("default.target");
+      $server->succeed("ip link show dev eth1 >&2");
+    };
+
+    subtest "simple physical interface", sub {
+      $server->succeed("nixos-container start server");
+      $server->waitForUnit("container\@server");
+      $server->succeed("systemctl -M server list-dependencies network-addresses-eth1.service >&2");
+
+      # The other tests will ping this container on its ip. Here we just check
+      # that the device is present in the container.
+      $server->succeed("nixos-container run server -- ip a show dev eth1 >&2");
+    };
+
+    subtest "physical device in bridge in container", sub {
+      $bridged->waitForUnit("default.target");
+      $bridged->succeed("nixos-container start bridged");
+      $bridged->waitForUnit("container\@bridged");
+      $bridged->succeed("systemctl -M bridged list-dependencies network-addresses-br0.service >&2");
+      $bridged->succeed("systemctl -M bridged status -n 30 -l network-addresses-br0.service");
+      $bridged->succeed("nixos-container run bridged -- ping -w 10 -c 1 -n 10.10.0.1");
+    };
+
+    subtest "physical device in bond in container", sub {
+      $bonded->waitForUnit("default.target");
+      $bonded->succeed("nixos-container start bonded");
+      $bonded->waitForUnit("container\@bonded");
+      $bonded->succeed("systemctl -M bonded list-dependencies network-addresses-bond0 >&2");
+      $bonded->succeed("systemctl -M bonded status -n 30 -l network-addresses-bond0 >&2");
+      $bonded->succeed("nixos-container run bonded -- ping -w 10 -c 1 -n 10.10.0.1");
+    };
+
+    subtest "physical device in bond in bridge in container", sub {
+      $bridgedbond->waitForUnit("default.target");
+      $bridgedbond->succeed("nixos-container start bridgedbond");
+      $bridgedbond->waitForUnit("container\@bridgedbond");
+      $bridgedbond->succeed("systemctl -M bridgedbond list-dependencies network-addresses-br0.service >&2");
+      $bridgedbond->succeed("systemctl -M bridgedbond status -n 30 -l network-addresses-br0.service");
+      $bridgedbond->succeed("nixos-container run bridgedbond -- ping -w 10 -c 1 -n 10.10.0.1");
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-portforward.nix b/nixpkgs/nixos/tests/containers-portforward.nix
new file mode 100644
index 000000000000..d2dda926fc0e
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-portforward.nix
@@ -0,0 +1,62 @@
+# Test for NixOS' container support.
+
+let
+  hostIp = "192.168.0.1";
+  hostPort = 10080;
+  containerIp = "192.168.0.100";
+  containerPort = 80;
+in 
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-portforward";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aristid aszlig eelco chaoflow kampfschlaefer ianwookim ];
+  };
+
+  machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      containers.webserver =
+        { privateNetwork = true;
+          hostAddress = hostIp;
+          localAddress = containerIp;
+          forwardPorts = [ { protocol = "tcp"; hostPort = hostPort; containerPort = containerPort; } ];
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+      # Start the webserver container.
+      $machine->succeed("nixos-container start webserver");
+
+      # wait two seconds for the container to start and the network to be up
+      sleep 2;
+
+      # Since "start" returns after the container has reached
+      # multi-user.target, we should now be able to access it.
+      #my $ip = $machine->succeed("nixos-container show-ip webserver");
+      #chomp $ip;
+      $machine->succeed("ping -n -c1 ${hostIp}");
+      $machine->succeed("curl --fail http://${hostIp}:${toString hostPort}/ > /dev/null");
+
+      # Stop the container.
+      $machine->succeed("nixos-container stop webserver");
+      $machine->fail("curl --fail --connect-timeout 2 http://${hostIp}:${toString hostPort}/ > /dev/null");
+
+      # Destroying a declarative container should fail.
+      $machine->fail("nixos-container destroy webserver");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-reloadable.nix b/nixpkgs/nixos/tests/containers-reloadable.nix
new file mode 100644
index 000000000000..9726ca0cb0e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-reloadable.nix
@@ -0,0 +1,65 @@
+import ./make-test.nix ({ pkgs, lib, ...} :
+let
+  client_base = rec {
+    
+    containers.test1 = {
+      autoStart = true;
+      config = {
+        environment.etc."check".text = "client_base";
+      };
+    };
+
+    # prevent make-test.nix to change IP
+    networking.interfaces = {
+      eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+    };
+  };
+in {
+  name = "cotnainers-reloadable";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ danbst ];
+  };
+
+  nodes = {
+    client = { ... }: {
+      imports = [ client_base ];
+    };
+
+    client_c1 = { lib, ... }: {
+      imports = [ client_base ];
+
+      containers.test1.config = {
+        environment.etc."check".text = lib.mkForce "client_c1";
+        services.httpd.enable = true;
+        services.httpd.adminAddr = "nixos@example.com";
+      };
+    };
+    client_c2 = { lib, ... }: {
+      imports = [ client_base ];
+
+      containers.test1.config = {
+        environment.etc."check".text = lib.mkForce "client_c2";
+        services.nginx.enable = true;
+      };
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    c1System = nodes.client_c1.config.system.build.toplevel;
+    c2System = nodes.client_c2.config.system.build.toplevel;
+  in ''
+    $client->start();
+    $client->waitForUnit("default.target");
+    $client->succeed("[[ \$(nixos-container run test1 cat /etc/check) == client_base ]] >&2");
+
+    $client->succeed("${c1System}/bin/switch-to-configuration test >&2");
+    $client->succeed("[[ \$(nixos-container run test1 cat /etc/check) == client_c1 ]] >&2");
+    $client->succeed("systemctl status httpd -M test1 >&2");
+
+    $client->succeed("${c2System}/bin/switch-to-configuration test >&2");
+    $client->succeed("[[ \$(nixos-container run test1 cat /etc/check) == client_c2 ]] >&2");
+    $client->fail("systemctl status httpd -M test1 >&2");
+    $client->succeed("systemctl status nginx -M test1 >&2");
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-restart_networking.nix b/nixpkgs/nixos/tests/containers-restart_networking.nix
new file mode 100644
index 000000000000..0fb3b591e9f9
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-restart_networking.nix
@@ -0,0 +1,113 @@
+# Test for NixOS' container support.
+
+let
+  client_base = rec {
+    networking.firewall.enable = false;
+
+    containers.webserver = {
+      autoStart = true;
+      privateNetwork = true;
+      hostBridge = "br0";
+      config = {
+        networking.firewall.enable = false;
+        networking.interfaces.eth0.ipv4.addresses = [
+          { address = "192.168.1.122"; prefixLength = 24; }
+        ];
+      };
+    };
+  };
+in import ./make-test.nix ({ pkgs, ...} :
+{
+  name = "containers-restart_networking";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ kampfschlaefer ];
+  };
+
+  nodes = {
+    client = { lib, ... }: client_base // {
+      virtualisation.vlans = [ 1 ];
+
+      networking.bridges.br0 = {
+        interfaces = [];
+        rstp = false;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+      };
+
+    };
+    client_eth1 = { lib, ... }: client_base // {
+      networking.bridges.br0 = {
+        interfaces = [ "eth1" ];
+        rstp = false;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+    };
+    client_eth1_rstp = { lib, ... }: client_base // {
+      networking.bridges.br0 = {
+        interfaces = [ "eth1" ];
+        rstp = true;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses =  [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    originalSystem = nodes.client.config.system.build.toplevel;
+    eth1_bridged = nodes.client_eth1.config.system.build.toplevel;
+    eth1_rstp = nodes.client_eth1_rstp.config.system.build.toplevel;
+  in ''
+    $client->start();
+
+    $client->waitForUnit("default.target");
+
+    subtest "initial state", sub {
+      $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
+      $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2");
+
+      $client->fail("ip l show eth1 |grep \"master br0\" >&2");
+      $client->fail("grep eth1 /run/br0.interfaces >&2");
+    };
+
+    subtest "interfaces without stp", sub {
+      $client->succeed("${eth1_bridged}/bin/switch-to-configuration test >&2");
+
+      $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
+      $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2");
+
+      $client->succeed("ip l show eth1 |grep \"master br0\" >&2");
+      $client->succeed("grep eth1 /run/br0.interfaces >&2");
+    };
+
+    # activating rstp needs another service, therefor the bridge will restart and the container will loose its connectivity
+    #subtest "interfaces with rstp", sub {
+    #  $client->succeed("${eth1_rstp}/bin/switch-to-configuration test >&2");
+    #  $client->execute("ip -4 a >&2");
+    #  $client->execute("ip l >&2");
+    #
+    #  $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
+    #  $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2");
+    #
+    #  $client->succeed("ip l show eth1 |grep \"master br0\" >&2");
+    #  $client->succeed("grep eth1 /run/br0.interfaces >&2");
+    #};
+
+    subtest "back to no interfaces and no stp", sub {
+      $client->succeed("${originalSystem}/bin/switch-to-configuration test >&2");
+
+      $client->succeed("ping 192.168.1.122 -c 1 -n >&2");
+      $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2");
+
+      $client->fail("ip l show eth1 |grep \"master br0\" >&2");
+      $client->fail("grep eth1 /run/br0.interfaces >&2");
+    };
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-tmpfs.nix b/nixpkgs/nixos/tests/containers-tmpfs.nix
new file mode 100644
index 000000000000..05c21f4907bf
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-tmpfs.nix
@@ -0,0 +1,79 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "containers-tmpfs";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ckampka ];
+  };
+
+  machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.memorySize = 768;
+
+      containers.tmpfs =
+        {
+          autoStart = true;
+          tmpfs = [
+            # Mount var as a tmpfs
+            "/var"
+
+            # Add a nested mount inside a tmpfs
+            "/var/log"
+
+            # Add a tmpfs on a path that does not exist
+            "/some/random/path"
+          ];
+          config = { };
+        };
+
+      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("nixos-container list") =~ /tmpfs/ or die;
+
+      # Start the tmpfs container.
+      #$machine->succeed("nixos-container status tmpfs") =~ /up/ or die;
+
+      # Verify that /var is mounted as a tmpfs
+      #$machine->succeed("nixos-container run tmpfs -- systemctl status var.mount --no-pager 2>/dev/null") =~ /What: tmpfs/ or die;
+      $machine->succeed("nixos-container run tmpfs -- mountpoint -q /var 2>/dev/null");
+
+      # Verify that /var/log is mounted as a tmpfs
+      $machine->succeed("nixos-container run tmpfs -- systemctl status var-log.mount --no-pager 2>/dev/null") =~ /What: tmpfs/ or die;
+      $machine->succeed("nixos-container run tmpfs -- mountpoint -q /var/log 2>/dev/null");
+
+      # Verify that /some/random/path is mounted as a tmpfs
+      $machine->succeed("nixos-container run tmpfs -- systemctl status some-random-path.mount --no-pager 2>/dev/null") =~ /What: tmpfs/ or die;
+      $machine->succeed("nixos-container run tmpfs -- mountpoint -q /some/random/path 2>/dev/null");
+
+      # Verify that files created in the container in a non-tmpfs directory are visible on the host.
+      # This establishes legitimacy for the following tests
+      $machine->succeed("nixos-container run tmpfs -- touch /root/test.file 2>/dev/null");
+      $machine->succeed("nixos-container run tmpfs -- ls -l  /root | grep -q test.file 2>/dev/null");
+      $machine->succeed("test -e /var/lib/containers/tmpfs/root/test.file");
+
+
+      # Verify that /some/random/path is writable and that files created there
+      # are not in the hosts container dir but in the tmpfs
+      $machine->succeed("nixos-container run tmpfs -- touch /some/random/path/test.file 2>/dev/null");
+      $machine->succeed("nixos-container run tmpfs -- test -e /some/random/path/test.file 2>/dev/null");
+
+      $machine->fail("test -e /var/lib/containers/tmpfs/some/random/path/test.file");
+
+      # Verify that files created in the hosts container dir in a path where a tmpfs file system has been mounted
+      # are not visible to the container as the do not exist in the tmpfs
+      $machine->succeed("touch /var/lib/containers/tmpfs/var/test.file");
+
+      $machine->succeed("test -e /var/lib/containers/tmpfs/var/test.file");
+      $machine->succeed("ls -l /var/lib/containers/tmpfs/var/ | grep -q test.file 2>/dev/null");
+
+      $machine->fail("nixos-container run tmpfs -- ls -l /var | grep -q test.file 2>/dev/null");
+
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/couchdb.nix b/nixpkgs/nixos/tests/couchdb.nix
new file mode 100644
index 000000000000..48ea48eebbb3
--- /dev/null
+++ b/nixpkgs/nixos/tests/couchdb.nix
@@ -0,0 +1,56 @@
+import ./make-test.nix ({ pkgs, lib, ...}:
+
+with lib;
+
+{
+  name = "couchdb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ fpletz ];
+  };
+
+  nodes = {
+    couchdb1 =
+      { pkgs, ... }:
+
+      { environment.systemPackages = with pkgs; [ jq ];
+        services.couchdb.enable = true;
+      };
+
+    couchdb2 =
+      { pkgs, ... }:
+
+      { environment.systemPackages = with pkgs; [ jq ];
+        services.couchdb.enable = true;
+        services.couchdb.package = pkgs.couchdb2;
+      };
+  };
+
+  testScript = let
+    curlJqCheck = action: path: jqexpr: result:
+      pkgs.writeScript "curl-jq-check-${action}-${path}.sh" ''
+        RESULT=$(curl -X ${action} http://127.0.0.1:5984/${path} | jq -r '${jqexpr}')
+        echo $RESULT >&2
+        if [ "$RESULT" != "${result}" ]; then
+          exit 1
+        fi
+      '';
+  in ''
+    startAll;
+
+    $couchdb1->waitForUnit("couchdb.service");
+    $couchdb1->waitUntilSucceeds("${curlJqCheck "GET" "" ".couchdb" "Welcome"}");
+    $couchdb1->waitUntilSucceeds("${curlJqCheck "GET" "_all_dbs" ". | length" "2"}");
+    $couchdb1->succeed("${curlJqCheck "PUT" "foo" ".ok" "true"}");
+    $couchdb1->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "3"}");
+    $couchdb1->succeed("${curlJqCheck "DELETE" "foo" ".ok" "true"}");
+    $couchdb1->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "2"}");
+
+    $couchdb2->waitForUnit("couchdb.service");
+    $couchdb2->waitUntilSucceeds("${curlJqCheck "GET" "" ".couchdb" "Welcome"}");
+    $couchdb2->waitUntilSucceeds("${curlJqCheck "GET" "_all_dbs" ". | length" "0"}");
+    $couchdb2->succeed("${curlJqCheck "PUT" "foo" ".ok" "true"}");
+    $couchdb2->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "1"}");
+    $couchdb2->succeed("${curlJqCheck "DELETE" "foo" ".ok" "true"}");
+    $couchdb2->succeed("${curlJqCheck "GET" "_all_dbs" ". | length" "0"}");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/deluge.nix b/nixpkgs/nixos/tests/deluge.nix
new file mode 100644
index 000000000000..b4be5e465cc0
--- /dev/null
+++ b/nixpkgs/nixos/tests/deluge.nix
@@ -0,0 +1,29 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "deluge";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ flokli ];
+  };
+
+  nodes = {
+    server =
+      { ... }:
+
+      { services.deluge = {
+          enable = true;
+          web.enable = true;
+        };
+        networking.firewall.allowedTCPPorts = [ 8112 ];
+      };
+
+    client = { };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("deluged");
+    $server->waitForUnit("delugeweb");
+    $client->waitForUnit("network.target");
+    $client->waitUntilSucceeds("curl --fail http://server:8112");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/dhparams.nix b/nixpkgs/nixos/tests/dhparams.nix
new file mode 100644
index 000000000000..d11dfeec5d0c
--- /dev/null
+++ b/nixpkgs/nixos/tests/dhparams.nix
@@ -0,0 +1,144 @@
+let
+  common = { pkgs, ... }: {
+    security.dhparams.enable = true;
+    environment.systemPackages = [ pkgs.openssl ];
+  };
+
+in import ./make-test.nix {
+  name = "dhparams";
+
+  nodes.generation1 = { pkgs, config, ... }: {
+    imports = [ common ];
+    security.dhparams.params = {
+      # Use low values here because we don't want the test to run for ages.
+      foo.bits = 16;
+      # Also use the old format to make sure the type is coerced in the right
+      # way.
+      bar = 17;
+    };
+
+    systemd.services.foo = {
+      description = "Check systemd Ordering";
+      wantedBy = [ "multi-user.target" ];
+      unitConfig = {
+        # This is to make sure that the dhparams generation of foo occurs
+        # before this service so we need this service to start as early as
+        # possible to provoke a race condition.
+        DefaultDependencies = false;
+
+        # We check later whether the service has been started or not.
+        ConditionPathExists = config.security.dhparams.params.foo.path;
+      };
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      # The reason we only provide an ExecStop here is to ensure that we don't
+      # accidentally trigger an error because a file system is not yet ready
+      # during very early startup (we might not even have the Nix store
+      # available, for example if future changes in NixOS use systemd mount
+      # units to do early file system initialisation).
+      serviceConfig.ExecStop = "${pkgs.coreutils}/bin/true";
+    };
+  };
+
+  nodes.generation2 = {
+    imports = [ common ];
+    security.dhparams.params.foo.bits = 18;
+  };
+
+  nodes.generation3 = common;
+
+  nodes.generation4 = {
+    imports = [ common ];
+    security.dhparams.stateful = false;
+    security.dhparams.params.foo2.bits = 18;
+    security.dhparams.params.bar2.bits = 19;
+  };
+
+  nodes.generation5 = {
+    imports = [ common ];
+    security.dhparams.defaultBitSize = 30;
+    security.dhparams.params.foo3 = {};
+    security.dhparams.params.bar3 = {};
+  };
+
+  testScript = { nodes, ... }: let
+    getParamPath = gen: name: let
+      node = "generation${toString gen}";
+    in nodes.${node}.config.security.dhparams.params.${name}.path;
+
+    assertParamBits = gen: name: bits: let
+      path = getParamPath gen name;
+    in ''
+      $machine->nest('check bit size of ${path}', sub {
+        my $out = $machine->succeed('openssl dhparam -in ${path} -text');
+        $out =~ /^\s*DH Parameters:\s+\((\d+)\s+bit\)\s*$/m;
+        die "bit size should be ${toString bits} but it is $1 instead."
+          if $1 != ${toString bits};
+      });
+    '';
+
+    switchToGeneration = gen: let
+      node = "generation${toString gen}";
+      inherit (nodes.${node}.config.system.build) toplevel;
+      switchCmd = "${toplevel}/bin/switch-to-configuration test";
+    in ''
+      $machine->nest('switch to generation ${toString gen}', sub {
+        $machine->succeed('${switchCmd}');
+        $main::machine = ''$${node};
+      });
+    '';
+
+  in ''
+    my $machine = $generation1;
+
+    $machine->waitForUnit('multi-user.target');
+
+    subtest "verify startup order", sub {
+      $machine->succeed('systemctl is-active foo.service');
+    };
+
+    subtest "check bit sizes of dhparam files", sub {
+      ${assertParamBits 1 "foo" 16}
+      ${assertParamBits 1 "bar" 17}
+    };
+
+    ${switchToGeneration 2}
+
+    subtest "check whether bit size has changed", sub {
+      ${assertParamBits 2 "foo" 18}
+    };
+
+    subtest "ensure that dhparams file for 'bar' was deleted", sub {
+      $machine->fail('test -e ${getParamPath 1 "bar"}');
+    };
+
+    ${switchToGeneration 3}
+
+    subtest "ensure that 'security.dhparams.path' has been deleted", sub {
+      $machine->fail(
+        'test -e ${nodes.generation3.config.security.dhparams.path}'
+      );
+    };
+
+    ${switchToGeneration 4}
+
+    subtest "check bit sizes dhparam files", sub {
+      ${assertParamBits 4 "foo2" 18}
+      ${assertParamBits 4 "bar2" 19}
+    };
+
+    subtest "check whether dhparam files are in the Nix store", sub {
+      $machine->succeed(
+        'expr match ${getParamPath 4 "foo2"} ${builtins.storeDir}',
+        'expr match ${getParamPath 4 "bar2"} ${builtins.storeDir}',
+      );
+    };
+
+    ${switchToGeneration 5}
+
+    subtest "check whether defaultBitSize works as intended", sub {
+      ${assertParamBits 5 "foo3" 30}
+      ${assertParamBits 5 "bar3" 30}
+    };
+  '';
+}
diff --git a/nixpkgs/nixos/tests/dnscrypt-proxy.nix b/nixpkgs/nixos/tests/dnscrypt-proxy.nix
new file mode 100644
index 000000000000..13bc9d3d9168
--- /dev/null
+++ b/nixpkgs/nixos/tests/dnscrypt-proxy.nix
@@ -0,0 +1,33 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "dnscrypt-proxy";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ joachifm ];
+  };
+
+  nodes = {
+    # A client running the recommended setup: DNSCrypt proxy as a forwarder
+    # for a caching DNS client.
+    client =
+    { ... }:
+    let localProxyPort = 43; in
+    {
+      security.apparmor.enable = true;
+
+      services.dnscrypt-proxy.enable = true;
+      services.dnscrypt-proxy.localPort = localProxyPort;
+      services.dnscrypt-proxy.extraArgs = [ "-X libdcplugin_example.so" ];
+
+      services.dnsmasq.enable = true;
+      services.dnsmasq.servers = [ "127.0.0.1#${toString localProxyPort}" ];
+    };
+  };
+
+  testScript = ''
+    $client->waitForUnit("dnsmasq");
+
+    # The daemon is socket activated; sending a single ping should activate it.
+    $client->fail("systemctl is-active dnscrypt-proxy");
+    $client->execute("${pkgs.iputils}/bin/ping -c1 example.com");
+    $client->waitUntilSucceeds("systemctl is-active dnscrypt-proxy");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/docker-edge.nix b/nixpkgs/nixos/tests/docker-edge.nix
new file mode 100644
index 000000000000..b306c149be91
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-edge.nix
@@ -0,0 +1,47 @@
+# This test runs docker and checks if simple container starts
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "docker";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus offline ];
+  };
+
+  nodes = {
+    docker =
+      { pkgs, ... }:
+        {
+          virtualisation.docker.enable = true;
+          virtualisation.docker.package = pkgs.docker-edge;
+
+          users.users = {
+            noprivs = {
+              isNormalUser = true;
+              description = "Can't access the docker daemon";
+              password = "foobar";
+            };
+
+            hasprivs = {
+              isNormalUser = true;
+              description = "Can access the docker daemon";
+              password = "foobar";
+              extraGroups = [ "docker" ];
+            };
+          };
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $docker->waitForUnit("sockets.target");
+    $docker->succeed("tar cv --files-from /dev/null | docker import - scratchimg");
+    $docker->succeed("docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10");
+    $docker->succeed("docker ps | grep sleeping");
+    $docker->succeed("sudo -u hasprivs docker ps");
+    $docker->fail("sudo -u noprivs docker ps");
+    $docker->succeed("docker stop sleeping");
+
+    # Must match version twice to ensure client and server versions are correct
+    $docker->succeed('[ $(docker version | grep ${pkgs.docker-edge.version} | wc -l) = "2" ]');
+  '';
+})
diff --git a/nixpkgs/nixos/tests/docker-preloader.nix b/nixpkgs/nixos/tests/docker-preloader.nix
new file mode 100644
index 000000000000..eeedec9a392e
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-preloader.nix
@@ -0,0 +1,27 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "docker-preloader";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lewo ];
+  };
+
+  nodes = {
+    docker =
+      { pkgs, ... }:
+        {
+          virtualisation.docker.enable = true;
+          virtualisation.dockerPreloader.images = [ pkgs.dockerTools.examples.nix pkgs.dockerTools.examples.bash ];
+
+          services.openssh.enable = true;
+          services.openssh.permitRootLogin = "yes";
+          services.openssh.extraConfig = "PermitEmptyPasswords yes";
+          users.extraUsers.root.password = "";
+        };
+  };    
+  testScript = ''
+    startAll;
+    
+    $docker->waitForUnit("sockets.target");
+    $docker->succeed("docker run nix nix-store --version");
+    $docker->succeed("docker run bash bash --version");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/docker-registry.nix b/nixpkgs/nixos/tests/docker-registry.nix
new file mode 100644
index 000000000000..8936421072a9
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-registry.nix
@@ -0,0 +1,63 @@
+# This test runs docker-registry and check if it works
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "docker-registry";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ globin ma27 ironpinguin ];
+  };
+
+  nodes = {
+    registry = { ... }: {
+      services.dockerRegistry.enable = true;
+      services.dockerRegistry.enableDelete = true;
+      services.dockerRegistry.port = 8080;
+      services.dockerRegistry.listenAddress = "0.0.0.0";
+      services.dockerRegistry.enableGarbageCollect = true;
+      networking.firewall.allowedTCPPorts = [ 8080 ];
+    };
+
+    client1 = { ... }: {
+      virtualisation.docker.enable = true;
+      virtualisation.docker.extraOptions = "--insecure-registry registry:8080";
+    };
+
+    client2 = { ... }: {
+      virtualisation.docker.enable = true;
+      virtualisation.docker.extraOptions = "--insecure-registry registry:8080";
+    };
+  };
+
+  testScript = ''
+    $client1->start();
+    $client1->waitForUnit("docker.service");
+    $client1->succeed("tar cv --files-from /dev/null | docker import - scratch");
+    $client1->succeed("docker tag scratch registry:8080/scratch");
+
+    $registry->start();
+    $registry->waitForUnit("docker-registry.service");
+    $registry->waitForOpenPort("8080");
+    $client1->succeed("docker push registry:8080/scratch");
+
+    $client2->start();
+    $client2->waitForUnit("docker.service");
+    $client2->succeed("docker pull registry:8080/scratch");
+    $client2->succeed("docker images | grep scratch");
+
+    $client2->succeed(
+      'curl -fsS -X DELETE registry:8080/v2/scratch/manifests/$(curl -fsS -I -H"Accept: application/vnd.docker.distribution.manifest.v2+json" registry:8080/v2/scratch/manifests/latest | grep Docker-Content-Digest | sed -e \'s/Docker-Content-Digest: //\' | tr -d \'\r\')'
+    );
+
+    $registry->systemctl("start docker-registry-garbage-collect.service");
+    $registry->waitUntilFails("systemctl status docker-registry-garbage-collect.service");
+    $registry->waitForUnit("docker-registry.service");
+
+    $registry->fail(
+      'ls -l /var/lib/docker-registry/docker/registry/v2/blobs/sha256/*/*/data'
+    );
+
+    $client1->succeed("docker push registry:8080/scratch");
+    $registry->succeed(
+      'ls -l /var/lib/docker-registry/docker/registry/v2/blobs/sha256/*/*/data'
+    );
+  '';
+})
diff --git a/nixpkgs/nixos/tests/docker-tools-overlay.nix b/nixpkgs/nixos/tests/docker-tools-overlay.nix
new file mode 100644
index 000000000000..637957bd3e8b
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-tools-overlay.nix
@@ -0,0 +1,32 @@
+# this test creates a simple GNU image with docker tools and sees if it executes
+
+import ./make-test.nix ({ pkgs, ... }:
+{
+  name = "docker-tools-overlay";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lnl7 ];
+  };
+
+  nodes = {
+    docker =
+      { ... }:
+      {
+        virtualisation.docker.enable = true;
+        virtualisation.docker.storageDriver = "overlay";  # defaults to overlay2
+      };
+  };
+
+  testScript =
+    ''
+      $docker->waitForUnit("sockets.target");
+
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version");
+
+      # Check if the nix store has correct user permissions depending on what
+      # storage driver is used, incorrectly built images can show up as readonly.
+      # drw-------  3 0 0   3 Apr 14 11:36 /nix
+      # drw------- 99 0 0 100 Apr 14 11:36 /nix/store
+      $docker->succeed("docker run --rm -u 1000:1000 ${pkgs.dockerTools.examples.bash.imageName} bash --version");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/docker-tools.nix b/nixpkgs/nixos/tests/docker-tools.nix
new file mode 100644
index 000000000000..58f106314ab3
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-tools.nix
@@ -0,0 +1,71 @@
+# this test creates a simple GNU image with docker tools and sees if it executes
+
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "docker-tools";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lnl7 ];
+  };
+
+  nodes = {
+    docker =
+      { ... }: {
+        virtualisation = {
+          diskSize = 2048;
+          docker.enable = true;
+        };
+      };
+  };
+
+  testScript =
+    ''
+      $docker->waitForUnit("sockets.target");
+
+      # Ensure Docker images use a stable date by default
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("[ '1970-01-01T00:00:01Z' = \"\$(docker inspect ${pkgs.dockerTools.examples.bash.imageName} | ${pkgs.jq}/bin/jq -r .[].Created)\" ]");
+
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version");
+      $docker->succeed("docker rmi ${pkgs.dockerTools.examples.bash.imageName}");
+
+      # Check if the nix store is correctly initialized by listing dependencies of the installed Nix binary
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nix}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.nix.imageName} nix-store -qR ${pkgs.nix}");
+      $docker->succeed("docker rmi ${pkgs.dockerTools.examples.nix.imageName}");
+
+      # To test the pullImage tool
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nixFromDockerHub}'");
+      $docker->succeed("docker run --rm nixos/nix:1.11 nix-store --version");
+      $docker->succeed("docker rmi nixos/nix:1.11");
+
+      # To test runAsRoot and entry point
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nginx}'");
+      $docker->succeed("docker run --name nginx -d -p 8000:80 ${pkgs.dockerTools.examples.nginx.imageName}");
+      $docker->waitUntilSucceeds('curl http://localhost:8000/');
+      $docker->succeed("docker rm --force nginx");
+      $docker->succeed("docker rmi '${pkgs.dockerTools.examples.nginx.imageName}'");
+
+      # An pulled image can be used as base image
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.onTopOfPulledImage}'");
+      $docker->succeed("docker run --rm ontopofpulledimage hello");
+      $docker->succeed("docker rmi ontopofpulledimage");
+
+      # Regression test for issue #34779
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.runAsRootExtraCommands}'");
+      $docker->succeed("docker run --rm runasrootextracommands cat extraCommands");
+      $docker->succeed("docker run --rm runasrootextracommands cat runAsRoot");
+      $docker->succeed("docker rmi '${pkgs.dockerTools.examples.runAsRootExtraCommands.imageName}'");
+
+      # Ensure Docker images can use an unstable date
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("[ '1970-01-01T00:00:01Z' != \"\$(docker inspect ${pkgs.dockerTools.examples.unstableDate.imageName} | ${pkgs.jq}/bin/jq -r .[].Created)\" ]");
+
+      # Ensure Layered Docker images work
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.layered-image}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-image.imageName}");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-image.imageName} cat extraCommands");
+
+      # Ensure building an image on top of a layered Docker images work
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.layered-on-top}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-on-top.imageName}");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/docker.nix b/nixpkgs/nixos/tests/docker.nix
new file mode 100644
index 000000000000..d67b2f8743d8
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker.nix
@@ -0,0 +1,47 @@
+# This test runs docker and checks if simple container starts
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "docker";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus offline ];
+  };
+
+  nodes = {
+    docker =
+      { pkgs, ... }:
+        {
+          virtualisation.docker.enable = true;
+          virtualisation.docker.package = pkgs.docker;
+
+          users.users = {
+            noprivs = {
+              isNormalUser = true;
+              description = "Can't access the docker daemon";
+              password = "foobar";
+            };
+
+            hasprivs = {
+              isNormalUser = true;
+              description = "Can access the docker daemon";
+              password = "foobar";
+              extraGroups = [ "docker" ];
+            };
+          };
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $docker->waitForUnit("sockets.target");
+    $docker->succeed("tar cv --files-from /dev/null | docker import - scratchimg");
+    $docker->succeed("docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10");
+    $docker->succeed("docker ps | grep sleeping");
+    $docker->succeed("sudo -u hasprivs docker ps");
+    $docker->fail("sudo -u noprivs docker ps");
+    $docker->succeed("docker stop sleeping");
+
+    # Must match version twice to ensure client and server versions are correct
+    $docker->succeed('[ $(docker version | grep ${pkgs.docker.version} | wc -l) = "2" ]');
+  '';
+})
diff --git a/nixpkgs/nixos/tests/dovecot.nix b/nixpkgs/nixos/tests/dovecot.nix
new file mode 100644
index 000000000000..156079d1d585
--- /dev/null
+++ b/nixpkgs/nixos/tests/dovecot.nix
@@ -0,0 +1,77 @@
+import ./make-test.nix {
+  name = "dovecot";
+
+  machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix.enable = true;
+    services.dovecot2.enable = true;
+    services.dovecot2.protocols = [ "imap" "pop3" ];
+    environment.systemPackages = let
+      sendTestMail = pkgs.writeScriptBin "send-testmail" ''
+        #!${pkgs.stdenv.shell}
+        exec sendmail -vt <<MAIL
+        From: root@localhost
+        To: alice@localhost
+        Subject: Very important!
+
+        Hello world!
+        MAIL
+      '';
+
+      sendTestMailViaDeliveryAgent = pkgs.writeScriptBin "send-lda" ''
+        #!${pkgs.stdenv.shell}
+
+        exec ${pkgs.dovecot}/libexec/dovecot/deliver -d bob <<MAIL
+        From: root@localhost
+        To: bob@localhost
+        Subject: Something else...
+
+        I'm running short of ideas!
+        MAIL
+      '';
+
+      testImap = pkgs.writeScriptBin "test-imap" ''
+        #!${pkgs.python3.interpreter}
+        import imaplib
+
+        with imaplib.IMAP4('localhost') as imap:
+          imap.login('alice', 'foobar')
+          imap.select()
+          status, refs = imap.search(None, 'ALL')
+          assert status == 'OK'
+          assert len(refs) == 1
+          status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+          assert status == 'OK'
+          assert msg[0][1].strip() == b'Hello world!'
+      '';
+
+      testPop = pkgs.writeScriptBin "test-pop" ''
+        #!${pkgs.python3.interpreter}
+        import poplib
+
+        pop = poplib.POP3('localhost')
+        try:
+          pop.user('bob')
+          pop.pass_('foobar')
+          assert len(pop.list()[1]) == 1
+          status, fullmail, size = pop.retr(1)
+          assert status.startswith(b'+OK ')
+          body = b"".join(fullmail[fullmail.index(b""):]).strip()
+          assert body == b"I'm running short of ideas!"
+        finally:
+          pop.quit()
+      '';
+
+    in [ sendTestMail sendTestMailViaDeliveryAgent testImap testPop ];
+  };
+
+  testScript = ''
+    $machine->waitForUnit('postfix.service');
+    $machine->waitForUnit('dovecot2.service');
+    $machine->succeed('send-testmail');
+    $machine->succeed('send-lda');
+    $machine->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
+    $machine->succeed('test-imap');
+    $machine->succeed('test-pop');
+  '';
+}
diff --git a/nixpkgs/nixos/tests/ec2.nix b/nixpkgs/nixos/tests/ec2.nix
new file mode 100644
index 000000000000..ed6bf7da988c
--- /dev/null
+++ b/nixpkgs/nixos/tests/ec2.nix
@@ -0,0 +1,178 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  image =
+    (import ../lib/eval-config.nix {
+      inherit system;
+      modules = [
+        ../maintainers/scripts/ec2/amazon-image.nix
+        ../modules/testing/test-instrumentation.nix
+        ../modules/profiles/qemu-guest.nix
+        { ec2.hvm = true;
+
+          # Hack to make the partition resizing work in QEMU.
+          boot.initrd.postDeviceCommands = mkBefore
+            ''
+              ln -s vda /dev/xvda
+              ln -s vda1 /dev/xvda1
+            '';
+
+          # Needed by nixos-rebuild due to the lack of network
+          # access. Mostly copied from
+          # modules/profiles/installation-device.nix.
+          system.extraDependencies =
+            with pkgs; [
+              stdenv busybox perlPackages.ArchiveCpio unionfs-fuse mkinitcpio-nfs-utils
+
+              # These are used in the configure-from-userdata tests for EC2. Httpd and valgrind are requested
+              # directly by the configuration we set, and libxslt.bin is used indirectly as a build dependency
+              # of the derivation for dbus configuration files.
+              apacheHttpd valgrind.doc libxslt.bin
+            ];
+        }
+      ];
+    }).config.system.build.amazonImage;
+
+  makeEc2Test = { name, userData, script, hostname ? "ec2-instance", sshPublicKey ? null }:
+    let
+      metaData = pkgs.stdenv.mkDerivation {
+        name = "metadata";
+        buildCommand = ''
+          mkdir -p $out/1.0/meta-data
+          ln -s ${pkgs.writeText "userData" userData} $out/1.0/user-data
+          echo "${hostname}" > $out/1.0/meta-data/hostname
+          echo "(unknown)" > $out/1.0/meta-data/ami-manifest-path
+        '' + optionalString (sshPublicKey != null) ''
+          mkdir -p $out/1.0/meta-data/public-keys/0
+          ln -s ${pkgs.writeText "sshPublicKey" sshPublicKey} $out/1.0/meta-data/public-keys/0/openssh-key
+        '';
+      };
+    in makeTest {
+      name = "ec2-" + name;
+      nodes = {};
+      testScript =
+        ''
+          my $imageDir = ($ENV{'TMPDIR'} // "/tmp") . "/vm-state-machine";
+          mkdir $imageDir, 0700;
+          my $diskImage = "$imageDir/machine.qcow2";
+          system("qemu-img create -f qcow2 -o backing_file=${image}/nixos.qcow2 $diskImage") == 0 or die;
+          system("qemu-img resize $diskImage 10G") == 0 or die;
+
+          # Note: we use net=169.0.0.0/8 rather than
+          # net=169.254.0.0/16 to prevent dhcpcd from getting horribly
+          # confused. (It would get a DHCP lease in the 169.254.*
+          # range, which it would then configure and prompty delete
+          # again when it deletes link-local addresses.) Ideally we'd
+          # turn off the DHCP server, but qemu does not have an option
+          # to do that.
+          my $startCommand = "qemu-kvm -m 768";
+          $startCommand .= " -device virtio-net-pci,netdev=vlan0";
+          $startCommand .= " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'";
+          $startCommand .= " -drive file=$diskImage,if=virtio,werror=report";
+          $startCommand .= " \$QEMU_OPTS";
+
+          my $machine = createMachine({ startCommand => $startCommand });
+
+          ${script}
+        '';
+    };
+
+  snakeOilPrivateKey = ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACDEPmwZv5dDPrMUaq0dDP+6eBTTe+QNrz14KBEIdhHd1QAAAJDufJ4S7nye
+    EgAAAAtzc2gtZWQyNTUxOQAAACDEPmwZv5dDPrMUaq0dDP+6eBTTe+QNrz14KBEIdhHd1Q
+    AAAECgwbDlYATM5/jypuptb0GF/+zWZcJfoVIFBG3LQeRyGsQ+bBm/l0M+sxRqrR0M/7p4
+    FNN75A2vPXgoEQh2Ed3VAAAADEVDMiB0ZXN0IGtleQE=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  snakeOilPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQ+bBm/l0M+sxRqrR0M/7p4FNN75A2vPXgoEQh2Ed3V EC2 test key";
+
+in {
+  boot-ec2-nixops = makeEc2Test {
+    name         = "nixops-userdata";
+    sshPublicKey = snakeOilPublicKey; # That's right folks! My user's key is also the host key!
+
+    userData = ''
+      SSH_HOST_ED25519_KEY_PUB:${snakeOilPublicKey}
+      SSH_HOST_ED25519_KEY:${replaceStrings ["\n"] ["|"] snakeOilPrivateKey}
+    '';
+    script = ''
+      $machine->start;
+      $machine->waitForFile("/etc/ec2-metadata/user-data");
+      $machine->waitForUnit("sshd.service");
+
+      $machine->succeed("grep unknown /etc/ec2-metadata/ami-manifest-path");
+
+      # We have no keys configured on the client side yet, so this should fail
+      $machine->fail("ssh -o BatchMode=yes localhost exit");
+
+      # Let's install our client private key
+      $machine->succeed("mkdir -p ~/.ssh");
+
+      $machine->succeed("echo '${snakeOilPrivateKey}' > ~/.ssh/id_ed25519");
+      $machine->succeed("chmod 600 ~/.ssh/id_ed25519");
+
+      # We haven't configured the host key yet, so this should still fail
+      $machine->fail("ssh -o BatchMode=yes localhost exit");
+
+      # Add the host key; ssh should finally succeed
+      $machine->succeed("echo localhost,127.0.0.1 ${snakeOilPublicKey} > ~/.ssh/known_hosts");
+      $machine->succeed("ssh -o BatchMode=yes localhost exit");
+
+      # Test whether the root disk was resized.
+      my $blocks = $machine->succeed("stat -c %b -f /");
+      my $bsize = $machine->succeed("stat -c %S -f /");
+      my $size = $blocks * $bsize;
+      die "wrong free space $size" if $size < 9.7 * 1024 * 1024 * 1024 || $size > 10 * 1024 * 1024 * 1024;
+
+      # Just to make sure resizing is idempotent.
+      $machine->shutdown;
+      $machine->start;
+      $machine->waitForFile("/etc/ec2-metadata/user-data");
+    '';
+  };
+
+  boot-ec2-config = makeEc2Test {
+    name         = "config-userdata";
+    sshPublicKey = snakeOilPublicKey;
+
+    # ### http://nixos.org/channels/nixos-unstable nixos
+    userData = ''
+      { pkgs, ... }:
+
+      {
+        imports = [
+          <nixpkgs/nixos/modules/virtualisation/amazon-image.nix>
+          <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          <nixpkgs/nixos/modules/profiles/qemu-guest.nix>
+        ];
+        environment.etc.testFile = {
+          text = "whoa";
+        };
+
+        services.httpd = {
+          enable = true;
+          adminAddr = "test@example.org";
+          documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
+        };
+        networking.firewall.allowedTCPPorts = [ 80 ];
+      }
+    '';
+    script = ''
+      $machine->start;
+      $machine->waitForFile("/etc/testFile");
+      $machine->succeed("cat /etc/testFile | grep -q 'whoa'");
+
+      $machine->waitForUnit("httpd.service");
+      $machine->succeed("curl http://localhost | grep Valgrind");
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/ecryptfs.nix b/nixpkgs/nixos/tests/ecryptfs.nix
new file mode 100644
index 000000000000..3f02cecb8662
--- /dev/null
+++ b/nixpkgs/nixos/tests/ecryptfs.nix
@@ -0,0 +1,84 @@
+import ./make-test.nix ({ ... }:
+{
+  name = "ecryptfs";
+
+  machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    boot.kernelModules = [ "ecryptfs" ];
+    security.pam.enableEcryptfs = true;
+    environment.systemPackages = with pkgs; [ keyutils ];
+  };
+
+  testScript = ''
+    $machine->waitForUnit("default.target");
+
+    # Set alice up with a password and a home
+    $machine->succeed("(echo foobar; echo foobar) | passwd alice");
+    $machine->succeed("chown -R alice.users ~alice");
+
+    # Migrate alice's home
+    my $out = $machine->succeed("echo foobar | ecryptfs-migrate-home -u alice");
+    $machine->log("ecryptfs-migrate-home said: $out");
+
+    # Log alice in (ecryptfs passwhrase is wrapped during first login)
+    $machine->waitUntilTTYMatches(1, "login: ");
+    $machine->sendChars("alice\n");
+    $machine->waitUntilTTYMatches(1, "Password: ");
+    $machine->sendChars("foobar\n");
+    $machine->waitUntilTTYMatches(1, "alice\@machine");
+    $machine->sendChars("logout\n");
+    $machine->waitUntilTTYMatches(1, "login: ");
+
+    # Why do I need to do this??
+    $machine->succeed("su alice -c ecryptfs-umount-private || true");
+    $machine->sleep(1);
+    $machine->fail("mount | grep ecryptfs"); # check that encrypted home is not mounted
+
+    # Show contents of the user keyring
+    my $out = $machine->succeed("su - alice -c 'keyctl list \@u'");
+    $machine->log("keyctl unlink said: " . $out);
+
+    # Log alice again
+    $machine->waitUntilTTYMatches(1, "login: ");
+    $machine->sendChars("alice\n");
+    $machine->waitUntilTTYMatches(1, "Password: ");
+    $machine->sendChars("foobar\n");
+    $machine->waitUntilTTYMatches(1, "alice\@machine");
+
+    # Create some files in encrypted home
+    $machine->succeed("su alice -c 'touch ~alice/a'");
+    $machine->succeed("su alice -c 'echo c > ~alice/b'");
+
+    # Logout
+    $machine->sendChars("logout\n");
+    $machine->waitUntilTTYMatches(1, "login: ");
+
+    # Why do I need to do this??
+    $machine->succeed("su alice -c ecryptfs-umount-private || true");
+    $machine->sleep(1);
+
+    # Check that the filesystem is not accessible
+    $machine->fail("mount | grep ecryptfs");
+    $machine->succeed("su alice -c 'test \! -f ~alice/a'");
+    $machine->succeed("su alice -c 'test \! -f ~alice/b'");
+
+    # Log alice once more
+    $machine->waitUntilTTYMatches(1, "login: ");
+    $machine->sendChars("alice\n");
+    $machine->waitUntilTTYMatches(1, "Password: ");
+    $machine->sendChars("foobar\n");
+    $machine->waitUntilTTYMatches(1, "alice\@machine");
+
+    # Check that the files are there
+    $machine->sleep(1);
+    $machine->succeed("su alice -c 'test -f ~alice/a'");
+    $machine->succeed("su alice -c 'test -f ~alice/b'");
+    $machine->succeed(qq%test "\$(cat ~alice/b)" = "c"%);
+
+    # Catch https://github.com/NixOS/nixpkgs/issues/16766
+    $machine->succeed("su alice -c 'ls -lh ~alice/'");
+
+    $machine->sendChars("logout\n");
+    $machine->waitUntilTTYMatches(1, "login: ");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/elk.nix b/nixpkgs/nixos/tests/elk.nix
new file mode 100644
index 000000000000..d787ac973005
--- /dev/null
+++ b/nixpkgs/nixos/tests/elk.nix
@@ -0,0 +1,152 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+  enableUnfree ? false
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  esUrl = "http://localhost:9200";
+
+  mkElkTest = name : elk : makeTest {
+    inherit name;
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ eelco chaoflow offline basvandijk ];
+    };
+    nodes = {
+      one =
+        { pkgs, ... }: {
+            # Not giving the machine at least 2060MB results in elasticsearch failing with the following error:
+            #
+            #   OpenJDK 64-Bit Server VM warning:
+            #     INFO: os::commit_memory(0x0000000085330000, 2060255232, 0)
+            #     failed; error='Cannot allocate memory' (errno=12)
+            #
+            #   There is insufficient memory for the Java Runtime Environment to continue.
+            #   Native memory allocation (mmap) failed to map 2060255232 bytes for committing reserved memory.
+            #
+            # When setting this to 2500 I got "Kernel panic - not syncing: Out of
+            # memory: compulsory panic_on_oom is enabled" so lets give it even a
+            # bit more room:
+            virtualisation.memorySize = 3000;
+
+            # For querying JSON objects returned from elasticsearch and kibana.
+            environment.systemPackages = [ pkgs.jq ];
+
+            services = {
+              logstash = {
+                enable = true;
+                package = elk.logstash;
+                inputConfig = ''
+                  exec { command => "echo -n flowers" interval => 1 type => "test" }
+                  exec { command => "echo -n dragons" interval => 1 type => "test" }
+                '';
+                filterConfig = ''
+                  if [message] =~ /dragons/ {
+                    drop {}
+                  }
+                '';
+                outputConfig = ''
+                  file {
+                    path => "/tmp/logstash.out"
+                    codec => line { format => "%{message}" }
+                  }
+                  elasticsearch {
+                    hosts => [ "${esUrl}" ]
+                  }
+                '';
+              };
+
+              elasticsearch = {
+                enable = true;
+                package = elk.elasticsearch;
+              };
+
+              kibana = {
+                enable = true;
+                package = elk.kibana;
+                elasticsearch.url = esUrl;
+              };
+
+              elasticsearch-curator = {
+                enable = true;
+                actionYAML = ''
+                ---
+                actions:
+                  1:
+                    action: delete_indices
+                    description: >-
+                      Delete indices older than 1 second (based on index name), for logstash-
+                      prefixed indices. Ignore the error if the filter does not result in an
+                      actionable list of indices (ignore_empty_list) and exit cleanly.
+                    options:
+                      ignore_empty_list: True
+                      disable_action: False
+                    filters:
+                    - filtertype: pattern
+                      kind: prefix
+                      value: logstash-
+                    - filtertype: age
+                      source: name
+                      direction: older
+                      timestring: '%Y.%m.%d'
+                      unit: seconds
+                      unit_count: 1
+                '';
+              };
+            };
+          };
+      };
+
+    testScript = ''
+      startAll;
+
+      $one->waitForUnit("elasticsearch.service");
+
+      # Continue as long as the status is not "red". The status is probably
+      # "yellow" instead of "green" because we are using a single elasticsearch
+      # node which elasticsearch considers risky.
+      #
+      # TODO: extend this test with multiple elasticsearch nodes and see if the status turns "green".
+      $one->waitUntilSucceeds("curl --silent --show-error '${esUrl}/_cluster/health' | jq .status | grep -v red");
+
+      # Perform some simple logstash tests.
+      $one->waitForUnit("logstash.service");
+      $one->waitUntilSucceeds("cat /tmp/logstash.out | grep flowers");
+      $one->waitUntilSucceeds("cat /tmp/logstash.out | grep -v dragons");
+
+      # See if kibana is healthy.
+      $one->waitForUnit("kibana.service");
+      $one->waitUntilSucceeds("curl --silent --show-error 'http://localhost:5601/api/status' | jq .status.overall.state | grep green");
+
+      # See if logstash messages arive in elasticsearch.
+      $one->waitUntilSucceeds("curl --silent --show-error '${esUrl}/_search' -H 'Content-Type: application/json' -d '{\"query\" : { \"match\" : { \"message\" : \"flowers\"}}}' | jq .hits.total | grep -v 0");
+      $one->waitUntilSucceeds("curl --silent --show-error '${esUrl}/_search' -H 'Content-Type: application/json' -d '{\"query\" : { \"match\" : { \"message\" : \"dragons\"}}}' | jq .hits.total | grep 0");
+
+      # Test elasticsearch-curator.
+      $one->systemctl("stop logstash");
+      $one->systemctl("start elasticsearch-curator");
+      $one->waitUntilSucceeds("! curl --silent --show-error '${esUrl}/_cat/indices' | grep logstash | grep -q ^$1");
+    '';
+  };
+in mapAttrs mkElkTest {
+  "ELK-5" = {
+    elasticsearch = pkgs.elasticsearch5;
+    logstash      = pkgs.logstash5;
+    kibana        = pkgs.kibana5;
+  };
+  "ELK-6" =
+    if enableUnfree
+    then {
+      elasticsearch = pkgs.elasticsearch6;
+      logstash      = pkgs.logstash6;
+      kibana        = pkgs.kibana6;
+    }
+    else {
+      elasticsearch = pkgs.elasticsearch6-oss;
+      logstash      = pkgs.logstash6-oss;
+      kibana        = pkgs.kibana6-oss;
+    };
+}
diff --git a/nixpkgs/nixos/tests/emacs-daemon.nix b/nixpkgs/nixos/tests/emacs-daemon.nix
new file mode 100644
index 000000000000..3594e35e343c
--- /dev/null
+++ b/nixpkgs/nixos/tests/emacs-daemon.nix
@@ -0,0 +1,45 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "emacs-daemon";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  enableOCR = true;
+
+  machine =
+    { ... }:
+
+    { imports = [ ./common/x11.nix ];
+      services.emacs = {
+        enable = true;
+        defaultEditor = true;
+      };
+
+      # Important to get the systemd service running for root
+      environment.variables.XDG_RUNTIME_DIR = "/run/user/0";
+
+      environment.variables.TEST_SYSTEM_VARIABLE = "system variable";
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("multi-user.target");
+
+      # checks that the EDITOR environment variable is set
+      $machine->succeed("test \$(basename \"\$EDITOR\") = emacseditor");
+
+      # waits for the emacs service to be ready
+      $machine->waitUntilSucceeds("systemctl --user status emacs.service | grep 'Active: active'");
+
+      # connects to the daemon
+      $machine->succeed("emacsclient --create-frame \$EDITOR &");
+
+      # checks that Emacs shows the edited filename
+      $machine->waitForText("emacseditor");
+
+      # makes sure environment variables are accessible from Emacs
+      $machine->succeed("emacsclient --eval '(getenv \"TEST_SYSTEM_VARIABLE\")'") =~ /system variable/ or die;
+
+      $machine->screenshot("emacsclient");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/env.nix b/nixpkgs/nixos/tests/env.nix
new file mode 100644
index 000000000000..064c498204ae
--- /dev/null
+++ b/nixpkgs/nixos/tests/env.nix
@@ -0,0 +1,35 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "environment";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages;
+      environment.etc."plainFile".text = ''
+        Hello World
+      '';
+      environment.etc."folder/with/file".text = ''
+        Foo Bar!
+      '';
+
+      environment.sessionVariables = {
+        TERMINFO_DIRS = "/run/current-system/sw/share/terminfo";
+        NIXCON = "awesome";
+      };
+    };
+
+  testScript =
+    ''
+      $machine->succeed('[ -L "/etc/plainFile" ]');
+      $machine->succeed('cat "/etc/plainFile" | grep "Hello World"');
+      $machine->succeed('[ -d "/etc/folder" ]');
+      $machine->succeed('[ -d "/etc/folder/with" ]');
+      $machine->succeed('[ -L "/etc/folder/with/file" ]');
+      $machine->succeed('cat "/etc/plainFile" | grep "Hello World"');
+
+      $machine->succeed('echo ''${TERMINFO_DIRS} | grep "/run/current-system/sw/share/terminfo"');
+      $machine->succeed('echo ''${NIXCON} | grep "awesome"');
+    '';
+})
diff --git a/nixpkgs/nixos/tests/etcd-cluster.nix b/nixpkgs/nixos/tests/etcd-cluster.nix
new file mode 100644
index 000000000000..3c4de5950a79
--- /dev/null
+++ b/nixpkgs/nixos/tests/etcd-cluster.nix
@@ -0,0 +1,157 @@
+# This test runs simple etcd cluster
+
+import ./make-test.nix ({ pkgs, ... } : let
+
+  runWithOpenSSL = file: cmd: pkgs.runCommand file {
+    buildInputs = [ pkgs.openssl ];
+  } cmd;
+
+  ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+  ca_pem = runWithOpenSSL "ca.pem" ''
+    openssl req \
+      -x509 -new -nodes -key ${ca_key} \
+      -days 10000 -out $out -subj "/CN=etcd-ca"
+  '';
+  etcd_key = runWithOpenSSL "etcd-key.pem" "openssl genrsa -out $out 2048";
+  etcd_csr = runWithOpenSSL "etcd.csr" ''
+    openssl req \
+       -new -key ${etcd_key} \
+       -out $out -subj "/CN=etcd" \
+       -config ${openssl_cnf}
+  '';
+  etcd_cert = runWithOpenSSL "etcd.pem" ''
+    openssl x509 \
+      -req -in ${etcd_csr} \
+      -CA ${ca_pem} -CAkey ${ca_key} \
+      -CAcreateserial -out $out \
+      -days 365 -extensions v3_req \
+      -extfile ${openssl_cnf}
+  '';
+
+  etcd_client_key = runWithOpenSSL "etcd-client-key.pem"
+    "openssl genrsa -out $out 2048";
+
+  etcd_client_csr = runWithOpenSSL "etcd-client-key.pem" ''
+    openssl req \
+      -new -key ${etcd_client_key} \
+      -out $out -subj "/CN=etcd-client" \
+      -config ${client_openssl_cnf}
+  '';
+
+  etcd_client_cert = runWithOpenSSL "etcd-client.crt" ''
+    openssl x509 \
+      -req -in ${etcd_client_csr} \
+      -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \
+      -out $out -days 365 -extensions v3_req \
+      -extfile ${client_openssl_cnf}
+  '';
+
+  openssl_cnf = pkgs.writeText "openssl.cnf" ''
+    ions = v3_req
+    distinguished_name = req_distinguished_name
+    [req_distinguished_name]
+    [ v3_req ]
+    basicConstraints = CA:FALSE
+    keyUsage = digitalSignature, keyEncipherment
+    extendedKeyUsage = serverAuth
+    subjectAltName = @alt_names
+    [alt_names]
+    DNS.1 = node1
+    DNS.2 = node2
+    DNS.3 = node3
+    IP.1 = 127.0.0.1
+  '';
+
+  client_openssl_cnf = pkgs.writeText "client-openssl.cnf" ''
+    ions = v3_req
+    distinguished_name = req_distinguished_name
+    [req_distinguished_name]
+    [ v3_req ]
+    basicConstraints = CA:FALSE
+    keyUsage = digitalSignature, keyEncipherment
+    extendedKeyUsage = clientAuth
+  '';
+
+  nodeConfig = {
+    services = {
+      etcd = {
+        enable = true;
+        keyFile = etcd_key;
+        certFile = etcd_cert;
+        trustedCaFile = ca_pem;
+        peerClientCertAuth = true;
+        listenClientUrls = ["https://127.0.0.1:2379"];
+        listenPeerUrls = ["https://0.0.0.0:2380"];
+      };
+    };
+
+    environment.variables = {
+      ETCDCTL_CERT_FILE = "${etcd_client_cert}";
+      ETCDCTL_KEY_FILE = "${etcd_client_key}";
+      ETCDCTL_CA_FILE = "${ca_pem}";
+      ETCDCTL_PEERS = "https://127.0.0.1:2379";
+    };
+
+    networking.firewall.allowedTCPPorts = [ 2380 ];
+  };
+in {
+  name = "etcd";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    node1 = { ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
+        initialAdvertisePeerUrls = ["https://node1:2380"];
+      };
+    };
+
+    node2 = { ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
+        initialAdvertisePeerUrls = ["https://node2:2380"];
+      };
+    };
+
+    node3 = { ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380" "node3=https://node3:2380"];
+        initialAdvertisePeerUrls = ["https://node3:2380"];
+        initialClusterState = "existing";
+      };
+    };
+  };
+
+  testScript = ''
+    subtest "should start etcd cluster", sub {
+      $node1->start();
+      $node2->start();
+      $node1->waitForUnit("etcd.service");
+      $node2->waitForUnit("etcd.service");
+      $node2->waitUntilSucceeds("etcdctl cluster-health");
+      $node1->succeed("etcdctl set /foo/bar 'Hello world'");
+      $node2->succeed("etcdctl get /foo/bar | grep 'Hello world'");
+    };
+
+    subtest "should add another member", sub {
+      $node1->succeed("etcdctl member add node3 https://node3:2380");
+      $node3->start();
+      $node3->waitForUnit("etcd.service");
+      $node3->waitUntilSucceeds("etcdctl member list | grep 'node3'");
+      $node3->succeed("etcdctl cluster-health");
+    };
+
+    subtest "should survive member crash", sub {
+      $node3->crash;
+      $node1->succeed("etcdctl cluster-health");
+      $node1->succeed("etcdctl set /foo/bar 'Hello degraded world'");
+      $node1->succeed("etcdctl get /foo/bar | grep 'Hello degraded world'");
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/etcd.nix b/nixpkgs/nixos/tests/etcd.nix
new file mode 100644
index 000000000000..6c23b31779bc
--- /dev/null
+++ b/nixpkgs/nixos/tests/etcd.nix
@@ -0,0 +1,27 @@
+# This test runs simple etcd node
+
+import ./make-test.nix ({ pkgs, ... } : {
+  name = "etcd";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    node = { ... }: {
+      services.etcd.enable = true;
+    };
+  };
+
+  testScript = ''
+    subtest "should start etcd node", sub {
+      $node->start();
+      $node->waitForUnit("etcd.service");
+    };
+
+    subtest "should write and read some values to etcd", sub {
+      $node->succeed("etcdctl set /foo/bar 'Hello world'");
+      $node->succeed("etcdctl get /foo/bar | grep 'Hello world'");
+    }
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ferm.nix b/nixpkgs/nixos/tests/ferm.nix
new file mode 100644
index 000000000000..b8e8663e3ad2
--- /dev/null
+++ b/nixpkgs/nixos/tests/ferm.nix
@@ -0,0 +1,74 @@
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "ferm";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mic92 ];
+  };
+
+  nodes =
+    { client =
+        { pkgs, ... }:
+        with pkgs.lib;
+        {
+          networking = {
+            dhcpcd.enable = false;
+            interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::2"; prefixLength = 64; } ];
+            interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          };
+      };
+      server =
+        { pkgs, ... }:
+        with pkgs.lib;
+        {
+          networking = {
+            dhcpcd.enable = false;
+            interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::1"; prefixLength = 64; } ];
+            interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          };
+
+          services = {
+            ferm.enable = true;
+            ferm.config = ''
+              domain (ip ip6) table filter chain INPUT {
+                interface lo ACCEPT;
+                proto tcp dport 8080 REJECT reject-with tcp-reset;
+              }
+            '';
+            nginx.enable = true;
+            nginx.httpConfig = ''
+              server {
+                listen 80;
+                listen [::]:80;
+                listen 8080;
+                listen [::]:8080;
+
+                location /status { stub_status on; }
+              }
+            '';
+          };
+        };
+    };
+
+  testScript =
+    ''
+      startAll;
+
+      $client->waitForUnit("network-online.target");
+      $server->waitForUnit("ferm.service");
+      $server->waitForUnit("nginx.service");
+      $server->waitUntilSucceeds("ss -ntl | grep -q 80");
+
+      subtest "port 80 is allowed", sub {
+          $client->succeed("curl --fail -g http://192.168.1.1:80/status");
+          $client->succeed("curl --fail -g http://[fd00::1]:80/status");
+      };
+
+      subtest "port 8080 is not allowed", sub {
+          $server->succeed("curl --fail -g http://192.168.1.1:8080/status");
+          $server->succeed("curl --fail -g http://[fd00::1]:8080/status");
+
+          $client->fail("curl --fail -g http://192.168.1.1:8080/status");
+          $client->fail("curl --fail -g http://[fd00::1]:8080/status");
+      };
+    '';
+})
diff --git a/nixpkgs/nixos/tests/firefox.nix b/nixpkgs/nixos/tests/firefox.nix
new file mode 100644
index 000000000000..58a80243ea9c
--- /dev/null
+++ b/nixpkgs/nixos/tests/firefox.nix
@@ -0,0 +1,29 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "firefox";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow shlevy ];
+  };
+
+  machine =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      environment.systemPackages = [ pkgs.firefox pkgs.xdotool ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForX;
+      $machine->execute("xterm -e 'firefox file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' &");
+      $machine->waitForWindow(qr/Valgrind/);
+      $machine->sleep(40); # wait until Firefox has finished loading the page
+      $machine->execute("xdotool key space"); # do I want to make Firefox the
+                             # default browser? I just want to close the dialog
+      $machine->sleep(2); # wait until Firefox hides the default browser window
+      $machine->execute("xdotool key F12");
+      $machine->sleep(10); # wait until Firefox draws the developer tool panel
+      $machine->succeed("xwininfo -root -tree | grep Valgrind");
+      $machine->screenshot("screen");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/firewall.nix b/nixpkgs/nixos/tests/firewall.nix
new file mode 100644
index 000000000000..7207a880d8e2
--- /dev/null
+++ b/nixpkgs/nixos/tests/firewall.nix
@@ -0,0 +1,65 @@
+# Test the firewall module.
+
+import ./make-test.nix ( { pkgs, ... } : {
+  name = "firewall";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  nodes =
+    { walled =
+        { ... }:
+        { networking.firewall.enable = true;
+          networking.firewall.logRefusedPackets = true;
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+        };
+
+      # Dummy configuration to check whether firewall.service will be honored
+      # during system activation. This only needs to be different to the
+      # original walled configuration so that there is a change in the service
+      # file.
+      walled2 =
+        { ... }:
+        { networking.firewall.enable = true;
+          networking.firewall.rejectPackets = true;
+        };
+
+      attacker =
+        { ... }:
+        { services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript = { nodes, ... }: let
+    newSystem = nodes.walled2.config.system.build.toplevel;
+  in ''
+    $walled->start;
+    $attacker->start;
+
+    $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, but ping should succeed.
+    $attacker->fail("curl --fail --connect-timeout 2 http://walled/ >&2");
+    $attacker->succeed("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");
+
+    # Check whether activation of a new configuration reloads the firewall.
+    $walled->succeed("${newSystem}/bin/switch-to-configuration test 2>&1" .
+                     " | grep -qF firewall.service");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/flannel.nix b/nixpkgs/nixos/tests/flannel.nix
new file mode 100644
index 000000000000..fb66fe282090
--- /dev/null
+++ b/nixpkgs/nixos/tests/flannel.nix
@@ -0,0 +1,55 @@
+import ./make-test.nix ({ pkgs, ...} : rec {
+  name = "flannel";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = let
+    flannelConfig = {
+      services.flannel = {
+        enable = true;
+        network = "10.1.0.0/16";
+        iface = "eth1";
+        etcd.endpoints = ["http://etcd:2379"];
+      };
+
+      networking.firewall.allowedUDPPorts = [ 8472 ];
+    };
+  in {
+    etcd = { ... }: {
+      services = {
+        etcd = {
+          enable = true;
+          listenClientUrls = ["http://etcd:2379"];
+          listenPeerUrls = ["http://etcd:2380"];
+          initialAdvertisePeerUrls = ["http://etcd:2379"];
+          initialCluster = ["etcd=http://etcd:2379"];
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 2379 ];
+    };
+
+    node1 = { ... }: {
+      require = [flannelConfig];
+    };
+
+    node2 = { ... }: {
+      require = [flannelConfig];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $node1->waitForUnit("flannel.service");
+    $node2->waitForUnit("flannel.service");
+
+    my $ip1 = $node1->succeed("ip -4 addr show flannel.1 | grep -oP '(?<=inet).*(?=/)'");
+    my $ip2 = $node2->succeed("ip -4 addr show flannel.1 | grep -oP '(?<=inet).*(?=/)'");
+
+    $node1->waitUntilSucceeds("ping -c 1 $ip2");
+    $node2->waitUntilSucceeds("ping -c 1 $ip1");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/flatpak.nix b/nixpkgs/nixos/tests/flatpak.nix
new file mode 100644
index 000000000000..b0c61830d05a
--- /dev/null
+++ b/nixpkgs/nixos/tests/flatpak.nix
@@ -0,0 +1,26 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "flatpak";
+  meta = {
+    maintainers = pkgs.flatpak.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    imports = [ ./common/x11.nix ];
+    services.xserver.desktopManager.gnome3.enable = true; # TODO: figure out minimal environment where the tests work
+    # common/x11.nix enables the auto display manager (lightdm)
+    services.xserver.displayManager.gdm.enable = false;
+    environment.gnome3.excludePackages = pkgs.gnome3.optionalPackages;
+    services.flatpak.enable = true;
+    environment.systemPackages = with pkgs; [ gnupg gnome-desktop-testing ostree python2 ];
+    virtualisation.memorySize = 2047;
+    virtualisation.diskSize = 1024;
+  };
+
+  testScript = ''
+    $machine->waitForX();
+    $machine->succeed("gnome-desktop-testing-runner -d '${pkgs.flatpak.installedTests}/share' --timeout 3600");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fsck.nix b/nixpkgs/nixos/tests/fsck.nix
new file mode 100644
index 000000000000..f943bb7f2350
--- /dev/null
+++ b/nixpkgs/nixos/tests/fsck.nix
@@ -0,0 +1,29 @@
+import ./make-test.nix {
+  name = "fsck";
+
+  machine = { lib, ... }: {
+    virtualisation.emptyDiskImages = [ 1 ];
+
+    fileSystems = lib.mkVMOverride {
+      "/mnt" = {
+        device = "/dev/vdb";
+        fsType = "ext4";
+        autoFormat = true;
+      };
+    };
+  };
+
+  testScript = ''
+    $machine->waitForUnit('default.target');
+
+    subtest "root fs is fsckd", sub {
+      $machine->succeed('journalctl -b | grep "fsck.ext4.*/dev/vda"');
+    };
+
+    subtest "mnt fs is fsckd", sub {
+      $machine->succeed('journalctl -b | grep "fsck.*/dev/vdb.*clean"');
+      $machine->succeed('grep "Requires=systemd-fsck@dev-vdb.service" /run/systemd/generator/mnt.mount');
+      $machine->succeed('grep "After=systemd-fsck@dev-vdb.service" /run/systemd/generator/mnt.mount');
+    };
+  '';
+}
diff --git a/nixpkgs/nixos/tests/fwupd.nix b/nixpkgs/nixos/tests/fwupd.nix
new file mode 100644
index 000000000000..88dac8ccbcdb
--- /dev/null
+++ b/nixpkgs/nixos/tests/fwupd.nix
@@ -0,0 +1,21 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "fwupd";
+
+  meta = {
+    maintainers = pkgs.fwupd.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    services.fwupd.enable = true;
+    services.fwupd.blacklistPlugins = []; # don't blacklist test plugin
+    services.fwupd.enableTestRemote = true;
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+    environment.variables.XDG_DATA_DIRS = [ "${pkgs.fwupd.installedTests}/share" ];
+    virtualisation.memorySize = 768;
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gdk-pixbuf.nix b/nixpkgs/nixos/tests/gdk-pixbuf.nix
new file mode 100644
index 000000000000..005c5111da2b
--- /dev/null
+++ b/nixpkgs/nixos/tests/gdk-pixbuf.nix
@@ -0,0 +1,21 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "gdk-pixbuf";
+
+  meta = {
+    maintainers = pkgs.gdk_pixbuf.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+    environment.variables.XDG_DATA_DIRS = [ "${pkgs.gdk_pixbuf.installedTests}/share" ];
+
+    # Tests allocate a lot of memory trying to exploit a CVE
+    # but qemu-system-i386 has a 2047M memory limit
+    virtualisation.memorySize = if pkgs.stdenv.isi686 then 2047 else 4096;
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner -t 1800"); # increase timeout to 1800s
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gitea.nix b/nixpkgs/nixos/tests/gitea.nix
new file mode 100644
index 000000000000..28e6479e9cbe
--- /dev/null
+++ b/nixpkgs/nixos/tests/gitea.nix
@@ -0,0 +1,79 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  mysql = makeTest {
+    name = "gitea-mysql";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.mysql.enable = true;
+        services.mysql.package = pkgs.mariadb;
+        services.mysql.ensureDatabases = [ "gitea" ];
+        services.mysql.ensureUsers = [
+          { name = "gitea";
+            ensurePermissions = { "gitea.*" = "ALL PRIVILEGES"; };
+          }
+        ];
+
+        services.gitea.enable = true;
+        services.gitea.database.type = "mysql";
+        services.gitea.database.socket = "/run/mysqld/mysqld.sock";
+      };
+
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('gitea.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+    '';
+  };
+
+  postgres = makeTest {
+    name = "gitea-postgres";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      {
+        services.gitea.enable = true;
+        services.gitea.database.type = "postgres";
+        services.gitea.database.password = "secret";
+      };
+
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('gitea.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+    '';
+  };
+
+  sqlite = makeTest {
+    name = "gitea-sqlite";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.gitea.enable = true;
+        services.gitea.disableRegistration = true;
+      };
+
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('gitea.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+      $machine->succeed("curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. Please contact your site administrator.'");
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/gitlab.nix b/nixpkgs/nixos/tests/gitlab.nix
new file mode 100644
index 000000000000..16e0dd723ecf
--- /dev/null
+++ b/nixpkgs/nixos/tests/gitlab.nix
@@ -0,0 +1,83 @@
+# This test runs gitlab and checks if it works
+
+import ./make-test.nix ({ pkgs, lib, ...} : with lib; {
+  name = "gitlab";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ globin ];
+  };
+
+  nodes = {
+    gitlab = { ... }: {
+      virtualisation.memorySize = if pkgs.stdenv.is64bit then 4096 else 2047;
+      systemd.services.gitlab.serviceConfig.Restart = mkForce "no";
+      systemd.services.gitlab-workhorse.serviceConfig.Restart = mkForce "no";
+      systemd.services.gitaly.serviceConfig.Restart = mkForce "no";
+      systemd.services.gitlab-sidekiq.serviceConfig.Restart = mkForce "no";
+
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+        virtualHosts = {
+          "localhost" = {
+            locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket";
+          };
+        };
+      };
+
+      services.gitlab = {
+        enable = true;
+        databasePassword = "dbPassword";
+        initialRootPassword = "notproduction";
+        smtp.enable = true;
+        secrets = {
+          secret = "secret";
+          otp = "otpsecret";
+          db = "dbsecret";
+
+          # nix-shell -p openssl --run "openssl genrsa 2048"
+          jws = ''
+            -----BEGIN RSA PRIVATE KEY-----
+            MIIEpAIBAAKCAQEA13/qEio76OWUtWO0WIz9lWnsTWOU8Esv4sQHDq9PCEFsLt21
+            PAXrlWhLjjWcxGfsrDwnh7YErGHYL62BMSxMdFJolaknlQK/O/V8UETDe45VoHM+
+            Znk270RfUcfYFgiihnXUZXVmL0om9TsQSk646wCcjCY9LxtxUyKNhvT7KjgYw2aX
+            z34aw7M+Js3T2p1TjZPSC82GtmtKkJEKFMi5EjprLTDE7EdcUzr9Xuw+kQ+gRm9k
+            7FE+JQqSoprwE3Q0v2OAn3UhLMgg0gNFRnsc5l6IAshDzV+H22RPqKKlJjVjjfPY
+            0TQSvYLVApigHbDPH0BoCXfjFfQazbbP3OUHrwIDAQABAoIBAQCMU+tkcMQaYIV5
+            qLdjgkwO467QpivyXcOM8wF1eosIYTHFQvIlZ+WEoSmyLQ8shlADyBgls01Pw1c3
+            lNAv6RzQEmmwKzpvOh61OKH+0whIiOMRXHoh2IUBQZCgfHYlwvGyhUAN4WjtGmhM
+            AG4XNTQNM5S9Xpkw97nP3Qwz+YskbbkrfqtCEVy9ro+4nhbjqPsuO3adbnkva4zR
+            cyurRhrHgHU6LPjn5NHnHH4qw2faY2oAsL8pmpkTbO5IqWDvOcbjNfjVPgVoq26O
+            bbaa1qs4nmc80qQgMjRPJef535xyf3eLsSlDvpf6O8sPrJzVR1zaqEqixpQCZDac
+            +kRiSBrhAoGBAOwHiq0PuyJh6VzBu7ybqX6+gF/wA4Jkwzx6mbfaBgurvU1aospp
+            kisIonAkxSbxllZMnjbkShZEdATYKeT9o5NEhnU4YnHfc5bJZbiWOZAzYGLcY7g8
+            vDQ31pBItyY4pFgPbSpNlbUvUsoPVJ45RasRADDTNCzMzdjFQQXst2V9AoGBAOm7
+            sSpzYfFPLEAhieAkuhtbsX58Boo46djiKVfzGftfp6F9aHTOfzGORU5jrZ16mSbS
+            qkkC6BEFrATX2051dzzXC89fWoJYALrsffE5I3KlKXsCAWSnCP1MMxOfH+Ls61Mr
+            7pK/LKfvJt53mUH4jIdbmmFUDwbg18oBEH+x9PmbAoGAS/+JqXu9N67rIxDGUE6W
+            3tacI0f2+U9Uhe67/DTZaXyc8YFTlXU0uWKIWy+bw5RaYeM9tlL/f/f+m2i25KK+
+            vrZ7zNag7CWU5GJovGyykDnauTpZaYM03mN0VPT08/uc/zXIYqyknbhlIeaZynCK
+            fDB3LUF0NVCknz20WCIGU0kCgYEAkxY0ZXx61Dp4pFr2wwEZxQGs7uXpz64FKyEX
+            12r6nMATY4Lh6y/Px0W6w5vis8lk+5Ny6cNUevHQ0LNuJS+yu6ywl+1vrbrnqroM
+            f3LvpcPeGLSoX8jl1VDQi7aFgG6LoKly1xJLbdsH4NPutB9PgBbbTghx9GgmI88L
+            rPA2M6UCgYBOmkYJocNgxg6B1/n4Tb9fN1Q/XuJrFDE6NxVUoke+IIyMPRH7FC3m
+            VMYzu+b7zTVJjaBb1cmJemxl/xajziWDofJYPefhdbOVU7HXtmJFY0IG3pVxU1zW
+            3bmDj5QAtCUDpuuNa6GEIT0YR4+D/V7o3DmlZ0tVIwKJmVJoQ2f5dw==
+            -----END RSA PRIVATE KEY-----
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    $gitlab->start();
+    $gitlab->waitForUnit("gitaly.service");
+    $gitlab->waitForUnit("gitlab-workhorse.service");
+    $gitlab->waitForUnit("gitlab.service");
+    $gitlab->waitForUnit("gitlab-sidekiq.service");
+    $gitlab->waitForFile("/var/gitlab/state/tmp/sockets/gitlab.socket");
+    $gitlab->waitUntilSucceeds("curl -sSf http://gitlab/users/sign_in");
+    $gitlab->succeed("curl -isSf http://gitlab  | grep -i location | grep -q http://gitlab/users/sign_in");
+    $gitlab->succeed("${pkgs.sudo}/bin/sudo -u gitlab -H gitlab-rake gitlab:check 1>&2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gitolite.nix b/nixpkgs/nixos/tests/gitolite.nix
new file mode 100644
index 000000000000..690e456ed7c8
--- /dev/null
+++ b/nixpkgs/nixos/tests/gitolite.nix
@@ -0,0 +1,139 @@
+import ./make-test.nix ({ pkgs, ...}:
+
+let
+  adminPrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACDu7qxYQAPdAU6RrhB3llk2N1v4PTwcVzcX1oX265uC3gAAAJBJiYxDSYmM
+    QwAAAAtzc2gtZWQyNTUxOQAAACDu7qxYQAPdAU6RrhB3llk2N1v4PTwcVzcX1oX265uC3g
+    AAAEDE1W6vMwSEUcF1r7Hyypm/+sCOoDmKZgPxi3WOa1mD2u7urFhAA90BTpGuEHeWWTY3
+    W/g9PBxXNxfWhfbrm4LeAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  adminPublicKey = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7urFhAA90BTpGuEHeWWTY3W/g9PBxXNxfWhfbrm4Le root@client
+  '';
+
+  alicePrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBbeWvHh/AWGWI6EIc1xlSihyXtacNQ9KeztlW/VUy8wQAAAJAwVQ5VMFUO
+    VQAAAAtzc2gtZWQyNTUxOQAAACBbeWvHh/AWGWI6EIc1xlSihyXtacNQ9KeztlW/VUy8wQ
+    AAAEB7lbfkkdkJoE+4TKHPdPQWBKLSx+J54Eg8DaTr+3KoSlt5a8eH8BYZYjoQhzXGVKKH
+    Je1pw1D0p7O2Vb9VTLzBAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  alicePublicKey = pkgs.writeText "id_ed25519.pub" ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFt5a8eH8BYZYjoQhzXGVKKHJe1pw1D0p7O2Vb9VTLzB alice@client
+  '';
+
+  bobPrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACCWTaJ1D9Xjxy6759FvQ9oXTes1lmWBciXPkEeqTikBMAAAAJDQBmNV0AZj
+    VQAAAAtzc2gtZWQyNTUxOQAAACCWTaJ1D9Xjxy6759FvQ9oXTes1lmWBciXPkEeqTikBMA
+    AAAEDM1IYYFUwk/IVxauha9kuR6bbRtT3gZ6ZA0GLb9txb/pZNonUP1ePHLrvn0W9D2hdN
+    6zWWZYFyJc+QR6pOKQEwAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  bobPublicKey = pkgs.writeText "id_ed25519.pub" ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZNonUP1ePHLrvn0W9D2hdN6zWWZYFyJc+QR6pOKQEw bob@client
+  '';
+
+  gitoliteAdminConfSnippet = ''
+    repo alice-project
+        RW+     =   alice
+  '';
+in
+{
+  name = "gitolite";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bjornfor ];
+  };
+
+  nodes = {
+
+    server =
+      { ... }:
+      {
+        services.gitolite = {
+          enable = true;
+          adminPubkey = adminPublicKey;
+        };
+        services.openssh.enable = true;
+      };
+
+    client =
+      { pkgs, ... }:
+      {
+        environment.systemPackages = [ pkgs.git ];
+        programs.ssh.extraConfig = ''
+          Host *
+            UserKnownHostsFile /dev/null
+            StrictHostKeyChecking no
+            # there's nobody around that can input password
+            PreferredAuthentications publickey
+        '';
+        users.users.alice = { isNormalUser = true; };
+        users.users.bob = { isNormalUser = true; };
+      };
+
+  };
+
+  testScript = ''
+    startAll;
+
+    subtest "can setup ssh keys on system", sub {
+      $client->mustSucceed("mkdir -p ~root/.ssh");
+      $client->mustSucceed("cp ${adminPrivateKey} ~root/.ssh/id_ed25519");
+      $client->mustSucceed("chmod 600 ~root/.ssh/id_ed25519");
+
+      $client->mustSucceed("sudo -u alice mkdir -p ~alice/.ssh");
+      $client->mustSucceed("sudo -u alice cp ${alicePrivateKey} ~alice/.ssh/id_ed25519");
+      $client->mustSucceed("sudo -u alice chmod 600 ~alice/.ssh/id_ed25519");
+
+      $client->mustSucceed("sudo -u bob mkdir -p ~bob/.ssh");
+      $client->mustSucceed("sudo -u bob cp ${bobPrivateKey} ~bob/.ssh/id_ed25519");
+      $client->mustSucceed("sudo -u bob chmod 600 ~bob/.ssh/id_ed25519");
+    };
+
+    subtest "gitolite server starts", sub {
+      $server->waitForUnit("gitolite-init.service");
+      $server->waitForUnit("sshd.service");
+      $client->mustSucceed('ssh gitolite@server info');
+    };
+
+    subtest "admin can clone and configure gitolite-admin.git", sub {
+      $client->mustSucceed('git clone gitolite@server:gitolite-admin.git');
+      $client->mustSucceed("git config --global user.name 'System Administrator'");
+      $client->mustSucceed("git config --global user.email root\@domain.example");
+      $client->mustSucceed("cp ${alicePublicKey} gitolite-admin/keydir/alice.pub");
+      $client->mustSucceed("cp ${bobPublicKey} gitolite-admin/keydir/bob.pub");
+      $client->mustSucceed('(cd gitolite-admin && git add . && git commit -m "Add keys for alice, bob" && git push)');
+      $client->mustSucceed("printf '${gitoliteAdminConfSnippet}' >> gitolite-admin/conf/gitolite.conf");
+      $client->mustSucceed('(cd gitolite-admin && git add . && git commit -m "Add repo for alice" && git push)');
+    };
+
+    subtest "non-admins cannot clone gitolite-admin.git", sub {
+      $client->mustFail('sudo -i -u alice git clone gitolite@server:gitolite-admin.git');
+      $client->mustFail('sudo -i -u bob git clone gitolite@server:gitolite-admin.git');
+    };
+
+    subtest "non-admins can clone testing.git", sub {
+      $client->mustSucceed('sudo -i -u alice git clone gitolite@server:testing.git');
+      $client->mustSucceed('sudo -i -u bob git clone gitolite@server:testing.git');
+    };
+
+    subtest "alice can clone alice-project.git", sub {
+      $client->mustSucceed('sudo -i -u alice git clone gitolite@server:alice-project.git');
+    };
+
+    subtest "bob cannot clone alice-project.git", sub {
+      $client->mustFail('sudo -i -u bob git clone gitolite@server:alice-project.git');
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gjs.nix b/nixpkgs/nixos/tests/gjs.nix
new file mode 100644
index 000000000000..e6002ef98dd0
--- /dev/null
+++ b/nixpkgs/nixos/tests/gjs.nix
@@ -0,0 +1,19 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "gjs";
+
+  meta = {
+    maintainers = pkgs.gnome3.gjs.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    imports = [ ./common/x11.nix ];
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+    environment.variables.XDG_DATA_DIRS = [ "${pkgs.gnome3.gjs.installedTests}/share" ];
+  };
+
+  testScript = ''
+    $machine->waitForX;
+    $machine->succeed("gnome-desktop-testing-runner");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gnome3-gdm.nix b/nixpkgs/nixos/tests/gnome3-gdm.nix
new file mode 100644
index 000000000000..c2808d87d99d
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnome3-gdm.nix
@@ -0,0 +1,63 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "gnome3-gdm";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lethalman ];
+  };
+
+  machine =
+    { ... }:
+
+    { imports = [ ./common/user-account.nix ];
+
+      services.xserver.enable = true;
+
+      services.xserver.displayManager.gdm = {
+        enable = true;
+        autoLogin = {
+          enable = true;
+          user = "alice";
+        };
+      };
+      services.xserver.desktopManager.gnome3.enable = true;
+
+      virtualisation.memorySize = 1024;
+    };
+
+  testScript = let
+    # Keep line widths somewhat managable
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus";
+    gdbus = "${bus} gdbus";
+    # Call javascript in gnome shell, returns a tuple (success, output), where
+    # `success` is true if the dbus call was successful and output is what the
+    # javascript evaluates to.
+    eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval";
+    # False when startup is done
+    startingUp = "${gdbus} ${eval} Main.layoutManager._startingUp";
+    # Hopefully gnome-terminal's wm class
+    wmClass = "${gdbus} ${eval} global.display.focus_window.wm_class";
+  in ''
+      # wait for gdm to start
+      $machine->waitForUnit("display-manager.service");
+
+      # wait for alice to be logged in
+      $machine->waitForUnit("default.target","alice");
+
+      # Check that logging in has given the user ownership of devices.
+      $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
+
+      # Wait for the wayland server
+      $machine->waitForFile("/run/user/1000/wayland-0");
+
+      # Wait for gnome shell, correct output should be "(true, 'false')"
+      $machine->waitUntilSucceeds("su - alice -c '${startingUp} | grep -q true,..false'");
+
+      # open a terminal
+      $machine->succeed("su - alice -c '${bus} gnome-terminal'");
+      # and check it's there
+      $machine->waitUntilSucceeds("su - alice -c '${wmClass} | grep -q gnome-terminal-server'");
+
+      # wait to get a nice screenshot
+      $machine->sleep(20);
+      $machine->screenshot("screen");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gnome3.nix b/nixpkgs/nixos/tests/gnome3.nix
new file mode 100644
index 000000000000..95694ea4828d
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnome3.nix
@@ -0,0 +1,41 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "gnome3";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ domenkozar eelco chaoflow lethalman ];
+  };
+
+  machine =
+    { ... }:
+
+    { imports = [ ./common/user-account.nix ];
+
+      services.xserver.enable = true;
+
+      services.xserver.displayManager.gdm.enable = false;
+      services.xserver.displayManager.lightdm.enable = true;
+      services.xserver.displayManager.lightdm.autoLogin.enable = true;
+      services.xserver.displayManager.lightdm.autoLogin.user = "alice";
+      services.xserver.desktopManager.gnome3.enable = true;
+      services.xserver.desktopManager.default = "gnome-xorg";
+
+      virtualisation.memorySize = 1024;
+    };
+
+  testScript =
+    ''
+      $machine->waitForX;
+
+      # wait for alice to be logged in
+      $machine->waitForUnit("default.target","alice");
+
+      # 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 gnome-terminal &'");
+      $machine->succeed("xauth merge ~alice/.Xauthority");
+      $machine->waitForWindow(qr/alice.*machine/);
+      $machine->succeed("timeout 900 bash -c 'while read msg; do if [[ \$msg =~ \"GNOME Shell started\" ]]; then break; fi; done < <(journalctl -f)'");
+      $machine->sleep(10);
+      $machine->screenshot("screen");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gocd-agent.nix b/nixpkgs/nixos/tests/gocd-agent.nix
new file mode 100644
index 000000000000..50470379576b
--- /dev/null
+++ b/nixpkgs/nixos/tests/gocd-agent.nix
@@ -0,0 +1,40 @@
+# verifies:
+#   1. GoCD agent starts
+#   2. GoCD agent responds
+#   3. GoCD agent is available on GoCD server using GoCD API
+#     3.1. https://api.go.cd/current/#get-all-agents
+
+let
+  serverUrl = "localhost:8153/go/api/agents";
+  header = "Accept: application/vnd.go.cd.v2+json";
+in
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "gocd-agent";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ grahamc swarren83 ];
+  };
+
+  nodes = {
+    gocd_agent =
+      { ... }:
+      {
+        virtualisation.memorySize = 2046;
+        services.gocd-agent = {
+          enable = true;
+        };
+        services.gocd-server = {
+          enable = true;
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $gocd_agent->waitForUnit("gocd-server");
+    $gocd_agent->waitForOpenPort("8153");
+    $gocd_agent->waitForUnit("gocd-agent");
+    $gocd_agent->waitUntilSucceeds("curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].uuid");
+    $gocd_agent->succeed("curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].agent_state | grep -q Idle");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gocd-server.nix b/nixpkgs/nixos/tests/gocd-server.nix
new file mode 100644
index 000000000000..80cf04ed6404
--- /dev/null
+++ b/nixpkgs/nixos/tests/gocd-server.nix
@@ -0,0 +1,28 @@
+# verifies:
+#   1. GoCD server starts
+#   2. GoCD server responds
+
+import ./make-test.nix ({ pkgs, ...} :
+
+{
+  name = "gocd-server";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ swarren83 ];
+  };
+
+nodes = {
+  gocd_server =
+    { ... }:
+    {
+      virtualisation.memorySize = 2046;
+      services.gocd-server.enable = true;
+    };
+};
+
+  testScript = ''
+    $gocd_server->start;
+    $gocd_server->waitForUnit("gocd-server");
+    $gocd_server->waitForOpenPort("8153");
+    $gocd_server->waitUntilSucceeds("curl -s -f localhost:8153/go");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/google-oslogin/default.nix b/nixpkgs/nixos/tests/google-oslogin/default.nix
new file mode 100644
index 000000000000..3b84bba3f985
--- /dev/null
+++ b/nixpkgs/nixos/tests/google-oslogin/default.nix
@@ -0,0 +1,52 @@
+import ../make-test.nix ({ pkgs, ... } :
+let
+  inherit (import ./../ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+in {
+  name = "google-oslogin";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ adisbladis flokli ];
+  };
+
+  nodes = {
+    # the server provides both the the mocked google metadata server and the ssh server
+    server = (import ./server.nix pkgs);
+
+    client = { ... }: {};
+  };
+  testScript =  ''
+    startAll;
+
+    $server->waitForUnit("mock-google-metadata.service");
+    $server->waitForOpenPort(80);
+
+    # mockserver should return a non-expired ssh key for both mockuser and mockadmin
+    $server->succeed('${pkgs.google-compute-engine-oslogin}/bin/google_authorized_keys mockuser | grep -q "${snakeOilPublicKey}"');
+    $server->succeed('${pkgs.google-compute-engine-oslogin}/bin/google_authorized_keys mockadmin | grep -q "${snakeOilPublicKey}"');
+
+    # install snakeoil ssh key on the client
+    $client->succeed("mkdir -p ~/.ssh");
+    $client->succeed("cat ${snakeOilPrivateKey} > ~/.ssh/id_snakeoil");
+    $client->succeed("chmod 600 ~/.ssh/id_snakeoil");
+
+    $client->waitForUnit("network.target");
+    $server->waitForUnit("sshd.service");
+
+    # we should not be able to connect as non-existing user
+    $client->fail("ssh -o User=ghost -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'");
+
+    # we should be able to connect as mockuser
+    $client->succeed("ssh -o User=mockuser -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'");
+    # but we shouldn't be able to sudo
+    $client->fail("ssh -o User=mockuser -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil '/run/wrappers/bin/sudo /run/current-system/sw/bin/id' | grep -q 'root'");
+
+    # we should also be able to log in as mockadmin
+    $client->succeed("ssh -o User=mockadmin -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'");
+    # pam_oslogin_admin.so should now have generated a sudoers file
+    $server->succeed("find /run/google-sudoers.d | grep -q '/run/google-sudoers.d/mockadmin'");
+
+    # and we should be able to sudo
+    $client->succeed("ssh -o User=mockadmin -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil '/run/wrappers/bin/sudo /run/current-system/sw/bin/id' | grep -q 'root'");
+  '';
+  })
+
diff --git a/nixpkgs/nixos/tests/google-oslogin/server.nix b/nixpkgs/nixos/tests/google-oslogin/server.nix
new file mode 100644
index 000000000000..fdb7141da317
--- /dev/null
+++ b/nixpkgs/nixos/tests/google-oslogin/server.nix
@@ -0,0 +1,29 @@
+{ pkgs, ... }:
+let
+  inherit (import ./../ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+in {
+  networking.firewall.allowedTCPPorts = [ 80 ];
+
+  systemd.services.mock-google-metadata = {
+    description = "Mock Google metadata service";
+    serviceConfig.Type = "simple";
+    serviceConfig.ExecStart = "${pkgs.python3}/bin/python ${./server.py}";
+    environment = {
+      SNAKEOIL_PUBLIC_KEY = snakeOilPublicKey;
+    };
+    wantedBy = [ "multi-user.target" ];
+    after = [ "network.target" ];
+  };
+
+  services.openssh.enable = true;
+  services.openssh.challengeResponseAuthentication = false;
+  services.openssh.passwordAuthentication = false;
+
+  security.googleOsLogin.enable = true;
+
+  # Mock google service
+  networking.extraHosts = ''
+    127.0.0.1 metadata.google.internal
+  '';
+}
diff --git a/nixpkgs/nixos/tests/google-oslogin/server.py b/nixpkgs/nixos/tests/google-oslogin/server.py
new file mode 100644
index 000000000000..bfc527cb97d3
--- /dev/null
+++ b/nixpkgs/nixos/tests/google-oslogin/server.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+import json
+import sys
+import time
+import os
+import hashlib
+import base64
+
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from typing import Dict
+
+SNAKEOIL_PUBLIC_KEY = os.environ['SNAKEOIL_PUBLIC_KEY']
+
+
+def w(msg):
+    sys.stderr.write(f"{msg}\n")
+    sys.stderr.flush()
+
+
+def gen_fingerprint(pubkey):
+    decoded_key = base64.b64decode(pubkey.encode("ascii").split()[1])
+    return hashlib.sha256(decoded_key).hexdigest()
+
+def gen_email(username):
+    """username seems to be a 21 characters long number string, so mimic that in a reproducible way"""
+    return str(int(hashlib.sha256(username.encode()).hexdigest(), 16))[0:21]
+
+def gen_mockuser(username: str, uid: str, gid: str, home_directory: str, snakeoil_pubkey: str) -> Dict:
+    snakeoil_pubkey_fingerprint = gen_fingerprint(snakeoil_pubkey)
+    # seems to be a 21 characters long numberstring, so mimic that in a reproducible way
+    email = gen_email(username)
+    return {
+        "loginProfiles": [
+            {
+                "name": email,
+                "posixAccounts": [
+                    {
+                        "primary": True,
+                        "username": username,
+                        "uid": uid,
+                        "gid": gid,
+                        "homeDirectory": home_directory,
+                        "operatingSystemType": "LINUX"
+                    }
+                ],
+                "sshPublicKeys": {
+                    snakeoil_pubkey_fingerprint: {
+                        "key": snakeoil_pubkey,
+                        "expirationTimeUsec": str((time.time() + 600) * 1000000),  # 10 minutes in the future
+                        "fingerprint": snakeoil_pubkey_fingerprint
+                    }
+                }
+            }
+        ]
+    }
+
+
+class ReqHandler(BaseHTTPRequestHandler):
+    def _send_json_ok(self, data):
+        self.send_response(200)
+        self.send_header('Content-type', 'application/json')
+        self.end_headers()
+        out = json.dumps(data).encode()
+        w(out)
+        self.wfile.write(out)
+
+    def do_GET(self):
+        p = str(self.path)
+        # mockuser and mockadmin are allowed to login, both use the same snakeoil public key
+        if p == '/computeMetadata/v1/oslogin/users?username=mockuser' \
+            or p == '/computeMetadata/v1/oslogin/users?uid=1009719690':
+            self._send_json_ok(gen_mockuser(username='mockuser', uid='1009719690', gid='1009719690',
+                                            home_directory='/home/mockuser', snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY))
+        elif p == '/computeMetadata/v1/oslogin/users?username=mockadmin' \
+            or p == '/computeMetadata/v1/oslogin/users?uid=1009719691':
+            self._send_json_ok(gen_mockuser(username='mockadmin', uid='1009719691', gid='1009719691',
+                                            home_directory='/home/mockadmin', snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY))
+
+        # mockuser is allowed to login
+        elif p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockuser')}&policy=login":
+            self._send_json_ok({'success': True})
+
+        # mockadmin may also become root
+        elif p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockadmin')}&policy=login" or p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockadmin')}&policy=adminLogin":
+            self._send_json_ok({'success': True})
+        else:
+            sys.stderr.write(f"Unhandled path: {p}\n")
+            sys.stderr.flush()
+            self.send_response(501)
+            self.end_headers()
+            self.wfile.write(b'')
+
+
+if __name__ == '__main__':
+    s = HTTPServer(('0.0.0.0', 80), ReqHandler)
+    s.serve_forever()
diff --git a/nixpkgs/nixos/tests/grafana.nix b/nixpkgs/nixos/tests/grafana.nix
new file mode 100644
index 000000000000..9dc765a879bc
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana.nix
@@ -0,0 +1,25 @@
+import ./make-test.nix ({ lib, ... }:
+{
+  name = "grafana";
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  machine = { ... }: {
+    services.grafana = {
+      enable = true;
+      addr = "localhost";
+      analytics.reporting.enable = false;
+      domain = "localhost";
+      security.adminUser = "testusername";
+    };
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForUnit("grafana.service");
+    $machine->waitForOpenPort(3000);
+    $machine->succeed("curl -sSfL http://127.0.0.1:3000/");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/graphite.nix b/nixpkgs/nixos/tests/graphite.nix
new file mode 100644
index 000000000000..27a87bdbb9f2
--- /dev/null
+++ b/nixpkgs/nixos/tests/graphite.nix
@@ -0,0 +1,41 @@
+import ./make-test.nix ({ pkgs, ... } :
+{
+  name = "graphite";
+  nodes = {
+    one =
+      { ... }: {
+        virtualisation.memorySize = 1024;
+        time.timeZone = "UTC";
+        services.graphite = {
+          web.enable = true;
+          api = {
+            enable = true;
+            port = 8082;
+            finders = [ pkgs.python27Packages.influxgraph ];
+          };
+          carbon.enableCache = true;
+          seyren.enable = true;
+          pager.enable = true;
+          beacon.enable = true;
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $one->waitForUnit("default.target");
+    $one->waitForUnit("graphiteWeb.service");
+    $one->waitForUnit("graphiteApi.service");
+    $one->waitForUnit("graphitePager.service");
+    $one->waitForUnit("graphite-beacon.service");
+    $one->waitForUnit("carbonCache.service");
+    $one->waitForUnit("seyren.service");
+    # The services above are of type "simple". systemd considers them active immediately
+    # even if they're still in preStart (which takes quite long for graphiteWeb).
+    # Wait for ports to open so we're sure the services are up and listening.
+    $one->waitForOpenPort(8080);
+    $one->waitForOpenPort(2003);
+    $one->succeed("echo \"foo 1 `date +%s`\" | nc -N localhost 2003");
+    $one->waitUntilSucceeds("curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hadoop/hdfs.nix b/nixpkgs/nixos/tests/hadoop/hdfs.nix
new file mode 100644
index 000000000000..e7d72a56e1e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/hadoop/hdfs.nix
@@ -0,0 +1,54 @@
+import ../make-test.nix ({...}: {
+  nodes = {
+    namenode = {pkgs, ...}: {
+      services.hadoop = {
+        package = pkgs.hadoop_3_1;
+        hdfs.namenode.enabled = true;
+        coreSite = {
+          "fs.defaultFS" = "hdfs://namenode:8020";
+        };
+        hdfsSite = {
+          "dfs.replication" = 1;
+          "dfs.namenode.rpc-bind-host" = "0.0.0.0";
+          "dfs.namenode.http-bind-host" = "0.0.0.0";
+        };
+      };
+      networking.firewall.allowedTCPPorts = [
+        9870 # namenode.http-address
+        8020 # namenode.rpc-address
+      ];
+    };
+    datanode = {pkgs, ...}: {
+      services.hadoop = {
+        package = pkgs.hadoop_3_1;
+        hdfs.datanode.enabled = true;
+        coreSite = {
+          "fs.defaultFS" = "hdfs://namenode:8020";
+        };
+      };
+      networking.firewall.allowedTCPPorts = [
+        9864 # datanode.http.address
+        9866 # datanode.address
+        9867 # datanode.ipc.address
+      ];
+    };
+  };
+
+  testScript = ''
+    startAll
+
+    $namenode->waitForUnit("hdfs-namenode");
+    $namenode->waitForUnit("network.target");
+    $namenode->waitForOpenPort(8020);
+    $namenode->waitForOpenPort(9870);
+
+    $datanode->waitForUnit("hdfs-datanode");
+    $datanode->waitForUnit("network.target");
+    $datanode->waitForOpenPort(9864);
+    $datanode->waitForOpenPort(9866);
+    $datanode->waitForOpenPort(9867);
+
+    $namenode->succeed("curl http://namenode:9870");
+    $datanode->succeed("curl http://datanode:9864");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hadoop/yarn.nix b/nixpkgs/nixos/tests/hadoop/yarn.nix
new file mode 100644
index 000000000000..031592301f17
--- /dev/null
+++ b/nixpkgs/nixos/tests/hadoop/yarn.nix
@@ -0,0 +1,46 @@
+import ../make-test.nix ({...}: {
+  nodes = {
+    resourcemanager = {pkgs, ...}: {
+      services.hadoop.package = pkgs.hadoop_3_1;
+      services.hadoop.yarn.resourcemanager.enabled = true;
+      services.hadoop.yarnSite = {
+        "yarn.resourcemanager.scheduler.class" = "org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler";
+      };
+      networking.firewall.allowedTCPPorts = [
+        8088 # resourcemanager.webapp.address
+        8031 # resourcemanager.resource-tracker.address
+      ];
+    };
+    nodemanager = {pkgs, ...}: {
+      services.hadoop.package = pkgs.hadoop_3_1;
+      services.hadoop.yarn.nodemanager.enabled = true;
+      services.hadoop.yarnSite = {
+        "yarn.resourcemanager.hostname" = "resourcemanager";
+        "yarn.nodemanager.log-dirs" = "/tmp/userlogs";
+        "yarn.nodemanager.address" = "0.0.0.0:8041";
+      };
+      networking.firewall.allowedTCPPorts = [
+        8042 # nodemanager.webapp.address
+        8041 # nodemanager.address
+      ];
+    };
+
+  };
+
+  testScript = ''
+    startAll;
+
+    $resourcemanager->waitForUnit("yarn-resourcemanager");
+    $resourcemanager->waitForUnit("network.target");
+    $resourcemanager->waitForOpenPort(8031);
+    $resourcemanager->waitForOpenPort(8088);
+
+    $nodemanager->waitForUnit("yarn-nodemanager");
+    $nodemanager->waitForUnit("network.target");
+    $nodemanager->waitForOpenPort(8042);
+    $nodemanager->waitForOpenPort(8041);
+
+    $resourcemanager->succeed("curl http://localhost:8088");
+    $nodemanager->succeed("curl http://localhost:8042");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/haka.nix b/nixpkgs/nixos/tests/haka.nix
new file mode 100644
index 000000000000..6277ebb4933f
--- /dev/null
+++ b/nixpkgs/nixos/tests/haka.nix
@@ -0,0 +1,24 @@
+# This test runs haka and probes it with hakactl
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "haka";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ tvestelind ];
+  };
+
+  nodes = {
+    haka =
+      { ... }:
+        {
+          services.haka.enable = true;
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $haka->waitForUnit("haka.service");
+    $haka->succeed("hakactl status");
+    $haka->succeed("hakactl stop");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/handbrake.nix b/nixpkgs/nixos/tests/handbrake.nix
new file mode 100644
index 000000000000..ae87e1f69a7d
--- /dev/null
+++ b/nixpkgs/nixos/tests/handbrake.nix
@@ -0,0 +1,25 @@
+import ./make-test.nix ({ pkgs, ... }:
+let
+  # Download Big Buck Bunny example, licensed under CC Attribution 3.0.
+  testMkv = pkgs.fetchurl {
+    url = "https://github.com/Matroska-Org/matroska-test-files/blob/cf0792be144ac470c4b8052cfe19bb691993e3a2/test_files/test1.mkv?raw=true";
+    sha256 = "1hfxbbgxwfkzv85pvpvx55a72qsd0hxjbm9hkl5r3590zw4s75h9";
+  };
+in {
+  name = "handbrake";
+
+  meta = {
+    maintainers = with pkgs.stdenv.lib.maintainers; [ danieldk ];
+  };
+
+  machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ handbrake ];
+  };
+
+  testScript = ''
+    # Test MP4 and MKV transcoding. Since this is a short clip, transcoding typically
+    # only takes a few seconds.
+    $machine->succeed("HandBrakeCLI -i ${testMkv} -o test.mp4 -e x264 -q 20 -B 160");
+    $machine->succeed("HandBrakeCLI -i ${testMkv} -o test.mkv -e x264 -q 20 -B 160");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/haproxy.nix b/nixpkgs/nixos/tests/haproxy.nix
new file mode 100644
index 000000000000..22a83e9d1eab
--- /dev/null
+++ b/nixpkgs/nixos/tests/haproxy.nix
@@ -0,0 +1,41 @@
+import ./make-test.nix ({ pkgs, ...}: {
+  name = "haproxy";
+  nodes = {
+    machine = { ... }: {
+      imports = [ ../modules/profiles/minimal.nix ];
+      services.haproxy = {
+        enable = true;
+        config = ''
+          defaults
+            timeout connect 10s
+
+          backend http_server
+            mode http
+            server httpd [::1]:8000
+
+          frontend http
+            bind *:80
+            mode http
+            use_backend http_server
+        '';
+      };
+      services.httpd = {
+        enable = true;
+        documentRoot = pkgs.writeTextDir "index.txt" "We are all good!";
+        adminAddr = "notme@yourhost.local";
+        listen = [{
+          ip = "::1";
+          port = 8000;
+        }];
+      };
+    };
+  };
+  testScript = ''
+    startAll;
+    $machine->waitForUnit('multi-user.target');
+    $machine->waitForUnit('haproxy.service');
+    $machine->waitForUnit('httpd.service');
+    $machine->succeed('curl -k http://localhost:80/index.txt | grep "We are all good!"');
+
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hardened.nix b/nixpkgs/nixos/tests/hardened.nix
new file mode 100644
index 000000000000..07bd10963bab
--- /dev/null
+++ b/nixpkgs/nixos/tests/hardened.nix
@@ -0,0 +1,87 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "hardened";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ joachifm ];
+  };
+
+  machine =
+    { lib, pkgs, config, ... }:
+    with lib;
+    { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
+      users.users.sybil = { isNormalUser = true; group = "wheel"; };
+      imports = [ ../modules/profiles/hardened.nix ];
+      nix.useSandbox = false;
+      virtualisation.emptyDiskImages = [ 4096 ];
+      boot.initrd.postDeviceCommands = ''
+        ${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
+      '';
+      fileSystems = lib.mkVMOverride {
+        "/efi" = {
+          device = "/dev/disk/by-label/EFISYS";
+          fsType = "vfat";
+          options = [ "noauto" ];
+        };
+      };
+      boot.extraModulePackages = [ config.boot.kernelPackages.wireguard ];
+      boot.kernelModules = [ "wireguard" ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit("multi-user.target");
+
+      # Test loading out-of-tree modules
+      subtest "extra-module-packages", sub {
+          $machine->succeed("grep -Fq wireguard /proc/modules");
+      };
+
+      # Test hidepid
+      subtest "hidepid", sub {
+          $machine->succeed("grep -Fq hidepid=2 /proc/mounts");
+          # cannot use pgrep -u here, it segfaults when access to process info is denied
+          $machine->succeed("[ `su - sybil -c 'ps --no-headers --user root | wc -l'` = 0 ]");
+          $machine->succeed("[ `su - alice -c 'ps --no-headers --user root | wc -l'` != 0 ]");
+      };
+
+      # Test kernel module hardening
+      subtest "lock-modules", sub {
+          # note: this better a be module we normally wouldn't load ...
+          $machine->fail("modprobe dccp");
+      };
+
+      # Test userns
+      subtest "userns", sub {
+          $machine->fail("unshare --user");
+      };
+
+      # Test dmesg restriction
+      subtest "dmesg", sub {
+          $machine->fail("su -l alice -c dmesg");
+      };
+
+      # Test access to kcore
+      subtest "kcore", sub {
+          $machine->fail("cat /proc/kcore");
+      };
+
+      # Test deferred mount
+      subtest "mount", sub {
+        $machine->fail("mountpoint -q /efi"); # was deferred
+        $machine->execute("mkdir -p /efi");
+        $machine->succeed("mount /dev/disk/by-label/EFISYS /efi");
+        $machine->succeed("mountpoint -q /efi"); # now mounted
+      };
+
+      # Test Nix dæmon usage
+      subtest "nix-daemon", sub {
+        $machine->fail("su -l nobody -s /bin/sh -c 'nix ping-store'");
+        $machine->succeed("su -l alice -c 'nix ping-store'") =~ "OK";
+      };
+
+      # Test kernel image protection
+      subtest "kernelimage", sub {
+        $machine->fail("systemctl hibernate");
+        $machine->fail("systemctl kexec");
+      };
+    '';
+})
diff --git a/nixpkgs/nixos/tests/hibernate.nix b/nixpkgs/nixos/tests/hibernate.nix
new file mode 100644
index 000000000000..274aa7becc82
--- /dev/null
+++ b/nixpkgs/nixos/tests/hibernate.nix
@@ -0,0 +1,43 @@
+# Test whether hibernation from partition works.
+
+import ./make-test.nix (pkgs: {
+  name = "hibernate";
+
+  nodes = {
+    machine = { config, lib, pkgs, ... }: with lib; {
+      virtualisation.emptyDiskImages = [ config.virtualisation.memorySize ];
+
+      systemd.services.backdoor.conflicts = [ "sleep.target" ];
+
+      swapDevices = mkOverride 0 [ { device = "/dev/vdb"; } ];
+
+      networking.firewall.allowedTCPPorts = [ 4444 ];
+
+      systemd.services.listener.serviceConfig.ExecStart = "${pkgs.netcat}/bin/nc -l 4444 -k";
+    };
+
+    probe = { pkgs, ...}: {
+      environment.systemPackages = [ pkgs.netcat ];
+    };
+  };
+
+  # 9P doesn't support reconnection to virtio transport after a hibernation.
+  # Therefore, machine just hangs on any Nix store access.
+  # To work around it we run a daemon which listens to a TCP connection and
+  # try to connect to it as a test.
+
+  testScript =
+    ''
+      $machine->waitForUnit("multi-user.target");
+      $machine->succeed("mkswap /dev/vdb");
+      $machine->succeed("swapon -a");
+      $machine->startJob("listener");
+      $machine->waitForOpenPort(4444);
+      $machine->succeed("systemctl hibernate &");
+      $machine->waitForShutdown;
+      $probe->waitForUnit("multi-user.target");
+      $machine->start;
+      $probe->waitUntilSucceeds("echo test | nc machine 4444 -N");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/hitch/default.nix b/nixpkgs/nixos/tests/hitch/default.nix
new file mode 100644
index 000000000000..cb24c4dcffc2
--- /dev/null
+++ b/nixpkgs/nixos/tests/hitch/default.nix
@@ -0,0 +1,33 @@
+import ../make-test.nix ({ pkgs, ... }:
+{
+  name = "hitch";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ jflanglois ];
+  };
+  machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.hitch = {
+      enable = true;
+      backend = "[127.0.0.1]:80";
+      pem-files = [
+        ./example.pem
+      ];
+    };
+
+    services.httpd = {
+      enable = true;
+      documentRoot = ./example;
+      adminAddr = "noone@testing.nowhere";
+    };
+  };
+
+  testScript =
+    ''
+      startAll;
+
+      $machine->waitForUnit('multi-user.target');
+      $machine->waitForUnit('hitch.service');
+      $machine->waitForOpenPort(443);
+      $machine->succeed('curl -k https://localhost:443/index.txt | grep "We are all good!"');
+    '';
+})
diff --git a/nixpkgs/nixos/tests/hitch/example.pem b/nixpkgs/nixos/tests/hitch/example.pem
new file mode 100644
index 000000000000..fde6f3cbd19a
--- /dev/null
+++ b/nixpkgs/nixos/tests/hitch/example.pem
@@ -0,0 +1,53 @@
+-----BEGIN CERTIFICATE-----
+MIIEKTCCAxGgAwIBAgIJAIFAWQXSZ7lIMA0GCSqGSIb3DQEBCwUAMIGqMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMUmVkd29vZCBD
+aXR5MRkwFwYDVQQKDBBUZXN0aW5nIDEyMyBJbmMuMRQwEgYDVQQLDAtJVCBTZXJ2
+aWNlczEYMBYGA1UEAwwPdGVzdGluZy5ub3doZXJlMSQwIgYJKoZIhvcNAQkBFhVu
+b29uZUB0ZXN0aW5nLm5vd2hlcmUwHhcNMTgwNDIzMDcxMTI5WhcNMTkwNDIzMDcx
+MTI5WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNV
+BAcMDFJlZHdvb2QgQ2l0eTEZMBcGA1UECgwQVGVzdGluZyAxMjMgSW5jLjEUMBIG
+A1UECwwLSVQgU2VydmljZXMxGDAWBgNVBAMMD3Rlc3Rpbmcubm93aGVyZTEkMCIG
+CSqGSIb3DQEJARYVbm9vbmVAdGVzdGluZy5ub3doZXJlMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAxQq6AA9o/QErMbQwfgDF4mqXcvglRTwPr2zPE6Rv
+1g0ncRBSMM8iKbPapHM6qHNfg2e1fU2SFqzD6HkyZqHHLCgLzkdzswEcEjsMqiUP
+OR++5g4CWoQrdTi31itzYzCjnQ45BrAMrLEhBQgDTNwrEE+Tit0gpOGggtj/ktLk
+OD8BKa640lkmWEUGF18fd3rYTUC4hwM5qhAVXTe21vj9ZWsgprpQKdN61v0dCUap
+C5eAgvZ8Re+Cd0Id674hK4cJ4SekqfHKv/jLyIg3Vsdc9nkhmiC4O6KH5f1Zzq2i
+E4Kd5mnJDFxfSzIErKWmbhriLWsj3KEJ983AGLJ9hxQTAwIDAQABo1AwTjAdBgNV
+HQ4EFgQU76Mm6DP/BePJRQUNrJ9z038zjocwHwYDVR0jBBgwFoAU76Mm6DP/BePJ
+RQUNrJ9z038zjocwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAAZzt
+VdPaUqrvDAh5rMYqzYMJ3tj6daNYoX6CbTFoevK5J5D4FESM0D/FMKgpNiVz39kB
+8Cjaw5rPHMHY61rHz7JRDK1sWXsonwzCF21BK7Tx0G1CIfLpYHWYb/FfdWGROx+O
+hPgKuoMRWQB+txozkZp5BqWJmk5MOyFCDEXhMOmrfsJq0IYU6QaH3Lsf1oJRy4yU
+afFrT9o3DLOyYLG/j/HXijCu8DVjZVa4aboum79ecYzPjjGF1posrFUnvQiuAeYy
+t7cuHNUB8gW9lWR5J7tP8fzFWtIcyT2oRL8u3H+fXf0i4bW73wtOBOoeULBzBNE7
+6rphcSrQunSZQIc+hg==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFCroAD2j9ASsx
+tDB+AMXiapdy+CVFPA+vbM8TpG/WDSdxEFIwzyIps9qkczqoc1+DZ7V9TZIWrMPo
+eTJmoccsKAvOR3OzARwSOwyqJQ85H77mDgJahCt1OLfWK3NjMKOdDjkGsAyssSEF
+CANM3CsQT5OK3SCk4aCC2P+S0uQ4PwEprrjSWSZYRQYXXx93ethNQLiHAzmqEBVd
+N7bW+P1layCmulAp03rW/R0JRqkLl4CC9nxF74J3Qh3rviErhwnhJ6Sp8cq/+MvI
+iDdWx1z2eSGaILg7oofl/VnOraITgp3mackMXF9LMgSspaZuGuItayPcoQn3zcAY
+sn2HFBMDAgMBAAECggEAcaR8HijFHpab+PC5vxJnDuz3KEHiDQpU6ZJR5DxEnCm+
+A8GsBaaRR4gJpCspO5o/DiS0Ue55QUanPt8XqIXJv7fhBznCiw0qyYDxDviMzR94
+FGskBFySS+tIa+dnh1+4HY7kaO0Egl0udB5o+N1KoP+kUsSyXSYcUxsgW+fx5FW9
+22Ya3HNWnWxMCSfSGGlTFXGj2whf25SkL25dM9iblO4ZOx4MX8kaXij7TaYy8hMM
+Vf6/OMnXqtPKho+ctZZVKZkE9PxdS4f/pnp5EsdoOZwNBtfQ1WqVLWd3DlGWhnsH
+7L8ZSP2HkoI4Pd1wtkpOKZc+yM2bFXWa8WY4TcmpUQKBgQD33HxGdtmtZehrexSA
+/ZwWJlMslUsNz4Ivv6s7J4WCRhdh94+r9TWQP/yHdT9Ry5bvn84I5ZLUdp+aA962
+mvjz+GIglkCGpA7HU/hqurB1O63pj2cIDB8qhV21zjVIoqXcQ7IBJ+tqD79nF8vm
+h3KfuHUhuu1rayGepbtIyNhLdwKBgQDLgw4TJBg/QB8RzYECk78QnfZpCExsQA/z
+YJpc+dF2/nsid5R2u9jWzfmgHM2Jjo2/+ofRUaTqcFYU0K57CqmQkOLIzsbNQoYt
+e2NOANNVHiZLuzTZC2r3BrrkNbo3YvQzhAesUA5lS6LfrxBLUKiwo2LU9NlmJs3b
+UPVFYI0/1QKBgCswxIcS1sOcam+wNtZzWuuRKhUuvrFdY3YmlBPuwxj8Vb7AgMya
+IgdM3xhLmgkKzPZchm6OcpOLSCxyWDDBuHfq5E6BYCUWGW0qeLNAbNdA2wFD99Qz
+KIskSjwP/sD1dql3MmF5L1CABf5U6zb0i0jBv8ds50o8lNMsVgJM3UPpAoGBAL1+
+nzllb4pdi1CJWKnspoizfQCZsIdPM0r71V/jYY36MO+MBtpz2NlSWzAiAaQm74gl
+oBdgfT2qMg0Zro11BSRONEykdOolGkj5TiMQk7b65s+3VeMPRZ8UTis2d9kgs5/Q
+PVDODkl1nwfGu1ZVmW04BUujXVZHpYCkJm1eFMetAoGAImE7gWj+qRMhpbtCCGCg
+z06gDKvMrF6S+GJsvUoSyM8oUtfdPodI6gWAC65NfYkIiqbpCaEVNzfui73f5Lnz
+p5X1IbzhuH5UZs/k5A3OR2PPDbPs3lqEw7YJdBdLVRmO1o824uaXaJJwkL/1C+lq
+8dh1wV3CnynNmZApkz4vpzQ=
+-----END PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/hitch/example/index.txt b/nixpkgs/nixos/tests/hitch/example/index.txt
new file mode 100644
index 000000000000..0478b1c26351
--- /dev/null
+++ b/nixpkgs/nixos/tests/hitch/example/index.txt
@@ -0,0 +1 @@
+We are all good!
diff --git a/nixpkgs/nixos/tests/hocker-fetchdocker/default.nix b/nixpkgs/nixos/tests/hocker-fetchdocker/default.nix
new file mode 100644
index 000000000000..4f30f01e4032
--- /dev/null
+++ b/nixpkgs/nixos/tests/hocker-fetchdocker/default.nix
@@ -0,0 +1,15 @@
+import ../make-test.nix ({ pkgs, ...} : {
+  name = "test-hocker-fetchdocker";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ixmatus ];
+  };
+
+  machine = import ./machine.nix;
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("sockets.target");
+    $machine->waitUntilSucceeds("docker run registry-1.docker.io/v2/library/hello-world:latest");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hocker-fetchdocker/hello-world-container.nix b/nixpkgs/nixos/tests/hocker-fetchdocker/hello-world-container.nix
new file mode 100644
index 000000000000..a127875264e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/hocker-fetchdocker/hello-world-container.nix
@@ -0,0 +1,19 @@
+{ fetchDockerConfig, fetchDockerLayer, fetchdocker }:
+fetchdocker rec {
+    name = "hello-world";
+    registry = "https://registry-1.docker.io/v2/";
+    repository = "library";
+    imageName = "hello-world";
+    tag = "latest";
+    imageConfig = fetchDockerConfig {
+      inherit tag registry repository imageName;
+      sha256 = "1ivbd23hyindkahzfw4kahgzi6ibzz2ablmgsz6340vc6qr1gagj";
+    };
+    imageLayers = let
+      layer0 = fetchDockerLayer {
+        inherit registry repository imageName;
+        layerDigest = "ca4f61b1923c10e9eb81228bd46bee1dfba02b9c7dac1844527a734752688ede";
+        sha256 = "1plfd194fwvsa921ib3xkhms1yqxxrmx92r2h7myj41wjaqn2kya";
+      };
+      in [ layer0 ];
+  }
diff --git a/nixpkgs/nixos/tests/hocker-fetchdocker/machine.nix b/nixpkgs/nixos/tests/hocker-fetchdocker/machine.nix
new file mode 100644
index 000000000000..78343f0e02f0
--- /dev/null
+++ b/nixpkgs/nixos/tests/hocker-fetchdocker/machine.nix
@@ -0,0 +1,26 @@
+{ pkgs, ... }:
+{ nixpkgs.config.packageOverrides = pkgs': {
+    hello-world-container = pkgs'.callPackage ./hello-world-container.nix { };
+  };
+
+  virtualisation.docker = {
+    enable  = true;
+    package = pkgs.docker;
+  };
+
+  systemd.services.docker-load-fetchdocker-image = {
+    description = "Docker load hello-world-container";
+    wantedBy    = [ "multi-user.target" ];
+    wants       = [ "docker.service" "local-fs.target" ];
+    after       = [ "docker.service" "local-fs.target" ];
+
+    script = ''
+      ${pkgs.hello-world-container}/compositeImage.sh | ${pkgs.docker}/bin/docker load
+    '';
+
+    serviceConfig = {
+      Type = "oneshot";
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/tests/home-assistant.nix b/nixpkgs/nixos/tests/home-assistant.nix
new file mode 100644
index 000000000000..a93360b252f6
--- /dev/null
+++ b/nixpkgs/nixos/tests/home-assistant.nix
@@ -0,0 +1,102 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+  configDir = "/var/lib/foobar";
+  apiPassword = "some_secret";
+  mqttPassword = "another_secret";
+  hassCli = "hass-cli --server http://hass:8123 --password '${apiPassword}'";
+
+in {
+  name = "home-assistant";
+  meta = with pkgs.stdenv.lib; {
+    maintainers = with maintainers; [ dotlambda ];
+  };
+
+  nodes = {
+    hass =
+      { pkgs, ... }:
+      {
+        environment.systemPackages = with pkgs; [
+          mosquitto home-assistant-cli
+        ];
+        services.home-assistant = {
+          inherit configDir;
+          enable = true;
+          package = pkgs.home-assistant.override {
+            extraPackages = ps: with ps; [ hbmqtt ];
+          };
+          config = {
+            homeassistant = {
+              name = "Home";
+              time_zone = "UTC";
+              latitude = "0.0";
+              longitude = "0.0";
+              elevation = 0;
+              auth_providers = [
+                { type = "legacy_api_password"; }
+              ];
+            };
+            frontend = { };
+            http.api_password = apiPassword;
+            mqtt = { # Use hbmqtt as broker
+              password = mqttPassword;
+            };
+            binary_sensor = [
+              {
+                platform = "mqtt";
+                state_topic = "home-assistant/test";
+                payload_on = "let_there_be_light";
+                payload_off = "off";
+              }
+            ];
+          };
+          lovelaceConfig = {
+            title = "My Awesome Home";
+            views = [ {
+              title = "Example";
+              cards = [ {
+                type = "markdown";
+                title = "Lovelace";
+                content = "Welcome to your **Lovelace UI**.";
+              } ];
+            } ];
+          };
+          lovelaceConfigWritable = true;
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $hass->waitForUnit("home-assistant.service");
+
+    # The config is specified using a Nix attribute set,
+    # converted from JSON to YAML, and linked to the config dir
+    $hass->succeed("test -L ${configDir}/configuration.yaml");
+    # The lovelace config is copied because lovelaceConfigWritable = true
+    $hass->succeed("test -f ${configDir}/ui-lovelace.yaml");
+
+    # Check that Home Assistant's web interface and API can be reached
+    $hass->waitForOpenPort(8123);
+    $hass->succeed("curl --fail http://localhost:8123/states");
+    $hass->succeed("curl --fail -H 'x-ha-access: ${apiPassword}' http://localhost:8123/api/ | grep -qF 'API running'");
+
+    # Toggle a binary sensor using MQTT
+    $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"off\"'");
+    $hass->waitUntilSucceeds("mosquitto_pub -V mqttv311 -t home-assistant/test -u homeassistant -P '${mqttPassword}' -m let_there_be_light");
+    $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"on\"'");
+
+    # Toggle a binary sensor using hass-cli
+    $hass->succeed("${hassCli} --output json entity get binary_sensor.mqtt_binary_sensor | grep -qF '\"state\": \"on\"'");
+    $hass->succeed("${hassCli} entity edit binary_sensor.mqtt_binary_sensor --json='{\"state\": \"off\"}'");
+    $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"off\"'");
+
+    # Print log to ease debugging
+    my $log = $hass->succeed("cat ${configDir}/home-assistant.log");
+    print "\n### home-assistant.log ###\n";
+    print "$log\n";
+
+    # Check that no errors were logged
+    $hass->fail("cat ${configDir}/home-assistant.log | grep -qF ERROR");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hound.nix b/nixpkgs/nixos/tests/hound.nix
new file mode 100644
index 000000000000..cb8e25332c07
--- /dev/null
+++ b/nixpkgs/nixos/tests/hound.nix
@@ -0,0 +1,58 @@
+# Test whether `houndd` indexes nixpkgs
+import ./make-test.nix ({ pkgs, ... } : {
+  name = "hound";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ grahamc ];
+  };
+  machine = { pkgs, ... }: {
+    services.hound = {
+      enable = true;
+      config = ''
+        {
+          "max-concurrent-indexers": 1,
+          "dbpath": "/var/lib/hound/data",
+          "repos": {
+            "nix": {
+              "url": "file:///var/lib/hound/my-git"
+            }
+          }
+        }
+      '';
+    };
+
+    systemd.services.houndseed = {
+      description = "seed hound with a git repo";
+      requiredBy = [ "hound.service" ];
+      before = [ "hound.service" ];
+
+      serviceConfig = {
+        User = "hound";
+        Group = "hound";
+        WorkingDirectory = "/var/lib/hound";
+      };
+      path = [ pkgs.git ];
+      script = ''
+        git config --global user.email "you@example.com"
+        git config --global user.name "Your Name"
+        git init my-git --bare
+        git init my-git-clone
+        cd my-git-clone
+        echo 'hi nix!' > hello
+        git add hello
+        git commit -m "hello there :)"
+        git remote add origin /var/lib/hound/my-git
+        git push origin master
+      '';
+    };
+  };
+
+  testScript =
+    '' startAll;
+
+       $machine->waitForUnit("network.target");
+       $machine->waitForUnit("hound.service");
+       $machine->waitForOpenPort(6080);
+       $machine->waitUntilSucceeds('curl http://127.0.0.1:6080/api/v1/search\?stats\=fosho\&repos\=\*\&rng=%3A20\&q\=hi\&files\=\&i=nope | grep "Filename" | grep "hello"');
+
+    '';
+})
diff --git a/nixpkgs/nixos/tests/hydra/create-trivial-project.sh b/nixpkgs/nixos/tests/hydra/create-trivial-project.sh
new file mode 100755
index 000000000000..39122c9b473a
--- /dev/null
+++ b/nixpkgs/nixos/tests/hydra/create-trivial-project.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+#
+# This script creates a project, a jobset with an input of type local
+# path. This local path is a directory that contains a Nix expression
+# to define a job.
+# The EXPR-PATH environment variable must be set with the local path.
+
+set -e
+
+URL=http://localhost:3000
+USERNAME="admin"
+PASSWORD="admin"
+PROJECT_NAME="trivial"
+JOBSET_NAME="trivial"
+EXPR_PATH=${EXPR_PATH:-}
+
+if [ -z $EXPR_PATH ]; then
+   echo "Environment variable EXPR_PATH must be set"
+   exit 1
+fi
+
+mycurl() {
+  curl --referer $URL -H "Accept: application/json" -H "Content-Type: application/json" $@
+}
+
+cat >data.json <<EOF
+{ "username": "$USERNAME", "password": "$PASSWORD" }
+EOF
+mycurl -X POST -d '@data.json' $URL/login -c hydra-cookie.txt
+
+cat >data.json <<EOF
+{
+  "displayname":"Trivial",
+  "enabled":"1",
+  "visible":"1"
+}
+EOF
+mycurl --silent -X PUT $URL/project/$PROJECT_NAME -d @data.json -b hydra-cookie.txt
+
+cat >data.json <<EOF
+{
+  "description": "Trivial",
+  "checkinterval": "60",
+  "enabled": "1",
+  "visible": "1",
+  "keepnr": "1",
+  "nixexprinput": "trivial",
+  "nixexprpath": "trivial.nix",
+  "inputs": {
+    "trivial": {
+      "value": "$EXPR_PATH",
+      "type": "path"
+    }
+  }
+}
+EOF
+mycurl --silent -X PUT $URL/jobset/$PROJECT_NAME/$JOBSET_NAME -d @data.json -b hydra-cookie.txt
diff --git a/nixpkgs/nixos/tests/hydra/default.nix b/nixpkgs/nixos/tests/hydra/default.nix
new file mode 100644
index 000000000000..db4e97e0039b
--- /dev/null
+++ b/nixpkgs/nixos/tests/hydra/default.nix
@@ -0,0 +1,77 @@
+import ../make-test.nix ({ pkgs, ...} :
+
+let
+   trivialJob = pkgs.writeTextDir "trivial.nix" ''
+     { trivial = builtins.derivation {
+         name = "trivial";
+         system = "x86_64-linux";
+         builder = "/bin/sh";
+         args = ["-c" "echo success > $out; exit 0"];
+       };
+     }
+   '';
+
+    createTrivialProject = pkgs.stdenv.mkDerivation {
+      name = "create-trivial-project";
+      unpackPhase = ":";
+      buildInputs = [ pkgs.makeWrapper ];
+      installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh";
+      postFixup = ''
+        wrapProgram "$out/bin/create-trivial-project.sh" --prefix PATH ":" ${pkgs.stdenv.lib.makeBinPath [ pkgs.curl ]} --set EXPR_PATH ${trivialJob}
+      '';
+    };
+
+in {
+  name = "hydra-init-localdb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ pstn lewo ma27 ];
+  };
+
+  machine =
+    { pkgs, ... }:
+
+    {
+      virtualisation.memorySize = 1024;
+      time.timeZone = "UTC";
+
+      environment.systemPackages = [ createTrivialProject pkgs.jq ];
+      services.hydra = {
+        enable = true;
+
+        #Hydra needs those settings to start up, so we add something not harmfull.
+        hydraURL = "example.com";
+        notificationSender = "example@example.com";
+      };
+      nix = {
+        buildMachines = [{
+          hostName = "localhost";
+          systems = [ "x86_64-linux" ];
+        }];
+
+        binaryCaches = [];
+      };
+    };
+
+  testScript =
+    ''
+      # let the system boot up
+      $machine->waitForUnit("multi-user.target");
+      # test whether the database is running
+      $machine->succeed("systemctl status postgresql.service");
+      # test whether the actual hydra daemons are running
+      $machine->succeed("systemctl status hydra-queue-runner.service");
+      $machine->succeed("systemctl status hydra-init.service");
+      $machine->succeed("systemctl status hydra-evaluator.service");
+      $machine->succeed("systemctl status hydra-send-stats.service");
+
+      $machine->succeed("hydra-create-user admin --role admin --password admin");
+
+      # create a project with a trivial job
+      $machine->waitForOpenPort(3000);
+
+      # make sure the build as been successfully built
+      $machine->succeed("create-trivial-project.sh");
+
+      $machine->waitUntilSucceeds('curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" |  jq .buildstatus | xargs test 0 -eq');
+    '';
+})
diff --git a/nixpkgs/nixos/tests/i3wm.nix b/nixpkgs/nixos/tests/i3wm.nix
new file mode 100644
index 000000000000..d309f19a0b4a
--- /dev/null
+++ b/nixpkgs/nixos/tests/i3wm.nix
@@ -0,0 +1,35 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "i3wm";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  machine = { lib, ... }: {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    services.xserver.displayManager.auto.user = "alice";
+    services.xserver.windowManager.default = lib.mkForce "i3";
+    services.xserver.windowManager.i3.enable = true;
+  };
+
+  testScript = { ... }: ''
+    $machine->waitForX;
+    $machine->waitForFile("/home/alice/.Xauthority");
+    $machine->succeed("xauth merge ~alice/.Xauthority");
+    $machine->waitForWindow(qr/first configuration/);
+    $machine->sleep(2);
+    $machine->screenshot("started");
+    $machine->sendKeys("ret");
+    $machine->sleep(2);
+    $machine->sendKeys("alt");
+    $machine->sleep(2);
+    $machine->screenshot("configured");
+    $machine->sendKeys("ret");
+    # make sure the config file is created before we continue
+    $machine->waitForFile("/home/alice/.config/i3/config");
+    $machine->sleep(2);
+    $machine->sendKeys("alt-ret");
+    $machine->waitForWindow(qr/alice.*machine/);
+    $machine->sleep(2);
+    $machine->screenshot("terminal");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/iftop.nix b/nixpkgs/nixos/tests/iftop.nix
new file mode 100644
index 000000000000..a4f524ceb27b
--- /dev/null
+++ b/nixpkgs/nixos/tests/iftop.nix
@@ -0,0 +1,34 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "iftop";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ];
+
+  nodes = {
+    withIftop = {
+      imports = [ ./common/user-account.nix ];
+      programs.iftop.enable = true;
+    };
+    withoutIftop = {
+      imports = [ ./common/user-account.nix ];
+      environment.systemPackages = [ pkgs.iftop ];
+    };
+  };
+
+  testScript = ''
+    subtest "machine with iftop enabled", sub {
+      $withIftop->waitForUnit("default.target");
+      # limit to eth1 (eth0 is the test driver's control interface)
+      # and don't try name lookups
+      $withIftop->succeed("su -l alice -c 'iftop -t -s 1 -n -i eth1'");
+    };
+    subtest "machine without iftop", sub {
+      $withoutIftop->waitForUnit("default.target");
+      # check that iftop is there but user alice lacks capabilities
+      $withoutIftop->succeed("iftop -t -s 1 -n -i eth1");
+      $withoutIftop->fail("su -l alice -c 'iftop -t -s 1 -n -i eth1'");
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/incron.nix b/nixpkgs/nixos/tests/incron.nix
new file mode 100644
index 000000000000..e39bbb5f096b
--- /dev/null
+++ b/nixpkgs/nixos/tests/incron.nix
@@ -0,0 +1,52 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+{
+  name = "incron";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  machine =
+    { ... }:
+    { services.incron.enable = true;
+      services.incron.extraPackages = [ pkgs.coreutils ];
+      services.incron.systab = ''
+        /test IN_CREATE,IN_MODIFY,IN_CLOSE_WRITE,IN_MOVED_FROM,IN_MOVED_TO echo "$@/$# $%" >> /root/incron.log
+      '';
+
+      # ensure the directory to be monitored exists before incron is started
+      system.activationScripts.incronTest = ''
+        mkdir /test
+      '';
+    };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("multi-user.target");
+    $machine->waitForUnit("incron.service");
+
+    $machine->succeed("test -d /test");
+    # create some activity for incron to monitor
+    $machine->succeed("touch /test/file");
+    $machine->succeed("echo foo >> /test/file");
+    $machine->succeed("mv /test/file /root");
+    $machine->succeed("mv /root/file /test");
+
+    $machine->sleep(1);
+
+    # touch /test/file
+    $machine->succeed("grep '/test/file IN_CREATE' /root/incron.log");
+
+    # echo foo >> /test/file
+    $machine->succeed("grep '/test/file IN_MODIFY' /root/incron.log");
+    $machine->succeed("grep '/test/file IN_CLOSE_WRITE' /root/incron.log");
+
+    # mv /test/file /root
+    $machine->succeed("grep '/test/file IN_MOVED_FROM' /root/incron.log");
+
+    # mv /root/file /test
+    $machine->succeed("grep '/test/file IN_MOVED_TO' /root/incron.log");
+
+    # ensure something unexpected is not present
+    $machine->fail("grep 'IN_OPEN' /root/incron.log");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/influxdb.nix b/nixpkgs/nixos/tests/influxdb.nix
new file mode 100644
index 000000000000..440049d95111
--- /dev/null
+++ b/nixpkgs/nixos/tests/influxdb.nix
@@ -0,0 +1,33 @@
+# This test runs influxdb and checks if influxdb is up and running
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "influxdb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ chaoflow offline ];
+  };
+
+  nodes = {
+    one = { ... }: {
+      services.influxdb.enable = true;
+    };
+  };
+
+  testScript = ''
+    startAll;
+  
+    $one->waitForUnit("influxdb.service");
+
+    # create database
+    $one->succeed(q~
+      curl -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE test"
+    ~);
+
+    # write some points and run simple query
+    $one->succeed(q~
+      curl -XPOST 'http://localhost:8086/write?db=test' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000'
+    ~);
+    $one->succeed(q~
+      curl -GET 'http://localhost:8086/query' --data-urlencode "db=test" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"  | grep "0\.64"
+    ~);
+  '';
+})
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/default.nix b/nixpkgs/nixos/tests/initrd-network-ssh/default.nix
new file mode 100644
index 000000000000..b2209f297a4f
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/default.nix
@@ -0,0 +1,59 @@
+import ../make-test.nix ({ lib, ... }:
+
+{
+  name = "initrd-network-ssh";
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  nodes = with lib; rec {
+    server =
+      { config, ... }:
+      {
+        boot.kernelParams = [
+          "ip=${config.networking.primaryIPAddress}:::255.255.255.0::eth1:none"
+        ];
+        boot.initrd.network = {
+          enable = true;
+          ssh = {
+            enable = true;
+            authorizedKeys = [ "${readFile ./openssh.pub}" ];
+            port = 22;
+            hostRSAKey = ./dropbear.priv;
+          };
+        };
+        boot.initrd.preLVMCommands = ''
+          while true; do
+            if [ -f fnord ]; then
+              poweroff
+            fi
+            sleep 1
+          done
+        '';
+      };
+
+    client =
+      { config, ... }:
+      {
+        environment.etc.knownHosts = {
+          text = concatStrings [
+            "server,"
+            "${toString (head (splitString " " (
+              toString (elemAt (splitString "\n" config.networking.extraHosts) 2)
+            )))} "
+            "${readFile ./dropbear.pub}"
+          ];
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $client->waitForUnit("network.target");
+    $client->copyFileFromHost("${./openssh.priv}","/etc/sshKey");
+    $client->succeed("chmod 0600 /etc/sshKey");
+    $client->waitUntilSucceeds("ping -c 1 server");
+    $client->succeed("ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'touch /fnord'");
+    $client->shutdown;
+  '';
+})
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/dropbear.priv b/nixpkgs/nixos/tests/initrd-network-ssh/dropbear.priv
new file mode 100644
index 000000000000..af340535f0a3
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/dropbear.priv
Binary files differdiff --git a/nixpkgs/nixos/tests/initrd-network-ssh/dropbear.pub b/nixpkgs/nixos/tests/initrd-network-ssh/dropbear.pub
new file mode 100644
index 000000000000..385c625522aa
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/dropbear.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCzJ0OniLB91MpPC86I1m3wwJeAc+Gme7bAuaLIU/cSfPwxT5NO7MfCp0Pu94gYDKtDXMs/wXg0bTAVDeAFFkdIj6kBBumEmQLCTL48q2UxDIXVLT/E/AAgj6q7WwgCg7fwm4Vjn4z7aUyBx8EfRy+5/SQyeYla3D/lFYgMi5x4D6J+yeR+JPAptDE/IR5IizNV7mY0ZcoXYyHrrehI1tTYEEqjX13ZqS4OCBFWwHe1QHhRNM+jHhcATbgikjAj8FyFPtLvc+dSVtkuhQktQl36Bi8zMUQcV6+mM7Ln6DBcDlM9urHKLYPTWmUAyhxM955iglOn5z0RaAIcyNMT6hz0rHaNf0BIlmbXoTC0XGjHh/OnoOEC/zg0JqgQTnPiU45K4TnRSSXp2GfiDfiQAK0+HaXACkjuFR68u7WCZpB1Bse1OgKNClFqtRhIr5DilUb2/e5DCCmFkddMUcjmYqzZdbXNt7fo8CFULe+mbiCp8+tMg4aRTaDZ/Hk93nCvGE5TP2ypEMbfL6nRVKvXOjhdvSQQgKwx+O003FDEHCSG0Bpageh7yVpna+SPrbGklce7MjTpbx3iIwmvKpQ6asnK1L3KkahpY1S3NhQ+/S3Gs8KWQ5LAU+d3xiPX3jfIVHsCIIyxHDbwcJvxM4MFBFQpqRMD6E+LoM9RHjl4C9k2iQ== tmtynkky@duuni
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/generate-keys.nix b/nixpkgs/nixos/tests/initrd-network-ssh/generate-keys.nix
new file mode 100644
index 000000000000..0183e12d7a88
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/generate-keys.nix
@@ -0,0 +1,12 @@
+with import ../../.. {};
+
+runCommand "gen-keys" {
+    buildInputs = [ dropbear openssh ];
+  }
+  ''
+    mkdir $out
+    dropbearkey -t rsa -f $out/dropbear.priv -s 4096 | sed -n 2p > $out/dropbear.pub
+    ssh-keygen -q -t rsa -b 4096 -N "" -f client
+    mv client $out/openssh.priv
+    mv client.pub $out/openssh.pub
+  ''
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/openssh.priv b/nixpkgs/nixos/tests/initrd-network-ssh/openssh.priv
new file mode 100644
index 000000000000..816d65435fd7
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/openssh.priv
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEA7+9A2PCPOTAlFmrablrUWA+VZdAuLfM6JXeHsOF7ZbC2F6lv
+WmvDM925DQqhiAjcgWnt5WHWS5Y+b7lGnuzT7fyKegXd80nCRmqlpSG3srX0/lxR
+aQAJLzfoDjcsF+ceswQo6GSsYnCHVxMNs007gbbVY3f7o+sWZtLdxJPD2iHvl5Zr
+LK0d1RLMmU6cfIhIABlL0S8EWiv29RROepsCQnS0dnK2b+von1SCYoggvAMe2ToA
+IAJ8+uqaYfGAyn9q8fjZiRHxLmKDq90tKoCUL5r/2dmEIE+t8T/3PfHoq1QzZts9
+W9idhBdT21dEXBtGyoMtckp5njk5m82LQDYiOXkuSoIUhSOteh5g7fBv1BtVSERx
+Jg3UeJjPeGKFwdnzapmAKC2w/6V8xcIINNA+fhZA7B9fD1RAi2TECZ+gyMYDc4T+
+USlMSm9cfvSOrf2+5ngtFb84nHjqvClxCMLu+bCWK8HamqUzhE/a5LbR+48E7PyG
+s3KV+sWFN9KOnakTjj/6iQhXZRhgeAK39F2XTk5Ms5Y+BRSStnMoMZA2grIV+jHi
+1zbWokVqXPI5YRo5isR/PgtKAV6FfNWumcYoFJ9F40pMHQ6hJVEmtrCBx7EApSl3
+mSGbQJUmilLC51qNhwQRbD//ZtpIrN82HTMKzZ6kj7kDCdsff+wsnkIXmmMCAwEA
+AQKCAgA4tMINw6UF7hQF3VEsnbjr6xrzCiWv5HlMm5htPI1OdlpC81+G7ksfOfrf
+UzDkFrwOtftsqBfem268Nvyy2OQprfMIbdSMCFWrEM9/XJ2u1gRGDYmMGF8TUtI8
+cduw9oWx53zHl+uKBHBoKu+k/c7flFeQf63wisIroRCawhWau0SF/h3sXCndzuie
+Hw8q+4aQx2m80bDkotlmCNuXbIU3MZ/pEql9gDLlXTLHmMaryM0EqAmZhx0ErGe6
+WDqJIV4kPB0loSDwRoY6GzbugZ8ENUzcruTkQhCpIOYNNNw5idfwKkaxK1vm+SBv
+iYt1fVjYyfH2vhVKSNoNsaGEloa1u4Dymt/FpFztEpRzHXcw93N8BdLxJ4OUhzm2
+iAbpiyjniTIeAVVi7BUwLXh5WAx8nT0eeb1zKoZg1p1ciK5cYl1Uel7j8xRycsSW
+3YgmtuPqY4Agbc9v3eXbQZNDk48JFMEqpIxk97FAkRYpzfxg5Qq14WJCp60CkdRt
+T60hXy8lT/BcI8OWLfGJuBbsVLNRiC7PpwqRKQAinXSv134FpP7jrhpkMybs2oIS
+5obRG7J5OfOTp925erG5mrpwqa3BPkgqx347Wj9z8quOZyuhi+XaPvqmPtvs5JOl
+4RCqjt6RQlHm7xos9ZZGI4jDAIFaFWgyVZrYplOgwxWma4DTgQKCAQEA9+tizQRU
+lF0lxNcEPvsFnYJo80Y+MQK9VdtlhR19YuSfwP1NCaMG1MhQ+PVBVmepOwJMRJR7
+9PLfOouNMfixKBGP12dtStMuh7jowq/BxhRI6JWp3RhTZ1yJ9ouzHze7IDrEBa6w
+p0hUu9H0Sbt51LXbC3JmTyhbdhfry559DfyGW1Ma/bv/pihL9B5Y7sNf1thNp1gi
+GbQ9B+o2Yyw8ZD8zY+sl+aYDSWyCtcBV/KXEF74Bkfs/a5ExJ00X0jYj/TAp2ray
+T4PY0FR8wN/O10bFLP9j+Xa/ywbcPhoj8nvVRIg9VfWT/QaEd+KR0EZVxdjCCqne
+enbSQksTpAZNwQKCAQEA98E+BMmS+yHUVUhNZABtQ5avwuV4+DoSN8KTp3xwQ0CH
+m9fWxSDs12FdyMhDxrJPeywvHtZ18/7cl3dr8wnFVE0s4ongnRDXsNk5xN6J3AaO
+KqW4HF9cbwZqzLILy8TrO+EK/EQV9FypbrxqvxAlP1kezIA2CJNzVRAgimSuV/H7
+05HTnp5W06fjtEf8U1CUrdNetoSROUo1j/IMGPYGlsBFYAGrj5y/BlKd+3T3kjRp
+Xje7HpiykjrZHn0WDp04Ln+u9nveEewXmHKch313emt7HpW0xspp8JM8OZtEKozk
+D5PfYdBfMJJOUlqovCCzTTJ6kNOahknKXFeO/qs5IwKCAQEAjF0/zhWikXF/fcfD
+Bql2z2vTYdEmSvdjHSYff1Nn90K71DdVk5wytOxJM/sfp/z+yoMNjVKIL/IGQw5Z
+va4xFx+CUhGjxlZ0pLEjT37U9gHsGYsK5jvslLvG/MixfH5AOwoqi5ERQVTpbIF9
+jvVPEAh6YSu/ExglWGJIxTsRUIblxvTxdjEnl/p+rlM0RNJnA6vpo1J51BXA7CdF
+7bZQ5u0Feo/bK1I70ClYg/DGfkmYEV0pZG5cxNkqfDbgwsqWa7YGLGd94xkh+ymq
+jETqxeWyozxhbQ83nYpfzeVc7t//qlJ8b5uf0wUKoRmtNr9rtp13lzP/21REzPXW
+w+oxwQKCAQAoAf2Y2lAw25KlPuq4ZlU+n9u8FkBFnWMJvBMJ7c9XHNmJMf6NkLaO
+RTvWy3geYvbwxf7J9QnRH+vRTciR05cY+Olxn6A03N5nwXxRrToH3MsiWeZ0NnX/
+u8KNUYcUHbV60ulqOThuYHQ/3I9EUUAijaqqjV2sXts19ke68W0x6HKpBJhuudT9
+ktPzbdhyP8Xyl/pocNnerXwexZBsi3Ye6+eIDFz+8OnsBHVcgNPluS72tvsxgqj7
+ciNTiBGCxKKo55eCWBhRPpXE2WUrf/hGPYsBMl2h6FfZMH1+M/N7B4tgdJmS+woU
+Ftws8lTjJEiwA6HFN1ZxrwLNjJobx9yPAoIBAE0igsBuWWn6rXeOPylYg4264XOq
+8gb94pte2n9amDgCzyCn8m6AL3snLC/AoCD19DK+gyK0ukoesXPa3iX6w2xv69ZC
+urDx36Jhd4zrJb4QsFPoeKfDP+UvNVZaS41vipRRzY/y11em15prUZ4U8FA/UT1Y
+FzkBo9r6iUZRnyBLppMuEfWASDtuRNmeIHynoT1AcQOH3l9vR210iEpmAuJr0CYA
+bvTuz3UzzGGEAuIUvuaiRtkfKY52jBmiEr7SSPCr1HvLj3Ccz8bgjgR2kiXmcU50
+1zLnaPAD44LZ/0Fjqj+PimQGT6K7CNXPllmYh7MvoU52g3SVPf6rHlIR0Nc=
+-----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/openssh.pub b/nixpkgs/nixos/tests/initrd-network-ssh/openssh.pub
new file mode 100644
index 000000000000..5b72b8085f27
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/openssh.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDv70DY8I85MCUWatpuWtRYD5Vl0C4t8zold4ew4XtlsLYXqW9aa8Mz3bkNCqGICNyBae3lYdZLlj5vuUae7NPt/Ip6Bd3zScJGaqWlIbeytfT+XFFpAAkvN+gONywX5x6zBCjoZKxicIdXEw2zTTuBttVjd/uj6xZm0t3Ek8PaIe+XlmssrR3VEsyZTpx8iEgAGUvRLwRaK/b1FE56mwJCdLR2crZv6+ifVIJiiCC8Ax7ZOgAgAnz66pph8YDKf2rx+NmJEfEuYoOr3S0qgJQvmv/Z2YQgT63xP/c98eirVDNm2z1b2J2EF1PbV0RcG0bKgy1ySnmeOTmbzYtANiI5eS5KghSFI616HmDt8G/UG1VIRHEmDdR4mM94YoXB2fNqmYAoLbD/pXzFwgg00D5+FkDsH18PVECLZMQJn6DIxgNzhP5RKUxKb1x+9I6t/b7meC0VvziceOq8KXEIwu75sJYrwdqapTOET9rkttH7jwTs/IazcpX6xYU30o6dqROOP/qJCFdlGGB4Arf0XZdOTkyzlj4FFJK2cygxkDaCshX6MeLXNtaiRWpc8jlhGjmKxH8+C0oBXoV81a6ZxigUn0XjSkwdDqElUSa2sIHHsQClKXeZIZtAlSaKUsLnWo2HBBFsP/9m2kis3zYdMwrNnqSPuQMJ2x9/7CyeQheaYw== tmtynkky@duuni
diff --git a/nixpkgs/nixos/tests/initrd-network.nix b/nixpkgs/nixos/tests/initrd-network.nix
new file mode 100644
index 000000000000..ed9b82e2da77
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network.nix
@@ -0,0 +1,22 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "initrd-network";
+
+  meta.maintainers = [ pkgs.stdenv.lib.maintainers.eelco ];
+
+  machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    boot.initrd.network.enable = true;
+    boot.initrd.network.postCommands =
+      ''
+        ip addr | grep 10.0.2.15 || exit 1
+        ping -c1 10.0.2.2 || exit 1
+      '';
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("multi-user.target");
+      $machine->succeed("ip link >&2");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/installer.nix b/nixpkgs/nixos/tests/installer.nix
new file mode 100644
index 000000000000..2553a0d116ae
--- /dev/null
+++ b/nixpkgs/nixos/tests/installer.nix
@@ -0,0 +1,677 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+
+  # The configuration to install.
+  makeConfig = { bootLoader, grubVersion, grubDevice, grubIdentifier, grubUseEfi
+               , extraConfig, forceGrubReinstallCount ? 0
+               }:
+    pkgs.writeText "configuration.nix" ''
+      { config, lib, pkgs, modulesPath, ... }:
+
+      { imports =
+          [ ./hardware-configuration.nix
+            <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          ];
+
+        # To ensure that we can rebuild the grub configuration on the nixos-rebuild
+        system.extraDependencies = with pkgs; [ stdenvNoCC ];
+
+        ${optionalString (bootLoader == "grub") ''
+          boot.loader.grub.version = ${toString grubVersion};
+          ${optionalString (grubVersion == 1) ''
+            boot.loader.grub.splashImage = null;
+          ''}
+
+          boot.loader.grub.extraConfig = "serial; terminal_output.serial";
+          ${if grubUseEfi then ''
+            boot.loader.grub.device = "nodev";
+            boot.loader.grub.efiSupport = true;
+            boot.loader.grub.efiInstallAsRemovable = true; # XXX: needed for OVMF?
+          '' else ''
+            boot.loader.grub.device = "${grubDevice}";
+            boot.loader.grub.fsIdentifier = "${grubIdentifier}";
+          ''}
+
+          boot.loader.grub.configurationLimit = 100 + ${toString forceGrubReinstallCount};
+        ''}
+
+        ${optionalString (bootLoader == "systemd-boot") ''
+          boot.loader.systemd-boot.enable = true;
+        ''}
+
+        users.users.alice = {
+          isNormalUser = true;
+          home = "/home/alice";
+          description = "Alice Foobar";
+        };
+
+        hardware.enableAllFirmware = lib.mkForce false;
+
+        services.udisks2.enable = lib.mkDefault false;
+
+        ${replaceChars ["\n"] ["\n  "] extraConfig}
+      }
+    '';
+
+
+  # The test script boots a NixOS VM, 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.
+  testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
+                  , grubIdentifier, preBootCommands, extraConfig
+                  }:
+    let
+      iface = if grubVersion == 1 then "ide" else "virtio";
+      isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
+
+      # FIXME don't duplicate the -enable-kvm etc. flags here yet again!
+      qemuFlags =
+        (if system == "x86_64-linux" then "-m 768 " else "-m 512 ") +
+        (optionalString (system == "x86_64-linux") "-cpu kvm64 ") +
+        (optionalString (system == "aarch64-linux") "-enable-kvm -machine virt,gic-version=host -cpu host ");
+
+      hdFlags = ''hda => "vm-state-machine/machine.qcow2", hdaInterface => "${iface}", ''
+        + optionalString isEfi (if pkgs.stdenv.isAarch64
+            then ''bios => "${pkgs.OVMF.fd}/FV/QEMU_EFI.fd", ''
+            else ''bios => "${pkgs.OVMF.fd}/FV/OVMF.fd", '');
+    in if !isEfi && !(pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) then
+      throw "Non-EFI boot methods are only supported on i686 / x86_64"
+    else ''
+      $machine->start;
+
+      # Make sure that we get a login prompt etc.
+      $machine->succeed("echo hello");
+      #$machine->waitForUnit('getty@tty2');
+      #$machine->waitForUnit("rogue");
+      $machine->waitForUnit("nixos-manual");
+
+      # Wait for hard disks to appear in /dev
+      $machine->succeed("udevadm settle");
+
+      # Partition the disk.
+      ${createPartitions}
+
+      # Create the NixOS configuration.
+      $machine->succeed("nixos-generate-config --root /mnt");
+
+      $machine->succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2");
+
+      $machine->copyFileFromHost(
+          "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; } }",
+          "/mnt/etc/nixos/configuration.nix");
+
+      # Perform the installation.
+      $machine->succeed("nixos-install < /dev/null >&2");
+
+      # Do it again to make sure it's idempotent.
+      $machine->succeed("nixos-install < /dev/null >&2");
+
+      $machine->succeed("umount /mnt/boot || true");
+      $machine->succeed("umount /mnt");
+      $machine->succeed("sync");
+
+      $machine->shutdown;
+
+      # Now see if we can boot the installation.
+      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "boot-after-install" });
+
+      # For example to enter LUKS passphrase.
+      ${preBootCommands}
+
+      # Did /boot get mounted?
+      $machine->waitForUnit("local-fs.target");
+
+      ${if bootLoader == "grub" then
+          ''$machine->succeed("test -e /boot/grub");''
+        else
+          ''$machine->succeed("test -e /boot/loader/loader.conf");''
+      }
+
+      # Check whether /root has correct permissions.
+      $machine->succeed("stat -c '%a' /root") =~ /700/ or die;
+
+      # Did the swap device get activated?
+      # uncomment once https://bugs.freedesktop.org/show_bug.cgi?id=86930 is resolved
+      $machine->waitForUnit("swap.target");
+      $machine->succeed("cat /proc/swaps | grep -q /dev");
+
+      # Check that the store is in good shape
+      $machine->succeed("nix-store --verify --check-contents >&2");
+
+      # Check whether the channel works.
+      $machine->succeed("nix-env -iA nixos.procps >&2");
+      $machine->succeed("type -tP ps | tee /dev/stderr") =~ /.nix-profile/
+          or die "nix-env failed";
+
+      # Check that the daemon works, and that non-root users can run builds (this will build a new profile generation through the daemon)
+      $machine->succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2");
+
+      # We need a writable Nix store on next boot.
+      $machine->copyFileFromHost(
+          "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; forceGrubReinstallCount = 1; } }",
+          "/etc/nixos/configuration.nix");
+
+      # Check whether nixos-rebuild works.
+      $machine->succeed("nixos-rebuild switch >&2");
+
+      # Test nixos-option.
+      $machine->succeed("nixos-option boot.initrd.kernelModules | grep virtio_console");
+      $machine->succeed("nixos-option boot.initrd.kernelModules | grep 'List of modules'");
+      $machine->succeed("nixos-option boot.initrd.kernelModules | grep qemu-guest.nix");
+
+      $machine->shutdown;
+
+      # Check whether a writable store build works
+      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "rebuild-switch" });
+      ${preBootCommands}
+      $machine->waitForUnit("multi-user.target");
+      $machine->copyFileFromHost(
+          "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; forceGrubReinstallCount = 2; } }",
+          "/etc/nixos/configuration.nix");
+      $machine->succeed("nixos-rebuild boot >&2");
+      $machine->shutdown;
+
+      # And just to be sure, check that the machine still boots after
+      # "nixos-rebuild switch".
+      $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", "boot-after-rebuild-switch" });
+      ${preBootCommands}
+      $machine->waitForUnit("network.target");
+      $machine->shutdown;
+    '';
+
+
+  makeInstallerTest = name:
+    { createPartitions, preBootCommands ? "", extraConfig ? ""
+    , extraInstallerConfig ? {}
+    , bootLoader ? "grub" # either "grub" or "systemd-boot"
+    , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
+    , enableOCR ? false, meta ? {}
+    }:
+    makeTest {
+      inherit enableOCR;
+      name = "installer-" + name;
+      meta = with pkgs.stdenv.lib.maintainers; {
+        # put global maintainers here, individuals go into makeInstallerTest fkt call
+        maintainers = (meta.maintainers or []);
+      };
+      nodes = {
+
+        # The configuration of the machine used to run "nixos-install".
+        machine =
+          { pkgs, ... }:
+
+          { imports =
+              [ ../modules/profiles/installation-device.nix
+                ../modules/profiles/base.nix
+                extraInstallerConfig
+              ];
+
+            virtualisation.diskSize = 8 * 1024;
+            virtualisation.memorySize = 1024;
+
+            # Use a small /dev/vdb as the root disk for the
+            # installer. This ensures the target disk (/dev/vda) is
+            # the same during and after installation.
+            virtualisation.emptyDiskImages = [ 512 ];
+            virtualisation.bootDevice =
+              if grubVersion == 1 then "/dev/sdb" else "/dev/vdb";
+            virtualisation.qemu.diskInterface =
+              if grubVersion == 1 then "scsi" else "virtio";
+
+            boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true;
+
+            hardware.enableAllFirmware = mkForce false;
+
+            # The test cannot access the network, so any packages we
+            # need must be included in the VM.
+            system.extraDependencies = with pkgs;
+              [ sudo
+                libxml2.bin
+                libxslt.bin
+                desktop-file-utils
+                docbook5
+                docbook_xsl_ns
+                unionfs-fuse
+                ntp
+                nixos-artwork.wallpapers.simple-dark-gray-bottom
+                perlPackages.XMLLibXML
+                perlPackages.ListCompare
+                shared-mime-info
+                texinfo
+                xorg.lndir
+
+                # add curl so that rather than seeing the test attempt to download
+                # curl's tarball, we see what it's trying to download
+                curl
+              ]
+              ++ optional (bootLoader == "grub" && grubVersion == 1) pkgs.grub
+              ++ optionals (bootLoader == "grub" && grubVersion == 2) [ pkgs.grub2 pkgs.grub2_efi ];
+
+            services.udisks2.enable = mkDefault false;
+
+            nix.binaryCaches = mkForce [ ];
+            nix.extraOptions =
+              ''
+                hashed-mirrors =
+                connect-timeout = 1
+              '';
+          };
+
+      };
+
+      testScript = testScriptFun {
+        inherit bootLoader createPartitions preBootCommands
+                grubVersion grubDevice grubIdentifier grubUseEfi extraConfig;
+      };
+    };
+
+
+in {
+
+  # !!! `parted mkpart' seems to silently create overlapping partitions.
+
+
+  # The (almost) simplest partitioning scheme: a swap partition and
+  # one big filesystem partition.
+  simple = makeInstallerTest "simple"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary linux-swap 1M 1024M"
+              . " 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",
+          );
+        '';
+    };
+
+  # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
+  simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+              . " mkpart ESP fat32 1M 50MiB" # /boot
+              . " set 1 boot on"
+              . " mkpart primary linux-swap 50MiB 1024MiB"
+              . " mkpart primary ext2 1024MiB -1MiB", # /
+              "udevadm settle",
+              "mkswap /dev/vda2 -L swap",
+              "swapon -L swap",
+              "mkfs.ext3 -L nixos /dev/vda3",
+              "mount LABEL=nixos /mnt",
+              "mkfs.vfat -n BOOT /dev/vda1",
+              "mkdir -p /mnt/boot",
+              "mount LABEL=BOOT /mnt/boot",
+          );
+        '';
+        bootLoader = "systemd-boot";
+    };
+
+  simpleUefiGrub = makeInstallerTest "simpleUefiGrub"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+              . " mkpart ESP fat32 1M 50MiB" # /boot
+              . " set 1 boot on"
+              . " mkpart primary linux-swap 50MiB 1024MiB"
+              . " mkpart primary ext2 1024MiB -1MiB", # /
+              "udevadm settle",
+              "mkswap /dev/vda2 -L swap",
+              "swapon -L swap",
+              "mkfs.ext3 -L nixos /dev/vda3",
+              "mount LABEL=nixos /mnt",
+              "mkfs.vfat -n BOOT /dev/vda1",
+              "mkdir -p /mnt/boot",
+              "mount LABEL=BOOT /mnt/boot",
+          );
+        '';
+        bootLoader = "grub";
+        grubUseEfi = true;
+    };
+
+  # Same as the previous, but now with a separate /boot partition.
+  separateBoot = makeInstallerTest "separateBoot"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary ext2 1M 50MB" # /boot
+              . " mkpart primary linux-swap 50MB 1024M"
+              . " 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 -p /mnt/boot",
+              "mount LABEL=boot /mnt/boot",
+          );
+        '';
+    };
+
+  # Same as the previous, but with fat32 /boot.
+  separateBootFat = makeInstallerTest "separateBootFat"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary ext2 1M 50MB" # /boot
+              . " mkpart primary linux-swap 50MB 1024M"
+              . " 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.vfat -n BOOT /dev/vda1",
+              "mkdir -p /mnt/boot",
+              "mount LABEL=BOOT /mnt/boot",
+          );
+        '';
+    };
+
+  # zfs on / with swap
+  zfsroot = makeInstallerTest "zfs-root"
+    {
+      extraInstallerConfig = {
+        boot.supportedFilesystems = [ "zfs" ];
+      };
+
+      extraConfig = ''
+        boot.supportedFilesystems = [ "zfs" ];
+
+        # Using by-uuid overrides the default of by-id, and is unique
+        # to the qemu disks, as they don't produce by-id paths for
+        # some reason.
+        boot.zfs.devNodes = "/dev/disk/by-uuid/";
+        networking.hostId = "00000000";
+      '';
+
+      createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary linux-swap 1M 1024M"
+              . " mkpart primary 1024M -1s",
+              "udevadm settle",
+
+              "mkswap /dev/vda1 -L swap",
+              "swapon -L swap",
+
+              "zpool create rpool /dev/vda2",
+              "zfs create -o mountpoint=legacy rpool/root",
+              "mount -t zfs rpool/root /mnt",
+
+              "udevadm settle"
+          );
+        '';
+    };
+
+  # Create two physical LVM partitions combined into one volume group
+  # that contains the logical swap and root partitions.
+  lvm = makeInstallerTest "lvm"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary 1M 2048M" # PV1
+              . " set 1 lvm on"
+              . " mkpart primary 2048M -1s" # PV2
+              . " 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",
+          );
+        '';
+    };
+
+  # Boot off an encrypted root partition
+  luksroot = makeInstallerTest "luksroot"
+    { createPartitions = ''
+        $machine->succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          . " mkpart primary ext2 1M 50MB" # /boot
+          . " mkpart primary linux-swap 50M 1024M"
+          . " mkpart primary 1024M -1s", # LUKS
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "modprobe dm_mod dm_crypt",
+          "echo -n supersecret | cryptsetup luksFormat -q /dev/vda3 -",
+          "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda3 cryptroot",
+          "mkfs.ext3 -L nixos /dev/mapper/cryptroot",
+          "mount LABEL=nixos /mnt",
+          "mkfs.ext3 -L boot /dev/vda1",
+          "mkdir -p /mnt/boot",
+          "mount LABEL=boot /mnt/boot",
+        );
+      '';
+      extraConfig = ''
+        boot.kernelParams = lib.mkAfter [ "console=tty0" ];
+      '';
+      enableOCR = true;
+      preBootCommands = ''
+        $machine->start;
+        $machine->waitForText(qr/Passphrase for/);
+        $machine->sendChars("supersecret\n");
+      '';
+    };
+
+  # Test whether opening encrypted filesystem with keyfile
+  # Checks for regression of missing cryptsetup, when no luks device without
+  # keyfile is configured
+  encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile"
+    { createPartitions = ''
+       $machine->succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          . " mkpart primary ext2 1M 50MB" # /boot
+          . " mkpart primary linux-swap 50M 1024M"
+          . " mkpart primary 1024M 1280M" # LUKS with keyfile
+          . " mkpart primary 1280M -1s",
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/vda4",
+          "mount LABEL=nixos /mnt",
+          "mkfs.ext3 -L boot /dev/vda1",
+          "mkdir -p /mnt/boot",
+          "mount LABEL=boot /mnt/boot",
+          "modprobe dm_mod dm_crypt",
+          "echo -n supersecret > /mnt/keyfile",
+          "cryptsetup luksFormat -q /dev/vda3 --key-file /mnt/keyfile",
+          "cryptsetup luksOpen --key-file /mnt/keyfile /dev/vda3 crypt",
+          "mkfs.ext3 -L test /dev/mapper/crypt",
+          "cryptsetup luksClose crypt",
+          "mkdir -p /mnt/test"
+        );
+      '';
+      extraConfig = ''
+        fileSystems."/test" =
+        { device = "/dev/disk/by-label/test";
+          fsType = "ext3";
+          encrypted.enable = true;
+          encrypted.blkDev = "/dev/vda3";
+          encrypted.label = "crypt";
+          encrypted.keyFile = "/mnt-root/keyfile";
+        };
+      '';
+    };
+
+
+  swraid = makeInstallerTest "swraid"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/vda parted --script /dev/vda --"
+              . " mklabel msdos"
+              . " mkpart primary ext2 1M 100MB" # /boot
+              . " mkpart extended 100M -1s"
+              . " mkpart logical 102M 2102M" # md0 (root), first device
+              . " mkpart logical 2103M 4103M" # md0 (root), second device
+              . " mkpart logical 4104M 4360M" # md1 (swap), first device
+              . " mkpart logical 4361M 4617M", # md1 (swap), second device
+              "udevadm settle",
+              "ls -l /dev/vda* >&2",
+              "cat /proc/partitions >&2",
+              "udevadm control --stop-exec-queue",
+              "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 control --start-exec-queue",
+              "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",
+          );
+        '';
+      preBootCommands = ''
+        $machine->start;
+        $machine->fail("dmesg | grep 'immediate safe mode'");
+      '';
+    };
+
+  # Test a basic install using GRUB 1.
+  grub1 = makeInstallerTest "grub1"
+    { createPartitions =
+        ''
+          $machine->succeed(
+              "flock /dev/sda parted --script /dev/sda -- mklabel msdos"
+              . " mkpart primary linux-swap 1M 1024M"
+              . " 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",
+              "mkdir -p /mnt/tmp",
+          );
+        '';
+      grubVersion = 1;
+      grubDevice = "/dev/sda";
+    };
+
+  # Test using labels to identify volumes in grub
+  simpleLabels = makeInstallerTest "simpleLabels" {
+    createPartitions = ''
+      $machine->succeed(
+        "sgdisk -Z /dev/vda",
+        "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.ext4 -L root /dev/vda3",
+        "mount LABEL=root /mnt",
+      );
+    '';
+    grubIdentifier = "label";
+  };
+
+  # Test using the provided disk name within grub
+  # TODO: Fix udev so the symlinks are unneeded in /dev/disks
+  simpleProvided = makeInstallerTest "simpleProvided" {
+    createPartitions = ''
+      my $UUID = "\$(blkid -s UUID -o value /dev/vda2)";
+      $machine->succeed(
+        "sgdisk -Z /dev/vda",
+        "sgdisk -n 1:0:+1M -n 2:0:+100M -n 3:0:+1G -N 4 -t 1:ef02 -t 2:8300 -t 3:8200 -t 4:8300 -c 2:boot -c 4:root /dev/vda",
+        "mkswap /dev/vda3 -L swap",
+        "swapon -L swap",
+        "mkfs.ext4 -L boot /dev/vda2",
+        "mkfs.ext4 -L root /dev/vda4",
+      );
+      $machine->execute("ln -s ../../vda2 /dev/disk/by-uuid/$UUID");
+      $machine->execute("ln -s ../../vda4 /dev/disk/by-label/root");
+      $machine->succeed(
+        "mount /dev/disk/by-label/root /mnt",
+        "mkdir /mnt/boot",
+        "mount /dev/disk/by-uuid/$UUID /mnt/boot"
+      );
+    '';
+    grubIdentifier = "provided";
+  };
+
+  # Simple btrfs grub testing
+  btrfsSimple = makeInstallerTest "btrfsSimple" {
+    createPartitions = ''
+      $machine->succeed(
+        "sgdisk -Z /dev/vda",
+        "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.btrfs -L root /dev/vda3",
+        "mount LABEL=root /mnt",
+      );
+    '';
+  };
+
+  # Test to see if we can detect /boot and /nix on subvolumes
+  btrfsSubvols = makeInstallerTest "btrfsSubvols" {
+    createPartitions = ''
+      $machine->succeed(
+        "sgdisk -Z /dev/vda",
+        "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.btrfs -L root /dev/vda3",
+        "btrfs device scan",
+        "mount LABEL=root /mnt",
+        "btrfs subvol create /mnt/boot",
+        "btrfs subvol create /mnt/nixos",
+        "btrfs subvol create /mnt/nixos/default",
+        "umount /mnt",
+        "mount -o defaults,subvol=nixos/default LABEL=root /mnt",
+        "mkdir /mnt/boot",
+        "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
+      );
+    '';
+  };
+
+  # Test to see if we can detect default and aux subvolumes correctly
+  btrfsSubvolDefault = makeInstallerTest "btrfsSubvolDefault" {
+    createPartitions = ''
+      $machine->succeed(
+        "sgdisk -Z /dev/vda",
+        "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.btrfs -L root /dev/vda3",
+        "btrfs device scan",
+        "mount LABEL=root /mnt",
+        "btrfs subvol create /mnt/badpath",
+        "btrfs subvol create /mnt/badpath/boot",
+        "btrfs subvol create /mnt/nixos",
+        "btrfs subvol set-default \$(btrfs subvol list /mnt | grep 'nixos' | awk '{print \$2}') /mnt",
+        "umount /mnt",
+        "mount -o defaults LABEL=root /mnt",
+        "mkdir -p /mnt/badpath/boot", # Help ensure the detection mechanism is actually looking up subvolumes
+        "mkdir /mnt/boot",
+        "mount -o defaults,subvol=badpath/boot LABEL=root /mnt/boot",
+      );
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/tests/ipfs.nix b/nixpkgs/nixos/tests/ipfs.nix
new file mode 100644
index 000000000000..3cff7e99ff88
--- /dev/null
+++ b/nixpkgs/nixos/tests/ipfs.nix
@@ -0,0 +1,55 @@
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "ipfs";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mguentner ];
+  };
+
+  nodes = {
+    adder =
+      { ... }:
+      {
+        services.ipfs = {
+          enable = true;
+          defaultMode = "norouting";
+          gatewayAddress = "/ip4/127.0.0.1/tcp/2323";
+          apiAddress = "/ip4/127.0.0.1/tcp/2324";
+        };
+        networking.firewall.allowedTCPPorts = [ 4001 ];
+      };
+    getter =
+      { ... }:
+      {
+        services.ipfs = {
+          enable = true;
+          defaultMode = "norouting";
+          autoMount = true;
+        };
+        networking.firewall.allowedTCPPorts = [ 4001 ];
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $adder->waitForUnit("ipfs-norouting");
+    $getter->waitForUnit("ipfs-norouting");
+
+    # wait until api is available
+    $adder->waitUntilSucceeds("ipfs --api /ip4/127.0.0.1/tcp/2324 id");
+    my $addrId = $adder->succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id -f=\"<id>\"");
+    my $addrIp = (split /[ \/]+/, $adder->succeed("ip -o -4 addr show dev eth1"))[3];
+
+    $adder->mustSucceed("[ -n \"\$(ipfs --api /ip4/127.0.0.1/tcp/2324 config Addresses.Gateway | grep /ip4/127.0.0.1/tcp/2323)\" ]");
+
+    # wait until api is available
+    $getter->waitUntilSucceeds("ipfs --api /ip4/127.0.0.1/tcp/5001 id");
+    my $ipfsHash = $adder->mustSucceed("echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add | cut -d' ' -f2");
+    chomp($ipfsHash);
+
+    $adder->mustSucceed("[ -n \"\$(echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add | grep added)\" ]");
+
+    $getter->mustSucceed("ipfs --api /ip4/127.0.0.1/tcp/5001 swarm connect /ip4/$addrIp/tcp/4001/ipfs/$addrId");
+    $getter->mustSucceed("[ -n \"\$(ipfs --api /ip4/127.0.0.1/tcp/5001 cat /ipfs/$ipfsHash | grep fnord)\" ]");
+    $getter->mustSucceed("[ -n \"$(cat /ipfs/$ipfsHash | grep fnord)\" ]");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/ipv6.nix b/nixpkgs/nixos/tests/ipv6.nix
new file mode 100644
index 000000000000..97f348a9beeb
--- /dev/null
+++ b/nixpkgs/nixos/tests/ipv6.nix
@@ -0,0 +1,79 @@
+# Test of IPv6 functionality in NixOS, including whether router
+# solicication/advertisement using radvd works.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "ipv6";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  nodes =
+    { client = { ... }: { };
+
+      server =
+        { ... }:
+        { services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          networking.firewall.allowedTCPPorts = [ 80 ];
+        };
+
+      router =
+        { ... }:
+        { 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");
+      $server->waitForUnit("httpd.service");
+
+      # 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` -ge 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("ping -c 1 ::1 >&2");
+          $client->fail("ping -c 1 ::2 >&2");
+      };
+
+      subtest "local link addressing", sub {
+          my $clientIp = waitForAddress $client, "eth1", "link";
+          my $serverIp = waitForAddress $server, "eth1", "link";
+          $client->succeed("ping -c 1 $clientIp%eth1 >&2");
+          $client->succeed("ping -c 1 $serverIp%eth1 >&2");
+      };
+
+      subtest "global addressing", sub {
+          my $clientIp = waitForAddress $client, "eth1", "global";
+          my $serverIp = waitForAddress $server, "eth1", "global";
+          $client->succeed("ping -c 1 $clientIp >&2");
+          $client->succeed("ping -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/nixpkgs/nixos/tests/jackett.nix b/nixpkgs/nixos/tests/jackett.nix
new file mode 100644
index 000000000000..399a0c272327
--- /dev/null
+++ b/nixpkgs/nixos/tests/jackett.nix
@@ -0,0 +1,18 @@
+import ./make-test.nix ({ lib, ... }:
+
+with lib;
+
+rec {
+  name = "jackett";
+  meta.maintainers = with maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.jackett.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('jackett.service');
+    $machine->waitForOpenPort('9117');
+    $machine->succeed("curl --fail http://localhost:9117/");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/jenkins.nix b/nixpkgs/nixos/tests/jenkins.nix
new file mode 100644
index 000000000000..4f2d2085cd1a
--- /dev/null
+++ b/nixpkgs/nixos/tests/jenkins.nix
@@ -0,0 +1,50 @@
+# verifies:
+#   1. jenkins service starts on master node
+#   2. jenkins user can be extended on both master and slave
+#   3. jenkins service not started on slave node
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "jenkins";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bjornfor coconnor domenkozar eelco chaoflow ];
+  };
+
+  nodes = {
+
+    master =
+      { ... }:
+      { services.jenkins.enable = true;
+
+        # should have no effect
+        services.jenkinsSlave.enable = true;
+
+        users.users.jenkins.extraGroups = [ "users" ];
+
+        systemd.services.jenkins.serviceConfig.TimeoutStartSec = "6min";
+      };
+
+    slave =
+      { ... }:
+      { services.jenkinsSlave.enable = true;
+
+        users.users.jenkins.extraGroups = [ "users" ];
+      };
+
+  };
+
+  testScript = ''
+    startAll;
+
+    $master->waitForUnit("jenkins");
+
+    $master->mustSucceed("curl http://localhost:8080 | grep 'Authentication required'");
+
+    print $master->execute("sudo -u jenkins groups");
+    $master->mustSucceed("sudo -u jenkins groups | grep jenkins | grep users");
+
+    print $slave->execute("sudo -u jenkins groups");
+    $slave->mustSucceed("sudo -u jenkins groups | grep jenkins | grep users");
+
+    $slave->mustFail("systemctl is-enabled jenkins.service");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kafka.nix b/nixpkgs/nixos/tests/kafka.nix
new file mode 100644
index 000000000000..72f91f6428a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/kafka.nix
@@ -0,0 +1,75 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  makeKafkaTest = name: kafkaPackage: (makeTest {
+    inherit name;
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ nequissimus ];
+    };
+
+    nodes = {
+      zookeeper1 = { ... }: {
+        services.zookeeper = {
+          enable = true;
+        };
+
+        networking.firewall.allowedTCPPorts = [ 2181 ];
+        virtualisation.memorySize = 1024;
+      };
+      kafka = { ... }: {
+        services.apache-kafka = {
+          enable = true;
+          extraProperties = ''
+            offsets.topic.replication.factor = 1
+            zookeeper.session.timeout.ms = 600000
+          '';
+          package = kafkaPackage;
+          zookeeper = "zookeeper1:2181";
+          # These are the default options, but UseCompressedOops doesn't work with 32bit JVM
+          jvmOptions = [
+            "-server" "-Xmx1G" "-Xms1G" "-XX:+UseParNewGC" "-XX:+UseConcMarkSweepGC" "-XX:+CMSClassUnloadingEnabled"
+            "-XX:+CMSScavengeBeforeRemark" "-XX:+DisableExplicitGC" "-Djava.awt.headless=true" "-Djava.net.preferIPv4Stack=true"
+          ] ++ optionals (! pkgs.stdenv.isi686 ) [ "-XX:+UseCompressedOops" ];
+        };
+
+        networking.firewall.allowedTCPPorts = [ 9092 ];
+        # i686 tests: qemu-system-i386 can simulate max 2047MB RAM (not 2048)
+        virtualisation.memorySize = 2047;
+      };
+    };
+
+    testScript = ''
+      startAll;
+
+      $zookeeper1->waitForUnit("default.target");
+      $zookeeper1->waitForUnit("zookeeper.service");
+      $zookeeper1->waitForOpenPort(2181);
+
+      $kafka->waitForUnit("default.target");
+      $kafka->waitForUnit("apache-kafka.service");
+      $kafka->waitForOpenPort(9092);
+
+      $kafka->waitUntilSucceeds("${kafkaPackage}/bin/kafka-topics.sh --create --zookeeper zookeeper1:2181 --partitions 1 --replication-factor 1 --topic testtopic");
+      $kafka->mustSucceed("echo 'test 1' | ${kafkaPackage}/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testtopic");
+    '' + (if name == "kafka_0_9" then ''
+      $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --zookeeper zookeeper1:2181 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'");
+    '' else ''
+      $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'");
+    '');
+  });
+
+in with pkgs; {
+  kafka_0_9  = makeKafkaTest "kafka_0_9"  apacheKafka_0_9;
+  kafka_0_10 = makeKafkaTest "kafka_0_10" apacheKafka_0_10;
+  kafka_0_11 = makeKafkaTest "kafka_0_11" apacheKafka_0_11;
+  kafka_1_0  = makeKafkaTest "kafka_1_0"  apacheKafka_1_0;
+  kafka_1_1  = makeKafkaTest "kafka_1_1"  apacheKafka_1_1;
+  kafka_2_0  = makeKafkaTest "kafka_2_0"  apacheKafka_2_0;
+  kafka_2_1  = makeKafkaTest "kafka_2_1"  apacheKafka_2_1;
+}
diff --git a/nixpkgs/nixos/tests/kerberos/default.nix b/nixpkgs/nixos/tests/kerberos/default.nix
new file mode 100644
index 000000000000..f2f1a438918c
--- /dev/null
+++ b/nixpkgs/nixos/tests/kerberos/default.nix
@@ -0,0 +1,7 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+{
+  mit = import ./mit.nix { inherit system pkgs; };
+  heimdal = import ./heimdal.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/kerberos/heimdal.nix b/nixpkgs/nixos/tests/kerberos/heimdal.nix
new file mode 100644
index 000000000000..a0551b131e91
--- /dev/null
+++ b/nixpkgs/nixos/tests/kerberos/heimdal.nix
@@ -0,0 +1,53 @@
+import ../make-test.nix ({pkgs, ...}: {
+  name = "kerberos_server-heimdal";
+  machine = { config, libs, pkgs, ...}:
+  { services.kerberos_server =
+    { enable = true;
+      realms = {
+        "FOO.BAR".acl = [{principal = "admin"; access = ["add" "cpw"];}];
+      };
+    };
+    krb5 = {
+      enable = true;
+      kerberos = pkgs.heimdalFull;
+      libdefaults = {
+        default_realm = "FOO.BAR";
+      };
+      realms = {
+        "FOO.BAR" = {
+          admin_server = "machine";
+          kdc = "machine";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    $machine->start;
+
+    $machine->succeed(
+      "kadmin -l init --realm-max-ticket-life='8 day' \\
+       --realm-max-renewable-life='10 day' FOO.BAR"
+    );
+
+    $machine->succeed("systemctl restart kadmind.service kdc.service");
+    $machine->waitForUnit("kadmind.service");
+    $machine->waitForUnit("kdc.service");
+    $machine->waitForUnit("kpasswdd.service");
+
+    $machine->succeed(
+      "kadmin -l add --password=admin_pw --use-defaults admin"
+    );
+    $machine->succeed(
+      "kadmin -l ext_keytab --keytab=admin.keytab admin"
+    );
+    $machine->succeed(
+      "kadmin -p admin -K admin.keytab add --password=alice_pw --use-defaults \\
+       alice"
+    );
+    $machine->succeed(
+      "kadmin -l ext_keytab --keytab=alice.keytab alice"
+    );
+    $machine->succeed("kinit -kt alice.keytab alice");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kerberos/mit.nix b/nixpkgs/nixos/tests/kerberos/mit.nix
new file mode 100644
index 000000000000..6da3a384aa99
--- /dev/null
+++ b/nixpkgs/nixos/tests/kerberos/mit.nix
@@ -0,0 +1,45 @@
+import ../make-test.nix ({pkgs, ...}: {
+  name = "kerberos_server-mit";
+  machine = { config, libs, pkgs, ...}:
+  { services.kerberos_server =
+    { enable = true;
+      realms = {
+        "FOO.BAR".acl = [{principal = "admin"; access = ["add" "cpw"];}];
+      };
+    };
+    krb5 = {
+      enable = true;
+      kerberos = pkgs.krb5Full;
+      libdefaults = {
+        default_realm = "FOO.BAR";
+      };
+      realms = {
+        "FOO.BAR" = {
+          admin_server = "machine";
+          kdc = "machine";
+        };
+      };
+    };
+    users.extraUsers.alice = { isNormalUser = true; };
+  };
+
+  testScript = ''
+    $machine->start;
+
+    $machine->succeed(
+      "kdb5_util create -s -r FOO.BAR -P master_key"
+    );
+
+    $machine->succeed("systemctl restart kadmind.service kdc.service");
+    $machine->waitForUnit("kadmind.service");
+    $machine->waitForUnit("kdc.service");
+
+    $machine->succeed(
+      "kadmin.local add_principal -pw admin_pw admin"
+    );
+    $machine->succeed(
+      "kadmin -p admin -w admin_pw addprinc -pw alice_pw alice"
+    );
+    $machine->succeed("echo alice_pw | sudo -u alice kinit");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kernel-latest.nix b/nixpkgs/nixos/tests/kernel-latest.nix
new file mode 100644
index 000000000000..f30bd2e2e760
--- /dev/null
+++ b/nixpkgs/nixos/tests/kernel-latest.nix
@@ -0,0 +1,17 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "kernel-latest";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages_latest;
+    };
+
+  testScript =
+    ''
+      $machine->succeed("uname -s | grep 'Linux'");
+      $machine->succeed("uname -a | grep '${pkgs.linuxPackages_latest.kernel.version}'");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/kernel-lts.nix b/nixpkgs/nixos/tests/kernel-lts.nix
new file mode 100644
index 000000000000..28717fa6a844
--- /dev/null
+++ b/nixpkgs/nixos/tests/kernel-lts.nix
@@ -0,0 +1,17 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "kernel-lts";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages;
+    };
+
+  testScript =
+    ''
+      $machine->succeed("uname -s | grep 'Linux'");
+      $machine->succeed("uname -a | grep '${pkgs.linuxPackages.kernel.version}'");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/kexec.nix b/nixpkgs/nixos/tests/kexec.nix
new file mode 100644
index 000000000000..db596189d46d
--- /dev/null
+++ b/nixpkgs/nixos/tests/kexec.nix
@@ -0,0 +1,19 @@
+# Test whether fast reboots via kexec work.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "kexec";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  machine = { ... }:
+    { virtualisation.vlans = [ ]; };
+
+  testScript =
+    ''
+      $machine->waitForUnit("multi-user.target");
+      $machine->execute("systemctl kexec &");
+      $machine->{connected} = 0;
+      $machine->waitForUnit("multi-user.target");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/keymap.nix b/nixpkgs/nixos/tests/keymap.nix
new file mode 100644
index 000000000000..2b4c1ab7b052
--- /dev/null
+++ b/nixpkgs/nixos/tests/keymap.nix
@@ -0,0 +1,157 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+
+let
+  readyFile  = "/tmp/readerReady";
+  resultFile = "/tmp/readerResult";
+
+  testReader = pkgs.writeScript "test-input-reader" ''
+    #!${pkgs.stdenv.shell}
+    rm -f ${resultFile} ${resultFile}.tmp
+    logger "testReader: START: Waiting for $1 characters, expecting '$2'."
+    touch ${readyFile}
+    read -r -N $1 chars
+    rm -f ${readyFile}
+
+    if [ "$chars" == "$2" ]; then
+      logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp
+    else
+      logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp
+    fi
+    # rename after the file is written to prevent a race condition
+    mv  ${resultFile}.tmp ${resultFile}
+  '';
+
+
+  mkKeyboardTest = layout: { extraConfig ? {}, tests }: with pkgs.lib; let
+    combinedTests = foldAttrs (acc: val: acc ++ val) [] (builtins.attrValues tests);
+    perlStr = val: "'${escape ["'" "\\"] val}'";
+    lq = length combinedTests.qwerty;
+    le = length combinedTests.expect;
+    msg = "length mismatch between qwerty (${toString lq}) and expect (${toString le}) lists!";
+    send   = concatMapStringsSep ", " perlStr combinedTests.qwerty;
+    expect = if (lq == le) then concatStrings combinedTests.expect else throw msg;
+
+  in makeTest {
+    name = "keymap-${layout}";
+
+    machine.services.xserver.desktopManager.xterm.enable = false;
+    machine.i18n.consoleKeyMap = mkOverride 900 layout;
+    machine.services.xserver.layout = mkOverride 900 layout;
+    machine.imports = [ ./common/x11.nix extraConfig ];
+
+    testScript = ''
+
+      sub mkTest ($$) {
+        my ($desc, $cmd) = @_;
+
+        subtest $desc, sub {
+          # prepare and start testReader
+          $machine->execute("rm -f ${readyFile} ${resultFile}");
+          $machine->succeed("$cmd ${testReader} ${toString le} ".q(${escapeShellArg expect} & ));
+
+          if ($desc eq "Xorg keymap") {
+            # make sure the xterm window is open and has focus
+            $machine->waitForWindow(qr/testterm/);
+            $machine->waitUntilSucceeds("${pkgs.xdotool}/bin/xdotool search --sync --onlyvisible --class testterm windowfocus --sync");
+          }
+
+          # wait for reader to be ready
+          $machine->waitForFile("${readyFile}");
+          $machine->sleep(1);
+
+          # send all keys
+          foreach ((${send})) { $machine->sendKeys($_); };
+
+          # wait for result and check
+          $machine->waitForFile("${resultFile}");
+          $machine->succeed("grep -q 'PASS:' ${resultFile}");
+        };
+      };
+
+      $machine->waitForX;
+
+      mkTest "VT keymap", "openvt -sw --";
+      mkTest "Xorg keymap", "DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e";
+    '';
+  };
+
+in pkgs.lib.mapAttrs mkKeyboardTest {
+  azerty = {
+    tests = {
+      azqw.qwerty = [ "q" "w" ];
+      azqw.expect = [ "a" "z" ];
+      altgr.qwerty = [ "alt_r-2" "alt_r-3" "alt_r-4" "alt_r-5" "alt_r-6" ];
+      altgr.expect = [ "~"       "#"       "{"       "["       "|"       ];
+    };
+
+    extraConfig.i18n.consoleKeyMap = "azerty/fr";
+    extraConfig.services.xserver.layout = "fr";
+  };
+
+  colemak = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "r" "s" "t" "n" "e" "i" "o"         ];
+    };
+
+    extraConfig.i18n.consoleKeyMap = "colemak/colemak";
+    extraConfig.services.xserver.layout = "us";
+    extraConfig.services.xserver.xkbVariant = "colemak";
+  };
+
+  dvorak = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s"         ];
+      symbols.qwerty = [ "q" "w" "e" "minus" "equal" ];
+      symbols.expect = [ "'" "," "." "["     "]"     ];
+    };
+  };
+
+  dvp = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s"         ];
+      numbers.qwerty = map (x: "shift-${x}")
+                       [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
+      numbers.expect = [ "%" "7" "5" "3" "1" "9" "0" "2" "4" "6" "8" ];
+      symbols.qwerty = [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
+      symbols.expect = [ "&" "[" "{" "}" "(" "=" "*" ")" "+" "]" "!" ];
+    };
+
+    extraConfig.services.xserver.layout = "us";
+    extraConfig.services.xserver.xkbVariant = "dvp";
+  };
+
+  neo = {
+    tests = {
+      layer1.qwerty = [ "f"           "j"                     ];
+      layer1.expect = [ "e"           "n"                     ];
+      layer2.qwerty = [ "shift-f"     "shift-j"     "shift-6" ];
+      layer2.expect = [ "E"           "N"           "$"       ];
+      layer3.qwerty = [ "caps_lock-d" "caps_lock-f"           ];
+      layer3.expect = [ "{"           "}"                     ];
+    };
+
+    extraConfig.services.xserver.layout = "de";
+    extraConfig.services.xserver.xkbVariant = "neo";
+  };
+
+  qwertz = {
+    tests = {
+      zy.qwerty = [ "z" "y" ];
+      zy.expect = [ "y" "z" ];
+      altgr.qwerty = map (x: "alt_r-${x}")
+                     [ "q" "less" "7" "8" "9" "0" ];
+      altgr.expect = [ "@" "|"    "{" "[" "]" "}" ];
+    };
+
+    extraConfig.i18n.consoleKeyMap = "de";
+    extraConfig.services.xserver.layout = "de";
+  };
+}
diff --git a/nixpkgs/nixos/tests/krb5/default.nix b/nixpkgs/nixos/tests/krb5/default.nix
new file mode 100644
index 000000000000..dd5b2f37202e
--- /dev/null
+++ b/nixpkgs/nixos/tests/krb5/default.nix
@@ -0,0 +1,5 @@
+{ system ? builtins.currentSystem }:
+{
+  example-config = import ./example-config.nix { inherit system; };
+  deprecated-config = import ./deprecated-config.nix { inherit system; };
+}
diff --git a/nixpkgs/nixos/tests/krb5/deprecated-config.nix b/nixpkgs/nixos/tests/krb5/deprecated-config.nix
new file mode 100644
index 000000000000..7d7926309c95
--- /dev/null
+++ b/nixpkgs/nixos/tests/krb5/deprecated-config.nix
@@ -0,0 +1,48 @@
+# Verifies that the configuration suggested in deprecated example values
+# will result in the expected output.
+
+import ../make-test.nix ({ pkgs, ...} : {
+  name = "krb5-with-deprecated-config";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  machine =
+    { ... }: {
+      krb5 = {
+        enable = true;
+        defaultRealm = "ATHENA.MIT.EDU";
+        domainRealm = "athena.mit.edu";
+        kdc = "kerberos.mit.edu";
+        kerberosAdminServer = "kerberos.mit.edu";
+      };
+    };
+
+  testScript =
+    let snapshot = pkgs.writeText "krb5-with-deprecated-config.conf" ''
+      [libdefaults]
+        default_realm = ATHENA.MIT.EDU
+
+      [realms]
+        ATHENA.MIT.EDU = {
+          admin_server = kerberos.mit.edu
+          kdc = kerberos.mit.edu
+        }
+
+      [domain_realm]
+        .athena.mit.edu = ATHENA.MIT.EDU
+        athena.mit.edu = ATHENA.MIT.EDU
+
+      [capaths]
+
+
+      [appdefaults]
+
+
+      [plugins]
+
+    '';
+  in ''
+    $machine->succeed("diff /etc/krb5.conf ${snapshot}");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/krb5/example-config.nix b/nixpkgs/nixos/tests/krb5/example-config.nix
new file mode 100644
index 000000000000..f01cf6988eef
--- /dev/null
+++ b/nixpkgs/nixos/tests/krb5/example-config.nix
@@ -0,0 +1,106 @@
+# Verifies that the configuration suggested in (non-deprecated) example values
+# will result in the expected output.
+
+import ../make-test.nix ({ pkgs, ...} : {
+  name = "krb5-with-example-config";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  machine =
+    { pkgs, ... }: {
+      krb5 = {
+        enable = true;
+        kerberos = pkgs.krb5Full;
+        libdefaults = {
+          default_realm = "ATHENA.MIT.EDU";
+        };
+        realms = {
+          "ATHENA.MIT.EDU" = {
+            admin_server = "athena.mit.edu";
+            kdc = "athena.mit.edu";
+          };
+        };
+        domain_realm = {
+          "example.com" = "EXAMPLE.COM";
+          ".example.com" = "EXAMPLE.COM";
+        };
+        capaths = {
+          "ATHENA.MIT.EDU" = {
+            "EXAMPLE.COM" = ".";
+          };
+          "EXAMPLE.COM" = {
+            "ATHENA.MIT.EDU" = ".";
+          };
+        };
+        appdefaults = {
+          pam = {
+            debug = false;
+            ticket_lifetime = 36000;
+            renew_lifetime = 36000;
+            max_timeout = 30;
+            timeout_shift = 2;
+            initial_timeout = 1;
+          };
+        };
+        plugins = {
+          ccselect = {
+            disable = "k5identity";
+          };
+        };
+        extraConfig = ''
+          [logging]
+            kdc          = SYSLOG:NOTICE
+            admin_server = SYSLOG:NOTICE
+            default      = SYSLOG:NOTICE
+        '';
+      };
+    };
+
+  testScript =
+    let snapshot = pkgs.writeText "krb5-with-example-config.conf" ''
+      [libdefaults]
+        default_realm = ATHENA.MIT.EDU
+
+      [realms]
+        ATHENA.MIT.EDU = {
+          admin_server = athena.mit.edu
+          kdc = athena.mit.edu
+        }
+
+      [domain_realm]
+        .example.com = EXAMPLE.COM
+        example.com = EXAMPLE.COM
+
+      [capaths]
+        ATHENA.MIT.EDU = {
+          EXAMPLE.COM = .
+        }
+        EXAMPLE.COM = {
+          ATHENA.MIT.EDU = .
+        }
+
+      [appdefaults]
+        pam = {
+          debug = false
+          initial_timeout = 1
+          max_timeout = 30
+          renew_lifetime = 36000
+          ticket_lifetime = 36000
+          timeout_shift = 2
+        }
+
+      [plugins]
+        ccselect = {
+          disable = k5identity
+        }
+
+      [logging]
+        kdc          = SYSLOG:NOTICE
+        admin_server = SYSLOG:NOTICE
+        default      = SYSLOG:NOTICE
+    '';
+  in ''
+    $machine->succeed("diff /etc/krb5.conf ${snapshot}");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kubernetes/base.nix b/nixpkgs/nixos/tests/kubernetes/base.nix
new file mode 100644
index 000000000000..9d77be131751
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/base.nix
@@ -0,0 +1,115 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; }
+}:
+
+with import ../../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  mkKubernetesBaseTest =
+    { name, domain ? "my.zyx", test, machines
+    , pkgs ? import <nixpkgs> { inherit system; }
+    , certs ? import ./certs.nix { inherit pkgs; externalDomain = domain; kubelets = attrNames machines; }
+    , extraConfiguration ? null }:
+    let
+      masterName = head (filter (machineName: any (role: role == "master") machines.${machineName}.roles) (attrNames machines));
+      master = machines.${masterName};
+      extraHosts = ''
+        ${master.ip}  etcd.${domain}
+        ${master.ip}  api.${domain}
+        ${concatMapStringsSep "\n" (machineName: "${machines.${machineName}.ip}  ${machineName}.${domain}") (attrNames machines)}
+      '';
+    in makeTest {
+      inherit name;
+
+      nodes = mapAttrs (machineName: machine:
+        { config, pkgs, lib, nodes, ... }:
+          mkMerge [
+            {
+              virtualisation.memorySize = mkDefault 1536;
+              virtualisation.diskSize = mkDefault 4096;
+              networking = {
+                inherit domain extraHosts;
+                primaryIPAddress = mkForce machine.ip;
+
+                firewall = {
+                  allowedTCPPorts = [
+                    10250 # kubelet
+                  ];
+                  trustedInterfaces = ["docker0"];
+
+                  extraCommands = concatMapStrings  (node: ''
+                    iptables -A INPUT -s ${node.config.networking.primaryIPAddress} -j ACCEPT
+                  '') (attrValues nodes);
+                };
+              };
+              programs.bash.enableCompletion = true;
+              environment.variables = {
+                ETCDCTL_CERT_FILE = "${certs.worker}/etcd-client.pem";
+                ETCDCTL_KEY_FILE = "${certs.worker}/etcd-client-key.pem";
+                ETCDCTL_CA_FILE = "${certs.worker}/ca.pem";
+                ETCDCTL_PEERS = "https://etcd.${domain}:2379";
+              };
+              services.flannel.iface = "eth1";
+              services.kubernetes.apiserver.advertiseAddress = master.ip;
+            }
+            (optionalAttrs (any (role: role == "master") machine.roles) {
+              networking.firewall.allowedTCPPorts = [
+                2379 2380  # etcd
+                443 # kubernetes apiserver
+              ];
+              services.etcd = {
+                enable = true;
+                certFile = "${certs.master}/etcd.pem";
+                keyFile = "${certs.master}/etcd-key.pem";
+                trustedCaFile = "${certs.master}/ca.pem";
+                peerClientCertAuth = true;
+                listenClientUrls = ["https://0.0.0.0:2379"];
+                listenPeerUrls = ["https://0.0.0.0:2380"];
+                advertiseClientUrls = ["https://etcd.${config.networking.domain}:2379"];
+                initialCluster = ["${masterName}=https://etcd.${config.networking.domain}:2380"];
+                initialAdvertisePeerUrls = ["https://etcd.${config.networking.domain}:2380"];
+              };
+            })
+            (import ./kubernetes-common.nix { inherit (machine) roles; inherit pkgs config certs; })
+            (optionalAttrs (machine ? "extraConfiguration") (machine.extraConfiguration { inherit config pkgs lib nodes; }))
+            (optionalAttrs (extraConfiguration != null) (extraConfiguration { inherit config pkgs lib nodes; }))
+          ]
+      ) machines;
+
+      testScript = ''
+        startAll;
+
+        ${test}
+      '';
+    };
+
+  mkKubernetesMultiNodeTest = attrs: mkKubernetesBaseTest ({
+    machines = {
+      machine1 = {
+        roles = ["master"];
+        ip = "192.168.1.1";
+      };
+      machine2 = {
+        roles = ["node"];
+        ip = "192.168.1.2";
+      };
+    };
+  } // attrs // {
+    name = "kubernetes-${attrs.name}-multinode";
+  });
+
+  mkKubernetesSingleNodeTest = attrs: mkKubernetesBaseTest ({
+    machines = {
+      machine1 = {
+        roles = ["master" "node"];
+        ip = "192.168.1.1";
+      };
+    };
+  } // attrs // {
+    name = "kubernetes-${attrs.name}-singlenode";
+  });
+in {
+  inherit mkKubernetesBaseTest mkKubernetesSingleNodeTest mkKubernetesMultiNodeTest;
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/certs.nix b/nixpkgs/nixos/tests/kubernetes/certs.nix
new file mode 100644
index 000000000000..85e92f6330c9
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/certs.nix
@@ -0,0 +1,219 @@
+{
+  pkgs ? import <nixpkgs> {},
+  externalDomain ? "myawesomecluster.cluster.yourdomain.net",
+  serviceClusterIp ? "10.0.0.1",
+  kubelets,
+  ...
+}:
+let
+   runWithCFSSL = name: cmd:
+     let secrets = pkgs.runCommand "${name}-cfss.json" {
+         buildInputs = [ pkgs.cfssl pkgs.jq ];
+         outputs = [ "out" "cert" "key" "csr" ];
+       }
+       ''
+         (
+           echo "${cmd}"
+           cfssl ${cmd} > tmp
+           cat tmp | jq -r .key > $key
+           cat tmp | jq -r .cert > $cert
+           cat tmp | jq -r .csr > $csr
+
+           touch $out
+         ) 2>&1 | fold -w 80 -s
+       '';
+     in {
+       key = secrets.key;
+       cert = secrets.cert;
+       csr = secrets.csr;
+     };
+
+   writeCFSSL = content:
+     pkgs.runCommand content.name {
+      buildInputs = [ pkgs.cfssl pkgs.jq ];
+     } ''
+       mkdir -p $out
+       cd $out
+
+       json=${pkgs.lib.escapeShellArg (builtins.toJSON content)}
+
+       # for a given $field in the $json, treat the associated value as a
+       # file path and substitute the contents thereof into the $json
+       # object.
+       expandFileField() {
+         local field=$1
+         if jq -e --arg field "$field" 'has($field)'; then
+           local path="$(echo "$json" | jq -r ".$field")"
+           json="$(echo "$json" | jq --arg val "$(cat "$path")" ".$field = \$val")"
+         fi
+       }
+
+       expandFileField key
+       expandFileField ca
+       expandFileField cert
+
+       echo "$json" | cfssljson -bare ${content.name}
+     '';
+
+  noCSR = content: pkgs.lib.filterAttrs (n: v: n != "csr") content;
+  noKey = content: pkgs.lib.filterAttrs (n: v: n != "key") content;
+
+  writeFile = content:
+    if pkgs.lib.isDerivation content
+    then content
+    else pkgs.writeText "content" (builtins.toJSON content);
+
+  createServingCertKey = { ca, cn, hosts? [], size ? 2048, name ? cn }:
+    noCSR (
+      (runWithCFSSL name "gencert -ca=${writeFile ca.cert} -ca-key=${writeFile ca.key} -profile=server -config=${writeFile ca.config} ${writeFile {
+        CN = cn;
+        hosts = hosts;
+        key = { algo = "rsa"; inherit size; };
+      }}") // { inherit name; }
+    );
+
+  createClientCertKey = { ca, cn, groups ? [], size ? 2048, name ? cn }:
+    noCSR (
+      (runWithCFSSL name "gencert -ca=${writeFile ca.cert} -ca-key=${writeFile ca.key} -profile=client -config=${writeFile ca.config} ${writeFile {
+        CN = cn;
+        names = map (group: {O = group;}) groups;
+        hosts = [""];
+        key = { algo = "rsa"; inherit size; };
+      }}") // { inherit name; }
+    );
+
+  createSigningCertKey = { C ? "xx", ST ? "x", L ? "x", O ? "x", OU ? "x", CN ? "ca", emailAddress ? "x", expiry ? "43800h", size ? 2048, name ? CN }:
+    (noCSR (runWithCFSSL CN "genkey -initca ${writeFile {
+      key = { algo = "rsa"; inherit size; };
+      names = [{ inherit C ST L O OU CN emailAddress; }];
+    }}")) // {
+      inherit name;
+      config.signing = {
+        default.expiry = expiry;
+        profiles = {
+          server = {
+            inherit expiry;
+            usages = [
+              "signing"
+              "key encipherment"
+              "server auth"
+            ];
+          };
+          client = {
+            inherit expiry;
+            usages = [
+              "signing"
+              "key encipherment"
+              "client auth"
+            ];
+          };
+          peer = {
+            inherit expiry;
+            usages = [
+              "signing"
+              "key encipherment"
+              "server auth"
+              "client auth"
+            ];
+          };
+        };
+      };
+    };
+
+  ca = createSigningCertKey {};
+
+  kube-apiserver = createServingCertKey {
+    inherit ca;
+    cn = "kube-apiserver";
+    hosts = ["kubernetes.default" "kubernetes.default.svc" "localhost" "api.${externalDomain}" serviceClusterIp];
+  };
+
+  kubelet = createServingCertKey {
+    inherit ca;
+    cn = "kubelet";
+    hosts = ["*.${externalDomain}"];
+  };
+
+  service-accounts = createServingCertKey {
+    inherit ca;
+    cn = "kube-service-accounts";
+  };
+
+  etcd = createServingCertKey {
+    inherit ca;
+    cn = "etcd";
+    hosts = ["etcd.${externalDomain}"];
+  };
+
+  etcd-client = createClientCertKey {
+    inherit ca;
+    cn = "etcd-client";
+  };
+
+  kubelet-client = createClientCertKey {
+    inherit ca;
+    cn = "kubelet-client";
+    groups = ["system:masters"];
+  };
+
+  apiserver-client = {
+    kubelet = hostname: createClientCertKey {
+      inherit ca;
+      name = "apiserver-client-kubelet-${hostname}";
+      cn = "system:node:${hostname}.${externalDomain}";
+      groups = ["system:nodes"];
+    };
+
+    kube-proxy = createClientCertKey {
+      inherit ca;
+      name = "apiserver-client-kube-proxy";
+      cn = "system:kube-proxy";
+      groups = ["system:kube-proxy" "system:nodes"];
+    };
+
+    kube-controller-manager = createClientCertKey {
+      inherit ca;
+      name = "apiserver-client-kube-controller-manager";
+      cn = "system:kube-controller-manager";
+      groups = ["system:masters"];
+    };
+
+    kube-scheduler = createClientCertKey {
+      inherit ca;
+      name = "apiserver-client-kube-scheduler";
+      cn = "system:kube-scheduler";
+      groups = ["system:kube-scheduler"];
+    };
+
+    admin = createClientCertKey {
+      inherit ca;
+      cn = "admin";
+      groups = ["system:masters"];
+    };
+  };
+in {
+  master = pkgs.buildEnv {
+    name = "master-keys";
+    paths = [
+      (writeCFSSL (noKey ca))
+      (writeCFSSL kube-apiserver)
+      (writeCFSSL kubelet-client)
+      (writeCFSSL apiserver-client.kube-controller-manager)
+      (writeCFSSL apiserver-client.kube-scheduler)
+      (writeCFSSL service-accounts)
+      (writeCFSSL etcd)
+    ];
+  };
+
+  worker = pkgs.buildEnv {
+    name = "worker-keys";
+    paths = [
+      (writeCFSSL (noKey ca))
+      (writeCFSSL kubelet)
+      (writeCFSSL apiserver-client.kube-proxy)
+      (writeCFSSL etcd-client)
+    ] ++ map (hostname: writeCFSSL (apiserver-client.kubelet hostname)) kubelets;
+  };
+
+  admin = writeCFSSL apiserver-client.admin;
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/default.nix b/nixpkgs/nixos/tests/kubernetes/default.nix
new file mode 100644
index 000000000000..a801759bf582
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/default.nix
@@ -0,0 +1,7 @@
+{ system ? builtins.currentSystem }:
+{
+  dns = import ./dns.nix { inherit system; };
+  # e2e = import ./e2e.nix { inherit system; };  # TODO: make it pass
+  # the following test(s) can be removed when e2e is working:
+  rbac = import ./rbac.nix { inherit system; };
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/dns.nix b/nixpkgs/nixos/tests/kubernetes/dns.nix
new file mode 100644
index 000000000000..f25ea5b9ed84
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/dns.nix
@@ -0,0 +1,127 @@
+{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+with import ./base.nix { inherit system; };
+let
+  domain = "my.zyx";
+
+  certs = import ./certs.nix { externalDomain = domain; kubelets = [ "machine1" "machine2" ]; };
+
+  redisPod = pkgs.writeText "redis-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "redis";
+    metadata.labels.name = "redis";
+    spec.containers = [{
+      name = "redis";
+      image = "redis";
+      args = ["--bind" "0.0.0.0"];
+      imagePullPolicy = "Never";
+      ports = [{
+        name = "redis-server";
+        containerPort = 6379;
+      }];
+    }];
+  });
+
+  redisService = pkgs.writeText "redis-service.json" (builtins.toJSON {
+    kind = "Service";
+    apiVersion = "v1";
+    metadata.name = "redis";
+    spec = {
+      ports = [{port = 6379; targetPort = 6379;}];
+      selector = {name = "redis";};
+    };
+  });
+
+  redisImage = pkgs.dockerTools.buildImage {
+    name = "redis";
+    tag = "latest";
+    contents = [ pkgs.redis pkgs.bind.host ];
+    config.Entrypoint = "/bin/redis-server";
+  };
+
+  probePod = pkgs.writeText "probe-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "probe";
+    metadata.labels.name = "probe";
+    spec.containers = [{
+      name = "probe";
+      image = "probe";
+      args = [ "-f" ];
+      tty = true;
+      imagePullPolicy = "Never";
+    }];
+  });
+
+  probeImage = pkgs.dockerTools.buildImage {
+    name = "probe";
+    tag = "latest";
+    contents = [ pkgs.bind.host pkgs.busybox ];
+    config.Entrypoint = "/bin/tail";
+  };
+
+  extraConfiguration = { config, pkgs, ... }: {
+    environment.systemPackages = [ pkgs.bind.host ];
+    # virtualisation.docker.extraOptions = "--dns=${config.services.kubernetes.addons.dns.clusterIp}";
+    services.dnsmasq.enable = true;
+    services.dnsmasq.servers = [
+      "/cluster.local/${config.services.kubernetes.addons.dns.clusterIp}#53"
+    ];
+  };
+
+  base = {
+    name = "dns";
+    inherit domain certs extraConfiguration;
+  };
+
+  singleNodeTest = {
+    test = ''
+      # prepare machine1 for test
+      $machine1->waitUntilSucceeds("kubectl get node machine1.${domain} | grep -w Ready");
+      $machine1->execute("docker load < ${redisImage}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${redisPod}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${redisService}");
+      $machine1->execute("docker load < ${probeImage}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${probePod}");
+
+      # check if pods are running
+      $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running");
+      $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running");
+      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'");
+
+      # check dns on host (dnsmasq)
+      $machine1->succeed("host redis.default.svc.cluster.local");
+
+      # check dns inside the container
+      $machine1->succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local");
+    '';
+  };
+
+  multiNodeTest = {
+    test = ''
+      # prepare machines for test
+      $machine1->waitUntilSucceeds("kubectl get node machine1.${domain} | grep -w Ready");
+      $machine1->waitUntilSucceeds("kubectl get node machine2.${domain} | grep -w Ready");
+      $machine2->execute("docker load < ${redisImage}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${redisPod}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${redisService}");
+      $machine2->execute("docker load < ${probeImage}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${probePod}");
+
+      # check if pods are running
+      $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running");
+      $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running");
+      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'");
+
+      # check dns on hosts (dnsmasq)
+      $machine1->succeed("host redis.default.svc.cluster.local");
+      $machine2->succeed("host redis.default.svc.cluster.local");
+
+      # check dns inside the container
+      $machine1->succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local");
+    '';
+  };
+in {
+  singlenode = mkKubernetesSingleNodeTest (base // singleNodeTest);
+  multinode = mkKubernetesMultiNodeTest (base // multiNodeTest);
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/e2e.nix b/nixpkgs/nixos/tests/kubernetes/e2e.nix
new file mode 100644
index 000000000000..175d8413045e
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/e2e.nix
@@ -0,0 +1,40 @@
+{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+with import ./base.nix { inherit system; };
+let
+  domain = "my.zyx";
+  certs = import ./certs.nix { externalDomain = domain; kubelets = ["machine1" "machine2"]; };
+  kubeconfig = pkgs.writeText "kubeconfig.json" (builtins.toJSON {
+    apiVersion = "v1";
+    kind = "Config";
+    clusters = [{
+      name = "local";
+      cluster.certificate-authority = "${certs.master}/ca.pem";
+      cluster.server = "https://api.${domain}";
+    }];
+    users = [{
+      name = "kubelet";
+      user = {
+        client-certificate = "${certs.admin}/admin.pem";
+        client-key = "${certs.admin}/admin-key.pem";
+      };
+    }];
+    contexts = [{
+      context = {
+        cluster = "local";
+        user = "kubelet";
+      };
+      current-context = "kubelet-context";
+    }];
+  });
+
+  base = {
+    name = "e2e";
+    inherit domain certs;
+    test = ''
+      $machine1->succeed("e2e.test -kubeconfig ${kubeconfig} -provider local -ginkgo.focus '\\[Conformance\\]' -ginkgo.skip '\\[Flaky\\]|\\[Serial\\]'");
+    '';
+  };
+in {
+  singlenode = mkKubernetesSingleNodeTest base;
+  multinode = mkKubernetesMultiNodeTest base;
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/kubernetes-common.nix b/nixpkgs/nixos/tests/kubernetes/kubernetes-common.nix
new file mode 100644
index 000000000000..87c65b883659
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/kubernetes-common.nix
@@ -0,0 +1,57 @@
+{ roles, config, pkgs, certs }:
+with pkgs.lib;
+let
+  base = {
+    inherit roles;
+    flannel.enable = true;
+    addons.dashboard.enable = true;
+
+    caFile = "${certs.master}/ca.pem";
+    apiserver = {
+      tlsCertFile = "${certs.master}/kube-apiserver.pem";
+      tlsKeyFile = "${certs.master}/kube-apiserver-key.pem";
+      kubeletClientCertFile = "${certs.master}/kubelet-client.pem";
+      kubeletClientKeyFile = "${certs.master}/kubelet-client-key.pem";
+      serviceAccountKeyFile = "${certs.master}/kube-service-accounts.pem";
+    };
+    etcd = {
+      servers = ["https://etcd.${config.networking.domain}:2379"];
+      certFile = "${certs.worker}/etcd-client.pem";
+      keyFile = "${certs.worker}/etcd-client-key.pem";
+    };
+    kubeconfig = {
+      server = "https://api.${config.networking.domain}";
+    };
+    kubelet = {
+      tlsCertFile = "${certs.worker}/kubelet.pem";
+      tlsKeyFile = "${certs.worker}/kubelet-key.pem";
+      hostname = "${config.networking.hostName}.${config.networking.domain}";
+      kubeconfig = {
+        certFile = "${certs.worker}/apiserver-client-kubelet-${config.networking.hostName}.pem";
+        keyFile = "${certs.worker}/apiserver-client-kubelet-${config.networking.hostName}-key.pem";
+      };
+    };
+    controllerManager = {
+      serviceAccountKeyFile = "${certs.master}/kube-service-accounts-key.pem";
+      kubeconfig = {
+        certFile = "${certs.master}/apiserver-client-kube-controller-manager.pem";
+        keyFile = "${certs.master}/apiserver-client-kube-controller-manager-key.pem";
+      };
+    };
+    scheduler = {
+      kubeconfig = {
+        certFile = "${certs.master}/apiserver-client-kube-scheduler.pem";
+        keyFile = "${certs.master}/apiserver-client-kube-scheduler-key.pem";
+      };
+    };
+    proxy = {
+      kubeconfig = {
+        certFile = "${certs.worker}/apiserver-client-kube-proxy.pem";
+        keyFile = "${certs.worker}//apiserver-client-kube-proxy-key.pem";
+      };
+    };
+  };
+
+in {
+  services.kubernetes = base;
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/rbac.nix b/nixpkgs/nixos/tests/kubernetes/rbac.nix
new file mode 100644
index 000000000000..226808c4b263
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/rbac.nix
@@ -0,0 +1,137 @@
+{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+with import ./base.nix { inherit system; };
+let
+
+  roServiceAccount = pkgs.writeText "ro-service-account.json" (builtins.toJSON {
+    kind = "ServiceAccount";
+    apiVersion = "v1";
+    metadata = {
+      name = "read-only";
+      namespace = "default";
+    };
+  });
+
+  roRoleBinding = pkgs.writeText "ro-role-binding.json" (builtins.toJSON {
+    apiVersion = "rbac.authorization.k8s.io/v1";
+    kind = "RoleBinding";
+    metadata = {
+      name = "read-pods";
+      namespace = "default";
+    };
+    roleRef = {
+      apiGroup = "rbac.authorization.k8s.io";
+      kind = "Role";
+      name = "pod-reader";
+    };
+    subjects = [{
+      kind = "ServiceAccount";
+      name = "read-only";
+      namespace = "default";
+    }];
+  });
+
+  roRole = pkgs.writeText "ro-role.json" (builtins.toJSON {
+    apiVersion = "rbac.authorization.k8s.io/v1";
+    kind = "Role";
+    metadata = {
+      name = "pod-reader";
+      namespace = "default";
+    };
+    rules = [{
+      apiGroups = [""];
+      resources = ["pods"];
+      verbs = ["get" "list" "watch"];
+    }];
+  });
+
+  kubectlPod = pkgs.writeText "kubectl-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "kubectl";
+    metadata.namespace = "default";
+    metadata.labels.name = "kubectl";
+    spec.serviceAccountName = "read-only";
+    spec.containers = [{
+      name = "kubectl";
+      image = "kubectl:latest";
+      command = ["/bin/tail" "-f"];
+      imagePullPolicy = "Never";
+      tty = true;
+    }];
+  });
+
+  kubectlPod2 = pkgs.writeTextDir "kubectl-pod-2.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "kubectl-2";
+    metadata.namespace = "default";
+    metadata.labels.name = "kubectl-2";
+    spec.serviceAccountName = "read-only";
+    spec.containers = [{
+      name = "kubectl-2";
+      image = "kubectl:latest";
+      command = ["/bin/tail" "-f"];
+      imagePullPolicy = "Never";
+      tty = true;
+    }];
+  });
+
+  kubectl = pkgs.runCommand "copy-kubectl" { buildInputs = [ pkgs.kubernetes ]; } ''
+    mkdir -p $out/bin
+    cp ${pkgs.kubernetes}/bin/kubectl $out/bin/kubectl
+  '';
+
+  kubectlImage = pkgs.dockerTools.buildImage {
+    name = "kubectl";
+    tag = "latest";
+    contents = [ kubectl pkgs.busybox kubectlPod2 ];
+    config.Entrypoint = "/bin/sh";
+  };
+
+  base = {
+    name = "rbac";
+  };
+
+  singlenode = base // {
+    test = ''
+      $machine1->waitUntilSucceeds("kubectl get node machine1.my.zyx | grep -w Ready");
+
+      $machine1->execute("docker load < ${kubectlImage}");
+
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roServiceAccount}");
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roRole}");
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roRoleBinding}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${kubectlPod}");
+
+      $machine1->waitUntilSucceeds("kubectl get pod kubectl | grep Running");
+
+      $machine1->succeed("kubectl exec -ti kubectl -- kubectl get pods");
+      $machine1->fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json");
+      $machine1->fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl");
+    '';
+  };
+
+  multinode = base // {
+    test = ''
+      $machine1->waitUntilSucceeds("kubectl get node machine1.my.zyx | grep -w Ready");
+      $machine1->waitUntilSucceeds("kubectl get node machine2.my.zyx | grep -w Ready");
+
+      $machine2->execute("docker load < ${kubectlImage}");
+
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roServiceAccount}");
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roRole}");
+      $machine1->waitUntilSucceeds("kubectl apply -f ${roRoleBinding}");
+      $machine1->waitUntilSucceeds("kubectl create -f ${kubectlPod}");
+
+      $machine1->waitUntilSucceeds("kubectl get pod kubectl | grep Running");
+
+      $machine1->succeed("kubectl exec -ti kubectl -- kubectl get pods");
+      $machine1->fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json");
+      $machine1->fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl");
+    '';
+  };
+
+in {
+  singlenode = mkKubernetesSingleNodeTest singlenode;
+  multinode = mkKubernetesMultiNodeTest multinode;
+}
diff --git a/nixpkgs/nixos/tests/ldap.nix b/nixpkgs/nixos/tests/ldap.nix
new file mode 100644
index 000000000000..b3fd42e75886
--- /dev/null
+++ b/nixpkgs/nixos/tests/ldap.nix
@@ -0,0 +1,396 @@
+import ./make-test.nix ({ pkgs, lib, ...} :
+
+let
+  unlines = lib.concatStringsSep "\n";
+  unlinesAttrs = f: as: unlines (lib.mapAttrsToList f as);
+
+  dbDomain = "example.com";
+  dbSuffix = "dc=example,dc=com";
+  dbAdminDn = "cn=admin,${dbSuffix}";
+  dbAdminPwd = "admin-password";
+  # NOTE: slappasswd -h "{SSHA}" -s '${dbAdminPwd}'
+  dbAdminPwdHash = "{SSHA}i7FopSzkFQMrHzDMB1vrtkI0rBnwouP8";
+  ldapUser = "test-ldap-user";
+  ldapUserId = 10000;
+  ldapUserPwd = "user-password";
+  # NOTE: slappasswd -h "{SSHA}" -s '${ldapUserPwd}'
+  ldapUserPwdHash = "{SSHA}v12XICMZNGT6r2KJ26rIkN8Vvvp4QX6i";
+  ldapGroup = "test-ldap-group";
+  ldapGroupId = 10000;
+
+  mkClient = useDaemon:
+    { lib, ... }:
+    {
+      virtualisation.memorySize = 256;
+      virtualisation.vlans = [ 1 ];
+      security.pam.services.su.rootOK = lib.mkForce false;
+      users.ldap.enable = true;
+      users.ldap.daemon = {
+        enable = useDaemon;
+        rootpwmoddn = "cn=admin,${dbSuffix}";
+        rootpwmodpw = "/etc/nslcd.rootpwmodpw";
+      };
+      # NOTE: password stored in clear in Nix's store, but this is a test.
+      environment.etc."nslcd.rootpwmodpw".source = pkgs.writeText "rootpwmodpw" dbAdminPwd;
+      users.ldap.loginPam = true;
+      users.ldap.nsswitch = true;
+      users.ldap.server = "ldap://server";
+      users.ldap.base = "ou=posix,${dbSuffix}";
+      users.ldap.bind = {
+        distinguishedName = "cn=admin,${dbSuffix}";
+        password = "/etc/ldap/bind.password";
+      };
+      # NOTE: password stored in clear in Nix's store, but this is a test.
+      environment.etc."ldap/bind.password".source = pkgs.writeText "password" dbAdminPwd;
+    };
+in
+
+{
+  name = "ldap";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ montag451 ];
+  };
+
+  nodes = {
+
+    server =
+      { pkgs, config, ... }:
+      let
+        inherit (config.services) openldap;
+
+        slapdConfig = pkgs.writeText "cn=config.ldif" (''
+          dn: cn=config
+          objectClass: olcGlobal
+          #olcPidFile: /run/slapd/slapd.pid
+          # List of arguments that were passed to the server
+          #olcArgsFile: /run/slapd/slapd.args
+          # Read slapd-config(5) for possible values
+          olcLogLevel: none
+          # The tool-threads parameter sets the actual amount of CPU's
+          # that is used for indexing.
+          olcToolThreads: 1
+
+          dn: olcDatabase={-1}frontend,cn=config
+          objectClass: olcDatabaseConfig
+          objectClass: olcFrontendConfig
+          # The maximum number of entries that is returned for a search operation
+          olcSizeLimit: 500
+          # Allow unlimited access to local connection from the local root user
+          olcAccess: to *
+            by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
+            by * break
+          # Allow unauthenticated read access for schema and base DN autodiscovery
+          olcAccess: to dn.exact=""
+            by * read
+          olcAccess: to dn.base="cn=Subschema"
+            by * read
+
+          dn: olcDatabase=config,cn=config
+          objectClass: olcDatabaseConfig
+          olcRootDN: cn=admin,cn=config
+          #olcRootPW:
+          # NOTE: access to cn=config, system root can be manager
+          # with SASL mechanism (-Y EXTERNAL) over unix socket (-H ldapi://)
+          olcAccess: to *
+            by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
+            by * break
+
+          dn: cn=schema,cn=config
+          objectClass: olcSchemaConfig
+
+          include: file://${pkgs.openldap}/etc/schema/core.ldif
+          include: file://${pkgs.openldap}/etc/schema/cosine.ldif
+          include: file://${pkgs.openldap}/etc/schema/nis.ldif
+          include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
+
+          dn: cn=module{0},cn=config
+          objectClass: olcModuleList
+          # Where the dynamically loaded modules are stored
+          #olcModulePath: /usr/lib/ldap
+          olcModuleLoad: back_mdb
+
+          ''
+          + unlinesAttrs (olcSuffix: {conf, ...}:
+              "include: file://" + pkgs.writeText "config.ldif" conf
+            ) slapdDatabases
+          );
+
+        slapdDatabases = {
+          "${dbSuffix}" = {
+            conf = ''
+              dn: olcBackend={1}mdb,cn=config
+              objectClass: olcBackendConfig
+
+              dn: olcDatabase={1}mdb,cn=config
+              olcSuffix: ${dbSuffix}
+              olcDbDirectory: ${openldap.dataDir}/${dbSuffix}
+              objectClass: olcDatabaseConfig
+              objectClass: olcMdbConfig
+              # NOTE: checkpoint the database periodically in case of system failure
+              # and to speed up slapd shutdown.
+              olcDbCheckpoint: 512 30
+              # Database max size is 1G
+              olcDbMaxSize: 1073741824
+              olcLastMod: TRUE
+              # NOTE: database superuser. Needed for syncrepl,
+              # and used to auth as admin through a TCP connection.
+              olcRootDN: cn=admin,${dbSuffix}
+              olcRootPW: ${dbAdminPwdHash}
+              #
+              olcDbIndex: objectClass eq
+              olcDbIndex: cn,uid eq
+              olcDbIndex: uidNumber,gidNumber eq
+              olcDbIndex: member,memberUid eq
+              #
+              olcAccess: to attrs=userPassword
+                by self write
+                by anonymous auth
+                by dn="cn=admin,${dbSuffix}" write
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
+                by * none
+              olcAccess: to attrs=shadowLastChange
+                by self write
+                by dn="cn=admin,${dbSuffix}" write
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
+                by * none
+              olcAccess: to dn.sub="ou=posix,${dbSuffix}"
+                by self read
+                by dn="cn=admin,${dbSuffix}" read
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
+              olcAccess: to *
+                by self read
+                by * none
+            '';
+            data = ''
+              dn: ${dbSuffix}
+              objectClass: top
+              objectClass: dcObject
+              objectClass: organization
+              o: ${dbDomain}
+
+              dn: cn=admin,${dbSuffix}
+              objectClass: simpleSecurityObject
+              objectClass: organizationalRole
+              description: ${dbDomain} LDAP administrator
+              roleOccupant: ${dbSuffix}
+              userPassword: ${ldapUserPwdHash}
+
+              dn: ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+
+              dn: ou=accounts,ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+
+              dn: ou=groups,ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+            ''
+            + lib.concatMapStrings posixAccount [
+              { uid=ldapUser; uidNumber=ldapUserId; gidNumber=ldapGroupId; userPassword=ldapUserPwdHash; }
+            ]
+            + lib.concatMapStrings posixGroup [
+              { gid=ldapGroup; gidNumber=ldapGroupId; members=[]; }
+            ];
+          };
+        };
+
+        # NOTE: create a user account using the posixAccount objectClass.
+        posixAccount =
+          { uid
+          , uidNumber ? null
+          , gidNumber ? null
+          , cn ? ""
+          , sn ? ""
+          , userPassword ? ""
+          , loginShell ? "/bin/sh"
+          }: ''
+
+            dn: uid=${uid},ou=accounts,ou=posix,${dbSuffix}
+            objectClass: person
+            objectClass: posixAccount
+            objectClass: shadowAccount
+            cn: ${cn}
+            gecos:
+            ${if gidNumber == null then "#" else "gidNumber: ${toString gidNumber}"}
+            homeDirectory: /home/${uid}
+            loginShell: ${loginShell}
+            sn: ${sn}
+            ${if uidNumber == null then "#" else "uidNumber: ${toString uidNumber}"}
+            ${if userPassword == "" then "#" else "userPassword: ${userPassword}"}
+          '';
+
+        # NOTE: create a group using the posixGroup objectClass.
+        posixGroup =
+          { gid
+          , gidNumber
+          , members
+          }: ''
+
+            dn: cn=${gid},ou=groups,ou=posix,${dbSuffix}
+            objectClass: top
+            objectClass: posixGroup
+            gidNumber: ${toString gidNumber}
+            ${lib.concatMapStrings (member: "memberUid: ${member}\n") members}
+          '';
+      in
+      {
+        virtualisation.memorySize = 256;
+        virtualisation.vlans = [ 1 ];
+        networking.firewall.allowedTCPPorts = [ 389 ];
+        services.openldap.enable = true;
+        services.openldap.dataDir = "/var/db/openldap";
+        services.openldap.configDir = "/var/db/slapd";
+        services.openldap.urlList = [
+          "ldap:///"
+          "ldapi:///"
+        ];
+        systemd.services.openldap = {
+          preStart = ''
+              set -e
+              # NOTE: slapd's config is always re-initialized.
+              rm -rf "${openldap.configDir}"/cn=config \
+                     "${openldap.configDir}"/cn=config.ldif
+              install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${openldap.configDir}"
+              # NOTE: olcDbDirectory must be created before adding the config.
+              '' +
+              unlinesAttrs (olcSuffix: {data, ...}: ''
+                # NOTE: database is always re-initialized.
+                rm -rf "${openldap.dataDir}/${olcSuffix}"
+                install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" \
+                 "${openldap.dataDir}/${olcSuffix}"
+                '') slapdDatabases
+              + ''
+              # NOTE: slapd is supposed to be stopped while in preStart,
+              #       hence slap* commands can safely be used.
+              umask 0077
+              ${pkgs.openldap}/bin/slapadd -n 0 \
+               -F "${openldap.configDir}" \
+               -l ${slapdConfig}
+              chown -R "${openldap.user}:${openldap.group}" "${openldap.configDir}"
+              # NOTE: slapadd(8): To populate the config database slapd-config(5),
+              #                   use -n 0 as it is always the first database.
+              #                   It must physically exist on the filesystem prior to this, however.
+            '' +
+            unlinesAttrs (olcSuffix: {data, ...}: ''
+              # NOTE: load database ${olcSuffix}
+              # (as root to avoid depending on sudo or chpst)
+              ${pkgs.openldap}/bin/slapadd \
+               -F "${openldap.configDir}" \
+               -l ${pkgs.writeText "data.ldif" data}
+              '' + ''
+              # NOTE: redundant with default openldap's preStart, but do not harm.
+              chown -R "${openldap.user}:${openldap.group}" \
+               "${openldap.dataDir}/${olcSuffix}"
+            '') slapdDatabases;
+        };
+      };
+
+    client1 = mkClient true; # use nss_pam_ldapd
+    client2 = mkClient false; # use nss_ldap and pam_ldap
+
+  };
+
+  testScript = ''
+    $server->start;
+    $server->waitForUnit("default.target");
+
+    subtest "slapd", sub {
+      subtest "auth as database admin with SASL and check a POSIX account", sub {
+        $server->succeed(join ' ', 'test',
+         '"$(ldapsearch -LLL -H ldapi:// -Y EXTERNAL',
+             '-b \'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}\' ',
+             '-s base uidNumber |',
+           'sed -ne \'s/^uidNumber: \\(.*\\)/\\1/p\' ',
+         ')" -eq ${toString ldapUserId}');
+      };
+      subtest "auth as database admin with password and check a POSIX account", sub {
+        $server->succeed(join ' ', 'test',
+         '"$(ldapsearch -LLL -H ldap://server',
+             '-D \'cn=admin,${dbSuffix}\' -w \'${dbAdminPwd}\' ',
+             '-b \'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}\' ',
+             '-s base uidNumber |',
+           'sed -ne \'s/^uidNumber: \\(.*\\)/\\1/p\' ',
+         ')" -eq ${toString ldapUserId}');
+      };
+    };
+
+    $client1->start;
+    $client1->waitForUnit("default.target");
+
+    subtest "password", sub {
+      subtest "su with password to a POSIX account", sub {
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "${ldapUserPwd}\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+      };
+      subtest "change password of a POSIX account as root", sub {
+        $client1->succeed("chpasswd <<<'${ldapUser}:new-password'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "new-password\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+      };
+      subtest "change password of a POSIX account from itself", sub {
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su --login ${ldapUser} -c passwd',
+          'expect "Password: "',
+          'send "${ldapUserPwd}\n"',
+          'expect "(current) UNIX password: "',
+          'send "${ldapUserPwd}\n"',
+          'expect "New password: "',
+          'send "new-password\n"',
+          'expect "Retype new password: "',
+          'send "new-password\n"',
+          'expect "passwd: password updated successfully" {exit}',
+          'exit 1' . "'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "${ldapUserPwd}\n"',
+          'expect "su: Authentication failure" {exit}',
+          'exit 1' . "'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "new-password\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+      };
+    };
+
+    $client2->start;
+    $client2->waitForUnit("default.target");
+
+    subtest "NSS", sub {
+        $client1->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}");
+        $client1->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'");
+        $client1->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}");
+        $client1->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'");
+        $client2->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}");
+        $client2->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'");
+        $client2->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}");
+        $client2->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'");
+    };
+
+    subtest "PAM", sub {
+        $client1->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true");
+        $client2->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true");
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/leaps.nix b/nixpkgs/nixos/tests/leaps.nix
new file mode 100644
index 000000000000..6163fed56b6f
--- /dev/null
+++ b/nixpkgs/nixos/tests/leaps.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ pkgs,  ... }:
+
+{
+  name = "leaps";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ qknight ];
+  };
+
+  nodes =
+    { 
+      client = { };
+
+      server =
+        { services.leaps = {
+            enable = true;
+            port = 6666;
+            path = "/leaps/";
+          };
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript =
+    ''
+      startAll;
+      $server->waitForOpenPort(6666);
+      $client->waitForUnit("network.target");
+      $client->succeed("${pkgs.curl}/bin/curl http://server:6666/leaps/ | grep -i 'leaps'");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/lidarr.nix b/nixpkgs/nixos/tests/lidarr.nix
new file mode 100644
index 000000000000..58bf82503f8c
--- /dev/null
+++ b/nixpkgs/nixos/tests/lidarr.nix
@@ -0,0 +1,18 @@
+import ./make-test.nix ({ lib, ... }:
+
+with lib;
+
+rec {
+  name = "lidarr";
+  meta.maintainers = with maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.lidarr.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('lidarr.service');
+    $machine->waitForOpenPort('8686');
+    $machine->succeed("curl --fail http://localhost:8686/");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lightdm.nix b/nixpkgs/nixos/tests/lightdm.nix
new file mode 100644
index 000000000000..8a9a7408d292
--- /dev/null
+++ b/nixpkgs/nixos/tests/lightdm.nix
@@ -0,0 +1,29 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "lightdm";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.lightdm.enable = true;
+    services.xserver.windowManager.default = "icewm";
+    services.xserver.windowManager.icewm.enable = true;
+    services.xserver.desktopManager.default = "none";
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+  in ''
+    startAll;
+    $machine->waitForText(qr/${user.description}/);
+    $machine->screenshot("lightdm");
+    $machine->sendChars("${user.password}\n");
+    $machine->waitForFile("/home/alice/.Xauthority");
+    $machine->succeed("xauth merge ~alice/.Xauthority");
+    $machine->waitForWindow("^IceWM ");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/login.nix b/nixpkgs/nixos/tests/login.nix
new file mode 100644
index 000000000000..3dbb494b6895
--- /dev/null
+++ b/nixpkgs/nixos/tests/login.nix
@@ -0,0 +1,72 @@
+import ./make-test.nix ({ pkgs, latestKernel ? false, ... }:
+
+{
+  name = "login";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  machine =
+    { pkgs, lib, ... }:
+    { boot.kernelPackages = lib.mkIf latestKernel pkgs.linuxPackages_latest;
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit('multi-user.target');
+      $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'");
+      $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->fail("pgrep -f 'agetty.*tty2'");
+          $machine->sendKeys("alt-f2");
+          $machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
+          $machine->waitForUnit('getty@tty2.service');
+          $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'");
+      };
+
+      # Log in as alice on a virtual console.
+      subtest "virtual console login", sub {
+          $machine->waitUntilTTYMatches(2, "login: ");
+          $machine->sendChars("alice\n");
+          $machine->waitUntilTTYMatches(2, "login: alice");
+          $machine->waitUntilSucceeds("pgrep login");
+          $machine->waitUntilTTYMatches(2, "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/nixpkgs/nixos/tests/make-test.nix b/nixpkgs/nixos/tests/make-test.nix
new file mode 100644
index 000000000000..cee5da93454a
--- /dev/null
+++ b/nixpkgs/nixos/tests/make-test.nix
@@ -0,0 +1,9 @@
+f: {
+  system ? builtins.currentSystem,
+  pkgs ? import ../.. { inherit system; config = {}; },
+  ...
+} @ args:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+
+makeTest (if pkgs.lib.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f)
diff --git a/nixpkgs/nixos/tests/mathics.nix b/nixpkgs/nixos/tests/mathics.nix
new file mode 100644
index 000000000000..fcbeeb18a727
--- /dev/null
+++ b/nixpkgs/nixos/tests/mathics.nix
@@ -0,0 +1,20 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "mathics";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ benley ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.mathics.enable = true;
+      services.mathics.port = 8888;
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $machine->waitForUnit("mathics.service");
+    $machine->waitForOpenPort(8888);
+    $machine->succeed("curl http://localhost:8888/");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/matrix-synapse.nix b/nixpkgs/nixos/tests/matrix-synapse.nix
new file mode 100644
index 000000000000..8504a7c0d057
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix-synapse.nix
@@ -0,0 +1,31 @@
+import ./make-test.nix ({ pkgs, ... } : {
+
+  name = "matrix-synapse";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ corngood ];
+  };
+
+  nodes = {
+    # Since 0.33.0, matrix-synapse doesn't allow underscores in server names
+    serverpostgres = args: {
+      services.matrix-synapse.enable = true;
+      services.matrix-synapse.database_type = "psycopg2";
+    };
+
+    serversqlite = args: {
+      services.matrix-synapse.enable = true;
+      services.matrix-synapse.database_type = "sqlite3";
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $serverpostgres->waitForUnit("matrix-synapse.service");
+    $serverpostgres->waitUntilSucceeds("curl -Lk https://localhost:8448/");
+    $serverpostgres->requireActiveUnit("postgresql.service");
+    $serversqlite->waitForUnit("matrix-synapse.service");
+    $serversqlite->waitUntilSucceeds("curl -Lk https://localhost:8448/");
+    $serversqlite->mustSucceed("[ -e /var/lib/matrix-synapse/homeserver.db ]");
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/memcached.nix b/nixpkgs/nixos/tests/memcached.nix
new file mode 100644
index 000000000000..b120599c51dd
--- /dev/null
+++ b/nixpkgs/nixos/tests/memcached.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "memcached";
+
+  nodes = {
+    machine =
+      { ... }:
+      {
+        imports = [ ../modules/profiles/minimal.nix ];
+        services.memcached.enable = true;
+      };
+  };
+
+  testScript = let
+    testScript = pkgs.writeScript "testScript.py" ''
+      #!${pkgs.python3.withPackages (p: [p.memcached])}/bin/python
+
+      import memcache
+      c = memcache.Client(['localhost:11211'])
+      c.set('key', 'value')
+      assert 'value' == c.get('key')
+    '';
+  in ''
+    startAll;
+    $machine->waitForUnit("memcached.service");
+    $machine->waitForOpenPort("11211");
+    $machine->succeed("${testScript}");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mesos.nix b/nixpkgs/nixos/tests/mesos.nix
new file mode 100644
index 000000000000..2e6dc0eda063
--- /dev/null
+++ b/nixpkgs/nixos/tests/mesos.nix
@@ -0,0 +1,92 @@
+import ./make-test.nix ({ pkgs, ...} : rec {
+  name = "mesos";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ offline kamilchm cstrahan ];
+  };
+
+  nodes = {
+    master = { ... }: {
+      networking.firewall.enable = false;
+      services.zookeeper.enable = true;
+      services.mesos.master = {
+          enable = true;
+          zk = "zk://master:2181/mesos";
+      };
+    };
+
+    slave = { ... }: {
+      networking.firewall.enable = false;
+      networking.nat.enable = true;
+      virtualisation.docker.enable = true;
+      services.mesos = {
+        slave = {
+          enable = true;
+          master = "master:5050";
+          dockerRegistry = registry;
+          executorEnvironmentVariables = {
+            PATH = "/run/current-system/sw/bin";
+          };
+        };
+      };
+    };
+  };
+
+  simpleDocker = pkgs.dockerTools.buildImage {
+    name = "echo";
+    tag = "latest";
+    contents = [ pkgs.stdenv.shellPackage pkgs.coreutils ];
+    config = {
+      Env = [
+        # When shell=true, mesos invokes "sh -c '<cmd>'", so make sure "sh" is
+        # on the PATH.
+        "PATH=${pkgs.stdenv.shellPackage}/bin:${pkgs.coreutils}/bin"
+      ];
+      Entrypoint = [ "echo" ];
+    };
+  };
+
+  registry = pkgs.runCommand "registry" { } ''
+    mkdir -p $out
+    cp ${simpleDocker} $out/echo:latest.tar
+  '';
+
+  testFramework = pkgs.pythonPackages.buildPythonPackage {
+    name = "mesos-tests";
+    propagatedBuildInputs = [ pkgs.mesos ];
+    catchConflicts = false;
+    src = ./mesos_test.py;
+    phases = [ "installPhase" "fixupPhase" ];
+    installPhase = ''
+      install -Dvm 0755 $src $out/bin/mesos_test.py
+
+      echo "done" > test.result
+      tar czf $out/test.tar.gz test.result
+    '';
+  };
+
+  testScript =
+    ''
+      startAll;
+      $master->waitForUnit("zookeeper.service");
+      $master->waitForUnit("mesos-master.service");
+      $slave->waitForUnit("docker.service");
+      $slave->waitForUnit("mesos-slave.service");
+      $master->waitForOpenPort(2181);
+      $master->waitForOpenPort(5050);
+      $slave->waitForOpenPort(5051);
+
+      # is slave registered?
+      $master->waitUntilSucceeds("curl -s --fail http://master:5050/master/slaves".
+                                 " | grep -q \"\\\"hostname\\\":\\\"slave\\\"\"");
+
+      # try to run docker image
+      $master->succeed("${pkgs.mesos}/bin/mesos-execute --master=master:5050".
+                       " --resources=\"cpus:0.1;mem:32\" --name=simple-docker".
+                       " --containerizer=mesos --docker_image=echo:latest".
+                       " --shell=true --command=\"echo done\" | grep -q TASK_FINISHED");
+
+      # simple command with .tar.gz uri
+      $master->succeed("${testFramework}/bin/mesos_test.py master ".
+                       "${testFramework}/test.tar.gz");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/mesos_test.py b/nixpkgs/nixos/tests/mesos_test.py
new file mode 100644
index 000000000000..be8bb32e49a7
--- /dev/null
+++ b/nixpkgs/nixos/tests/mesos_test.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+import uuid
+import time
+import subprocess
+import os
+
+import sys
+
+from mesos.interface import Scheduler
+from mesos.native import MesosSchedulerDriver
+from mesos.interface import mesos_pb2
+
+def log(msg):
+    process = subprocess.Popen("systemd-cat", stdin=subprocess.PIPE)
+    (out,err) = process.communicate(msg)
+
+class NixosTestScheduler(Scheduler):
+    def __init__(self):
+        self.master_ip = sys.argv[1]
+        self.download_uri = sys.argv[2]
+
+    def resourceOffers(self, driver, offers):
+        log("XXX got resource offer")
+
+        offer = offers[0]
+        task = self.new_task(offer)
+        uri = task.command.uris.add()
+        uri.value = self.download_uri
+        task.command.value = "cat test.result"
+        driver.launchTasks(offer.id, [task])
+
+    def statusUpdate(self, driver, update):
+        log("XXX status update")
+        if update.state == mesos_pb2.TASK_FAILED:
+            log("XXX test task failed with message: " + update.message)
+            driver.stop()
+            sys.exit(1)
+        elif update.state == mesos_pb2.TASK_FINISHED:
+            driver.stop()
+            sys.exit(0)
+
+    def new_task(self, offer):
+        task = mesos_pb2.TaskInfo()
+        id = uuid.uuid4()
+        task.task_id.value = str(id)
+        task.slave_id.value = offer.slave_id.value
+        task.name = "task {}".format(str(id))
+
+        cpus = task.resources.add()
+        cpus.name = "cpus"
+        cpus.type = mesos_pb2.Value.SCALAR
+        cpus.scalar.value = 0.1
+
+        mem = task.resources.add()
+        mem.name = "mem"
+        mem.type = mesos_pb2.Value.SCALAR
+        mem.scalar.value = 32
+
+        return task
+
+if __name__ == '__main__':
+    log("XXX framework started")
+
+    framework = mesos_pb2.FrameworkInfo()
+    framework.user = "root"
+    framework.name = "nixos-test-framework"
+    driver = MesosSchedulerDriver(
+        NixosTestScheduler(),
+        framework,
+        sys.argv[1] + ":5050"
+    )
+    driver.run()
diff --git a/nixpkgs/nixos/tests/minio.nix b/nixpkgs/nixos/tests/minio.nix
new file mode 100644
index 000000000000..40a599546650
--- /dev/null
+++ b/nixpkgs/nixos/tests/minio.nix
@@ -0,0 +1,34 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "minio";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bachp ];
+  };
+
+  nodes = {
+    machine = { pkgs, ... }: {
+      services.minio = {
+        enable = true;
+        accessKey = "BKIKJAA5BMMU2RHO6IBB";
+        secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
+      };
+      environment.systemPackages = [ pkgs.minio-client ];
+
+      # Minio requires at least 1GiB of free disk space to run.
+      virtualisation.diskSize = 4 * 1024;
+    };
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("minio.service");
+      $machine->waitForOpenPort(9000);
+
+      # Create a test bucket on the server
+      $machine->succeed("mc config host add minio http://localhost:9000 BKIKJAA5BMMU2RHO6IBB V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12 S3v4");
+      $machine->succeed("mc mb minio/test-bucket");
+      $machine->succeed("mc ls minio") =~ /test-bucket/ or die;
+      $machine->shutdown;
+
+    '';
+})
diff --git a/nixpkgs/nixos/tests/misc.nix b/nixpkgs/nixos/tests/misc.nix
new file mode 100644
index 000000000000..3ad55651b112
--- /dev/null
+++ b/nixpkgs/nixos/tests/misc.nix
@@ -0,0 +1,142 @@
+# Miscellaneous small tests that don't warrant their own VM run.
+
+import ./make-test.nix ({ pkgs, ...} : rec {
+  name = "misc";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  foo = pkgs.writeText "foo" "Hello World";
+
+  machine =
+    { lib, ... }:
+    with lib;
+    { swapDevices = mkOverride 0
+        [ { device = "/root/swapfile"; size = 128; } ];
+      environment.variables.EDITOR = mkOverride 0 "emacs";
+      documentation.nixos.enable = mkOverride 0 true;
+      systemd.tmpfiles.rules = [ "d /tmp 1777 root root 10d" ];
+      fileSystems = mkVMOverride { "/tmp2" =
+        { fsType = "tmpfs";
+          options = [ "mode=1777" "noauto" ];
+        };
+      };
+      systemd.automounts = singleton
+        { wantedBy = [ "multi-user.target" ];
+          where = "/tmp2";
+        };
+      users.users.sybil = { isNormalUser = true; group = "wheel"; };
+      security.sudo = { enable = true; wheelNeedsPassword = false; };
+      boot.kernel.sysctl."vm.swappiness" = 1;
+      boot.kernelParams = [ "vsyscall=emulate" ];
+      system.extraDependencies = [ foo ];
+    };
+
+  testScript =
+    ''
+      subtest "nix-db", sub {
+          my $json = $machine->succeed("nix path-info --json ${foo}");
+          $json =~ /"narHash":"sha256:0afw0d9j1hvwiz066z93jiddc33nxg6i6qyp26vnqyglpyfivlq5"/ or die "narHash not set";
+          $json =~ /"narSize":128/ or die "narSize not set";
+      };
+
+      subtest "nixos-version", sub {
+          $machine->succeed("[ `nixos-version | wc -w` = 2 ]");
+      };
+
+      subtest "nixos-rebuild", sub {
+          $machine->succeed("nixos-rebuild --help | grep 'NixOS module' ");
+      };
+
+      # 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->shutdown;
+          $machine->waitForUnit('multi-user.target');
+          $machine->succeed("last | grep reboot >&2");
+      };
+
+      # Test whether we can override environment variables.
+      subtest "override-env-var", sub {
+          $machine->succeed('[ "$EDITOR" = emacs ]');
+      };
+
+      # Test whether hostname (and by extension nss_myhostname) works.
+      subtest "hostname", sub {
+          $machine->succeed('[ "`hostname`" = machine ]');
+          #$machine->succeed('[ "`hostname -s`" = machine ]');
+      };
+
+      # Test whether systemd-udevd automatically loads modules for our hardware.
+      $machine->succeed("systemctl start systemd-udev-settle.service");
+      subtest "udev-auto-load", sub {
+          $machine->waitForUnit('systemd-udev-settle.service');
+          $machine->succeed('lsmod | grep mousedev');
+      };
+
+      # Test whether systemd-tmpfiles-clean works.
+      subtest "tmpfiles", sub {
+          $machine->succeed('touch /tmp/foo');
+          $machine->succeed('systemctl start systemd-tmpfiles-clean');
+          $machine->succeed('[ -e /tmp/foo ]');
+          $machine->succeed('date -s "@$(($(date +%s) + 1000000))"'); # move into the future
+          $machine->succeed('systemctl start systemd-tmpfiles-clean');
+          $machine->fail('[ -e /tmp/foo ]');
+      };
+
+      # Test whether automounting works.
+      subtest "automount", sub {
+          $machine->fail("grep '/tmp2 tmpfs' /proc/mounts");
+          $machine->succeed("touch /tmp2/x");
+          $machine->succeed("grep '/tmp2 tmpfs' /proc/mounts");
+      };
+
+      subtest "shell-vars", sub {
+          $machine->succeed('[ -n "$NIX_PATH" ]');
+      };
+
+      subtest "nix-db", sub {
+          $machine->succeed("nix-store -qR /run/current-system | grep nixos-");
+      };
+
+      # Test sysctl
+      subtest "sysctl", sub {
+          $machine->waitForUnit("systemd-sysctl.service");
+          $machine->succeed('[ `sysctl -ne vm.swappiness` = 1 ]');
+          $machine->execute('sysctl vm.swappiness=60');
+          $machine->succeed('[ `sysctl -ne vm.swappiness` = 60 ]');
+      };
+
+      # Test boot parameters
+      subtest "bootparam", sub {
+          $machine->succeed('grep -Fq vsyscall=emulate /proc/cmdline');
+      };
+    '';
+})
diff --git a/nixpkgs/nixos/tests/mongodb.nix b/nixpkgs/nixos/tests/mongodb.nix
new file mode 100644
index 000000000000..c9439b65292d
--- /dev/null
+++ b/nixpkgs/nixos/tests/mongodb.nix
@@ -0,0 +1,34 @@
+# This test start mongodb, runs a query using mongo shell
+
+import ./make-test.nix ({ pkgs, ...} : let
+  testQuery = pkgs.writeScript "nixtest.js" ''
+    db.greetings.insert({ "greeting": "hello" });
+    print(db.greetings.findOne().greeting);
+  '';
+in {
+  name = "mongodb";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bluescreen303 offline cstrahan rvl ];
+  };
+
+  nodes = {
+    one =
+      { ... }:
+        {
+          services = {
+           mongodb.enable = true;
+           mongodb.extraConfig = ''
+             # Allow starting engine with only a small virtual disk
+             storage.journal.enabled: false
+             storage.mmapv1.smallFiles: true
+           '';
+          };
+        };
+    };
+
+  testScript = ''
+    startAll;
+    $one->waitForUnit("mongodb.service");
+    $one->succeed("mongo nixtest ${testQuery}") =~ /hello/ or die;
+  '';
+})
diff --git a/nixpkgs/nixos/tests/morty.nix b/nixpkgs/nixos/tests/morty.nix
new file mode 100644
index 000000000000..eab123bd50f8
--- /dev/null
+++ b/nixpkgs/nixos/tests/morty.nix
@@ -0,0 +1,32 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "morty";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ leenaars ];
+  };
+
+  nodes =
+    { mortyProxyWithKey =
+
+      { ... }:
+      { services.morty = {
+        enable = true;
+	key = "78a9cd0cfee20c672f78427efb2a2a96036027f0";
+	port = 3001;
+	};
+      };
+
+    };
+
+  testScript =
+    { ... }:
+    ''
+      $mortyProxyWithKey->waitForUnit("default.target");
+
+      $mortyProxyWithKey->waitForOpenPort(3001);
+      $mortyProxyWithKey->succeed("curl -L 127.0.0.1:3001 | grep MortyProxy");
+
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/mpd.nix b/nixpkgs/nixos/tests/mpd.nix
new file mode 100644
index 000000000000..ac2b810defe3
--- /dev/null
+++ b/nixpkgs/nixos/tests/mpd.nix
@@ -0,0 +1,119 @@
+import ./make-test.nix ({ pkgs, ... }:
+  let
+    track = pkgs.fetchurl {
+      # Sourced from http://freemusicarchive.org/music/Blue_Wave_Theory/Surf_Music_Month_Challenge/Skyhawk_Beach_fade_in
+      # License: http://creativecommons.org/licenses/by-sa/4.0/
+
+      name = "Blue_Wave_Theory-Skyhawk_Beach.mp3";
+      url = https://freemusicarchive.org/file/music/ccCommunity/Blue_Wave_Theory/Surf_Music_Month_Challenge/Blue_Wave_Theory_-_04_-_Skyhawk_Beach.mp3;
+      sha256 = "0xw417bxkx4gqqy139bb21yldi37xx8xjfxrwaqa0gyw19dl6mgp";
+    };
+
+    defaultCfg = rec {
+      user = "mpd";
+      group = "mpd";
+      dataDir = "/var/lib/mpd";
+      musicDirectory = "${dataDir}/music";
+    };
+
+    defaultMpdCfg = with defaultCfg; {
+      inherit dataDir musicDirectory user group;
+      enable = true;
+    };
+
+    musicService = { user, group, musicDirectory }: {
+      description = "Sets up the music file(s) for MPD to use.";
+      requires = [ "mpd.service" ];
+      after = [ "mpd.service" ];
+      wantedBy = [ "default.target" ];
+      script = ''
+        mkdir -p ${musicDirectory} && chown -R ${user}:${group} ${musicDirectory}
+        cp ${track} ${musicDirectory}
+        chown ${user}:${group} ${musicDirectory}/$(basename ${track})
+      '';
+    };
+
+    mkServer = { mpd, musicService, }:
+      { boot.kernelModules = [ "snd-dummy" ];
+        sound.enable = true;
+        services.mpd = mpd;
+        systemd.services.musicService = musicService;
+      };
+  in {
+    name = "mpd";
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ emmanuelrosa ];
+    };
+
+  nodes =
+    { client = 
+      { ... }: { };
+
+      serverALSA =
+        { ... }: (mkServer {
+          mpd = defaultMpdCfg // {
+            network.listenAddress = "any";
+            extraConfig = ''
+              audio_output {
+                type "alsa"
+                name "ALSA"
+                mixer_type "null"
+              }
+            '';
+          };
+
+          musicService = with defaultMpdCfg; musicService { inherit user group musicDirectory; };
+        }) // { networking.firewall.allowedTCPPorts = [ 6600 ]; };
+
+      serverPulseAudio =
+        { ... }: (mkServer {
+          mpd = defaultMpdCfg // {
+            extraConfig = ''
+              audio_output {
+                type "pulse"
+                name "The Pulse"
+              }
+            '';
+          };
+
+          musicService = with defaultCfg; musicService { inherit user group musicDirectory; };
+        }) // { hardware.pulseaudio.enable = true; };
+    };
+
+  testScript = ''
+    my $mpc = "${pkgs.mpc_cli}/bin/mpc --wait";
+
+    # Connects to the given server and attempts to play a tune.
+    sub play_some_music {
+        my $server = $_[0];
+
+        $server->waitForUnit("mpd.service");
+        $server->succeed("$mpc update");
+        my @tracks = $server->execute("$mpc ls");
+
+        for my $track (split(/\n/, $tracks[1])) {
+            $server->succeed("$mpc add $track");
+        };
+
+        my @added_tracks = $server->execute("$mpc listall");
+        (length $added_tracks[1]) > 0 or die "Failed to add audio tracks to the playlist.";
+
+        $server->succeed("$mpc play");
+
+        my @status = $server->execute("$mpc status");
+        my @output = split(/\n/, $status[1]);
+        $output[1] =~ /.*playing.*/ or die "Audio track is not playing, as expected.";
+
+        $server->succeed("$mpc stop");
+    };
+
+    play_some_music($serverALSA);
+    play_some_music($serverPulseAudio);
+
+    $client->succeed("$mpc -h serverALSA status");
+
+    # The PulseAudio-based server is configured not to accept external client connections
+    # to perform the following test:
+    $client->fail("$mpc -h serverPulseAudio status");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mpich-example.c b/nixpkgs/nixos/tests/mpich-example.c
new file mode 100644
index 000000000000..c48e3c45b72e
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/mumble.nix b/nixpkgs/nixos/tests/mumble.nix
new file mode 100644
index 000000000000..8146453bfd55
--- /dev/null
+++ b/nixpkgs/nixos/tests/mumble.nix
@@ -0,0 +1,73 @@
+import ./make-test.nix ({ pkgs, ...} : 
+
+let
+  client = { pkgs, ... }: {
+    imports = [ ./common/x11.nix ];
+    environment.systemPackages = [ pkgs.mumble ];
+  };
+in
+{
+  name = "mumble";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ thoughtpolice eelco chaoflow ];
+  };
+
+  nodes = {
+    server = { config, ... }: {
+      services.murmur.enable       = true;
+      services.murmur.registerName = "NixOS tests";
+      networking.firewall.allowedTCPPorts = [ config.services.murmur.port ];
+    };
+
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("murmur.service");
+    $client1->waitForX;
+    $client2->waitForX;
+
+    $client1->execute("mumble mumble://client1\@server/test &");
+    $client2->execute("mumble mumble://client2\@server/test &");
+
+    # cancel client audio configuration
+    $client1->waitForWindow(qr/Audio Tuning Wizard/);
+    $client2->waitForWindow(qr/Audio Tuning Wizard/);
+    $server->sleep(5); # wait because mumble is slow to register event handlers
+    $client1->sendKeys("esc");
+    $client2->sendKeys("esc");
+
+    # cancel client cert configuration
+    $client1->waitForWindow(qr/Certificate Management/);
+    $client2->waitForWindow(qr/Certificate Management/);
+    $server->sleep(5); # wait because mumble is slow to register event handlers
+    $client1->sendKeys("esc");
+    $client2->sendKeys("esc");
+
+    # accept server certificate
+    $client1->waitForWindow(qr/^Mumble$/);
+    $client2->waitForWindow(qr/^Mumble$/);
+    $server->sleep(5); # wait because mumble is slow to register event handlers
+    $client1->sendChars("y");
+    $client2->sendChars("y");
+    $server->sleep(5); # wait because mumble is slow to register event handlers
+
+    # sometimes the wrong of the 2 windows is focused, we switch focus and try pressing "y" again
+    $client1->sendKeys("alt-tab");
+    $client2->sendKeys("alt-tab");
+    $server->sleep(5); # wait because mumble is slow to register event handlers
+    $client1->sendChars("y");
+    $client2->sendChars("y");
+
+    # Find clients in logs
+    $server->waitUntilSucceeds("grep -q 'client1' /var/log/murmur/murmurd.log");
+    $server->waitUntilSucceeds("grep -q 'client2' /var/log/murmur/murmurd.log");
+
+    $server->sleep(5); # wait to get screenshot
+    $client1->screenshot("screen1");
+    $client2->screenshot("screen2");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/munin.nix b/nixpkgs/nixos/tests/munin.nix
new file mode 100644
index 000000000000..95cecf17b8cc
--- /dev/null
+++ b/nixpkgs/nixos/tests/munin.nix
@@ -0,0 +1,44 @@
+# This test runs basic munin setup with node and cron job running on the same
+# machine.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "munin";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ domenkozar eelco chaoflow ];
+  };
+
+  nodes = {
+    one =
+      { config, ... }:
+        {
+          services = {
+           munin-node = {
+             enable = true;
+             # disable a failing plugin to prevent irrelevant error message, see #23049
+             disabledPlugins = [ "apc_nis" ];
+           };
+           munin-cron = {
+            enable = true;
+            hosts = ''
+              [${config.networking.hostName}]
+              address localhost
+            '';
+           };
+          };
+          # long timeout to prevent hydra failure on high load
+          systemd.services.munin-node.serviceConfig.TimeoutStartSec = "10min";
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $one->waitForUnit("munin-node.service");
+    # make sure the node is actually listening
+    $one->waitForOpenPort(4949);
+    $one->succeed('systemctl start munin-cron');
+    # wait for munin-cron output
+    $one->waitForFile("/var/lib/munin/one/one-uptime-uptime-g.rrd");
+    $one->waitForFile("/var/www/munin/one/index.html");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mutable-users.nix b/nixpkgs/nixos/tests/mutable-users.nix
new file mode 100644
index 000000000000..e590703ab2f4
--- /dev/null
+++ b/nixpkgs/nixos/tests/mutable-users.nix
@@ -0,0 +1,39 @@
+# Mutable users tests.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "mutable-users";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ gleber ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      users.mutableUsers = false;
+    };
+    mutable = { ... }: {
+      users.mutableUsers = true;
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    immutableSystem = nodes.machine.config.system.build.toplevel;
+    mutableSystem = nodes.mutable.config.system.build.toplevel;
+  in ''
+    $machine->start();
+    $machine->waitForUnit("default.target");
+
+    # Machine starts in immutable mode. Add a user and test if reactivating
+    # configuration removes the user.
+    $machine->fail("cat /etc/passwd | grep ^foobar:");
+    $machine->succeed("sudo useradd foobar");
+    $machine->succeed("cat /etc/passwd | grep ^foobar:");
+    $machine->succeed("${immutableSystem}/bin/switch-to-configuration test");
+    $machine->fail("cat /etc/passwd | grep ^foobar:");
+
+    # In immutable mode passwd is not wrapped, while in mutable mode it is
+    # wrapped.
+    $machine->succeed('which passwd | grep /run/current-system/');
+    $machine->succeed("${mutableSystem}/bin/switch-to-configuration test");
+    $machine->succeed('which passwd | grep /run/wrappers/');
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mxisd.nix b/nixpkgs/nixos/tests/mxisd.nix
new file mode 100644
index 000000000000..3d03a5a53e38
--- /dev/null
+++ b/nixpkgs/nixos/tests/mxisd.nix
@@ -0,0 +1,21 @@
+import ./make-test.nix ({ pkgs, ... } : {
+
+  name = "mxisd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mguentner ];
+  };
+
+  nodes = {
+    server_mxisd = args : {
+      services.mxisd.enable = true;
+      services.mxisd.matrix.domain = "example.org";
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $server_mxisd->waitForUnit("mxisd.service");
+    $server_mxisd->waitForOpenPort(8090);
+    $server_mxisd->succeed("curl -Ssf \"http://127.0.0.1:8090/_matrix/identity/api/v1\"")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mysql-backup.nix b/nixpkgs/nixos/tests/mysql-backup.nix
new file mode 100644
index 000000000000..81482dfef7e5
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql-backup.nix
@@ -0,0 +1,50 @@
+# Test whether mysqlBackup option works
+import ./make-test.nix ({ pkgs, ... } : {
+  name = "mysql-backup";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ rvl ];
+  };
+
+  nodes = {
+    master = { pkgs, ... }: {
+      services.mysql = {
+        enable = true;
+        initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+        package = pkgs.mysql;
+      };
+
+      services.mysqlBackup = {
+        enable = true;
+        databases = [ "doesnotexist" "testdb" ];
+      };
+    };
+  };
+
+  testScript =
+    '' startAll;
+
+       # Delete backup file that may be left over from a previous test run.
+       # This is not needed on Hydra but useful for repeated local test runs.
+       $master->execute("rm -f /var/backup/mysql/testdb.gz");
+
+       # Need to have mysql started so that it can be populated with data.
+       $master->waitForUnit("mysql.service");
+
+       # Wait for testdb to be fully populated (5 rows).
+       $master->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
+
+       # Do a backup and wait for it to start
+       $master->startJob("mysql-backup.service");
+       $master->waitForJob("mysql-backup.service");
+
+       # wait for backup to fail, because of database 'doesnotexist'
+       $master->waitUntilFails("systemctl is-active -q mysql-backup.service");
+
+       # wait for backup file and check that data appears in backup
+       $master->waitForFile("/var/backup/mysql/testdb.gz");
+       $master->succeed("${pkgs.gzip}/bin/zcat /var/backup/mysql/testdb.gz | grep hello");
+
+       # Check that a failed backup is logged
+       $master->succeed("journalctl -u mysql-backup.service | grep 'fail.*doesnotexist' > /dev/null");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/mysql-replication.nix b/nixpkgs/nixos/tests/mysql-replication.nix
new file mode 100644
index 000000000000..84d70cf35246
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql-replication.nix
@@ -0,0 +1,81 @@
+import ./make-test.nix ({ pkgs, ...} :
+
+let
+  replicateUser = "replicate";
+  replicatePassword = "secret";
+in
+
+{
+  name = "mysql-replication";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow shlevy ];
+  };
+
+  nodes = {
+    master =
+      { pkgs, ... }:
+
+      {
+        services.mysql.enable = true;
+        services.mysql.package = pkgs.mysql;
+        services.mysql.replication.role = "master";
+        services.mysql.replication.slaveHost = "%";
+        services.mysql.replication.masterUser = replicateUser;
+        services.mysql.replication.masterPassword = replicatePassword;
+        services.mysql.initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+        networking.firewall.allowedTCPPorts = [ 3306 ];
+      };
+
+    slave1 =
+      { pkgs, nodes, ... }:
+
+      {
+        services.mysql.enable = true;
+        services.mysql.package = pkgs.mysql;
+        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, nodes, ... }:
+
+      {
+        services.mysql.enable = true;
+        services.mysql.package = pkgs.mysql;
+        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 = ''
+    $master->start;
+    $master->waitForUnit("mysql");
+    $master->waitForOpenPort(3306);
+    # Wait for testdb to be fully populated (5 rows).
+    $master->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
+
+    $slave1->start;
+    $slave2->start;
+    $slave1->waitForUnit("mysql");
+    $slave1->waitForOpenPort(3306);
+    $slave2->waitForUnit("mysql");
+    $slave2->waitForOpenPort(3306);
+
+    # wait for replications to finish
+    $slave1->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
+    $slave2->waitUntilSucceeds("mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5");
+
+    $slave2->succeed("systemctl stop mysql");
+    $master->succeed("echo 'insert into testdb.tests values (123, 456);' | mysql -u root -N");
+    $slave2->succeed("systemctl start mysql");
+    $slave2->waitForUnit("mysql");
+    $slave2->waitForOpenPort(3306);
+    $slave2->waitUntilSucceeds("echo 'select * from testdb.tests where Id = 123;' | mysql -u root -N | grep 456");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mysql.nix b/nixpkgs/nixos/tests/mysql.nix
new file mode 100644
index 000000000000..7251c4a86499
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql.nix
@@ -0,0 +1,24 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "mysql";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow shlevy ];
+  };
+
+  nodes = {
+    master =
+      { pkgs, ... }:
+
+      {
+        services.mysql.enable = true;
+        services.mysql.initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+        services.mysql.package = pkgs.mysql;
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $master->waitForUnit("mysql");
+    $master->succeed("echo 'use testdb; select * from tests' | mysql -u root -N | grep 4");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nat.nix b/nixpkgs/nixos/tests/nat.nix
new file mode 100644
index 000000000000..34229e913110
--- /dev/null
+++ b/nixpkgs/nixos/tests/nat.nix
@@ -0,0 +1,117 @@
+# 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.
+import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false, ... }:
+  let
+    unit = if withFirewall then "firewall" else "nat";
+
+    routerBase =
+      lib.mkMerge [
+        { virtualisation.vlans = [ 2 1 ];
+          networking.firewall.enable = withFirewall;
+          networking.nat.internalIPs = [ "192.168.1.0/24" ];
+          networking.nat.externalInterface = "eth1";
+        }
+        (lib.optionalAttrs withConntrackHelpers {
+          networking.firewall.connectionTrackingModules = [ "ftp" ];
+          networking.firewall.autoLoadConntrackHelpers = true;
+        })
+      ];
+  in
+  {
+    name = "nat" + (if withFirewall then "WithFirewall" else "Standalone")
+                 + (lib.optionalString withConntrackHelpers "withConntrackHelpers");
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ eelco chaoflow rob ];
+    };
+
+    nodes =
+      { client =
+          { pkgs, nodes, ... }:
+          lib.mkMerge [
+            { virtualisation.vlans = [ 1 ];
+              networking.defaultGateway =
+                (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
+            }
+            (lib.optionalAttrs withConntrackHelpers {
+              networking.firewall.connectionTrackingModules = [ "ftp" ];
+              networking.firewall.autoLoadConntrackHelpers = true;
+            })
+          ];
+
+        router =
+        { ... }: lib.mkMerge [
+          routerBase
+          { networking.nat.enable = true; }
+        ];
+
+        routerDummyNoNat =
+        { ... }: lib.mkMerge [
+          routerBase
+          { networking.nat.enable = false; }
+        ];
+
+        server =
+          { ... }:
+          { virtualisation.vlans = [ 2 ];
+            networking.firewall.enable = false;
+            services.httpd.enable = true;
+            services.httpd.adminAddr = "foo@example.org";
+            services.vsftpd.enable = true;
+            services.vsftpd.anonymousUser = true;
+          };
+      };
+
+    testScript =
+      { nodes, ... }: let
+        routerDummyNoNatClosure = nodes.routerDummyNoNat.config.system.build.toplevel;
+        routerClosure = nodes.router.config.system.build.toplevel;
+      in ''
+        $client->start;
+        $router->start;
+        $server->start;
+
+        # 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("${unit}");
+        $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->${if withConntrackHelpers then "succeed" else "fail"}(
+          "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->succeed("${routerDummyNoNatClosure}/bin/switch-to-configuration test 2>&1");
+        $client->fail("curl --fail --connect-timeout 5 http://server/ >&2");
+        $client->fail("ping -c 1 server >&2");
+
+        # And make sure that reloading the NAT job works.
+        $router->succeed("${routerClosure}/bin/switch-to-configuration test 2>&1");
+        # FIXME: this should not be necessary, but nat.service is not started because
+        #        network.target is not triggered
+        #        (https://github.com/NixOS/nixpkgs/issues/16230#issuecomment-226408359)
+        ${lib.optionalString (!withFirewall) ''
+          $router->succeed("systemctl start nat.service");
+        ''}
+        $client->succeed("curl --fail http://server/ >&2");
+        $client->succeed("ping -c 1 server >&2");
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/ndppd.nix b/nixpkgs/nixos/tests/ndppd.nix
new file mode 100644
index 000000000000..9f24eb6d9d45
--- /dev/null
+++ b/nixpkgs/nixos/tests/ndppd.nix
@@ -0,0 +1,61 @@
+import ./make-test.nix ({ pkgs, lib, ...} : {
+  name = "ndppd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ fpletz ];
+  };
+
+  nodes = {
+    upstream = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.tcpdump ];
+      networking.useDHCP = false;
+      networking.interfaces = {
+        eth1 = {
+          ipv6.addresses = [
+            { address = "fd23::1"; prefixLength = 112; }
+          ];
+          ipv6.routes = [
+            { address = "fd42::";
+              prefixLength = 112;
+            }
+          ];
+        };
+      };
+    };
+    server = { pkgs, ... }: {
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.all.forwarding" = "1";
+        "net.ipv6.conf.default.forwarding" = "1";
+      };
+      environment.systemPackages = [ pkgs.tcpdump ];
+      networking.useDHCP = false;
+      networking.interfaces = {
+        eth1 = {
+          ipv6.addresses = [
+            { address = "fd23::2"; prefixLength = 112; }
+          ];
+        };
+      };
+      services.ndppd = {
+        enable = true;
+        interface = "eth1";
+        network = "fd42::/112";
+      };
+      containers.client = {
+        autoStart = true;
+        privateNetwork = true;
+        hostAddress = "192.168.255.1";
+        localAddress = "192.168.255.2";
+        hostAddress6 = "fd42::1";
+        localAddress6 = "fd42::2";
+        config = {};
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $server->waitForUnit("multi-user.target");
+    $upstream->waitForUnit("multi-user.target");
+    $upstream->waitUntilSucceeds("ping -c5 fd42::2");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/neo4j.nix b/nixpkgs/nixos/tests/neo4j.nix
new file mode 100644
index 000000000000..86ed8970517c
--- /dev/null
+++ b/nixpkgs/nixos/tests/neo4j.nix
@@ -0,0 +1,20 @@
+import ./make-test.nix {
+  name = "neo4j";
+
+  nodes = {
+    master =
+      { ... }:
+
+      {
+        services.neo4j.enable = true;
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $master->waitForUnit("neo4j");
+    $master->sleep(20); # Hopefully this is long enough!!
+    $master->succeed("curl http://localhost:7474/");
+  '';
+}
diff --git a/nixpkgs/nixos/tests/netdata.nix b/nixpkgs/nixos/tests/netdata.nix
new file mode 100644
index 000000000000..9bd147968e4b
--- /dev/null
+++ b/nixpkgs/nixos/tests/netdata.nix
@@ -0,0 +1,38 @@
+# This test runs netdata and checks for data via apps.plugin
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "netdata";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ cransom ];
+  };
+
+  nodes = {
+    netdata =
+      { pkgs, ... }:
+        {
+          environment.systemPackages = with pkgs; [ curl jq ];
+          services.netdata.enable = true;
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $netdata->waitForUnit("netdata.service");
+
+    # wait for the service to listen before sending a request
+    $netdata->waitForOpenPort(19999);
+
+    # check if the netdata main page loads.
+    $netdata->succeed("curl --fail http://localhost:19999/");
+
+    # check if netdata can read disk ops for root owned processes.
+    # if > 0, successful. verifies both netdata working and
+    # apps.plugin has elevated capabilities.
+    my $cmd = <<'CMD';
+    curl -s http://localhost:19999/api/v1/data\?chart=users.pwrites | \
+       jq -e '[.data[range(10)][.labels | indices("root")[0]]] | add | . > 0'
+    CMD
+    $netdata->waitUntilSucceeds($cmd);
+  '';
+})
diff --git a/nixpkgs/nixos/tests/networking-proxy.nix b/nixpkgs/nixos/tests/networking-proxy.nix
new file mode 100644
index 000000000000..ab908c96e5ee
--- /dev/null
+++ b/nixpkgs/nixos/tests/networking-proxy.nix
@@ -0,0 +1,111 @@
+# Test whether `networking.proxy' work as expected.
+
+# TODO: use a real proxy node and put this test into networking.nix
+# TODO: test whether nix tools work as expected behind a proxy
+
+let default-config = {
+        imports = [ ./common/user-account.nix ];
+
+        services.xserver.enable = false;
+
+        virtualisation.memorySize = 128;
+      };
+in import ./make-test.nix ({ pkgs, ...} : {
+  name = "networking-proxy";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [  ];
+  };
+
+  nodes = {
+    # no proxy
+    machine =
+      { ... }:
+
+      default-config;
+
+    # proxy default
+    machine2 =
+      { ... }:
+
+      default-config // {
+        networking.proxy.default = "http://user:pass@host:port";
+      };
+
+    # specific proxy options
+    machine3 =
+      { ... }:
+
+      default-config //
+      {
+        networking.proxy = {
+          # useless because overriden by the next options
+          default = "http://user:pass@host:port";
+          # advanced proxy setup
+          httpProxy = "123-http://user:pass@http-host:port";
+          httpsProxy = "456-http://user:pass@https-host:port";
+          rsyncProxy = "789-http://user:pass@rsync-host:port";
+          ftpProxy = "101112-http://user:pass@ftp-host:port";
+          noProxy = "131415-127.0.0.1,localhost,.localdomain";
+        };
+      };
+
+    # mix default + proxy options
+    machine4 =
+      { ... }:
+
+      default-config // {
+        networking.proxy = {
+          # open for all *_proxy env var
+          default = "000-http://user:pass@default-host:port";
+          # except for those 2
+          rsyncProxy = "123-http://user:pass@http-host:port";
+          noProxy = "131415-127.0.0.1,localhost,.localdomain";
+        };
+      };
+    };
+
+  testScript =
+    ''
+      startAll;
+
+      # no proxy at all
+      print $machine->execute("env | grep -i proxy");
+      print $machine->execute("su - alice -c 'env | grep -i proxy'");
+      $machine->mustFail("env | grep -i proxy");
+      $machine->mustFail("su - alice -c 'env | grep -i proxy'");
+
+      # Use a default proxy option
+      print $machine2->execute("env | grep -i proxy");
+      print $machine2->execute("su - alice -c 'env | grep -i proxy'");
+      $machine2->mustSucceed("env | grep -i proxy");
+      $machine2->mustSucceed("su - alice -c 'env | grep -i proxy'");
+
+      # explicitly set each proxy option
+      print $machine3->execute("env | grep -i proxy");
+      print $machine3->execute("su - alice -c 'env | grep -i proxy'");
+      $machine3->mustSucceed("env | grep -i http_proxy | grep 123");
+      $machine3->mustSucceed("env | grep -i https_proxy | grep 456");
+      $machine3->mustSucceed("env | grep -i rsync_proxy | grep 789");
+      $machine3->mustSucceed("env | grep -i ftp_proxy | grep 101112");
+      $machine3->mustSucceed("env | grep -i no_proxy | grep 131415");
+      $machine3->mustSucceed("su - alice -c 'env | grep -i http_proxy | grep 123'");
+      $machine3->mustSucceed("su - alice -c 'env | grep -i https_proxy | grep 456'");
+      $machine3->mustSucceed("su - alice -c 'env | grep -i rsync_proxy | grep 789'");
+      $machine3->mustSucceed("su - alice -c 'env | grep -i ftp_proxy | grep 101112'");
+      $machine3->mustSucceed("su - alice -c 'env | grep -i no_proxy | grep 131415'");
+
+      # set default proxy option + some other specifics
+      print $machine4->execute("env | grep -i proxy");
+      print $machine4->execute("su - alice -c 'env | grep -i proxy'");
+      $machine4->mustSucceed("env | grep -i http_proxy | grep 000");
+      $machine4->mustSucceed("env | grep -i https_proxy | grep 000");
+      $machine4->mustSucceed("env | grep -i rsync_proxy | grep 123");
+      $machine4->mustSucceed("env | grep -i ftp_proxy | grep 000");
+      $machine4->mustSucceed("env | grep -i no_proxy | grep 131415");
+      $machine4->mustSucceed("su - alice -c 'env | grep -i http_proxy | grep 000'");
+      $machine4->mustSucceed("su - alice -c 'env | grep -i https_proxy | grep 000'");
+      $machine4->mustSucceed("su - alice -c 'env | grep -i rsync_proxy | grep 123'");
+      $machine4->mustSucceed("su - alice -c 'env | grep -i ftp_proxy | grep 000'");
+      $machine4->mustSucceed("su - alice -c 'env | grep -i no_proxy | grep 131415'");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/networking.nix b/nixpkgs/nixos/tests/networking.nix
new file mode 100644
index 000000000000..ed9f287d5582
--- /dev/null
+++ b/nixpkgs/nixos/tests/networking.nix
@@ -0,0 +1,609 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+# bool: whether to use networkd in the tests
+, networkd }:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  router = { config, pkgs, ... }:
+    with pkgs.lib;
+    let
+      vlanIfs = range 1 (length config.virtualisation.vlans);
+    in {
+      environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
+      virtualisation.vlans = [ 1 2 3 ];
+      boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+      networking = {
+        useDHCP = false;
+        useNetworkd = networkd;
+        firewall.checkReversePath = true;
+        firewall.allowedUDPPorts = [ 547 ];
+        interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
+          nameValuePair "eth${toString n}" {
+            ipv4.addresses = [ { address = "192.168.${toString n}.1"; prefixLength = 24; } ];
+            ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ];
+          })));
+      };
+      services.dhcpd4 = {
+        enable = true;
+        interfaces = map (n: "eth${toString n}") vlanIfs;
+        extraConfig = ''
+          authoritative;
+        '' + flip concatMapStrings vlanIfs (n: ''
+          subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
+            option routers 192.168.${toString n}.1;
+            # XXX: technically it's _not guaranteed_ that IP addresses will be
+            # issued from the first item in range onwards! We assume that in
+            # our tests however.
+            range 192.168.${toString n}.2 192.168.${toString n}.254;
+          }
+        '');
+      };
+      services.radvd = {
+        enable = true;
+        config = flip concatMapStrings vlanIfs (n: ''
+          interface eth${toString n} {
+            AdvSendAdvert on;
+            AdvManagedFlag on;
+            AdvOtherConfigFlag on;
+
+            prefix fd00:1234:5678:${toString n}::/64 {
+              AdvAutonomous off;
+            };
+          };
+        '');
+      };
+      services.dhcpd6 = {
+        enable = true;
+        interfaces = map (n: "eth${toString n}") vlanIfs;
+        extraConfig = ''
+          authoritative;
+        '' + flip concatMapStrings vlanIfs (n: ''
+          subnet6 fd00:1234:5678:${toString n}::/64 {
+            range6 fd00:1234:5678:${toString n}::2 fd00:1234:5678:${toString n}::2;
+          }
+        '');
+      };
+    };
+
+  testCases = {
+    loopback = {
+      name = "Loopback";
+      machine.networking.useNetworkd = networkd;
+      testScript = ''
+        startAll;
+        $machine->waitForUnit("network.target");
+        $machine->succeed("ip addr show lo | grep -q 'inet 127.0.0.1/8 '");
+        $machine->succeed("ip addr show lo | grep -q 'inet6 ::1/128 '");
+      '';
+    };
+    static = {
+      name = "Static";
+      nodes.router = router;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          defaultGateway = "192.168.1.1";
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [
+            { address = "192.168.1.2"; prefixLength = 24; }
+            { address = "192.168.1.3"; prefixLength = 32; }
+            { address = "192.168.1.10"; prefixLength = 32; }
+          ];
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [
+            { address = "192.168.2.2"; prefixLength = 24; }
+          ];
+        };
+      };
+      testScript = { ... }:
+        ''
+          startAll;
+
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network-online.target");
+
+          # Make sure dhcpcd is not started
+          $client->fail("systemctl status dhcpcd.service");
+
+          # Test vlan 1
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.3");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.10");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.10");
+
+          # Test vlan 2
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
+
+          # Test default gateway
+          $router->waitUntilSucceeds("ping -c 1 192.168.3.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.3.1");
+        '';
+    };
+    dhcpSimple = {
+      name = "SimpleDHCP";
+      nodes.router = router;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = true;
+          interfaces.eth1 = {
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
+          };
+          interfaces.eth2 = {
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
+          };
+        };
+      };
+      testScript = { ... }:
+        ''
+          startAll;
+
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network-online.target");
+
+          # Wait until we have an ip address on each interface
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
+          $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q '192.168.2'");
+          $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'");
+
+          # Test vlan 1
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::2");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
+          $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::2");
+
+          # Test vlan 2
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::1");
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::2");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
+          $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::1");
+          $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::2");
+        '';
+    };
+    dhcpOneIf = {
+      name = "OneInterfaceDHCP";
+      nodes.router = router;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.eth1 = {
+            ipv4.addresses = mkOverride 0 [ ];
+            useDHCP = true;
+          };
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
+        };
+      };
+      testScript = { ... }:
+        ''
+          startAll;
+
+          # Wait for networking to come up
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network.target");
+
+          # Wait until we have an ip address on each interface
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
+
+          # Test vlan 1
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+
+          # Test vlan 2
+          $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $client->fail("ping -c 1 192.168.2.2");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
+          $router->fail("ping -c 1 192.168.2.2");
+        '';
+    };
+    bond = let
+      node = address: { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          bonds.bond = {
+            interfaces = [ "eth1" "eth2" ];
+            driverOptions.mode = "balance-rr";
+          };
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.bond.ipv4.addresses = mkOverride 0
+            [ { inherit address; prefixLength = 30; } ];
+        };
+      };
+    in {
+      name = "Bond";
+      nodes.client1 = node "192.168.1.1";
+      nodes.client2 = node "192.168.1.2";
+      testScript = { ... }:
+        ''
+          startAll;
+
+          # Wait for networking to come up
+          $client1->waitForUnit("network.target");
+          $client2->waitForUnit("network.target");
+
+          # Test bonding
+          $client1->waitUntilSucceeds("ping -c 2 192.168.1.1");
+          $client1->waitUntilSucceeds("ping -c 2 192.168.1.2");
+
+          $client2->waitUntilSucceeds("ping -c 2 192.168.1.1");
+          $client2->waitUntilSucceeds("ping -c 2 192.168.1.2");
+        '';
+    };
+    bridge = let
+      node = { address, vlan }: { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ vlan ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.eth1.ipv4.addresses = mkOverride 0
+            [ { inherit address; prefixLength = 24; } ];
+        };
+      };
+    in {
+      name = "Bridge";
+      nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
+      nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
+      nodes.router = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 2 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          bridges.bridge.interfaces = [ "eth1" "eth2" ];
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.bridge.ipv4.addresses = mkOverride 0
+            [ { address = "192.168.1.1"; prefixLength = 24; } ];
+        };
+      };
+      testScript = { ... }:
+        ''
+          startAll;
+
+          # Wait for networking to come up
+          $client1->waitForUnit("network.target");
+          $client2->waitForUnit("network.target");
+          $router->waitForUnit("network.target");
+
+          # Test bridging
+          $client1->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client1->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client1->waitUntilSucceeds("ping -c 1 192.168.1.3");
+
+          $client2->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client2->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client2->waitUntilSucceeds("ping -c 1 192.168.1.3");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
+        '';
+    };
+    macvlan = {
+      name = "MACVLAN";
+      nodes.router = router;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.logReversePathDrops = true; # to debug firewall rules
+          # reverse path filtering rules for the macvlan interface seem
+          # to be incorrect, causing the test to fail. Disable temporarily.
+          firewall.checkReversePath = false;
+          useDHCP = true;
+          macvlans.macvlan.interface = "eth1";
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+        };
+      };
+      testScript = { ... }:
+        ''
+          startAll;
+
+          # Wait for networking to come up
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network.target");
+
+          # Wait until we have an ip address on each interface
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
+          $client->waitUntilSucceeds("ip addr show dev macvlan | grep -q '192.168.1'");
+
+          # Print lots of diagnostic information
+          $router->log('**********************************************');
+          $router->succeed("ip addr >&2");
+          $router->succeed("ip route >&2");
+          $router->execute("iptables-save >&2");
+          $client->log('==============================================');
+          $client->succeed("ip addr >&2");
+          $client->succeed("ip route >&2");
+          $client->execute("iptables-save >&2");
+          $client->log('##############################################');
+
+          # Test macvlan creates routable ips
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client->waitUntilSucceeds("ping -c 1 192.168.1.3");
+
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
+        '';
+    };
+    sit = let
+      node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          firewall.enable = false;
+          useDHCP = false;
+          sits.sit = {
+            inherit remote;
+            local = address4;
+            dev = "eth1";
+          };
+          interfaces.eth1.ipv4.addresses = mkOverride 0
+            [ { address = address4; prefixLength = 24; } ];
+          interfaces.sit.ipv6.addresses = mkOverride 0
+            [ { address = address6; prefixLength = 64; } ];
+        };
+      };
+    in {
+      name = "Sit";
+      nodes.client1 = node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; };
+      nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
+      testScript = { ... }:
+        ''
+          startAll;
+
+          # Wait for networking to be configured
+          $client1->waitForUnit("network.target");
+          $client2->waitForUnit("network.target");
+
+          # Print diagnostic information
+          $client1->succeed("ip addr >&2");
+          $client2->succeed("ip addr >&2");
+
+          # Test ipv6
+          $client1->waitUntilSucceeds("ping -c 1 fc00::1");
+          $client1->waitUntilSucceeds("ping -c 1 fc00::2");
+
+          $client2->waitUntilSucceeds("ping -c 1 fc00::1");
+          $client2->waitUntilSucceeds("ping -c 1 fc00::2");
+        '';
+    };
+    vlan = let
+      node = address: { pkgs, ... }: with pkgs.lib; {
+        #virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          vlans.vlan = {
+            id = 1;
+            interface = "eth0";
+          };
+          interfaces.eth0.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.vlan.ipv4.addresses = mkOverride 0
+            [ { inherit address; prefixLength = 24; } ];
+        };
+      };
+    in {
+      name = "vlan";
+      nodes.client1 = node "192.168.1.1";
+      nodes.client2 = node "192.168.1.2";
+      testScript = { ... }:
+        ''
+          startAll;
+
+          # Wait for networking to be configured
+          $client1->waitForUnit("network.target");
+          $client2->waitForUnit("network.target");
+
+          # Test vlan is setup
+          $client1->succeed("ip addr show dev vlan >&2");
+          $client2->succeed("ip addr show dev vlan >&2");
+        '';
+    };
+    virtual = {
+      name = "Virtual";
+      machine = {
+        networking.interfaces."tap0" = {
+          ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2096::"; prefixLength = 64; } ];
+          virtual = true;
+        };
+        networking.interfaces."tun0" = {
+          ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
+          virtual = true;
+        };
+      };
+
+      testScript = ''
+        my $targetList = <<'END';
+        tap0: tap persist user 0
+        tun0: tun persist user 0
+        END
+
+        # Wait for networking to come up
+        $machine->start;
+        $machine->waitForUnit("network-online.target");
+
+        # Test interfaces set up
+        my $list = $machine->succeed("ip tuntap list | sort");
+        "$list" eq "$targetList" or die(
+          "The list of virtual interfaces does not match the expected one:\n",
+          "Result:\n", "$list\n",
+          "Expected:\n", "$targetList\n"
+        );
+
+        # Test interfaces clean up
+        $machine->succeed("systemctl stop network-addresses-tap0");
+        $machine->sleep(10);
+        $machine->succeed("systemctl stop network-addresses-tun0");
+        $machine->sleep(10);
+        my $residue = $machine->succeed("ip tuntap list");
+        $residue eq "" or die(
+          "Some virtual interface has not been properly cleaned:\n",
+          "$residue\n"
+        );
+      '';
+    };
+    privacy = {
+      name = "Privacy";
+      nodes.router = { ... }: {
+        virtualisation.vlans = [ 1 ];
+        boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+        networking = {
+          useNetworkd = networkd;
+          interfaces.eth1.ipv6.addresses = singleton {
+            address = "fd00:1234:5678:1::1";
+            prefixLength = 64;
+          };
+        };
+        services.radvd = {
+          enable = true;
+          config = ''
+            interface eth1 {
+              AdvSendAdvert on;
+              AdvManagedFlag on;
+              AdvOtherConfigFlag on;
+
+              prefix fd00:1234:5678:1::/64 {
+                AdvAutonomous on;
+                AdvOnLink on;
+              };
+            };
+          '';
+        };
+      };
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = true;
+          interfaces.eth1 = {
+            preferTempAddress = true;
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
+          };
+        };
+      };
+      testScript = { ... }:
+        ''
+          startAll;
+
+          $client->waitForUnit("network.target");
+          $router->waitForUnit("network-online.target");
+
+          # Wait until we have an ip address
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
+
+          # Test vlan 1
+          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
+
+          # Test address used is temporary
+          $client->waitUntilSucceeds("! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'");
+        '';
+    };
+    routes = {
+      name = "routes";
+      machine = {
+        networking.useDHCP = false;
+        networking.interfaces."eth0" = {
+          ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
+          ipv6.routes = [
+            { address = "fdfd:b3f0::"; prefixLength = 48; }
+            { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
+          ];
+          ipv4.routes = [
+            { address = "10.0.0.0"; prefixLength = 16; options = { mtu = "1500"; }; }
+            { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
+          ];
+        };
+        virtualisation.vlans = [ ];
+      };
+
+      testScript = ''
+        my $targetIPv4Table = <<'END';
+        10.0.0.0/16 proto static scope link mtu 1500 
+        192.168.1.0/24 proto kernel scope link src 192.168.1.2 
+        192.168.2.0/24 via 192.168.1.1 proto static 
+        END
+
+        my $targetIPv6Table = <<'END';
+        2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium
+        2001:1470:fffd:2098::/64 via fdfd:b3f0::1 proto static metric 1024 pref medium
+        fdfd:b3f0::/48 proto static metric 1024 pref medium
+        END
+
+        $machine->start;
+        $machine->waitForUnit("network.target");
+
+        # test routing tables
+        my $ipv4Table = $machine->succeed("ip -4 route list dev eth0 | head -n3");
+        my $ipv6Table = $machine->succeed("ip -6 route list dev eth0 | head -n3");
+        "$ipv4Table" eq "$targetIPv4Table" or die(
+          "The IPv4 routing table does not match the expected one:\n",
+          "Result:\n", "$ipv4Table\n",
+          "Expected:\n", "$targetIPv4Table\n"
+        );
+        "$ipv6Table" eq "$targetIPv6Table" or die(
+          "The IPv6 routing table does not match the expected one:\n",
+          "Result:\n", "$ipv6Table\n",
+          "Expected:\n", "$targetIPv6Table\n"
+        );
+
+        # test clean-up of the tables
+        $machine->succeed("systemctl stop network-addresses-eth0");
+        my $ipv4Residue = $machine->succeed("ip -4 route list dev eth0 | head -n-3");
+        my $ipv6Residue = $machine->succeed("ip -6 route list dev eth0 | head -n-3");
+        $ipv4Residue eq "" or die(
+          "The IPv4 routing table has not been properly cleaned:\n",
+          "$ipv4Residue\n"
+        );
+        $ipv6Residue eq "" or die(
+          "The IPv6 routing table has not been properly cleaned:\n",
+          "$ipv6Residue\n"
+        );
+      '';
+    };
+  };
+
+in mapAttrs (const (attrs: makeTest (attrs // {
+  name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
+}))) testCases
diff --git a/nixpkgs/nixos/tests/nextcloud/basic.nix b/nixpkgs/nixos/tests/nextcloud/basic.nix
new file mode 100644
index 000000000000..c3b710f0f904
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/basic.nix
@@ -0,0 +1,56 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "notproduction";
+  adminuser = "root";
+in {
+  name = "nextcloud-basic";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ globin eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        nginx.enable = true;
+        hostName = "nextcloud";
+        config = {
+          # Don't inherit adminuser since "root" is supposed to be the default
+          inherit adminpass;
+        };
+      };
+    };
+  };
+
+  testScript = let
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${withRcloneEnv} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nextcloud/default.nix b/nixpkgs/nixos/tests/nextcloud/default.nix
new file mode 100644
index 000000000000..e4c7a70606cf
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/default.nix
@@ -0,0 +1,9 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; }
+}:
+{
+  basic = import ./basic.nix { inherit system pkgs; };
+  with-postgresql-and-redis = import ./with-postgresql-and-redis.nix { inherit system pkgs; };
+  with-mysql-and-memcached = import ./with-mysql-and-memcached.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix
new file mode 100644
index 000000000000..c0d347238b47
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -0,0 +1,97 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "root";
+in {
+  name = "nextcloud-with-mysql-and-memcached";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        nginx.enable = true;
+        https = true;
+        caching = {
+          apcu = true;
+          redis = false;
+          memcached = true;
+        };
+        config = {
+          dbtype = "mysql";
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbhost = "127.0.0.1";
+          dbport = 3306;
+          dbpass = "hunter2";
+          # Don't inherit adminuser since "root" is supposed to be the default
+          inherit adminpass;
+        };
+      };
+
+      services.mysql = {
+        enable = true;
+        bind = "127.0.0.1";
+        package = pkgs.mariadb;
+        initialScript = pkgs.writeText "mysql-init" ''
+          CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'hunter2';
+          CREATE DATABASE IF NOT EXISTS nextcloud;
+          GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
+            CREATE TEMPORARY TABLES ON nextcloud.* TO 'nextcloud'@'localhost'
+            IDENTIFIED BY 'hunter2';
+          FLUSH privileges;
+        '';
+      };
+
+      systemd.services."nextcloud-setup"= {
+        requires = ["mysql.service"];
+        after = ["mysql.service"];
+      };
+
+      services.memcached.enable = true;
+    };
+  };
+
+  testScript = let
+    configureMemcached = pkgs.writeScript "configure-memcached" ''
+      #!${pkgs.stdenv.shell}
+      nextcloud-occ config:system:set memcached_servers 0 0 --value 127.0.0.1 --type string
+      nextcloud-occ config:system:set memcached_servers 0 1 --value 11211 --type integer
+      nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\APCu' --type string
+      nextcloud-occ config:system:set memcache.distributed --value '\OC\Memcache\Memcached' --type string
+    '';
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("${configureMemcached}");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix
new file mode 100644
index 000000000000..0351d4db69ac
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -0,0 +1,130 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "custom-admin-username";
+in {
+  name = "nextcloud-with-postgresql-and-redis";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        nginx.enable = true;
+        caching = {
+          apcu = false;
+          redis = true;
+          memcached = false;
+        };
+        config = {
+          dbtype = "pgsql";
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbhost = "localhost";
+          dbpassFile = toString (pkgs.writeText "db-pass-file" ''
+            hunter2
+          '');
+          inherit adminuser;
+          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
+            ${adminpass}
+          '');
+        };
+      };
+
+      services.redis = {
+        unixSocket = "/var/run/redis/redis.sock";
+        enable = true;
+        extraConfig = ''
+          unixsocketperm 770
+        '';
+      };
+
+      systemd.services.redis = {
+        preStart = ''
+          mkdir -p /var/run/redis
+          chown ${config.services.redis.user}:${config.services.nginx.group} /var/run/redis
+        '';
+        serviceConfig.PermissionsStartOnly = true;
+      };
+
+      systemd.services."nextcloud-setup"= {
+        requires = ["postgresql.service"];
+        after = [
+          "postgresql.service"
+          "chown-redis-socket.service"
+        ];
+      };
+
+      # At the time of writing, redis creates its socket with the "nobody"
+      # group.  I figure this is slightly less bad than making the socket world
+      # readable.
+      systemd.services."chown-redis-socket" = {
+        enable = true;
+        script = ''
+          until ${pkgs.redis}/bin/redis-cli ping; do
+            echo "waiting for redis..."
+            sleep 1
+          done
+          chown ${config.services.redis.user}:${config.services.nginx.group} /var/run/redis/redis.sock
+        '';
+        after = [ "redis.service" ];
+        requires = [ "redis.service" ];
+        wantedBy = [ "redis.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      services.postgresql = {
+        enable = true;
+        initialScript = pkgs.writeText "psql-init" ''
+          create role nextcloud with login password 'hunter2';
+          create database nextcloud with owner nextcloud;
+        '';
+      };
+    };
+  };
+
+  testScript = let
+    configureRedis = pkgs.writeScript "configure-redis" ''
+      #!${pkgs.stdenv.shell}
+      nextcloud-occ config:system:set redis 'host' --value '/var/run/redis/redis.sock' --type string
+      nextcloud-occ config:system:set redis 'port' --value 0 --type integer
+      nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\Redis' --type string
+      nextcloud-occ config:system:set memcache.locking --value '\OC\Memcache\Redis' --type string
+    '';
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("${configureRedis}");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nexus.nix b/nixpkgs/nixos/tests/nexus.nix
new file mode 100644
index 000000000000..783c9f5c019f
--- /dev/null
+++ b/nixpkgs/nixos/tests/nexus.nix
@@ -0,0 +1,32 @@
+# verifies:
+#   1. nexus service starts on server
+#   2. nexus service can startup on server (creating database and all other initial stuff)
+#   3. the web application is reachable via HTTP
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "nexus";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ironpinguin ma27 ];
+  };
+
+  nodes = {
+
+    server =
+      { ... }:
+      { virtualisation.memorySize = 2047; # qemu-system-i386 has a 2047M limit
+        virtualisation.diskSize = 8192;
+
+        services.nexus.enable = true;
+      };
+
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("nexus");
+    $server->waitForOpenPort(8081);
+
+    $server->succeed("curl -f 127.0.0.1:8081");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nfs.nix b/nixpkgs/nixos/tests/nfs.nix
new file mode 100644
index 000000000000..ce1717018933
--- /dev/null
+++ b/nixpkgs/nixos/tests/nfs.nix
@@ -0,0 +1,90 @@
+import ./make-test.nix ({ pkgs, version ? 4, ... }:
+
+let
+
+  client =
+    { pkgs, ... }:
+    { fileSystems = pkgs.lib.mkVMOverride
+        [ { mountPoint = "/data";
+            # nfs4 exports the export with fsid=0 as a virtual root directory
+            device = if (version == 4) then "server:/" else "server:/data";
+            fsType = "nfs";
+            options = [ "vers=${toString version}" ];
+          }
+        ];
+      networking.firewall.enable = false; # FIXME: only open statd
+    };
+
+in
+
+{
+  name = "nfs";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow  ];
+  };
+
+  nodes =
+    { client1 = client;
+      client2 = client;
+
+      server =
+        { ... }:
+        { 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;
+          networking.firewall.enable = false; # FIXME: figure out what ports need to be allowed
+        };
+    };
+
+  testScript =
+    ''
+      $server->waitForUnit("nfs-server");
+      $server->succeed("systemctl start network-online.target");
+      $server->waitForUnit("network-online.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 ‘nfs-server’ works correctly.
+      $server->succeed("systemctl restart nfs-server");
+      $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/nixpkgs/nixos/tests/nghttpx.nix b/nixpkgs/nixos/tests/nghttpx.nix
new file mode 100644
index 000000000000..433562b97191
--- /dev/null
+++ b/nixpkgs/nixos/tests/nghttpx.nix
@@ -0,0 +1,61 @@
+let
+  nginxRoot = "/var/run/nginx";
+in
+  import ./make-test.nix ({...}: {
+    name  = "nghttpx";
+    nodes = {
+      webserver = {
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        systemd.services.nginx = {
+          preStart = ''
+            mkdir -p ${nginxRoot}
+            echo "Hello world!" > ${nginxRoot}/hello-world.txt
+          '';
+        };
+
+        services.nginx = {
+          enable = true;
+          virtualHosts."server" = {
+            locations."/".root = nginxRoot;
+          };
+        };
+      };
+
+      proxy = {
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        services.nghttpx = {
+          enable = true;
+          frontends = [
+            { server = {
+                host = "*";
+                port = 80;
+              };
+
+              params = {
+                tls = "no-tls";
+              };
+            }
+          ];
+          backends = [
+            { server = {
+                host = "webserver";
+                port = 80;
+              };
+              patterns = [ "/" ];
+              params.proto = "http/1.1";
+            }
+          ];
+        };
+      };
+
+      client = {};
+    };
+
+    testScript = ''
+      startAll;
+
+      $webserver->waitForOpenPort("80");
+      $proxy->waitForOpenPort("80");
+      $client->waitUntilSucceeds("curl -s --fail http://proxy/hello-world.txt");
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/nginx-sso.nix b/nixpkgs/nixos/tests/nginx-sso.nix
new file mode 100644
index 000000000000..e19992cb6bf7
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-sso.nix
@@ -0,0 +1,44 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "nginx-sso";
+  meta = {
+    maintainers = with pkgs.stdenv.lib.maintainers; [ delroth ];
+  };
+
+  machine = {
+    services.nginx.sso = {
+      enable = true;
+      configuration = {
+        listen = { addr = "127.0.0.1"; port = 8080; };
+
+        providers.token.tokens = {
+          myuser = "MyToken";
+        };
+
+        acl = {
+          rule_sets = [
+            {
+              rules = [ { field = "x-application"; equals = "MyApp"; } ];
+              allow = [ "myuser" ];
+            }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("nginx-sso.service");
+    $machine->waitForOpenPort(8080);
+
+    # No valid user -> 401.
+    $machine->fail("curl -sSf http://localhost:8080/auth");
+
+    # Valid user but no matching ACL -> 403.
+    $machine->fail("curl -sSf -H 'Authorization: Token MyToken' http://localhost:8080/auth");
+
+    # Valid user and matching ACL -> 200.
+    $machine->succeed("curl -sSf -H 'Authorization: Token MyToken' -H 'X-Application: MyApp' http://localhost:8080/auth");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx.nix b/nixpkgs/nixos/tests/nginx.nix
new file mode 100644
index 000000000000..32b113649237
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx.nix
@@ -0,0 +1,42 @@
+# verifies:
+#   1. nginx generates config file with shared http context definitions above
+#      generated virtual hosts config.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "nginx";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mbbx6spp ];
+  };
+
+  nodes = {
+    webserver =
+      { ... }:
+      { services.nginx.enable = true;
+        services.nginx.commonHttpConfig = ''
+        log_format ceeformat '@cee: {"status":"$status",'
+          '"request_time":$request_time,'
+          '"upstream_response_time":$upstream_response_time,'
+          '"pipe":"$pipe","bytes_sent":$bytes_sent,'
+          '"connection":"$connection",'
+          '"remote_addr":"$remote_addr",'
+          '"host":"$host",'
+          '"timestamp":"$time_iso8601",'
+          '"request":"$request",'
+          '"http_referer":"$http_referer",'
+          '"upstream_addr":"$upstream_addr"}';
+        '';
+        services.nginx.virtualHosts."0.my.test" = {
+          extraConfig = ''
+            access_log syslog:server=unix:/dev/log,facility=user,tag=mytag,severity=info ceeformat;
+          '';
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $webserver->waitForUnit("nginx");
+    $webserver->waitForOpenPort("80");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nix-ssh-serve.nix b/nixpkgs/nixos/tests/nix-ssh-serve.nix
new file mode 100644
index 000000000000..494d55121eb1
--- /dev/null
+++ b/nixpkgs/nixos/tests/nix-ssh-serve.nix
@@ -0,0 +1,39 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+let inherit (import ./ssh-keys.nix pkgs)
+      snakeOilPrivateKey snakeOilPublicKey;
+    ssh-config = builtins.toFile "ssh.conf" ''
+      UserKnownHostsFile=/dev/null
+      StrictHostKeyChecking=no
+    '';
+in
+   { name = "nix-ssh-serve";
+     meta.maintainers = [ lib.maintainers.shlevy ];
+     nodes =
+       { server.nix.sshServe =
+           { enable = true;
+             keys = [ snakeOilPublicKey ];
+             protocol = "ssh-ng";
+           };
+         server.nix.package = pkgs.nix;
+         client.nix.package = pkgs.nix;
+       };
+     testScript = ''
+       startAll;
+
+       $client->succeed("mkdir -m 700 /root/.ssh");
+       $client->copyFileFromHost("${ssh-config}", "/root/.ssh/config");
+       $client->succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa");
+       $client->succeed("chmod 600 /root/.ssh/id_ecdsa");
+
+       $client->succeed("nix-store --add /etc/machine-id > mach-id-path");
+
+       $server->waitForUnit("sshd");
+
+       $client->fail("diff /root/other-store\$(cat mach-id-path) /etc/machine-id");
+       # Currently due to shared store this is a noop :(
+       $client->succeed("nix copy --to ssh-ng://nix-ssh\@server \$(cat mach-id-path)");
+       $client->succeed("nix-store --realise \$(cat mach-id-path) --store /root/other-store --substituters ssh-ng://nix-ssh\@server");
+       $client->succeed("diff /root/other-store\$(cat mach-id-path) /etc/machine-id");
+     '';
+   }
+)
diff --git a/nixpkgs/nixos/tests/novacomd.nix b/nixpkgs/nixos/tests/novacomd.nix
new file mode 100644
index 000000000000..4eb60c0feb5c
--- /dev/null
+++ b/nixpkgs/nixos/tests/novacomd.nix
@@ -0,0 +1,34 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "novacomd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ dtzWill ];
+  };
+
+  machine = { ... }: {
+    services.novacomd.enable = true;
+  };
+
+  testScript = ''
+    $machine->waitForUnit("multi-user.target");
+
+    # multi-user.target wants novacomd.service, but let's make sure
+    $machine->waitForUnit("novacomd.service");
+
+    # Check status and try connecting with novacom
+    $machine->succeed("systemctl status novacomd.service >&2");
+    # to prevent non-deterministic failure,
+    # make sure the daemon is really listening
+    $machine->waitForOpenPort(6968);
+    $machine->succeed("novacom -l");
+
+    # Stop the daemon, double-check novacom fails if daemon isn't working
+    $machine->stopJob("novacomd");
+    $machine->fail("novacom -l");
+
+    # And back again for good measure
+    $machine->startJob("novacomd");
+    # make sure the daemon is really listening
+    $machine->waitForOpenPort(6968);
+    $machine->succeed("novacom -l");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nsd.nix b/nixpkgs/nixos/tests/nsd.nix
new file mode 100644
index 000000000000..c3c91e71b5ca
--- /dev/null
+++ b/nixpkgs/nixos/tests/nsd.nix
@@ -0,0 +1,101 @@
+let
+  common = { pkgs, ... }: {
+    networking.firewall.enable = false;
+    networking.useDHCP = false;
+    # for a host utility with IPv6 support
+    environment.systemPackages = [ pkgs.bind ];
+  };
+in import ./make-test.nix ({ pkgs, ...} : {
+  name = "nsd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  nodes = {
+    clientv4 = { lib, nodes, ... }: {
+      imports = [ common ];
+      networking.nameservers = lib.mkForce [
+        (lib.head nodes.server.config.networking.interfaces.eth1.ipv4.addresses).address
+      ];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "192.168.0.2"; prefixLength = 24; }
+      ];
+    };
+
+    clientv6 = { lib, nodes, ... }: {
+      imports = [ common ];
+      networking.nameservers = lib.mkForce [
+        (lib.head nodes.server.config.networking.interfaces.eth1.ipv6.addresses).address
+      ];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "dead:beef::2"; prefixLength = 24; }
+      ];
+    };
+
+    server = { lib, ... }: {
+      imports = [ common ];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "192.168.0.1"; prefixLength = 24; }
+      ];
+      networking.interfaces.eth1.ipv6.addresses = [
+        { address = "dead:beef::1"; prefixLength = 64; }
+      ];
+      services.nsd.enable = true;
+      services.nsd.rootServer = true;
+      services.nsd.interfaces = lib.mkForce [];
+      services.nsd.zones."example.com.".data = ''
+        @ SOA ns.example.com noc.example.com 666 7200 3600 1209600 3600
+        ipv4 A 1.2.3.4
+        ipv6 AAAA abcd::eeff
+        deleg NS ns.example.com
+        ns A 192.168.0.1
+        ns AAAA dead:beef::1
+      '';
+      services.nsd.zones."deleg.example.com.".data = ''
+        @ SOA ns.example.com noc.example.com 666 7200 3600 1209600 3600
+        @ A 9.8.7.6
+        @ AAAA fedc::bbaa
+      '';
+      services.nsd.zones.".".data = ''
+        @ SOA ns.example.com noc.example.com 666 7200 3600 1209600 3600
+        root A 1.8.7.4
+        root AAAA acbd::4
+      '';
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $clientv4->waitForUnit("network.target");
+    $clientv6->waitForUnit("network.target");
+    $server->waitForUnit("nsd.service");
+
+    sub assertHost {
+      my ($type, $rr, $query, $expected) = @_;
+      my $self = $type eq 4 ? $clientv4 : $clientv6;
+      my $out = $self->succeed("host -$type -t $rr $query");
+      $self->log("output: $out");
+      chomp $out;
+      die "DNS IPv$type query on $query gave '$out' instead of '$expected'"
+        if ($out !~ $expected);
+    }
+
+    foreach (4, 6) {
+      subtest "ipv$_", sub {
+        assertHost($_, "a", "example.com", qr/has no [^ ]+ record/);
+        assertHost($_, "aaaa", "example.com", qr/has no [^ ]+ record/);
+
+        assertHost($_, "soa", "example.com", qr/SOA.*?noc\.example\.com/);
+        assertHost($_, "a", "ipv4.example.com", qr/address 1.2.3.4$/);
+        assertHost($_, "aaaa", "ipv6.example.com", qr/address abcd::eeff$/);
+
+        assertHost($_, "a", "deleg.example.com", qr/address 9.8.7.6$/);
+        assertHost($_, "aaaa", "deleg.example.com", qr/address fedc::bbaa$/);
+
+        assertHost($_, "a", "root", qr/address 1.8.7.4$/);
+        assertHost($_, "aaaa", "root", qr/address acbd::4$/);
+      };
+    }
+  '';
+})
diff --git a/nixpkgs/nixos/tests/openldap.nix b/nixpkgs/nixos/tests/openldap.nix
new file mode 100644
index 000000000000..1eaf87a8eaa8
--- /dev/null
+++ b/nixpkgs/nixos/tests/openldap.nix
@@ -0,0 +1,35 @@
+import ./make-test.nix {
+  name = "openldap";
+
+  machine = { pkgs, ... }: {
+    services.openldap = {
+      enable = true;
+      extraConfig = ''
+        include ${pkgs.openldap}/etc/schema/core.schema
+        include ${pkgs.openldap}/etc/schema/cosine.schema
+        include ${pkgs.openldap}/etc/schema/inetorgperson.schema
+        include ${pkgs.openldap}/etc/schema/nis.schema
+        database bdb
+        suffix dc=example
+        directory /var/db/openldap
+        rootdn cn=root,dc=example
+        rootpw notapassword
+      '';
+      declarativeContents = ''
+        dn: dc=example
+        objectClass: domain
+        dc: example
+
+        dn: ou=users,dc=example
+        objectClass: organizationalUnit
+        ou: users
+      '';
+    };
+  };
+
+  testScript = ''
+    $machine->waitForUnit('openldap.service');
+    $machine->succeed('systemctl status openldap.service');
+    $machine->succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"');
+  '';
+}
diff --git a/nixpkgs/nixos/tests/opensmtpd.nix b/nixpkgs/nixos/tests/opensmtpd.nix
new file mode 100644
index 000000000000..883ad7604941
--- /dev/null
+++ b/nixpkgs/nixos/tests/opensmtpd.nix
@@ -0,0 +1,125 @@
+import ./make-test.nix {
+  name = "opensmtpd";
+
+  nodes = {
+    smtp1 = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      networking = {
+        firewall.allowedTCPPorts = [ 25 ];
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = [ pkgs.opensmtpd ];
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          listen on 0.0.0.0
+          action do_relay relay
+          # DO NOT DO THIS IN PRODUCTION!
+          # Setting up authentication requires a certificate which is painful in
+          # a test environment, but THIS WOULD BE DANGEROUS OUTSIDE OF A
+          # WELL-CONTROLLED ENVIRONMENT!
+          match from any for any action do_relay
+        '';
+      };
+    };
+
+    smtp2 = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      networking = {
+        firewall.allowedTCPPorts = [ 25 143 ];
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = [ pkgs.opensmtpd ];
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          listen on 0.0.0.0
+          action dovecot_deliver mda \
+            "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
+        '';
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        mailLocation = "maildir:~/mail";
+        protocols = [ "imap" ];
+      };
+    };
+
+    client = { pkgs, ... }: {
+      networking = {
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.3"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = let
+        sendTestMail = pkgs.writeScriptBin "send-a-test-mail" ''
+          #!${pkgs.python3.interpreter}
+          import smtplib, sys
+
+          with smtplib.SMTP('192.168.1.1') as smtp:
+            smtp.sendmail('alice@[192.168.1.1]', 'bob@[192.168.1.2]', """
+              From: alice@smtp1
+              To: bob@smtp2
+              Subject: Test
+
+              Hello World
+            """)
+        '';
+
+        checkMailLanded = pkgs.writeScriptBin "check-mail-landed" ''
+          #!${pkgs.python3.interpreter}
+          import imaplib
+
+          with imaplib.IMAP4('192.168.1.2', 143) as imap:
+            imap.login('bob', 'foobar')
+            imap.select()
+            status, refs = imap.search(None, 'ALL')
+            assert status == 'OK'
+            assert len(refs) == 1
+            status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+            assert status == 'OK'
+            content = msg[0][1]
+            print("===> content:", content)
+            split = content.split(b'\r\n')
+            print("===> split:", split)
+            lastline = split[-3]
+            print("===> lastline:", lastline)
+            assert lastline.strip() == b'Hello World'
+        '';
+      in [ sendTestMail checkMailLanded ];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $client->waitForUnit("network-online.target");
+    $smtp1->waitForUnit('opensmtpd');
+    $smtp2->waitForUnit('opensmtpd');
+    $smtp2->waitForUnit('dovecot2');
+
+    # To prevent sporadic failures during daemon startup, make sure
+    # services are listening on their ports before sending requests
+    $smtp1->waitForOpenPort(25);
+    $smtp2->waitForOpenPort(25);
+    $smtp2->waitForOpenPort(143);
+
+    $client->succeed('send-a-test-mail');
+    $smtp1->waitUntilFails('smtpctl show queue | egrep .');
+    $smtp2->waitUntilFails('smtpctl show queue | egrep .');
+    $client->succeed('check-mail-landed >&2');
+  '';
+
+  meta.timeout = 30;
+}
diff --git a/nixpkgs/nixos/tests/openssh.nix b/nixpkgs/nixos/tests/openssh.nix
new file mode 100644
index 000000000000..c66b90b802d5
--- /dev/null
+++ b/nixpkgs/nixos/tests/openssh.nix
@@ -0,0 +1,81 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+let inherit (import ./ssh-keys.nix pkgs)
+      snakeOilPrivateKey snakeOilPublicKey;
+in {
+  name = "openssh";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aszlig eelco chaoflow ];
+  };
+
+  nodes = {
+
+    server =
+      { ... }:
+
+      {
+        services.openssh.enable = true;
+        security.pam.services.sshd.limits =
+          [ { domain = "*"; item = "memlock"; type = "-"; value = 1024; } ];
+        users.users.root.openssh.authorizedKeys.keys = [
+          snakeOilPublicKey
+        ];
+      };
+
+    server_lazy =
+      { ... }:
+
+      {
+        services.openssh = { enable = true; startWhenNeeded = true; };
+        security.pam.services.sshd.limits =
+          [ { domain = "*"; item = "memlock"; type = "-"; value = 1024; } ];
+        users.users.root.openssh.authorizedKeys.keys = [
+          snakeOilPublicKey
+        ];
+      };
+
+    client =
+      { ... }: { };
+
+  };
+
+  testScript = ''
+    startAll;
+
+    my $key=`${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f key -N ""`;
+
+    $server->waitForUnit("sshd");
+
+    subtest "manual-authkey", sub {
+      $server->succeed("mkdir -m 700 /root/.ssh");
+      $server->copyFileFromHost("key.pub", "/root/.ssh/authorized_keys");
+      $server_lazy->succeed("mkdir -m 700 /root/.ssh");
+      $server_lazy->copyFileFromHost("key.pub", "/root/.ssh/authorized_keys");
+
+      $client->succeed("mkdir -m 700 /root/.ssh");
+      $client->copyFileFromHost("key", "/root/.ssh/id_ed25519");
+      $client->succeed("chmod 600 /root/.ssh/id_ed25519");
+
+      $client->waitForUnit("network.target");
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2");
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024");
+
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'echo hello world' >&2");
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'ulimit -l' | grep 1024");
+
+    };
+
+    subtest "configured-authkey", sub {
+      $client->succeed("cat ${snakeOilPrivateKey} > privkey.snakeoil");
+      $client->succeed("chmod 600 privkey.snakeoil");
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null" .
+                       " -o StrictHostKeyChecking=no -i privkey.snakeoil" .
+                       " server true");
+
+      $client->succeed("ssh -o UserKnownHostsFile=/dev/null" .
+                       " -o StrictHostKeyChecking=no -i privkey.snakeoil" .
+                       " server_lazy true");
+
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/osquery.nix b/nixpkgs/nixos/tests/osquery.nix
new file mode 100644
index 000000000000..281dbcff6643
--- /dev/null
+++ b/nixpkgs/nixos/tests/osquery.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "osquery";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  machine = {
+    services.osquery.enable = true;
+    services.osquery.loggerPath = "/var/log/osquery/logs";
+    services.osquery.pidfile = "/var/run/osqueryd.pid";
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForUnit("osqueryd.service");
+
+    $machine->succeed("echo 'SELECT address FROM etc_hosts LIMIT 1;' | osqueryi | grep '127.0.0.1'");
+    $machine->succeed(
+      "echo 'SELECT value FROM osquery_flags WHERE name = \"logger_path\";' | osqueryi | grep /var/log/osquery/logs"
+    );
+
+    $machine->succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"pidfile\";' | osqueryi | grep /var/run/osqueryd.pid");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ostree.nix b/nixpkgs/nixos/tests/ostree.nix
new file mode 100644
index 000000000000..8b19004874e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/ostree.nix
@@ -0,0 +1,21 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, lib, ... }: {
+  name = "ostree";
+
+  meta = {
+    maintainers = pkgs.ostree.meta.maintainers;
+  };
+
+  # TODO: Wrap/patch the tests directly in the package
+  machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [
+      gnome-desktop-testing ostree gnupg (python3.withPackages (p: with p; [ pyyaml ]))
+    ];
+
+    environment.variables.GI_TYPELIB_PATH = lib.makeSearchPath "lib/girepository-1.0" (with pkgs; [ gtk3 pango.out ostree gdk_pixbuf atk ]); # for GJS tests
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner -d ${pkgs.ostree.installedTests}/share");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pam-oath-login.nix b/nixpkgs/nixos/tests/pam-oath-login.nix
new file mode 100644
index 000000000000..b9d489950e72
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam-oath-login.nix
@@ -0,0 +1,124 @@
+import ./make-test.nix ({ ... }:
+
+let
+  oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3";
+
+  # With HOTP mode the password is calculated based on a counter of
+  # how many passwords have been made. In this env, we'll always be on
+  # the 0th counter, so the password is static.
+  #
+  # Generated in nix-shell -p oathToolkit
+  # via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3
+  # and picking a the first 4:
+  oathSnakeOilPassword1 = "143349";
+  oathSnakeOilPassword2 = "801753";
+
+  alicePassword = "foobar";
+  # Generated via: mkpasswd -m sha-512 and passing in "foobar"
+  hashedAlicePassword = "$6$MsMrE1q.1HrCgTS$Vq2e/uILzYjSN836TobAyN9xh9oi7EmCmucnZID25qgPoibkw8qTCugiAPnn4eCGvn1A.7oEBFJaaGUaJsQQY.";
+
+in
+{
+  name = "pam-oath-login";
+
+  machine =
+    { ... }:
+    {
+      security.pam.oath = {
+        enable = true;
+      };
+
+      users.users.alice = {
+        isNormalUser = true;
+        name = "alice";
+        uid = 1000;
+        hashedPassword = hashedAlicePassword;
+        extraGroups = [ "wheel" ];
+        createHome = true;
+        home = "/home/alice";
+      };
+
+
+      systemd.services.setupOathSnakeoilFile = {
+        wantedBy = [ "default.target" ];
+        before = [ "default.target" ];
+        unitConfig = {
+          type = "oneshot";
+          RemainAfterExit = true;
+        };
+        script = ''
+          touch /etc/users.oath
+          chmod 600 /etc/users.oath
+          chown root /etc/users.oath
+          echo "HOTP/E/6 alice - ${oathSnakeoilSecret}" > /etc/users.oath
+        '';
+      };
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit('multi-user.target');
+      $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'");
+      $machine->screenshot("postboot");
+
+
+      subtest "Invalid password", sub {
+        $machine->fail("pgrep -f 'agetty.*tty2'");
+        $machine->sendKeys("alt-f2");
+        $machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
+        $machine->waitForUnit('getty@tty2.service');
+        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'");
+
+        $machine->waitUntilTTYMatches(2, "login: ");
+        $machine->sendChars("alice\n");
+        $machine->waitUntilTTYMatches(2, "login: alice");
+        $machine->waitUntilSucceeds("pgrep login");
+
+        $machine->waitUntilTTYMatches(2, "One-time password");
+        $machine->sendChars("${oathSnakeOilPassword1}\n");
+        $machine->waitUntilTTYMatches(2, "Password: ");
+        $machine->sendChars("blorg\n");
+        $machine->waitUntilTTYMatches(2, "Login incorrect");
+      };
+
+      subtest "Invalid oath token", sub {
+        $machine->fail("pgrep -f 'agetty.*tty3'");
+        $machine->sendKeys("alt-f3");
+        $machine->waitUntilSucceeds("[ \$(fgconsole) = 3 ]");
+        $machine->waitForUnit('getty@tty3.service');
+        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty3'");
+
+        $machine->waitUntilTTYMatches(3, "login: ");
+        $machine->sendChars("alice\n");
+        $machine->waitUntilTTYMatches(3, "login: alice");
+        $machine->waitUntilSucceeds("pgrep login");
+        $machine->waitUntilTTYMatches(3, "One-time password");
+        $machine->sendChars("000000\n");
+        $machine->waitUntilTTYMatches(3, "Login incorrect");
+        $machine->waitUntilTTYMatches(3, "login:");
+      };
+
+      subtest "Happy path (both passwords are mandatory to get us in)", sub {
+        $machine->fail("pgrep -f 'agetty.*tty4'");
+        $machine->sendKeys("alt-f4");
+        $machine->waitUntilSucceeds("[ \$(fgconsole) = 4 ]");
+        $machine->waitForUnit('getty@tty4.service');
+        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty4'");
+
+        $machine->waitUntilTTYMatches(4, "login: ");
+        $machine->sendChars("alice\n");
+        $machine->waitUntilTTYMatches(4, "login: alice");
+        $machine->waitUntilSucceeds("pgrep login");
+        $machine->waitUntilTTYMatches(4, "One-time password");
+        $machine->sendChars("${oathSnakeOilPassword2}\n");
+        $machine->waitUntilTTYMatches(4, "Password: ");
+        $machine->sendChars("${alicePassword}\n");
+
+        $machine->waitUntilSucceeds("pgrep -u alice bash");
+        $machine->sendChars("touch  done4\n");
+        $machine->waitForFile("/home/alice/done4");
+      };
+
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/pam-u2f.nix b/nixpkgs/nixos/tests/pam-u2f.nix
new file mode 100644
index 000000000000..1052a2f3b916
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam-u2f.nix
@@ -0,0 +1,23 @@
+import ./make-test.nix ({ ... }:
+
+{
+  name = "pam-u2f";
+
+  machine =
+    { ... }:
+    {
+      security.pam.u2f = {
+        control = "required";
+        cue = true;
+        debug = true;
+        enable = true;
+        interactive = true;
+      };
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit('multi-user.target');
+      $machine->succeed('egrep "auth required .*/lib/security/pam_u2f.so.*debug.*interactive.*cue" /etc/pam.d/ -R');
+    '';
+})
diff --git a/nixpkgs/nixos/tests/partition.nix b/nixpkgs/nixos/tests/partition.nix
new file mode 100644
index 000000000000..01a08995950f
--- /dev/null
+++ b/nixpkgs/nixos/tests/partition.nix
@@ -0,0 +1,247 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+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
+  '';
+
+  ksF2fs = pkgs.writeText "ks-f2fs" ''
+    clearpart --all --initlabel --drives=vdb
+
+    part swap  --recommended --label=swap --fstype=swap --ondisk=vdb
+    part /boot --recommended --label=boot --fstype=f2fs --ondisk=vdb
+    part /     --recommended --label=root --fstype=f2fs --ondisk=vdb
+  '';
+
+  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 {
+  name = "partitiion";
+
+  machine = { pkgs, ... }: {
+    environment.systemPackages = [
+      pkgs.pythonPackages.nixpart0
+      pkgs.file pkgs.btrfs-progs 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 "f2fs filesystem", sub {
+      $machine->succeed("modprobe f2fs");
+      kickstart("${ksF2fs}");
+      ensurePartition("swap", "swap");
+      ensurePartition("boot", "f2fs");
+      ensurePartition("root", "f2fs");
+      remountAndCheck;
+      ensureMountPoint("/mnt/boot", "f2fs");
+    };
+
+    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/nixpkgs/nixos/tests/peerflix.nix b/nixpkgs/nixos/tests/peerflix.nix
new file mode 100644
index 000000000000..fae37fedaac7
--- /dev/null
+++ b/nixpkgs/nixos/tests/peerflix.nix
@@ -0,0 +1,23 @@
+# This test runs peerflix and checks if peerflix starts
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "peerflix";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    peerflix =
+      { ... }:
+        {
+          services.peerflix.enable = true;
+        };
+    };
+
+  testScript = ''
+    startAll;
+
+    $peerflix->waitForUnit("peerflix.service");
+    $peerflix->waitUntilSucceeds("curl localhost:9000");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pgjwt.nix b/nixpkgs/nixos/tests/pgjwt.nix
new file mode 100644
index 000000000000..a2d81288c812
--- /dev/null
+++ b/nixpkgs/nixos/tests/pgjwt.nix
@@ -0,0 +1,37 @@
+import ./make-test.nix ({ pkgs, lib, ...}:
+let
+  test = with pkgs; runCommand "patch-test" {
+    nativeBuildInputs = [ pgjwt ];
+  }
+  ''
+    sed -e '12 i CREATE EXTENSION pgcrypto;\nCREATE EXTENSION pgtap;\nSET search_path TO tap,public;' ${pgjwt.src}/test.sql > $out;
+  '';
+in
+with pkgs; {
+  name = "pgjwt";
+  meta = with lib.maintainers; {
+    maintainers = [ spinus willibutz ];
+  };
+
+  nodes = {
+    master = { ... }:
+    {
+      services.postgresql = {
+        enable = true;
+        extraPlugins = [ pgjwt pgtap ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+  let
+    sqlSU = "${nodes.master.config.services.postgresql.superUser}";
+    pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}";
+  in
+  ''
+    startAll;
+    $master->waitForUnit("postgresql");
+    $master->copyFileFromHost("${test}","/tmp/test.sql");
+    $master->succeed("${pkgs.sudo}/bin/sudo -u ${sqlSU} PGOPTIONS=--search_path=tap,public ${pgProve}/bin/pg_prove -d postgres -v -f /tmp/test.sql");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pgmanage.nix b/nixpkgs/nixos/tests/pgmanage.nix
new file mode 100644
index 000000000000..110cbd5c5b40
--- /dev/null
+++ b/nixpkgs/nixos/tests/pgmanage.nix
@@ -0,0 +1,39 @@
+import ./make-test.nix ({ pkgs, ... } :
+let
+  role     = "test";
+  password = "secret";
+  conn     = "local";
+in
+{
+  name = "pgmanage";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ basvandijk ];
+  };
+  nodes = {
+    one = { config, pkgs, ... }: {
+      services = {
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script" ''
+            CREATE ROLE ${role} SUPERUSER LOGIN PASSWORD '${password}';
+          '';
+        };
+        pgmanage = {
+          enable = true;
+          connections = {
+            "${conn}" = "hostaddr=127.0.0.1 port=${toString config.services.postgresql.port} dbname=postgres";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $one->waitForUnit("default.target");
+    $one->requireActiveUnit("pgmanage.service");
+
+    # Test if we can log in.
+    $one->waitUntilSucceeds("curl 'http://localhost:8080/pgmanage/auth' --data 'action=login&connname=${conn}&username=${role}&password=${password}' --fail");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/phabricator.nix b/nixpkgs/nixos/tests/phabricator.nix
new file mode 100644
index 000000000000..20b3b838aba8
--- /dev/null
+++ b/nixpkgs/nixos/tests/phabricator.nix
@@ -0,0 +1,77 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "phabricator";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ chaoflow ];
+  };
+
+  nodes = {
+    storage =
+      { ... }:
+      { 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;
+      };
+
+    webserver =
+      { pkgs, ... }:
+      { fileSystems = pkgs.lib.mkVMOverride
+          [ { mountPoint = "/repos";
+              device = "storage:/repos";
+              fsType = "nfs";
+            }
+          ];
+        networking.firewall.enable = false;
+        networking.useDHCP = false;
+
+        services = {
+          httpd = {
+            enable = true;
+            adminAddr = "root@localhost";
+            virtualHosts = [{
+              hostName = "phabricator.local";
+              extraSubservices = [{serviceType = "phabricator";}];
+            }];
+          };
+
+          phd = {
+            enable = true;
+          };
+
+          mysql = {
+            enable = true;
+            package = pkgs.mysql;
+            extraOptions = ''
+              sql_mode=STRICT_ALL_TABLES
+            '';
+          };
+        };
+
+        environment.systemPackages = [ pkgs.php ];
+      };
+
+    client =
+      { ... }:
+      { imports = [ ./common/x11.nix ];
+        services.xserver.desktopManager.plasma5.enable = true;
+      };
+  };
+
+  testScript =
+    ''
+      startAll;
+
+      $client->waitForX;
+
+      $webserver->waitForUnit("mysql");
+      $webserver->waitForUnit("httpd");
+      $webserver->execute("cd /nix/store; less >/repos/log1");
+
+      $client->sleep(30); # loading takes a long time
+      $client->execute("konqueror http://webserver/ &");
+      $client->sleep(90); # loading takes a long time
+
+      $client->screenshot("screen");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/php-pcre.nix b/nixpkgs/nixos/tests/php-pcre.nix
new file mode 100644
index 000000000000..19bde9babad5
--- /dev/null
+++ b/nixpkgs/nixos/tests/php-pcre.nix
@@ -0,0 +1,44 @@
+
+let testString = "can-use-subgroups"; in
+
+import ./make-test.nix ({ ...}: {
+  name = "php-httpd-pcre-jit-test";
+  machine = { lib, pkgs, ... }: {
+    time.timeZone = "UTC";
+    services.httpd = {
+      enable = true;
+      adminAddr = "please@dont.contact";
+      extraSubservices = lib.singleton {
+        function = f: {
+          enablePHP = true;
+          phpOptions = "pcre.jit = true";
+
+          extraConfig =
+          let
+            testRoot = pkgs.writeText "index.php"
+            ''
+              <?php
+                preg_match('/(${testString})/', '${testString}', $result);
+                var_dump($result);
+              ?>
+            '';
+          in
+            ''
+              Alias / ${testRoot}/
+
+              <Directory ${testRoot}>
+                Require all granted
+              </Directory>
+            '';
+        };
+      };
+    };
+  };
+  testScript = { ... }:
+  ''
+    $machine->waitForUnit('httpd.service');
+    # Ensure php evaluation by matching on the var_dump syntax
+    $machine->succeed('curl -vvv -s http://127.0.0.1:80/index.php \
+      | grep "string(${toString (builtins.stringLength testString)}) \"${testString}\""');
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plasma5.nix b/nixpkgs/nixos/tests/plasma5.nix
new file mode 100644
index 000000000000..788c8719c8d2
--- /dev/null
+++ b/nixpkgs/nixos/tests/plasma5.nix
@@ -0,0 +1,64 @@
+import ./make-test.nix ({ pkgs, ...} :
+
+{
+  name = "plasma5";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ttuegel ];
+  };
+
+  machine = { ... }:
+  let
+    sddm_theme = pkgs.stdenv.mkDerivation {
+      name = "breeze-ocr-theme";
+      phases = "buildPhase";
+      buildCommand = ''
+        mkdir -p $out/share/sddm/themes/
+        cp -r ${pkgs.plasma-workspace}/share/sddm/themes/breeze $out/share/sddm/themes/breeze-ocr-theme
+        chmod -R +w $out/share/sddm/themes/breeze-ocr-theme
+        printf "[General]\ntype=color\ncolor=#1d99f3\nbackground=\n" > $out/share/sddm/themes/breeze-ocr-theme/theme.conf
+      '';
+    };
+  in
+  {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.sddm.enable = true;
+    services.xserver.displayManager.sddm.theme = "breeze-ocr-theme";
+    services.xserver.desktopManager.plasma5.enable = true;
+    services.xserver.desktopManager.default = "plasma5";
+    services.xserver.displayManager.sddm.autoLogin = {
+      enable = true;
+      user = "alice";
+    };
+    virtualisation.memorySize = 1024;
+    environment.systemPackages = [ sddm_theme ];
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+    xdo = "${pkgs.xdotool}/bin/xdotool";
+  in ''
+    startAll;
+    # wait for log in
+    $machine->waitForFile("/home/alice/.Xauthority");
+    $machine->succeed("xauth merge ~alice/.Xauthority");
+
+    $machine->waitUntilSucceeds("pgrep plasmashell");
+    $machine->waitForWindow("^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 dolphin &'");
+    $machine->waitForWindow(" Dolphin");
+
+    $machine->execute("su - alice -c 'DISPLAY=:0.0 konsole &'");
+    $machine->waitForWindow("Konsole");
+
+    $machine->execute("su - alice -c 'DISPLAY=:0.0 systemsettings5 &'");
+    $machine->waitForWindow("Settings");
+
+    $machine->execute("${xdo} key Alt+F1 sleep 10");
+    $machine->screenshot("screen");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plotinus.nix b/nixpkgs/nixos/tests/plotinus.nix
new file mode 100644
index 000000000000..609afe7b2145
--- /dev/null
+++ b/nixpkgs/nixos/tests/plotinus.nix
@@ -0,0 +1,27 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "plotinus";
+  meta = {
+    maintainers = pkgs.plotinus.meta.maintainers;
+  };
+
+  machine =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      programs.plotinus.enable = true;
+      environment.systemPackages = [ pkgs.gnome3.gnome-calculator pkgs.xdotool ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForX;
+      $machine->succeed("gnome-calculator &");
+      $machine->waitForWindow(qr/gnome-calculator/);
+      $machine->succeed("xdotool search --sync --onlyvisible --class gnome-calculator windowfocus --sync key ctrl+shift+p");
+      $machine->sleep(5); # wait for the popup
+      $machine->succeed("xdotool key --delay 100 p r e f e r e n c e s Return");
+      $machine->waitForWindow(qr/Preferences/);
+      $machine->screenshot("screen");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/postgis.nix b/nixpkgs/nixos/tests/postgis.nix
new file mode 100644
index 000000000000..294eb50b5fe5
--- /dev/null
+++ b/nixpkgs/nixos/tests/postgis.nix
@@ -0,0 +1,29 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "postgis";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lsix ];
+  };
+
+  nodes = {
+    master =
+      { pkgs, ... }:
+
+      {
+        services.postgresql = let mypg = pkgs.postgresql_11; in {
+            enable = true;
+            package = mypg;
+            extraPlugins = with mypg.pkgs; [
+              postgis
+            ];
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $master->waitForUnit("postgresql");
+    $master->sleep(10); # Hopefully this is long enough!!
+    $master->succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis;'");
+    $master->succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_topology;'");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/postgresql.nix b/nixpkgs/nixos/tests/postgresql.nix
new file mode 100644
index 000000000000..ae5d6d095ea2
--- /dev/null
+++ b/nixpkgs/nixos/tests/postgresql.nix
@@ -0,0 +1,73 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE EXTENSION pgcrypto; -- just to check if lib loading works
+    CREATE TABLE sth (
+      id int
+    );
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    CREATE TABLE xmltest ( doc xml );
+    INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
+  '';
+  make-postgresql-test = postgresql-name: postgresql-package: backup-all: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ zagy ];
+    };
+
+    machine = {...}:
+      {
+        services.postgresql.enable = true;
+        services.postgresql.package = postgresql-package;
+
+        services.postgresqlBackup.enable = true;
+        services.postgresqlBackup.databases = optional (!backup-all) "postgres";
+      };
+
+    testScript = let
+      backupName = if backup-all then "all" else "postgres";
+      backupService = if backup-all then "postgresqlBackup" else "postgresqlBackup-postgres";
+    in ''
+      sub check_count {
+        my ($select, $nlines) = @_;
+        return 'test $(sudo -u postgres psql postgres -tAc "' . $select . '"|wc -l) -eq ' . $nlines;
+      }
+
+      $machine->start;
+      $machine->waitForUnit("postgresql");
+      # postgresql should be available just after unit start
+      $machine->succeed("cat ${test-sql} | sudo -u postgres psql");
+      $machine->shutdown; # make sure that postgresql survive restart (bug #1735)
+      sleep(2);
+      $machine->start;
+      $machine->waitForUnit("postgresql");
+      $machine->fail(check_count("SELECT * FROM sth;", 3));
+      $machine->succeed(check_count("SELECT * FROM sth;", 5));
+      $machine->fail(check_count("SELECT * FROM sth;", 4));
+      $machine->succeed(check_count("SELECT xpath(\'/test/text()\', doc) FROM xmltest;", 1));
+
+      # Check backup service
+      $machine->succeed("systemctl start ${backupService}.service");
+      $machine->succeed("zcat /var/backup/postgresql/${backupName}.sql.gz | grep '<test>ok</test>'");
+      $machine->succeed("stat -c '%a' /var/backup/postgresql/${backupName}.sql.gz | grep 600");
+      $machine->shutdown;
+    '';
+
+  };
+in
+  (mapAttrs' (name: package: { inherit name; value=make-postgresql-test name package false;}) postgresql-versions) // {
+    postgresql_11-backup-all = make-postgresql-test "postgresql_11-backup-all" postgresql-versions.postgresql_11 true;
+  }
+
diff --git a/nixpkgs/nixos/tests/powerdns.nix b/nixpkgs/nixos/tests/powerdns.nix
new file mode 100644
index 000000000000..8addcc784012
--- /dev/null
+++ b/nixpkgs/nixos/tests/powerdns.nix
@@ -0,0 +1,12 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "powerdns";
+
+  nodes.server = { ... }: {
+    services.powerdns.enable = true;
+  };
+
+  testScript = ''
+    $server->waitForUnit("pdns");
+    $server->succeed("${pkgs.dnsutils}/bin/dig version.bind txt chaos \@127.0.0.1");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/predictable-interface-names.nix b/nixpkgs/nixos/tests/predictable-interface-names.nix
new file mode 100644
index 000000000000..8306abb8c42f
--- /dev/null
+++ b/nixpkgs/nixos/tests/predictable-interface-names.nix
@@ -0,0 +1,27 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+let
+  inherit (import ../lib/testing.nix { inherit system pkgs; }) makeTest;
+in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: {
+  name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
+       + pkgs.lib.optionalString withNetworkd "Networkd";
+  value = makeTest {
+    name = "${if predictable then "" else "un"}predictableInterfaceNames${if withNetworkd then "-with-networkd" else ""}";
+    meta = {};
+
+    machine = { lib, ... }: {
+      networking.usePredictableInterfaceNames = lib.mkForce predictable;
+      networking.useNetworkd = withNetworkd;
+      networking.dhcpcd.enable = !withNetworkd;
+    };
+
+    testScript = ''
+      print $machine->succeed("ip link");
+      $machine->succeed("ip link show ${if predictable then "ens3" else "eth0"}");
+      $machine->fail("ip link show ${if predictable then "eth0" else "ens3"}");
+    '';
+  };
+}) [[true false] [true false]])
diff --git a/nixpkgs/nixos/tests/printing.nix b/nixpkgs/nixos/tests/printing.nix
new file mode 100644
index 000000000000..d85abf3c105c
--- /dev/null
+++ b/nixpkgs/nixos/tests/printing.nix
@@ -0,0 +1,101 @@
+# Test printing via CUPS.
+
+import ./make-test.nix ({pkgs, ... }: {
+  name = "printing";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ domenkozar eelco chaoflow jgeerds ];
+  };
+
+  nodes = {
+
+    server =
+      { ... }:
+      { services.printing.enable = true;
+        services.printing.listenAddresses = [ "*:631" ];
+        services.printing.defaultShared = true;
+        services.printing.extraConf =
+          ''
+            <Location />
+              Order allow,deny
+              Allow from all
+            </Location>
+          '';
+        networking.firewall.allowedTCPPorts = [ 631 ];
+      };
+
+    client =
+      { ... }:
+      { services.printing.enable = true;
+      };
+
+  };
+
+  testScript =
+    ''
+      startAll;
+
+      # Make sure that cups is up on both sides.
+      $server->waitForUnit("cups.service");
+      $client->waitForUnit("cups.service");
+      $client->sleep(10); # wait until cups is fully initialized
+      $client->succeed("lpstat -r") =~ /scheduler is running/ or die;
+      # Test that UNIX socket is used for connections.
+      $client->succeed("lpstat -H") =~ "/var/run/cups/cups.sock" or die;
+      # Test that HTTP server is available too.
+      $client->succeed("curl --fail http://localhost:631/");
+      $client->succeed("curl --fail http://server:631/");
+      $server->fail("curl --fail --connect-timeout 2  http://client:631/");
+
+      # Add a HP Deskjet printer connected via USB to the server.
+      $server->succeed("lpadmin -p DeskjetLocal -E -v usb://foobar/printers/foobar");
+
+      # Add it to the client as well via IPP.
+      $client->succeed("lpadmin -p DeskjetRemote -E -v ipp://server/printers/DeskjetLocal");
+      $client->succeed("lpadmin -d DeskjetRemote");
+
+      # Do some status checks.
+      $client->succeed("lpstat -a") =~ /DeskjetRemote accepting requests/ or die;
+      $client->succeed("lpstat -h server:631 -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.doc}/share/doc/*/examples/mom/penguin.pdf",
+                        "${pkgs.groff.doc}/share/doc/*/meref.ps",
+                        "${pkgs.cups.out}/share/doc/cups/images/cups.png",
+                        "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt")
+      {
+          $file =~ /([^\/]*)$/; my $fn = $1;
+
+          subtest "print $fn", sub {
+
+              # Print the file on the client.
+              $client->succeed("lp $file");
+              $client->sleep(10);
+              $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*-001");
+              $server->sleep(10);
+              $server->succeed("lpq -a") =~ /$fn/ or die;
+
+              # Delete the job on the client.  It should disappear on the
+              # server as well.
+              $client->succeed("lprm");
+              $client->sleep(10);
+              $client->succeed("lpq -a") =~ /no entries/;
+              Machine::retry sub {
+                return 1 if $server->succeed("lpq -a") =~ /no entries/;
+              };
+              # The queue is empty already, so this should be safe.
+              # Otherwise, pairs of "c*"-"d*-001" files might persist.
+              $server->execute("rm /var/spool/cups/*");
+          };
+      }
+    '';
+})
diff --git a/nixpkgs/nixos/tests/prometheus-exporters.nix b/nixpkgs/nixos/tests/prometheus-exporters.nix
new file mode 100644
index 000000000000..140687a8182f
--- /dev/null
+++ b/nixpkgs/nixos/tests/prometheus-exporters.nix
@@ -0,0 +1,335 @@
+import ./make-test.nix ({ lib, pkgs, ... }:
+let
+  escape' = str: lib.replaceChars [''"'' "$" "\n"] [''\\\"'' "\\$" ""] str;
+
+/*
+ * The attrset `exporterTests` contains one attribute
+ * for each exporter test. Each of these attributes
+ * is expected to be an attrset containing:
+ *
+ *  `exporterConfig`:
+ *    this attribute set contains config for the exporter itself
+ *
+ *  `exporterTest`
+ *    this attribute set contains test instructions
+ *
+ *  `metricProvider` (optional)
+ *    this attribute contains additional machine config
+ *
+ *  Example:
+ *    exporterTests.<exporterName> = {
+ *      exporterConfig = {
+ *        enable = true;
+ *      };
+ *      metricProvider = {
+ *        services.<metricProvider>.enable = true;
+ *      };
+ *      exporterTest = ''
+ *        waitForUnit("prometheus-<exporterName>-exporter.service");
+ *        waitForOpenPort("1234");
+ *        succeed("curl -sSf 'localhost:1234/metrics'");
+ *      '';
+ *    };
+ *
+ *  # this would generate the following test config:
+ *
+ *    nodes.<exporterName> = {
+ *      services.prometheus.<exporterName> = {
+ *        enable = true;
+ *      };
+ *      services.<metricProvider>.enable = true;
+ *    };
+ *
+ *    testScript = ''
+ *      $<exporterName>->start();
+ *      $<exporterName>->waitForUnit("prometheus-<exporterName>-exporter.service");
+ *      $<exporterName>->waitForOpenPort("1234");
+ *      $<exporterName>->succeed("curl -sSf 'localhost:1234/metrics'");
+ *      $<exporterName>->shutdown();
+ *    '';
+ */
+
+  exporterTests = {
+
+    blackbox = {
+      exporterConfig = {
+        enable = true;
+        configFile = pkgs.writeText "config.yml" (builtins.toJSON {
+          modules.icmp_v6 = {
+            prober = "icmp";
+            icmp.preferred_ip_protocol = "ip6";
+          };
+        });
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-blackbox-exporter.service");
+        waitForOpenPort(9115);
+        succeed("curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep -q 'probe_success 1'");
+      '';
+    };
+
+    collectd = {
+      exporterConfig = {
+        enable = true;
+        extraFlags = [ "--web.collectd-push-path /collectd" ];
+      };
+      exporterTest =let postData = escape' ''
+        [{
+          "values":[23],
+          "dstypes":["gauge"],
+          "type":"gauge",
+          "interval":1000,
+          "host":"testhost",
+          "plugin":"testplugin",
+          "time":$(date +%s)
+        }]
+        ''; in ''
+        waitForUnit("prometheus-collectd-exporter.service");
+        waitForOpenPort(9103);
+        succeed("curl -sSfH 'Content-Type: application/json' -X POST --data \"${postData}\" localhost:9103/collectd");
+        succeed("curl -sSf localhost:9103/metrics | grep -q 'collectd_testplugin_gauge{instance=\"testhost\"} 23'");
+      '';
+    };
+
+    dnsmasq = {
+      exporterConfig = {
+        enable = true;
+        leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
+      };
+      metricProvider = {
+        services.dnsmasq.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-dnsmasq-exporter.service");
+        waitForOpenPort(9153);
+        succeed("curl -sSf http://localhost:9153/metrics | grep -q 'dnsmasq_leases 0'");
+      '';
+    };
+
+    bind = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.bind.enable = true;
+        services.bind.extraConfig = ''
+          statistics-channels {
+            inet 127.0.0.1 port 8053 allow { localhost; };
+          };
+        '';
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-bind-exporter.service");
+        waitForOpenPort(9119);
+        succeed("curl -sSf http://localhost:9119/metrics" | grep -q 'bind_query_recursions_total 0');
+      '';
+    };
+
+    dovecot = {
+      exporterConfig = {
+        enable = true;
+        scopes = [ "global" ];
+        socketPath = "/var/run/dovecot2/old-stats";
+        user = "root"; # <- don't use user root in production
+      };
+      metricProvider = {
+        services.dovecot2.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-dovecot-exporter.service");
+        waitForOpenPort(9166);
+        succeed("curl -sSf http://localhost:9166/metrics | grep -q 'dovecot_up{scope=\"global\"} 1'");
+      '';
+    };
+
+    fritzbox = { # TODO add proper test case
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-fritzbox-exporter.service");
+        waitForOpenPort(9133);
+        succeed("curl -sSf http://localhost:9133/metrics | grep -q 'fritzbox_exporter_collect_errors 0'");
+      '';
+    };
+
+    json = {
+      exporterConfig = {
+        enable = true;
+        url = "http://localhost";
+        configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON [{
+          name = "json_test_metric";
+          path = "$.test";
+        }]);
+      };
+      metricProvider = {
+        systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost.locations."/".extraConfig = ''
+            return 200 "{\"test\":1}";
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service");
+        waitForOpenPort(80);
+        waitForUnit("prometheus-json-exporter.service");
+        waitForOpenPort(7979);
+        succeed("curl -sSf localhost:7979/metrics | grep -q 'json_test_metric 1'");
+      '';
+    };
+
+    nginx = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.nginx = {
+          enable = true;
+          statusPage = true;
+          virtualHosts."/".extraConfig = "return 204;";
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service")
+        waitForUnit("prometheus-nginx-exporter.service")
+        waitForOpenPort(9113)
+        succeed("curl -sSf http://localhost:9113/metrics | grep -q 'nginx_up 1'")
+      '';
+    };
+
+    node = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-node-exporter.service");
+        waitForOpenPort(9100);
+        succeed("curl -sSf http://localhost:9100/metrics | grep -q 'node_exporter_build_info{.\\+} 1'");
+      '';
+    };
+
+    postfix = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.postfix.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-postfix-exporter.service");
+        waitForOpenPort(9154);
+        succeed("curl -sSf http://localhost:9154/metrics | grep -q 'postfix_smtpd_connects_total 0'");
+      '';
+    };
+
+    snmp = {
+      exporterConfig = {
+        enable = true;
+        configuration.default = {
+          version = 2;
+          auth.community = "public";
+        };
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-snmp-exporter.service");
+        waitForOpenPort(9116);
+        succeed("curl -sSf localhost:9116/metrics | grep -q 'snmp_request_errors_total 0'");
+      '';
+    };
+
+    surfboard = {
+      exporterConfig = {
+        enable = true;
+        modemAddress = "localhost";
+      };
+      metricProvider = {
+        systemd.services.prometheus-surfboard-exporter.after = [ "nginx.service" ];
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost.locations."/cgi-bin/status".extraConfig = ''
+            return 204;
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service");
+        waitForOpenPort(80);
+        waitForUnit("prometheus-surfboard-exporter.service");
+        waitForOpenPort(9239);
+        succeed("curl -sSf localhost:9239/metrics | grep -q 'surfboard_up 1'");
+      '';
+    };
+
+    tor = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        # Note: this does not connect the test environment to the Tor network.
+        # Client, relay, bridge or exit connectivity are disabled by default.
+        services.tor.enable = true;
+        services.tor.controlPort = 9051;
+      };
+      exporterTest = ''
+        waitForUnit("tor.service");
+        waitForOpenPort(9051);
+        waitForUnit("prometheus-tor-exporter.service");
+        waitForOpenPort(9130);
+        succeed("curl -sSf localhost:9130/metrics | grep -q 'tor_version{.\\+} 1'");
+      '';
+    };
+
+    varnish = {
+      exporterConfig = {
+        enable = true;
+        instance = "/var/spool/varnish/varnish";
+        group = "varnish";
+      };
+      metricProvider = {
+        systemd.services.prometheus-varnish-exporter.after = [
+          "varnish.service"
+        ];
+        services.varnish = {
+          enable = true;
+          config = ''
+            vcl 4.0;
+            backend default {
+              .host = "127.0.0.1";
+              .port = "80";
+            }
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-varnish-exporter.service");
+        waitForOpenPort(9131);
+        succeed("curl -sSf http://localhost:9131/metrics | grep -q 'varnish_up 1'");
+      '';
+    };
+  };
+
+  nodes = lib.mapAttrs (exporter: testConfig: lib.mkMerge [{
+    services.prometheus.exporters.${exporter} = testConfig.exporterConfig;
+  } testConfig.metricProvider or {}]) exporterTests;
+
+  testScript = lib.concatStrings (lib.mapAttrsToList (exporter: testConfig: (''
+    subtest "${exporter}", sub {
+      ${"$"+exporter}->start();
+      ${lib.concatStringsSep "  " (map (line: ''
+        ${"$"+exporter}->${line};
+      '') (lib.splitString "\n" (lib.removeSuffix "\n" testConfig.exporterTest)))}
+      ${"$"+exporter}->shutdown();
+    };
+  '')) exporterTests);
+in
+{
+  name = "prometheus-exporters";
+
+  inherit nodes testScript;
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+})
diff --git a/nixpkgs/nixos/tests/prometheus.nix b/nixpkgs/nixos/tests/prometheus.nix
new file mode 100644
index 000000000000..f1b20a33d71e
--- /dev/null
+++ b/nixpkgs/nixos/tests/prometheus.nix
@@ -0,0 +1,48 @@
+import ./make-test.nix {
+  name = "prometheus";
+
+  nodes = {
+    one = { ... }: {
+      services.prometheus = {
+        enable = true;
+        scrapeConfigs = [{
+          job_name = "prometheus";
+          static_configs = [{
+            targets = [ "127.0.0.1:9090" ];
+            labels = { instance = "localhost"; };
+          }];
+        }];
+        rules = [ ''testrule = count(up{job="prometheus"})'' ];
+
+        # a very simple version of the alertmanager configuration just to see if
+        # configuration checks & service startup are working
+        alertmanager = {
+          enable = true;
+          listenAddress = "[::1]";
+          port = 9093;
+          configuration = {
+            route.receiver = "webhook";
+            receivers = [
+              {
+                name = "webhook";
+                webhook_configs = [
+                  { url = "http://localhost"; }
+                ];
+              }
+            ];
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $one->waitForUnit("prometheus.service");
+    $one->waitForOpenPort(9090);
+    $one->succeed("curl -s http://127.0.0.1:9090/metrics");
+    $one->waitForUnit("alertmanager.service");
+    $one->waitForOpenPort("9093");
+    $one->succeed("curl -f -s http://localhost:9093/");
+  '';
+}
diff --git a/nixpkgs/nixos/tests/prosody.nix b/nixpkgs/nixos/tests/prosody.nix
new file mode 100644
index 000000000000..61ae5bb38ed9
--- /dev/null
+++ b/nixpkgs/nixos/tests/prosody.nix
@@ -0,0 +1,78 @@
+import ./make-test.nix {
+  name = "prosody";
+
+  machine = { pkgs, ... }: {
+    services.prosody = {
+      enable = true;
+      # TODO: use a self-signed certificate
+      c2sRequireEncryption = false;
+      extraConfig = ''
+        storage = "sql"
+      '';
+    };
+    environment.systemPackages = let
+      sendMessage = pkgs.writeScriptBin "send-message" ''
+        #!/usr/bin/env python3
+        # Based on the sleekxmpp send_client example, look there for more details:
+        # https://github.com/fritzy/SleekXMPP/blob/develop/examples/send_client.py
+        import sleekxmpp
+
+        class SendMsgBot(sleekxmpp.ClientXMPP):
+            """
+            A basic SleekXMPP bot that will log in, send a message,
+            and then log out.
+            """
+            def __init__(self, jid, password, recipient, message):
+                sleekxmpp.ClientXMPP.__init__(self, jid, password)
+
+                self.recipient = recipient
+                self.msg = message
+
+                self.add_event_handler("session_start", self.start, threaded=True)
+
+            def start(self, event):
+                self.send_presence()
+                self.get_roster()
+
+                self.send_message(mto=self.recipient,
+                                  mbody=self.msg,
+                                  mtype='chat')
+
+                self.disconnect(wait=True)
+
+
+        if __name__ == '__main__':
+            xmpp = SendMsgBot("test1@localhost", "test1", "test2@localhost", "Hello World!")
+            xmpp.register_plugin('xep_0030') # Service Discovery
+            xmpp.register_plugin('xep_0199') # XMPP Ping
+
+            # TODO: verify certificate
+            # If you want to verify the SSL certificates offered by a server:
+            # xmpp.ca_certs = "path/to/ca/cert"
+
+            if xmpp.connect(('localhost', 5222)):
+                xmpp.process(block=True)
+            else:
+                print("Unable to connect.")
+                sys.exit(1)
+      '';
+    in [ (pkgs.python3.withPackages (ps: [ ps.sleekxmpp ])) sendMessage ];
+  };
+
+  testScript = ''
+    $machine->waitForUnit('prosody.service');
+    $machine->succeed('prosodyctl status') =~ /Prosody is running/;
+
+    # set password to 'test' (it's asked twice)
+    $machine->succeed('yes test1 | prosodyctl adduser test1@localhost');
+    # set password to 'y'
+    $machine->succeed('yes | prosodyctl adduser test2@localhost');
+    # correct password to 'test2'
+    $machine->succeed('yes test2 | prosodyctl passwd test2@localhost');
+
+    $machine->succeed("send-message");
+
+    $machine->succeed('prosodyctl deluser test1@localhost');
+    $machine->succeed('prosodyctl deluser test2@localhost');
+  '';
+}
diff --git a/nixpkgs/nixos/tests/proxy.nix b/nixpkgs/nixos/tests/proxy.nix
new file mode 100644
index 000000000000..181953120282
--- /dev/null
+++ b/nixpkgs/nixos/tests/proxy.nix
@@ -0,0 +1,96 @@
+import ./make-test.nix ({ pkgs, ...} : 
+
+let
+
+  backend =
+    { pkgs, ... }:
+
+    { services.httpd.enable = true;
+      services.httpd.adminAddr = "foo@example.org";
+      services.httpd.documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
+      networking.firewall.allowedTCPPorts = [ 80 ];
+    };
+
+in
+
+{
+  name = "proxy";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  nodes =
+    { proxy =
+        { nodes, ... }:
+
+        { services.httpd.enable = true;
+          services.httpd.adminAddr = "bar@example.org";
+          services.httpd.extraModules = [ "proxy_balancer" "lbmethod_byrequests" ];
+
+          services.httpd.extraConfig =
+            ''
+              ExtendedStatus on
+
+              <Location /server-status>
+                Require all granted
+                SetHandler server-status
+              </Location>
+
+              <Proxy balancer://cluster>
+                Require all granted
+                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
+            '';
+
+          networking.firewall.allowedTCPPorts = [ 80 ];
+        };
+
+      backend1 = backend;
+      backend2 = backend;
+
+      client = { ... }: { };
+    };
+
+  testScript =
+    ''
+      startAll;
+
+      $proxy->waitForUnit("httpd");
+      $backend1->waitForUnit("httpd");
+      $backend2->waitForUnit("httpd");
+      $client->waitForUnit("network.target");
+
+      # 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/nixpkgs/nixos/tests/quagga.nix b/nixpkgs/nixos/tests/quagga.nix
new file mode 100644
index 000000000000..6aee7ea57f03
--- /dev/null
+++ b/nixpkgs/nixos/tests/quagga.nix
@@ -0,0 +1,96 @@
+# This test runs Quagga and checks if OSPF routing works.
+#
+# Network topology:
+#   [ client ]--net1--[ router1 ]--net2--[ router2 ]--net3--[ server ]
+#
+# All interfaces are in OSPF Area 0.
+
+import ./make-test.nix ({ pkgs, ... }:
+  let
+
+    ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ipv4.addresses).address;
+
+    ospfConf = ''
+      interface eth2
+        ip ospf hello-interval 1
+        ip ospf dead-interval 5
+      !
+      router ospf
+        network 192.168.0.0/16 area 0
+    '';
+
+  in
+    {
+      name = "quagga";
+
+      meta = with pkgs.stdenv.lib.maintainers; {
+        maintainers = [ tavyc ];
+      };
+
+      nodes = {
+
+        client =
+          { nodes, ... }:
+          {
+            virtualisation.vlans = [ 1 ];
+            networking.defaultGateway = ifAddr nodes.router1 "eth1";
+          };
+
+        router1 =
+          { ... }:
+          {
+            virtualisation.vlans = [ 1 2 ];
+            boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+            networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospf -j ACCEPT";
+            services.quagga.ospf = {
+              enable = true;
+              config = ospfConf;
+            };
+          };
+
+        router2 =
+          { ... }:
+          {
+            virtualisation.vlans = [ 3 2 ];
+            boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+            networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospf -j ACCEPT";
+            services.quagga.ospf = {
+              enable = true;
+              config = ospfConf;
+            };
+          };
+
+        server =
+          { nodes, ... }:
+          {
+            virtualisation.vlans = [ 3 ];
+            networking.defaultGateway = ifAddr nodes.router2 "eth1";
+            networking.firewall.allowedTCPPorts = [ 80 ];
+            services.httpd.enable = true;
+            services.httpd.adminAddr = "foo@example.com";
+          };
+      };
+
+      testScript =
+        { ... }:
+        ''
+          startAll;
+
+          # Wait for the networking to start on all machines
+          $_->waitForUnit("network.target") foreach values %vms;
+
+          # Wait for OSPF to form adjacencies
+          for my $gw ($router1, $router2) {
+              $gw->waitForUnit("ospfd");
+              $gw->waitUntilSucceeds("vtysh -c 'show ip ospf neighbor' | grep Full");
+              $gw->waitUntilSucceeds("vtysh -c 'show ip route' | grep '^O>'");
+          }
+
+          # Test ICMP.
+          $client->succeed("ping -c 3 server >&2");
+
+          # Test whether HTTP works.
+          $server->waitForUnit("httpd");
+          $client->succeed("curl --fail http://server/ >&2");
+        '';
+    })
diff --git a/nixpkgs/nixos/tests/quake3.nix b/nixpkgs/nixos/tests/quake3.nix
new file mode 100644
index 000000000000..75c82cca63f5
--- /dev/null
+++ b/nixpkgs/nixos/tests/quake3.nix
@@ -0,0 +1,95 @@
+import ./make-test.nix ({ pkgs, ...} :
+
+let
+
+  # Build Quake with coverage instrumentation.
+  overrides = pkgs:
+    rec {
+      quake3game = pkgs.quake3game.override (args: {
+        stdenv = pkgs.stdenvAdapters.addCoverageInstrumentation args.stdenv;
+      });
+    };
+
+  # Only allow the demo data to be used (only if it's unfreeRedistributable).
+  unfreePredicate = pkg: with pkgs.lib; let
+    allowDrvPredicates = [ "quake3-demo" "quake3-pointrelease" ];
+    allowLicenses = [ pkgs.lib.licenses.unfreeRedistributable ];
+  in any (flip hasPrefix pkg.name) allowDrvPredicates &&
+     elem (pkg.meta.license or null) allowLicenses;
+
+in
+
+rec {
+  name = "quake3";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ domenkozar eelco chaoflow ];
+  };
+
+  # TODO: lcov doesn't work atm
+  #makeCoverageReport = true;
+
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      hardware.opengl.driSupport = true;
+      environment.systemPackages = [ pkgs.quake3demo ];
+      nixpkgs.config.packageOverrides = overrides;
+      nixpkgs.config.allowUnfreePredicate = unfreePredicate;
+    };
+
+  nodes =
+    { server =
+        { pkgs, ... }:
+
+        { systemd.services."quake3-server" =
+            { wantedBy = [ "multi-user.target" ];
+              script =
+                "${pkgs.quake3demo}/bin/quake3-server +set g_gametype 0 " +
+                "+map q3dm7 +addbot grunt +addbot daemia 2> /tmp/log";
+            };
+          nixpkgs.config.packageOverrides = overrides;
+          nixpkgs.config.allowUnfreePredicate = unfreePredicate;
+          networking.firewall.allowedUDPPorts = [ 27960 ];
+        };
+
+      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/nixpkgs/nixos/tests/rabbitmq.nix b/nixpkgs/nixos/tests/rabbitmq.nix
new file mode 100644
index 000000000000..34ab05787867
--- /dev/null
+++ b/nixpkgs/nixos/tests/rabbitmq.nix
@@ -0,0 +1,21 @@
+# This test runs rabbitmq and checks if rabbitmq is up and running.
+
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "rabbitmq";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow offline ];
+  };
+
+  nodes = {
+    one = { ... }: {
+      services.rabbitmq.enable = true;
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $one->waitForUnit("rabbitmq.service");
+    $one->waitUntilSucceeds("su -s ${pkgs.stdenv.shell} rabbitmq -c \"rabbitmqctl status\"");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/radarr.nix b/nixpkgs/nixos/tests/radarr.nix
new file mode 100644
index 000000000000..6b9a909e44b5
--- /dev/null
+++ b/nixpkgs/nixos/tests/radarr.nix
@@ -0,0 +1,18 @@
+import ./make-test.nix ({ lib, ... }:
+
+with lib;
+
+rec {
+  name = "radarr";
+  meta.maintainers = with maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.radarr.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('radarr.service');
+    $machine->waitForOpenPort('7878');
+    $machine->succeed("curl --fail http://localhost:7878/");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/radicale.nix b/nixpkgs/nixos/tests/radicale.nix
new file mode 100644
index 000000000000..bf22fc9291af
--- /dev/null
+++ b/nixpkgs/nixos/tests/radicale.nix
@@ -0,0 +1,106 @@
+let
+  user = "someuser";
+  password = "some_password";
+  port = builtins.toString 5232;
+
+  common = { pkgs, ... }: {
+    services.radicale = {
+      enable = true;
+      config = ''
+        [auth]
+        type = htpasswd
+        htpasswd_filename = /etc/radicale/htpasswd
+        htpasswd_encryption = bcrypt
+
+        [storage]
+        filesystem_folder = /tmp/collections
+
+        [logging]
+        debug = True
+      '';
+    };
+    # WARNING: DON'T DO THIS IN PRODUCTION!
+    # This puts unhashed secrets directly into the Nix store for ease of testing.
+    environment.etc."radicale/htpasswd".source = pkgs.runCommand "htpasswd" {} ''
+      ${pkgs.apacheHttpd}/bin/htpasswd -bcB "$out" ${user} ${password}
+    '';
+  };
+
+in
+
+  import ./make-test.nix ({ lib, ... }@args: {
+    name = "radicale";
+    meta.maintainers = with lib.maintainers; [ aneeshusa infinisil ];
+
+    nodes = rec {
+      radicale = radicale1; # Make the test script read more nicely
+      radicale1 = lib.recursiveUpdate (common args) {
+        nixpkgs.overlays = [
+          (self: super: {
+            radicale1 = super.radicale1.overrideAttrs (oldAttrs: {
+              propagatedBuildInputs = with self.pythonPackages;
+                (oldAttrs.propagatedBuildInputs or []) ++ [ passlib ];
+            });
+          })
+        ];
+        system.stateVersion = "17.03";
+      };
+      radicale1_export = lib.recursiveUpdate radicale1 {
+        services.radicale.extraArgs = [
+          "--export-storage" "/tmp/collections-new"
+        ];
+      };
+      radicale2_verify = lib.recursiveUpdate radicale2 {
+        services.radicale.extraArgs = [ "--verify-storage" ];
+      };
+      radicale2 = lib.recursiveUpdate (common args) {
+        system.stateVersion = "17.09";
+      };
+    };
+
+    # This tests whether the web interface is accessible to an authenticated user
+    testScript = { nodes }: let
+      switchToConfig = nodeName: let
+        newSystem = nodes.${nodeName}.config.system.build.toplevel;
+      in "${newSystem}/bin/switch-to-configuration test";
+    in ''
+      # Check Radicale 1 functionality
+      $radicale->succeed('${switchToConfig "radicale1"} >&2');
+      $radicale->waitForUnit('radicale.service');
+      $radicale->waitForOpenPort(${port});
+      $radicale->succeed('curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/');
+
+      # Export data in Radicale 2 format
+      $radicale->succeed('systemctl stop radicale');
+      $radicale->succeed('ls -al /tmp/collections');
+      $radicale->fail('ls -al /tmp/collections-new');
+      # Radicale exits immediately after exporting storage
+      $radicale->succeed('${switchToConfig "radicale1_export"} >&2');
+      $radicale->waitUntilFails('systemctl status radicale');
+      $radicale->succeed('ls -al /tmp/collections');
+      $radicale->succeed('ls -al /tmp/collections-new');
+
+      # Verify data in Radicale 2 format
+      $radicale->succeed('rm -r /tmp/collections/${user}');
+      $radicale->succeed('mv /tmp/collections-new/collection-root /tmp/collections');
+      $radicale->succeed('${switchToConfig "radicale2_verify"} >&2');
+      $radicale->waitUntilFails('systemctl status radicale');
+      my ($retcode, $logs) = $radicale->execute('journalctl -u radicale -n 5');
+      if ($retcode != 0 || index($logs, 'Verifying storage') == -1) {
+        die "Radicale 2 didn't verify storage"
+      }
+      if (index($logs, 'failed') != -1 || index($logs, 'exception') != -1) {
+        die "storage verification failed"
+      }
+
+      # Check Radicale 2 functionality
+      $radicale->succeed('${switchToConfig "radicale2"} >&2');
+      $radicale->waitForUnit('radicale.service');
+      $radicale->waitForOpenPort(${port});
+      my ($retcode, $output) = $radicale->execute('curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/');
+      if ($retcode != 0 || index($output, 'VCALENDAR') == -1) {
+        die "Could not read calendar from Radicale 2"
+      }
+      $radicale->succeed('curl --fail http://${user}:${password}@localhost:${port}/.web/');
+    '';
+})
diff --git a/nixpkgs/nixos/tests/redmine.nix b/nixpkgs/nixos/tests/redmine.nix
new file mode 100644
index 000000000000..ea72a0121d11
--- /dev/null
+++ b/nixpkgs/nixos/tests/redmine.nix
@@ -0,0 +1,58 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  redmineTest = package: makeTest {
+    machine =
+      { config, pkgs, ... }:
+      { services.mysql.enable = true;
+        services.mysql.package = pkgs.mariadb;
+        services.mysql.ensureDatabases = [ "redmine" ];
+        services.mysql.ensureUsers = [
+          { name = "redmine";
+            ensurePermissions = { "redmine.*" = "ALL PRIVILEGES"; };
+          }
+        ];
+
+        services.redmine.enable = true;
+        services.redmine.package = package;
+        services.redmine.database.socket = "/run/mysqld/mysqld.sock";
+        services.redmine.plugins = {
+          redmine_env_auth = pkgs.fetchurl {
+            url = https://github.com/Intera/redmine_env_auth/archive/0.7.zip;
+            sha256 = "1xb8lyarc7mpi86yflnlgyllh9hfwb9z304f19dx409gqpia99sc";
+          };
+        };
+        services.redmine.themes = {
+          dkuk-redmine_alex_skin = pkgs.fetchurl {
+            url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip;
+            sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+          };
+        };
+      };
+
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('redmine.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+    '';
+  };
+in
+{
+  redmine_3 = redmineTest pkgs.redmine // {
+    name = "redmine_3";
+    meta.maintainers = [ maintainers.aanderse ];
+  };
+
+  redmine_4 = redmineTest pkgs.redmine_4 // {
+    name = "redmine_4";
+    meta.maintainers = [ maintainers.aanderse ];
+  };
+}
diff --git a/nixpkgs/nixos/tests/riak.nix b/nixpkgs/nixos/tests/riak.nix
new file mode 100644
index 000000000000..68a9b7315b35
--- /dev/null
+++ b/nixpkgs/nixos/tests/riak.nix
@@ -0,0 +1,21 @@
+import ./make-test.nix {
+  name = "riak";
+
+  nodes = {
+    master =
+      { pkgs, ... }:
+
+      {
+        services.riak.enable = true;
+        services.riak.package = pkgs.riak;
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $master->waitForUnit("riak");
+    $master->sleep(20); # Hopefully this is long enough!!
+    $master->succeed("riak ping 2>&1");
+  '';
+}
diff --git a/nixpkgs/nixos/tests/roundcube.nix b/nixpkgs/nixos/tests/roundcube.nix
new file mode 100644
index 000000000000..178134fd9b30
--- /dev/null
+++ b/nixpkgs/nixos/tests/roundcube.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "roundcube";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ globin ];
+  };
+
+  nodes = {
+    roundcube = { config, pkgs, ... }: {
+      services.roundcube = {
+        enable = true;
+        hostName = "roundcube";
+        database.password = "notproduction";
+      };
+      services.nginx.virtualHosts.roundcube = {
+        forceSSL = false;
+        enableACME = false;
+      };
+    };
+  };
+
+  testScript = ''
+    $roundcube->start;
+    $roundcube->waitForUnit("postgresql.service");
+    $roundcube->waitForUnit("phpfpm-roundcube.service");
+    $roundcube->waitForUnit("nginx.service");
+    $roundcube->succeed("curl -sSfL http://roundcube/");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/rspamd.nix b/nixpkgs/nixos/tests/rspamd.nix
new file mode 100644
index 000000000000..396cd5b67d81
--- /dev/null
+++ b/nixpkgs/nixos/tests/rspamd.nix
@@ -0,0 +1,255 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  initMachine = ''
+    startAll
+    $machine->waitForUnit("rspamd.service");
+    $machine->succeed("id \"rspamd\" >/dev/null");
+  '';
+  checkSocket = socket: user: group: mode: ''
+    $machine->succeed("ls ${socket} >/dev/null");
+    $machine->succeed("[[ \"\$(stat -c %U ${socket})\" == \"${user}\" ]]");
+    $machine->succeed("[[ \"\$(stat -c %G ${socket})\" == \"${group}\" ]]");
+    $machine->succeed("[[ \"\$(stat -c %a ${socket})\" == \"${mode}\" ]]");
+  '';
+  simple = name: enableIPv6: makeTest {
+    name = "rspamd-${name}";
+    machine = {
+      services.rspamd.enable = true;
+      networking.enableIPv6 = enableIPv6;
+    };
+    testScript = ''
+      startAll
+      $machine->waitForUnit("multi-user.target");
+      $machine->waitForOpenPort(11334);
+      $machine->waitForUnit("rspamd.service");
+      $machine->succeed("id \"rspamd\" >/dev/null");
+      ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" }
+      sleep 10;
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("systemctl cat rspamd.service"));
+      $machine->log($machine->succeed("curl http://localhost:11334/auth"));
+      $machine->log($machine->succeed("curl http://127.0.0.1:11334/auth"));
+      ${optionalString enableIPv6 ''
+        $machine->log($machine->succeed("curl http://[::1]:11334/auth"));
+      ''}
+    '';
+  };
+in
+{
+  simple = simple "simple" true;
+  ipv4only = simple "ipv4only" false;
+  deprecated = makeTest {
+    name = "rspamd-deprecated";
+    machine = {
+      services.rspamd = {
+        enable = true;
+        bindSocket = [ "/run/rspamd.sock mode=0600 user=root group=root" ];
+        bindUISocket = [ "/run/rspamd-worker.sock mode=0666 user=root group=root" ];
+      };
+    };
+
+    testScript = ''
+      ${initMachine}
+      $machine->waitForFile("/run/rspamd.sock");
+      ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
+      ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
+      $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
+    '';
+  };
+
+  bindports = makeTest {
+    name = "rspamd-bindports";
+    machine = {
+      services.rspamd = {
+        enable = true;
+        workers.normal.bindSockets = [{
+          socket = "/run/rspamd.sock";
+          mode = "0600";
+          owner = "root";
+          group = "root";
+        }];
+        workers.controller.bindSockets = [{
+          socket = "/run/rspamd-worker.sock";
+          mode = "0666";
+          owner = "root";
+          group = "root";
+        }];
+        workers.controller2 = {
+          type = "controller";
+          bindSockets = [ "0.0.0.0:11335" ];
+          extraConfig = ''
+            static_dir = "''${WWWDIR}";
+            secure_ip = null;
+            password = "verysecretpassword";
+          '';
+        };
+      };
+    };
+
+    testScript = ''
+      ${initMachine}
+      $machine->waitForFile("/run/rspamd.sock");
+      ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
+      ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'LOCAL_CONFDIR/override.d/worker-controller2.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'verysecretpassword' /etc/rspamd/override.d/worker-controller2.inc"));
+      $machine->waitUntilSucceeds("journalctl -u rspamd | grep -i 'starting controller process' >&2");
+      $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
+      $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
+      $machine->log($machine->succeed("curl http://localhost:11335/ping"));
+    '';
+  };
+  customLuaRules = makeTest {
+    name = "rspamd-custom-lua-rules";
+    machine = {
+      environment.etc."tests/no-muh.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<mah@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
+      '';
+      environment.etc."tests/muh.eml".text = ''
+        From: Cow<cow@example.com>
+        To: Sheep2<mah@example.com>
+        Subject: Evil cows
+
+        Cows are majestic creatures don't Muh agree?
+      '';
+      services.rspamd = {
+        enable = true;
+        locals = {
+          "antivirus.conf" = mkIf false { text = ''
+              clamav {
+                action = "reject";
+                symbol = "CLAM_VIRUS";
+                type = "clamav";
+                log_clean = true;
+                servers = "/run/clamav/clamd.ctl";
+              }
+            '';};
+          "redis.conf" = {
+            enable = false;
+            text = ''
+              servers = "127.0.0.1";
+            '';
+          };
+          "groups.conf".text = ''
+            group "cows" {
+              symbol {
+                NO_MUH = {
+                  weight = 1.0;
+                  description = "Mails should not muh";
+                }
+              }
+            }
+          '';
+        };
+        localLuaRules = pkgs.writeText "rspamd.local.lua" ''
+          local rspamd_logger = require "rspamd_logger"
+          rspamd_config.NO_MUH = {
+            callback = function (task)
+              local parts = task:get_text_parts()
+              if parts then
+                for _,part in ipairs(parts) do
+                  local content = tostring(part:get_content())
+                  rspamd_logger.infox(rspamd_config, 'Found content %s', content)
+                  local found = string.find(content, "Muh");
+                  rspamd_logger.infox(rspamd_config, 'Found muh %s', tostring(found))
+                  if found then
+                    return true
+                  end
+                end
+              end
+              return false
+            end,
+            score = 5.0,
+	          description = 'Allow no cows',
+            group = "cows",
+          }
+          rspamd_logger.infox(rspamd_config, 'Work dammit!!!')
+        '';
+      };
+    };
+    testScript = ''
+      ${initMachine}
+      $machine->waitForOpenPort(11334);
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.local.lua"));
+      $machine->log($machine->succeed("cat /etc/rspamd/local.d/groups.conf"));
+      # Verify that redis.conf was not written
+      $machine->fail("cat /etc/rspamd/local.d/redis.conf >&2");
+      # Verify that antivirus.conf was not written
+      $machine->fail("cat /etc/rspamd/local.d/antivirus.conf >&2");
+      ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" }
+      $machine->log($machine->succeed("curl --unix-socket /run/rspamd/rspamd.sock http://localhost/ping"));
+      $machine->log($machine->succeed("rspamc -h 127.0.0.1:11334 stat"));
+      $machine->log($machine->succeed("cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334"));
+      $machine->log($machine->succeed("cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols"));
+      $machine->waitUntilSucceeds("journalctl -u rspamd | grep -i muh >&2");
+      $machine->log($machine->fail("cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH"));
+      $machine->log($machine->succeed("cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH"));
+    '';
+  };
+  postfixIntegration = makeTest {
+    name = "rspamd-postfix-integration";
+    machine = {
+      environment.systemPackages = with pkgs; [ msmtp ];
+      environment.etc."tests/gtube.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<tester@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
+
+        XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
+      '';
+      environment.etc."tests/example.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<tester@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
+      '';
+      users.users.tester.password = "test";
+      services.postfix = {
+        enable = true;
+        destination = ["example.com"];
+      };
+      services.rspamd = {
+        enable = true;
+        postfix.enable = true;
+        workers.rspamd_proxy.type = "proxy";
+      };
+    };
+    testScript = ''
+      ${initMachine}
+      $machine->waitForOpenPort(11334);
+      $machine->waitForOpenPort(25);
+      ${checkSocket "/run/rspamd/rspamd-milter.sock" "rspamd" "postfix" "660" }
+      $machine->log($machine->succeed("rspamc -h 127.0.0.1:11334 stat"));
+      $machine->log($machine->succeed("msmtp --host=localhost -t --read-envelope-from < /etc/tests/example.eml"));
+      $machine->log($machine->fail("msmtp --host=localhost -t --read-envelope-from < /etc/tests/gtube.eml"));
+
+      $machine->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
+      $machine->fail("journalctl -u postfix | grep -i error >&2");
+      $machine->fail("journalctl -u postfix | grep -i warning >&2");
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/rss2email.nix b/nixpkgs/nixos/tests/rss2email.nix
new file mode 100644
index 000000000000..492d47da9f56
--- /dev/null
+++ b/nixpkgs/nixos/tests/rss2email.nix
@@ -0,0 +1,66 @@
+import ./make-test.nix {
+  name = "opensmtpd";
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      services.nginx = {
+        enable = true;
+        virtualHosts."127.0.0.1".root = ./common/webroot;
+      };
+      services.rss2email = {
+        enable = true;
+        to = "alice@localhost";
+        interval = "1";
+        config.from = "test@example.org";
+        feeds = {
+          nixos = { url = "http://127.0.0.1/news-rss.xml"; };
+        };
+      };
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          listen on 127.0.0.1
+          action dovecot_deliver mda \
+            "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
+        '';
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        mailLocation = "maildir:~/mail";
+        protocols = [ "imap" ];
+      };
+      environment.systemPackages = let
+        checkMailLanded = pkgs.writeScriptBin "check-mail-landed" ''
+          #!${pkgs.python3.interpreter}
+          import imaplib
+
+          with imaplib.IMAP4('127.0.0.1', 143) as imap:
+            imap.login('alice', 'foobar')
+            imap.select()
+            status, refs = imap.search(None, 'ALL')
+            print("=====> Result of search for all:", status, refs)
+            assert status == 'OK'
+            assert len(refs) > 0
+            status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+            assert status == 'OK'
+        '';
+      in [ pkgs.opensmtpd checkMailLanded ];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("network-online.target");
+    $server->waitForUnit("opensmtpd");
+    $server->waitForUnit("dovecot2");
+    $server->waitForUnit("nginx");
+    $server->waitForUnit("rss2email");
+
+    $server->waitUntilSucceeds('check-mail-landed >&2');
+  '';
+}
diff --git a/nixpkgs/nixos/tests/rsyslogd.nix b/nixpkgs/nixos/tests/rsyslogd.nix
new file mode 100644
index 000000000000..f17e61814c5e
--- /dev/null
+++ b/nixpkgs/nixos/tests/rsyslogd.nix
@@ -0,0 +1,42 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  test1 = makeTest {
+    name = "rsyslogd-test1";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.rsyslogd.enable = true;
+        services.journald.forwardToSyslog = false;
+      };
+
+    # ensure rsyslogd isn't receiving messages from journald if explicitly disabled
+    testScript = ''
+      $machine->waitForUnit("default.target");
+      $machine->fail("test -f /var/log/messages");
+    '';
+  };
+
+  test2 = makeTest {
+    name = "rsyslogd-test2";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.rsyslogd.enable = true;
+      };
+
+    # ensure rsyslogd is receiving messages from journald
+    testScript = ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("test -f /var/log/messages");
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/run-in-machine.nix b/nixpkgs/nixos/tests/run-in-machine.nix
new file mode 100644
index 000000000000..339a4b9a7404
--- /dev/null
+++ b/nixpkgs/nixos/tests/run-in-machine.nix
@@ -0,0 +1,23 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+
+let
+  output = runInMachine {
+    drv = pkgs.hello;
+    machine = { ... }: { /* services.sshd.enable = true; */ };
+  };
+
+  test = pkgs.runCommand "verify-output" { inherit output; } ''
+    if [ ! -e "$output/bin/hello" ]; then
+      echo "Derivation built using runInMachine produced incorrect output:" >&2
+      ls -laR "$output" >&2
+      exit 1
+    fi
+    "$output/bin/hello" > "$out"
+  '';
+
+in test // { inherit test; } # To emulate behaviour of makeTest
diff --git a/nixpkgs/nixos/tests/rxe.nix b/nixpkgs/nixos/tests/rxe.nix
new file mode 100644
index 000000000000..d0b53db8eeb6
--- /dev/null
+++ b/nixpkgs/nixos/tests/rxe.nix
@@ -0,0 +1,53 @@
+import ./make-test.nix ({ ... } :
+
+let
+  node = { pkgs, ... } : {
+    networking = {
+      firewall = {
+        allowedUDPPorts = [ 4791 ]; # open RoCE port
+        allowedTCPPorts = [ 4800 ]; # port for test utils
+      };
+      rxe = {
+        enable = true;
+        interfaces = [ "eth1" ];
+      };
+    };
+
+    environment.systemPackages = with pkgs; [ rdma-core screen ];
+  };
+
+in {
+  name = "rxe";
+
+  nodes = {
+    server = node;
+    client = node;
+  };
+
+  testScript = ''
+    # Test if rxe interface comes up
+    $server->waitForUnit("default.target");
+    $server->succeed("systemctl status rxe.service");
+    $server->succeed("ibv_devices | grep rxe0");
+
+    $client->waitForUnit("default.target");
+
+    # ping pong test
+    $server->succeed("screen -dmS rc_pingpong ibv_rc_pingpong -p 4800 -g0");
+    $client->succeed("sleep 2; ibv_rc_pingpong -p 4800 -g0 server");
+
+    $server->succeed("screen -dmS uc_pingpong ibv_uc_pingpong -p 4800 -g0");
+    $client->succeed("sleep 2; ibv_uc_pingpong -p 4800 -g0 server");
+
+    $server->succeed("screen -dmS ud_pingpong ibv_ud_pingpong -p 4800 -s 1024 -g0");
+    $client->succeed("sleep 2; ibv_ud_pingpong -p 4800 -s 1024 -g0 server");
+
+    $server->succeed("screen -dmS srq_pingpong ibv_srq_pingpong -p 4800 -g0");
+    $client->succeed("sleep 2; ibv_srq_pingpong -p 4800 -g0 server");
+
+    $server->succeed("screen -dmS rping rping -s -a server -C 10");
+    $client->succeed("sleep 2; rping -c -a server -C 10");
+  '';
+})
+
+
diff --git a/nixpkgs/nixos/tests/samba.nix b/nixpkgs/nixos/tests/samba.nix
new file mode 100644
index 000000000000..2802e00a5b1a
--- /dev/null
+++ b/nixpkgs/nixos/tests/samba.nix
@@ -0,0 +1,47 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "samba";
+
+  meta.maintainers = [ pkgs.lib.maintainers.eelco ];
+
+  nodes =
+    { client =
+        { pkgs, ... }:
+        { fileSystems = pkgs.lib.mkVMOverride
+            { "/public" = {
+                fsType = "cifs";
+                device = "//server/public";
+                options = [ "guest" ];
+              };
+            };
+        };
+
+      server =
+        { ... }:
+        { services.samba.enable = true;
+          services.samba.shares.public =
+            { path = "/public";
+              "read only" = true;
+              browseable = "yes";
+              "guest ok" = "yes";
+              comment = "Public samba share.";
+            };
+          networking.firewall.allowedTCPPorts = [ 139 445 ];
+          networking.firewall.allowedUDPPorts = [ 137 138 ];
+        };
+    };
+
+  # client# [    4.542997] mount[777]: sh: systemd-ask-password: command not found
+
+  testScript =
+    ''
+      $server->start;
+      $server->waitForUnit("samba.target");
+      $server->succeed("mkdir -p /public; echo bar > /public/foo");
+
+      $client->start;
+      $client->waitForUnit("remote-fs.target");
+      $client->succeed("[[ \$(cat /public/foo) = bar ]]");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/sddm.nix b/nixpkgs/nixos/tests/sddm.nix
new file mode 100644
index 000000000000..678bcbeab20a
--- /dev/null
+++ b/nixpkgs/nixos/tests/sddm.nix
@@ -0,0 +1,69 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+
+let
+  inherit (pkgs) lib;
+
+  tests = {
+    default = {
+      name = "sddm";
+
+      machine = { ... }: {
+        imports = [ ./common/user-account.nix ];
+        services.xserver.enable = true;
+        services.xserver.displayManager.sddm.enable = true;
+        services.xserver.windowManager.default = "icewm";
+        services.xserver.windowManager.icewm.enable = true;
+        services.xserver.desktopManager.default = "none";
+      };
+
+      enableOCR = true;
+
+      testScript = { nodes, ... }: let
+        user = nodes.machine.config.users.users.alice;
+      in ''
+        startAll;
+        $machine->waitForText(qr/select your user/i);
+        $machine->screenshot("sddm");
+        $machine->sendChars("${user.password}\n");
+        $machine->waitForFile("/home/alice/.Xauthority");
+        $machine->succeed("xauth merge ~alice/.Xauthority");
+        $machine->waitForWindow("^IceWM ");
+      '';
+    };
+
+    autoLogin = {
+      name = "sddm-autologin";
+      meta = with pkgs.stdenv.lib.maintainers; {
+        maintainers = [ ttuegel ];
+      };
+
+      machine = { ... }: {
+        imports = [ ./common/user-account.nix ];
+        services.xserver.enable = true;
+        services.xserver.displayManager.sddm = {
+          enable = true;
+          autoLogin = {
+            enable = true;
+            user = "alice";
+          };
+        };
+        services.xserver.windowManager.default = "icewm";
+        services.xserver.windowManager.icewm.enable = true;
+        services.xserver.desktopManager.default = "none";
+      };
+
+      testScript = { ... }: ''
+        startAll;
+        $machine->waitForFile("/home/alice/.Xauthority");
+        $machine->succeed("xauth merge ~alice/.Xauthority");
+        $machine->waitForWindow("^IceWM ");
+      '';
+    };
+  };
+in
+  lib.mapAttrs (lib.const makeTest) tests
diff --git a/nixpkgs/nixos/tests/simple.nix b/nixpkgs/nixos/tests/simple.nix
new file mode 100644
index 000000000000..84c5621d962f
--- /dev/null
+++ b/nixpkgs/nixos/tests/simple.nix
@@ -0,0 +1,17 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "simple";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("multi-user.target");
+      $machine->shutdown;
+    '';
+})
diff --git a/nixpkgs/nixos/tests/slim.nix b/nixpkgs/nixos/tests/slim.nix
new file mode 100644
index 000000000000..42c87dfa039d
--- /dev/null
+++ b/nixpkgs/nixos/tests/slim.nix
@@ -0,0 +1,66 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "slim";
+
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.windowManager.default = "icewm";
+    services.xserver.windowManager.icewm.enable = true;
+    services.xserver.desktopManager.default = "none";
+    services.xserver.displayManager.slim = {
+      enable = true;
+
+      # Use a custom theme in order to get best OCR results
+      theme = pkgs.runCommand "slim-theme-ocr" {
+        nativeBuildInputs = [ pkgs.imagemagick ];
+      } ''
+        mkdir "$out"
+        convert -size 1x1 xc:white "$out/background.jpg"
+        convert -size 200x100 xc:white "$out/panel.jpg"
+        cat > "$out/slim.theme" <<EOF
+        background_color #ffffff
+        background_style tile
+
+        input_fgcolor #000000
+        msg_color #000000
+
+        session_color #000000
+        session_font Verdana:size=16:bold
+
+        username_msg Username:
+        username_font Verdana:size=16:bold
+        username_color #000000
+        username_x 50%
+        username_y 40%
+
+        password_msg Password:
+        password_x 50%
+        password_y 40%
+        EOF
+      '';
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+  in ''
+    startAll;
+    $machine->waitForText(qr/Username:/);
+    $machine->sendChars("${user.name}\n");
+    $machine->waitForText(qr/Password:/);
+    $machine->sendChars("${user.password}\n");
+
+    $machine->waitForFile('${user.home}/.Xauthority');
+    $machine->succeed('xauth merge ${user.home}/.Xauthority');
+    $machine->waitForWindow('^IceWM ');
+
+    # Make sure SLiM doesn't create a log file
+    $machine->fail('test -e /var/log/slim.log');
+  '';
+})
diff --git a/nixpkgs/nixos/tests/slurm.nix b/nixpkgs/nixos/tests/slurm.nix
new file mode 100644
index 000000000000..b4458d8d0954
--- /dev/null
+++ b/nixpkgs/nixos/tests/slurm.nix
@@ -0,0 +1,139 @@
+import ./make-test.nix ({ lib, ... }:
+let
+    mungekey = "mungeverryweakkeybuteasytointegratoinatest";
+
+    slurmconfig = {
+      controlMachine = "control";
+      nodeName = [ "node[1-3] CPUs=1 State=UNKNOWN" ];
+      partitionName = [ "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP" ];
+      extraConfig = ''
+        AccountingStorageHost=dbd
+        AccountingStorageType=accounting_storage/slurmdbd
+      '';
+    };
+in {
+  name = "slurm";
+
+  meta.maintainers = [ lib.maintainers.markuskowa ];
+
+  nodes =
+    let
+    computeNode =
+      { ...}:
+      {
+        # TODO slurmd port and slurmctld port should be configurations and
+        # automatically allowed by the  firewall.
+        networking.firewall.enable = false;
+        services.slurm = {
+          client.enable = true;
+        } // slurmconfig;
+      };
+    in {
+
+    control =
+      { ...}:
+      {
+        networking.firewall.enable = false;
+        services.slurm = {
+          server.enable = true;
+        } // slurmconfig;
+      };
+
+    submit =
+      { ...}:
+      {
+        networking.firewall.enable = false;
+        services.slurm = {
+          enableStools = true;
+        } // slurmconfig;
+      };
+
+    dbd =
+      { pkgs, ... } :
+      {
+        networking.firewall.enable = false;
+        services.slurm.dbdserver = {
+          enable = true;
+        };
+        services.mysql = {
+          enable = true;
+          package = pkgs.mysql;
+          ensureDatabases = [ "slurm_acct_db" ];
+          ensureUsers = [{
+            ensurePermissions = { "slurm_acct_db.*" = "ALL PRIVILEGES"; };
+            name = "slurm";
+          }];
+          extraOptions = ''
+            # recommendations from: https://slurm.schedmd.com/accounting.html#mysql-configuration
+            innodb_buffer_pool_size=1024M
+            innodb_log_file_size=64M
+            innodb_lock_wait_timeout=900
+          '';
+        };
+      };
+
+    node1 = computeNode;
+    node2 = computeNode;
+    node3 = computeNode;
+  };
+
+
+  testScript =
+  ''
+  startAll;
+
+  # Set up authentification across the cluster
+  foreach my $node (($submit,$control,$dbd,$node1,$node2,$node3))
+  {
+    $node->waitForUnit("default.target");
+
+    $node->succeed("mkdir /etc/munge");
+    $node->succeed("echo '${mungekey}' > /etc/munge/munge.key");
+    $node->succeed("chmod 0400 /etc/munge/munge.key");
+    $node->succeed("chown munge:munge /etc/munge/munge.key");
+    $node->succeed("systemctl restart munged");
+
+    $node->waitForUnit("munged");
+  };
+
+  # Restart the services since they have probably failed due to the munge init
+  # failure
+  subtest "can_start_slurmdbd", sub {
+    $dbd->succeed("systemctl restart slurmdbd");
+    $dbd->waitForUnit("slurmdbd.service");
+    $dbd->waitForOpenPort(6819);
+  };
+
+  # there needs to be an entry for the current
+  # cluster in the database before slurmctld is restarted
+  subtest "add_account", sub {
+    $control->succeed("sacctmgr -i add cluster default");
+  };
+
+  subtest "can_start_slurmctld", sub {
+    $control->succeed("systemctl restart slurmctld");
+    $control->waitForUnit("slurmctld.service");
+  };
+
+  subtest "can_start_slurmd", sub {
+    foreach my $node (($node1,$node2,$node3))
+    {
+      $node->succeed("systemctl restart slurmd.service");
+      $node->waitForUnit("slurmd");
+    }
+  };
+
+  # Test that the cluster works and can distribute jobs;
+
+  subtest "run_distributed_command", sub {
+    # Run `hostname` on 3 nodes of the partition (so on all the 3 nodes).
+    # The output must contain the 3 different names
+    $submit->succeed("srun -N 3 hostname | sort | uniq | wc -l | xargs test 3 -eq");
+  };
+
+  subtest "check_slurm_dbd", sub {
+    # find the srun job from above in the database
+    $submit->succeed("sacct | grep hostname");
+  };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/smokeping.nix b/nixpkgs/nixos/tests/smokeping.nix
new file mode 100644
index 000000000000..07d228051127
--- /dev/null
+++ b/nixpkgs/nixos/tests/smokeping.nix
@@ -0,0 +1,33 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "smokeping";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ cransom ];
+  };
+
+  nodes = {
+    sm =
+      { ... }:
+      {
+        services.smokeping = {
+          enable = true;
+          port = 8081;
+          mailHost = "127.0.0.2";
+          probeConfig = ''
+            + FPing
+            binary = /run/wrappers/bin/fping
+            offset = 0%
+          '';
+        };
+      };
+  };
+
+  testScript = ''
+    startAll;
+    $sm->waitForUnit("smokeping");
+    $sm->waitForUnit("thttpd");
+    $sm->waitForFile("/var/lib/smokeping/data/Local/LocalMachine.rrd");
+    $sm->succeed("curl -s -f localhost:8081/smokeping.fcgi?target=Local");
+    $sm->succeed("ls /var/lib/smokeping/cache/Local/LocalMachine_mini.png");
+    $sm->succeed("ls /var/lib/smokeping/cache/index.html");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/snapper.nix b/nixpkgs/nixos/tests/snapper.nix
new file mode 100644
index 000000000000..74ec22fd3499
--- /dev/null
+++ b/nixpkgs/nixos/tests/snapper.nix
@@ -0,0 +1,43 @@
+import ./make-test.nix ({ ... }:
+{
+  name = "snapper";
+
+  machine = { pkgs, lib, ... }: {
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux /dev/vdb
+    '';
+
+    virtualisation.emptyDiskImages = [ 4096 ];
+
+    fileSystems = lib.mkVMOverride {
+      "/home" = {
+        device = "/dev/disk/by-label/aux";
+        fsType = "btrfs";
+      };
+    };
+    services.snapper.configs.home.subvolume = "/home";
+    services.snapper.filters = "/nix";
+  };
+
+  testScript = ''
+    $machine->succeed("btrfs subvolume create /home/.snapshots");
+
+    $machine->succeed("snapper -c home list");
+
+    $machine->succeed("snapper -c home create --description empty");
+
+    $machine->succeed("echo test > /home/file");
+    $machine->succeed("snapper -c home create --description file");
+
+    $machine->succeed("snapper -c home status 1..2");
+
+    $machine->succeed("snapper -c home undochange 1..2");
+    $machine->fail("ls /home/file");
+
+    $machine->succeed("snapper -c home delete 2");
+
+    $machine->succeed("systemctl --wait start snapper-timeline.service");
+
+    $machine->succeed("systemctl --wait start snapper-cleanup.service");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/solr.nix b/nixpkgs/nixos/tests/solr.nix
new file mode 100644
index 000000000000..9ba3863411ea
--- /dev/null
+++ b/nixpkgs/nixos/tests/solr.nix
@@ -0,0 +1,47 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+{
+  name = "solr";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  machine =
+    { config, pkgs, ... }:
+    {
+      # Ensure the virtual machine has enough memory for Solr to avoid the following error:
+      #
+      #   OpenJDK 64-Bit Server VM warning:
+      #     INFO: os::commit_memory(0x00000000e8000000, 402653184, 0)
+      #     failed; error='Cannot allocate memory' (errno=12)
+      #
+      #   There is insufficient memory for the Java Runtime Environment to continue.
+      #   Native memory allocation (mmap) failed to map 402653184 bytes for committing reserved memory.
+      virtualisation.memorySize = 2000;
+
+      services.solr.enable = true;
+    };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit('solr.service');
+    $machine->waitForOpenPort('8983');
+    $machine->succeed('curl --fail http://localhost:8983/solr/');
+
+    # adapted from pkgs.solr/examples/films/README.txt
+    $machine->succeed('sudo -u solr solr create -c films');
+    $machine->succeed(q(curl http://localhost:8983/solr/films/schema -X POST -H 'Content-type:application/json' --data-binary '{
+      "add-field" : {
+        "name":"name",
+        "type":"text_general",
+        "multiValued":false,
+        "stored":true
+      },
+      "add-field" : {
+        "name":"initial_release_date",
+        "type":"pdate",
+        "stored":true
+      }
+    }')) =~ /"status":0/ or die;
+    $machine->succeed('sudo -u solr post -c films ${pkgs.solr}/example/films/films.json');
+    $machine->succeed('curl http://localhost:8983/solr/films/query?q=name:batman') =~ /"name":"Batman Begins"/ or die;
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sonarr.nix b/nixpkgs/nixos/tests/sonarr.nix
new file mode 100644
index 000000000000..3d5c3b19b6ea
--- /dev/null
+++ b/nixpkgs/nixos/tests/sonarr.nix
@@ -0,0 +1,18 @@
+import ./make-test.nix ({ lib, ... }:
+
+with lib;
+
+rec {
+  name = "sonarr";
+  meta.maintainers = with maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.sonarr.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('sonarr.service');
+    $machine->waitForOpenPort('8989');
+    $machine->succeed("curl --fail http://localhost:8989/");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ssh-keys.nix b/nixpkgs/nixos/tests/ssh-keys.nix
new file mode 100644
index 000000000000..07d422196efa
--- /dev/null
+++ b/nixpkgs/nixos/tests/ssh-keys.nix
@@ -0,0 +1,15 @@
+pkgs:
+{ snakeOilPrivateKey = pkgs.writeText "privkey.snakeoil" ''
+    -----BEGIN EC PRIVATE KEY-----
+    MHcCAQEEIHQf/khLvYrQ8IOika5yqtWvI0oquHlpRLTZiJy5dRJmoAoGCCqGSM49
+    AwEHoUQDQgAEKF0DYGbBwbj06tA3fd/+yP44cvmwmHBWXZCKbS+RQlAKvLXMWkpN
+    r1lwMyJZoSGgBHoUahoYjTh9/sJL7XLJtA==
+    -----END EC PRIVATE KEY-----
+  '';
+
+  snakeOilPublicKey = pkgs.lib.concatStrings [
+    "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHA"
+    "yNTYAAABBBChdA2BmwcG49OrQN33f/sj+OHL5sJhwVl2Qim0vkUJQCry1zFpKTa"
+    "9ZcDMiWaEhoAR6FGoaGI04ff7CS+1yybQ= sakeoil"
+  ];
+}
diff --git a/nixpkgs/nixos/tests/strongswan-swanctl.nix b/nixpkgs/nixos/tests/strongswan-swanctl.nix
new file mode 100644
index 000000000000..8bbebd423003
--- /dev/null
+++ b/nixpkgs/nixos/tests/strongswan-swanctl.nix
@@ -0,0 +1,148 @@
+# This strongswan-swanctl test is based on:
+# https://www.strongswan.org/testing/testresults/swanctl/rw-psk-ipv4/index.html
+# https://github.com/strongswan/strongswan/tree/master/testing/tests/swanctl/rw-psk-ipv4
+#
+# The roadwarrior carol sets up a connection to gateway moon. The authentication
+# is based on pre-shared keys and IPv4 addresses. Upon the successful
+# establishment of the IPsec tunnels, the specified updown script automatically
+# inserts iptables-based firewall rules that let pass the tunneled traffic. In
+# order to test both tunnel and firewall, carol pings the client alice behind
+# the gateway moon.
+#
+#     alice                       moon                        carol
+#      eth1------vlan_0------eth1        eth2------vlan_1------eth1
+#   192.168.0.1         192.168.0.3  192.168.1.3           192.168.1.2
+#
+# See the NixOS manual for how to run this test:
+# https://nixos.org/nixos/manual/index.html#sec-running-nixos-tests-interactively
+
+import ./make-test.nix ({ pkgs, ...} :
+
+let
+  allowESP = "iptables --insert INPUT --protocol ESP --jump ACCEPT";
+
+  # Shared VPN settings:
+  vlan0         = "192.168.0.0/24";
+  carolIp       = "192.168.1.2";
+  moonIp        = "192.168.1.3";
+  version       = 2;
+  secret        = "0sFpZAZqEN6Ti9sqt4ZP5EWcqx";
+  esp_proposals = [ "aes128gcm128-x25519" ];
+  proposals     = [ "aes128-sha256-x25519" ];
+in {
+  name = "strongswan-swanctl";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ basvandijk ];
+  nodes = {
+
+    alice = { ... } : {
+      virtualisation.vlans = [ 0 ];
+      networking = {
+        dhcpcd.enable = false;
+        defaultGateway = "192.168.0.3";
+      };
+    };
+
+    moon = { config, ...} :
+      let strongswan = config.services.strongswan-swanctl.package;
+      in {
+        virtualisation.vlans = [ 0 1 ];
+        networking = {
+          dhcpcd.enable = false;
+          firewall = {
+            allowedUDPPorts = [ 4500 500 ];
+            extraCommands = allowESP;
+          };
+          nat = {
+            enable             = true;
+            internalIPs        = [ vlan0 ];
+            internalInterfaces = [ "eth1" ];
+            externalIP         = moonIp;
+            externalInterface  = "eth2";
+          };
+        };
+        environment.systemPackages = [ strongswan ];
+        services.strongswan-swanctl = {
+          enable = true;
+          swanctl = {
+            connections = {
+              "rw" = {
+                local_addrs = [ moonIp ];
+                local."main" = {
+                  auth = "psk";
+                };
+                remote."main" = {
+                  auth = "psk";
+                };
+                children = {
+                  "net" = {
+                    local_ts = [ vlan0 ];
+                    updown = "${strongswan}/libexec/ipsec/_updown iptables";
+                    inherit esp_proposals;
+                  };
+                };
+                inherit version;
+                inherit proposals;
+              };
+            };
+            secrets = {
+              ike."carol" = {
+                id."main" = carolIp;
+                inherit secret;
+              };
+            };
+          };
+        };
+      };
+
+    carol = { config, ...} :
+      let strongswan = config.services.strongswan-swanctl.package;
+      in {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          dhcpcd.enable = false;
+          firewall.extraCommands = allowESP;
+        };
+        environment.systemPackages = [ strongswan ];
+        services.strongswan-swanctl = {
+          enable = true;
+          swanctl = {
+            connections = {
+              "home" = {
+                local_addrs = [ carolIp ];
+                remote_addrs = [ moonIp ];
+                local."main" = {
+                  auth = "psk";
+                  id = carolIp;
+                };
+                remote."main" = {
+                  auth = "psk";
+                  id = moonIp;
+                };
+                children = {
+                  "home" = {
+                    remote_ts = [ vlan0 ];
+                    start_action = "trap";
+                    updown = "${strongswan}/libexec/ipsec/_updown iptables";
+                    inherit esp_proposals;
+                  };
+                };
+                inherit version;
+                inherit proposals;
+              };
+            };
+            secrets = {
+              ike."moon" = {
+                id."main" = moonIp;
+                inherit secret;
+              };
+            };
+          };
+        };
+      };
+
+  };
+  testScript = ''
+    startAll();
+    $carol->waitUntilSucceeds("ping -c 1 alice");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/subversion.nix b/nixpkgs/nixos/tests/subversion.nix
new file mode 100644
index 000000000000..6175155cdfc9
--- /dev/null
+++ b/nixpkgs/nixos/tests/subversion.nix
@@ -0,0 +1,121 @@
+import ./make-test.nix ({ 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 = overrideInStdenv pkgs.stdenv [ pkgs.keepBuildTree ];
+          extraConfig =
+            ''
+              GCOV_KERNEL y
+              GCOV_PROFILE_ALL y
+            '';
+        });
+      };
+
+in
+
+{
+  name = "subversion";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  nodes =
+    { webserver =
+        { ... }:
+
+        {
+          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 =
+        { 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/nixpkgs/nixos/tests/sudo.nix b/nixpkgs/nixos/tests/sudo.nix
new file mode 100644
index 000000000000..fc16b99cc19c
--- /dev/null
+++ b/nixpkgs/nixos/tests/sudo.nix
@@ -0,0 +1,93 @@
+# Some tests to ensure sudo is working properly.
+
+let
+  password = "helloworld";
+
+in
+  import ./make-test.nix ({ pkgs, ...} : {
+    name = "sudo";
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ lschuermann ];
+    };
+
+    machine =
+      { lib, ... }:
+      with lib;
+      {
+        users.groups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
+        users.users = {
+          test0 = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+          test1 = { isNormalUser = true; password = password; };
+          test2 = { isNormalUser = true; extraGroups = [ "foobar" ]; password = password; };
+          test3 = { isNormalUser = true; extraGroups = [ "barfoo" ]; };
+          test4 = { isNormalUser = true; extraGroups = [ "baz" ]; };
+          test5 = { isNormalUser = true; };
+        };
+
+        security.sudo = {
+          enable = true;
+          wheelNeedsPassword = false;
+
+          extraRules = [
+            # SUDOERS SYNTAX CHECK (Test whether the module produces a valid output;
+            # errors being detected by the visudo checks.
+
+            # These should not create any entries
+            { users = [ "notest1" ]; commands = [ ]; }
+            { commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+            # Test defining commands with the options syntax, though not setting any options
+            { users = [ "notest2" ]; commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+
+            # CONFIGURATION FOR TEST CASES
+            { users = [ "test1" ]; groups = [ "foobar" ]; commands = [ "ALL" ]; }
+            { groups = [ "barfoo" 1337 ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" "NOSETENV" ]; } ]; }
+            { users = [ "test5" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" "SETENV" ]; } ]; runAs = "test1:barfoo"; }
+          ];
+        };
+      };
+
+    testScript =
+      ''
+        subtest "users in wheel group should have passwordless sudo", sub {
+            $machine->succeed("su - test0 -c \"sudo -u root true\"");
+        };
+
+        subtest "test1 user should have sudo with password", sub {
+            $machine->succeed("su - test1 -c \"echo ${password} | sudo -S -u root true\"");
+        };
+
+        subtest "test1 user should not be able to use sudo without password", sub {
+            $machine->fail("su - test1 -c \"sudo -n -u root true\"");
+        };
+
+        subtest "users in group 'foobar' should be able to use sudo with password", sub {
+            $machine->succeed("sudo -u test2 echo ${password} | sudo -S -u root true");
+        };
+
+        subtest "users in group 'barfoo' should be able to use sudo without password", sub {
+            $machine->succeed("sudo -u test3 sudo -n -u root true");
+        };
+
+        subtest "users in group 'baz' (GID 1337) should be able to use sudo without password", sub {
+            $machine->succeed("sudo -u test4 sudo -n -u root echo true");
+        };
+
+        subtest "test5 user should be able to run commands under test1", sub {
+            $machine->succeed("sudo -u test5 sudo -n -u test1 true");
+        };
+
+        subtest "test5 user should not be able to run commands under root", sub {
+            $machine->fail("sudo -u test5 sudo -n -u root true");
+        };
+
+        subtest "test5 user should be able to keep his environment", sub {
+            $machine->succeed("sudo -u test5 sudo -n -E -u test1 true");
+        };
+
+        subtest "users in group 'barfoo' should not be able to keep their environment", sub {
+            $machine->fail("sudo -u test3 sudo -n -E -u root true");
+        };
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/switch-test.nix b/nixpkgs/nixos/tests/switch-test.nix
new file mode 100644
index 000000000000..32010838e67b
--- /dev/null
+++ b/nixpkgs/nixos/tests/switch-test.nix
@@ -0,0 +1,25 @@
+# Test configuration switching.
+
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "switch-test";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ gleber ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      users.mutableUsers = false;
+    };
+    other = { ... }: {
+      users.mutableUsers = true;
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    originalSystem = nodes.machine.config.system.build.toplevel;
+    otherSystem = nodes.other.config.system.build.toplevel;
+  in ''
+    $machine->succeed("env -i ${originalSystem}/bin/switch-to-configuration test | tee /dev/stderr");
+    $machine->succeed("env -i ${otherSystem}/bin/switch-to-configuration test | tee /dev/stderr");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/syncthing-relay.nix b/nixpkgs/nixos/tests/syncthing-relay.nix
new file mode 100644
index 000000000000..f1ceb4993337
--- /dev/null
+++ b/nixpkgs/nixos/tests/syncthing-relay.nix
@@ -0,0 +1,22 @@
+import ./make-test.nix ({ lib, pkgs, ... }: {
+  name = "syncthing-relay";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ delroth ];
+
+  machine = {
+    environment.systemPackages = [ pkgs.jq ];
+    services.syncthing.relay = {
+      enable = true;
+      providedBy = "nixos-test";
+      pools = [];  # Don't connect to any pool while testing.
+      port = 12345;
+      statusPort = 12346;
+    };
+  };
+
+  testScript = ''
+    $machine->waitForUnit("syncthing-relay.service");
+    $machine->waitForOpenPort(12345);
+    $machine->waitForOpenPort(12346);
+    $machine->succeed("curl http://localhost:12346/status | jq -r '.options.\"provided-by\"'") =~ /nixos-test/ or die;
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd.nix b/nixpkgs/nixos/tests/systemd.nix
new file mode 100644
index 000000000000..4d470126abee
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd.nix
@@ -0,0 +1,73 @@
+import ./make-test.nix {
+  name = "systemd";
+
+  machine = { lib, ... }: {
+    imports = [ common/user-account.nix common/x11.nix ];
+
+    virtualisation.emptyDiskImages = [ 512 ];
+
+    fileSystems = lib.mkVMOverride {
+      "/test-x-initrd-mount" = {
+        device = "/dev/vdb";
+        fsType = "ext2";
+        autoFormat = true;
+        noCheck = true;
+        options = [ "x-initrd.mount" ];
+      };
+    };
+
+    systemd.extraConfig = "DefaultEnvironment=\"XXX_SYSTEM=foo\"";
+    systemd.user.extraConfig = "DefaultEnvironment=\"XXX_USER=bar\"";
+    services.journald.extraConfig = "Storage=volatile";
+    services.xserver.displayManager.auto.user = "alice";
+
+    systemd.services.testservice1 = {
+      description = "Test Service 1";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        if [ "$XXX_SYSTEM" = foo ]; then
+          touch /system_conf_read
+        fi
+      '';
+    };
+
+    systemd.user.services.testservice2 = {
+      description = "Test Service 2";
+      wantedBy = [ "default.target" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        if [ "$XXX_USER" = bar ]; then
+          touch "$HOME/user_conf_read"
+        fi
+      '';
+    };
+  };
+
+  testScript = ''
+    $machine->waitForX;
+    # wait for user services
+    $machine->waitForUnit("default.target","alice");
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/35415
+    subtest "configuration files are recognized by systemd", sub {
+      $machine->succeed('test -e /system_conf_read');
+      $machine->succeed('test -e /home/alice/user_conf_read');
+      $machine->succeed('test -z $(ls -1 /var/log/journal)');
+    };
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/50273
+    subtest "DynamicUser actually allocates a user", sub {
+        $machine->succeed('systemd-run --pty --property=Type=oneshot --property=DynamicUser=yes --property=User=iamatest whoami | grep iamatest');
+    };
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/35268
+    subtest "file system with x-initrd.mount is not unmounted", sub {
+      $machine->shutdown;
+      $machine->waitForUnit('multi-user.target');
+      # If the file system was unmounted during the shutdown the file system
+      # has a last mount time, because the file system wasn't checked.
+      $machine->fail('dumpe2fs /dev/vdb | grep -q "^Last mount time: *n/a"');
+    };
+  '';
+}
diff --git a/nixpkgs/nixos/tests/taskserver.nix b/nixpkgs/nixos/tests/taskserver.nix
new file mode 100644
index 000000000000..ab9b589f8593
--- /dev/null
+++ b/nixpkgs/nixos/tests/taskserver.nix
@@ -0,0 +1,290 @@
+import ./make-test.nix ({ pkgs, ... }: let
+  snakeOil = pkgs.runCommand "snakeoil-certs" {
+    outputs = [ "out" "cacert" "cert" "key" "crl" ];
+    buildInputs = [ pkgs.gnutls.bin ];
+    caTemplate = pkgs.writeText "snakeoil-ca.template" ''
+      cn = server
+      expiration_days = -1
+      cert_signing_key
+      ca
+    '';
+    certTemplate = pkgs.writeText "snakeoil-cert.template" ''
+      cn = server
+      expiration_days = -1
+      tls_www_server
+      encryption_key
+      signing_key
+    '';
+    crlTemplate = pkgs.writeText "snakeoil-crl.template" ''
+      expiration_days = -1
+    '';
+    userCertTemplate = pkgs.writeText "snakeoil-user-cert.template" ''
+      organization = snakeoil
+      cn = server
+      expiration_days = -1
+      tls_www_client
+      encryption_key
+      signing_key
+    '';
+  } ''
+    certtool -p --bits 4096 --outfile ca.key
+    certtool -s --template "$caTemplate" --load-privkey ca.key \
+                --outfile "$cacert"
+    certtool -p --bits 4096 --outfile "$key"
+    certtool -c --template "$certTemplate" \
+                --load-ca-privkey ca.key \
+                --load-ca-certificate "$cacert" \
+                --load-privkey "$key" \
+                --outfile "$cert"
+    certtool --generate-crl --template "$crlTemplate" \
+                            --load-ca-privkey ca.key \
+                            --load-ca-certificate "$cacert" \
+                            --outfile "$crl"
+
+    mkdir "$out"
+
+    # Stripping key information before the actual PEM-encoded values is solely
+    # to make test output a bit less verbose when copying the client key to the
+    # actual client.
+    certtool -p --bits 4096 | sed -n \
+      -e '/^----* *BEGIN/,/^----* *END/p' > "$out/alice.key"
+
+    certtool -c --template "$userCertTemplate" \
+                --load-privkey "$out/alice.key" \
+                --load-ca-privkey ca.key \
+                --load-ca-certificate "$cacert" \
+                --outfile "$out/alice.cert"
+  '';
+
+in {
+  name = "taskserver";
+
+  nodes = rec {
+    server = {
+      services.taskserver.enable = true;
+      services.taskserver.listenHost = "::";
+      services.taskserver.fqdn = "server";
+      services.taskserver.organisations = {
+        testOrganisation.users = [ "alice" "foo" ];
+        anotherOrganisation.users = [ "bob" ];
+      };
+    };
+
+    # New generation of the server with manual config
+    newServer = { lib, nodes, ... }: {
+      imports = [ server ];
+      services.taskserver.pki.manual = {
+        ca.cert = snakeOil.cacert;
+        server.cert = snakeOil.cert;
+        server.key = snakeOil.key;
+        server.crl = snakeOil.crl;
+      };
+      # This is to avoid assigning a different network address to the new
+      # generation.
+      networking = lib.mapAttrs (lib.const lib.mkForce) {
+        interfaces.eth1.ipv4 = nodes.server.config.networking.interfaces.eth1.ipv4;
+        inherit (nodes.server.config.networking)
+          hostName primaryIPAddress extraHosts;
+      };
+    };
+
+    client1 = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.taskwarrior pkgs.gnutls ];
+      users.users.alice.isNormalUser = true;
+      users.users.bob.isNormalUser = true;
+      users.users.foo.isNormalUser = true;
+      users.users.bar.isNormalUser = true;
+    };
+
+    client2 = client1;
+  };
+
+  testScript = { nodes, ... }: let
+    cfg = nodes.server.config.services.taskserver;
+    portStr = toString cfg.listenPort;
+    newServerSystem = nodes.newServer.config.system.build.toplevel;
+    switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
+  in ''
+    sub su ($$) {
+      my ($user, $cmd) = @_;
+      my $esc = $cmd =~ s/'/'\\${"'"}'/gr;
+      return "su - $user -c '$esc'";
+    }
+
+    sub setupClientsFor ($$;$) {
+      my ($org, $user, $extraInit) = @_;
+
+      for my $client ($client1, $client2) {
+        $client->nest("initialize client for user $user", sub {
+          $client->succeed(
+            (su $user, "rm -rf /home/$user/.task"),
+            (su $user, "task rc.confirmation=no config confirmation no")
+          );
+
+          my $exportinfo = $server->succeed(
+            "nixos-taskserver user export $org $user"
+          );
+
+          $exportinfo =~ s/'/'\\'''/g;
+
+          $client->nest("importing taskwarrior configuration", sub {
+            my $cmd = su $user, "eval '$exportinfo' >&2";
+            my ($status, $out) = $client->execute_($cmd);
+            if ($status != 0) {
+              $client->log("output: $out");
+              die "command `$cmd' did not succeed (exit code $status)\n";
+            }
+          });
+
+          eval { &$extraInit($client, $org, $user) };
+
+          $client->succeed(su $user,
+            "task config taskd.server server:${portStr} >&2"
+          );
+
+          $client->succeed(su $user, "task sync init >&2");
+        });
+      }
+    }
+
+    sub restartServer {
+      $server->succeed("systemctl restart taskserver.service");
+      $server->waitForOpenPort(${portStr});
+    }
+
+    sub readdImperativeUser {
+      $server->nest("(re-)add imperative user bar", sub {
+        $server->execute("nixos-taskserver org remove imperativeOrg");
+        $server->succeed(
+          "nixos-taskserver org add imperativeOrg",
+          "nixos-taskserver user add imperativeOrg bar"
+        );
+        setupClientsFor "imperativeOrg", "bar";
+      });
+    }
+
+    sub testSync ($) {
+      my $user = $_[0];
+      subtest "sync for user $user", sub {
+        $client1->succeed(su $user, "task add foo >&2");
+        $client1->succeed(su $user, "task sync >&2");
+        $client2->fail(su $user, "task list >&2");
+        $client2->succeed(su $user, "task sync >&2");
+        $client2->succeed(su $user, "task list >&2");
+      };
+    }
+
+    sub checkClientCert ($) {
+      my $user = $_[0];
+      my $cmd = "gnutls-cli".
+        " --x509cafile=/home/$user/.task/keys/ca.cert".
+        " --x509keyfile=/home/$user/.task/keys/private.key".
+        " --x509certfile=/home/$user/.task/keys/public.cert".
+        " --port=${portStr} server < /dev/null";
+      return su $user, $cmd;
+    }
+
+    # Explicitly start the VMs so that we don't accidentally start newServer
+    $server->start;
+    $client1->start;
+    $client2->start;
+
+    $server->waitForUnit("taskserver.service");
+
+    $server->succeed(
+      "nixos-taskserver user list testOrganisation | grep -qxF alice",
+      "nixos-taskserver user list testOrganisation | grep -qxF foo",
+      "nixos-taskserver user list anotherOrganisation | grep -qxF bob"
+    );
+
+    $server->waitForOpenPort(${portStr});
+
+    $client1->waitForUnit("multi-user.target");
+    $client2->waitForUnit("multi-user.target");
+
+    setupClientsFor "testOrganisation", "alice";
+    setupClientsFor "testOrganisation", "foo";
+    setupClientsFor "anotherOrganisation", "bob";
+
+    testSync $_ for ("alice", "bob", "foo");
+
+    $server->fail("nixos-taskserver user add imperativeOrg bar");
+    readdImperativeUser;
+
+    testSync "bar";
+
+    subtest "checking certificate revocation of user bar", sub {
+      $client1->succeed(checkClientCert "bar");
+
+      $server->succeed("nixos-taskserver user remove imperativeOrg bar");
+      restartServer;
+
+      $client1->fail(checkClientCert "bar");
+
+      $client1->succeed(su "bar", "task add destroy everything >&2");
+      $client1->fail(su "bar", "task sync >&2");
+    };
+
+    readdImperativeUser;
+
+    subtest "checking certificate revocation of org imperativeOrg", sub {
+      $client1->succeed(checkClientCert "bar");
+
+      $server->succeed("nixos-taskserver org remove imperativeOrg");
+      restartServer;
+
+      $client1->fail(checkClientCert "bar");
+
+      $client1->succeed(su "bar", "task add destroy even more >&2");
+      $client1->fail(su "bar", "task sync >&2");
+    };
+
+    readdImperativeUser;
+
+    subtest "check whether declarative config overrides user bar", sub {
+      restartServer;
+      testSync "bar";
+    };
+
+    subtest "check manual configuration", sub {
+      # Remove the keys from automatic CA creation, to make sure the new
+      # generation doesn't use keys from before.
+      $server->succeed('rm -rf ${cfg.dataDir}/keys/* >&2');
+
+      $server->succeed('${switchToNewServer} >&2');
+      $server->waitForUnit("taskserver.service");
+      $server->waitForOpenPort(${portStr});
+
+      $server->succeed(
+        "nixos-taskserver org add manualOrg",
+        "nixos-taskserver user add manualOrg alice"
+      );
+
+      setupClientsFor "manualOrg", "alice", sub {
+        my ($client, $org, $user) = @_;
+        my $cfgpath = "/home/$user/.task";
+
+        $client->copyFileFromHost("${snakeOil.cacert}", "$cfgpath/ca.cert");
+        for my $file ('alice.key', 'alice.cert') {
+          $client->copyFileFromHost("${snakeOil}/$file", "$cfgpath/$file");
+        }
+
+        for my $file ("$user.key", "$user.cert") {
+          $client->copyFileFromHost(
+            "${snakeOil}/$file", "$cfgpath/$file"
+          );
+        }
+        $client->copyFileFromHost(
+          "${snakeOil.cacert}", "$cfgpath/ca.cert"
+        );
+        $client->succeed(
+          (su "alice", "task config taskd.ca $cfgpath/ca.cert"),
+          (su "alice", "task config taskd.key $cfgpath/$user.key"),
+          (su $user, "task config taskd.certificate $cfgpath/$user.cert")
+        );
+      };
+
+      testSync "alice";
+    };
+  '';
+})
diff --git a/nixpkgs/nixos/tests/telegraf.nix b/nixpkgs/nixos/tests/telegraf.nix
new file mode 100644
index 000000000000..6776f8d8c37f
--- /dev/null
+++ b/nixpkgs/nixos/tests/telegraf.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "telegraf";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mic92 ];
+  };
+
+  machine = { ... }: {
+    services.telegraf.enable = true;
+    services.telegraf.extraConfig = {
+      agent.interval = "1s";
+      agent.flush_interval = "1s";
+      inputs.exec = {
+        commands = [
+          "${pkgs.runtimeShell} -c 'echo example,tag=a i=42i'"
+        ];
+        timeout = "5s";
+        data_format = "influx";
+      };
+      outputs.file.files = ["/tmp/metrics.out"];
+      outputs.file.data_format = "influx";
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("telegraf.service");
+    $machine->waitUntilSucceeds("grep -q example /tmp/metrics.out");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/testdb.sql b/nixpkgs/nixos/tests/testdb.sql
new file mode 100644
index 000000000000..3c68c49ae82c
--- /dev/null
+++ b/nixpkgs/nixos/tests/testdb.sql
@@ -0,0 +1,11 @@
+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');
+insert into tests values (5, 'hello');
diff --git a/nixpkgs/nixos/tests/timezone.nix b/nixpkgs/nixos/tests/timezone.nix
new file mode 100644
index 000000000000..2204649a3fc4
--- /dev/null
+++ b/nixpkgs/nixos/tests/timezone.nix
@@ -0,0 +1,45 @@
+{
+  timezone-static = import ./make-test.nix ({ pkgs, ... }: {
+    name = "timezone-static";
+    meta.maintainers = with pkgs.lib.maintainers; [ lheckemann ];
+
+    machine.time.timeZone = "Europe/Amsterdam";
+
+    testScript = ''
+      $machine->waitForUnit("dbus.socket");
+      $machine->fail("timedatectl set-timezone Asia/Tokyo");
+      my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"');
+      $dateResult[1] eq "1970-01-01 01:00:00\n" or die "Timezone seems to be wrong";
+    '';
+  });
+
+  timezone-imperative = import ./make-test.nix ({ pkgs, ... }: {
+    name = "timezone-imperative";
+    meta.maintainers = with pkgs.lib.maintainers; [ lheckemann ];
+
+    machine.time.timeZone = null;
+
+    testScript = ''
+      $machine->waitForUnit("dbus.socket");
+
+      # Should default to UTC
+      my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"');
+      print $dateResult[1];
+      $dateResult[1] eq "1970-01-01 00:00:00\n" or die "Timezone seems to be wrong";
+
+      $machine->succeed("timedatectl set-timezone Asia/Tokyo");
+
+      # Adjustment should be taken into account
+      my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"');
+      print $dateResult[1];
+      $dateResult[1] eq "1970-01-01 09:00:00\n" or die "Timezone was not adjusted";
+
+      # Adjustment should persist across a reboot
+      $machine->shutdown;
+      $machine->waitForUnit("dbus.socket");
+      my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"');
+      print $dateResult[1];
+      $dateResult[1] eq "1970-01-01 09:00:00\n" or die "Timezone adjustment was not persisted";
+    '';
+  });
+}
diff --git a/nixpkgs/nixos/tests/tomcat.nix b/nixpkgs/nixos/tests/tomcat.nix
new file mode 100644
index 000000000000..af63c7ee8e02
--- /dev/null
+++ b/nixpkgs/nixos/tests/tomcat.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "tomcat";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  nodes = {
+    server =
+      { ... }:
+
+      { services.tomcat.enable = true;
+        services.httpd.enable = true;
+        services.httpd.adminAddr = "foo@bar.com";
+        services.httpd.extraSubservices =
+          [ { serviceType = "tomcat-connector"; } ];
+        networking.firewall.allowedTCPPorts = [ 80 ];
+      };
+
+    client = { };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("tomcat");
+    $client->waitForUnit("network.target");
+    $client->waitUntilSucceeds("curl --fail http://server/examples/servlets/servlet/HelloWorldExample");
+    $client->waitUntilSucceeds("curl --fail http://server/examples/jsp/jsp2/simpletag/hello.jsp");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tor.nix b/nixpkgs/nixos/tests/tor.nix
new file mode 100644
index 000000000000..0cb44ddff248
--- /dev/null
+++ b/nixpkgs/nixos/tests/tor.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ lib, ... }: with lib;
+
+rec {
+  name = "tor";
+  meta.maintainers = with maintainers; [ joachifm ];
+
+  common =
+    { ... }:
+    { boot.kernelParams = [ "audit=0" "apparmor=0" "quiet" ];
+      networking.firewall.enable = false;
+      networking.useDHCP = false;
+    };
+
+  nodes.client =
+    { pkgs, ... }:
+    { imports = [ common ];
+      environment.systemPackages = with pkgs; [ netcat ];
+      services.tor.enable = true;
+      services.tor.client.enable = true;
+      services.tor.controlPort = 9051;
+    };
+
+  testScript = ''
+    $client->waitForUnit("tor.service");
+    $client->waitForOpenPort(9051);
+    $client->succeed("echo GETINFO version | nc 127.0.0.1 9051") =~ /514 Authentication required./ or die;
+  '';
+})
diff --git a/nixpkgs/nixos/tests/trac.nix b/nixpkgs/nixos/tests/trac.nix
new file mode 100644
index 000000000000..4599885acde6
--- /dev/null
+++ b/nixpkgs/nixos/tests/trac.nix
@@ -0,0 +1,74 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "trac";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  nodes = {
+    storage =
+      { ... }:
+      { 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 =
+      { pkgs, ... }:
+      { services.postgresql.enable = true;
+        services.postgresql.package = pkgs.postgresql;
+        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 =
+      { pkgs, ... }:
+      { fileSystems = pkgs.lib.mkVMOverride
+          [ { 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 =
+      { ... }:
+      { imports = [ ./common/x11.nix ];
+        services.xserver.desktopManager.plasma5.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/nixpkgs/nixos/tests/transmission.nix b/nixpkgs/nixos/tests/transmission.nix
new file mode 100644
index 000000000000..f1c238730ebb
--- /dev/null
+++ b/nixpkgs/nixos/tests/transmission.nix
@@ -0,0 +1,21 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "transmission";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ coconnor ];
+  };
+
+  machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    networking.firewall.allowedTCPPorts = [ 9091 ];
+
+    services.transmission.enable = true;
+  };
+
+  testScript =
+    ''
+      startAll;
+      $machine->waitForUnit("transmission");
+      $machine->shutdown;
+    '';
+})
diff --git a/nixpkgs/nixos/tests/udisks2.nix b/nixpkgs/nixos/tests/udisks2.nix
new file mode 100644
index 000000000000..8bbbe286efcf
--- /dev/null
+++ b/nixpkgs/nixos/tests/udisks2.nix
@@ -0,0 +1,61 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+
+  stick = pkgs.fetchurl {
+    url = http://nixos.org/~eelco/nix/udisks-test.img.xz;
+    sha256 = "0was1xgjkjad91nipzclaz5biv3m4b2nk029ga6nk7iklwi19l8b";
+  };
+
+in
+
+{
+  name = "udisks2";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow ];
+  };
+
+  machine =
+    { ... }:
+    { services.udisks2.enable = true;
+      imports = [ ./common/user-account.nix ];
+
+      security.polkit.extraConfig =
+        ''
+          polkit.addRule(function(action, subject) {
+            if (subject.user == "alice") return "yes";
+          });
+        '';
+    };
+
+  testScript =
+    ''
+      my $stick = $machine->stateDir . "/usbstick.img";
+      system("xz -d < ${stick} > $stick") == 0 or die;
+
+      $machine->succeed("udisksctl info -b /dev/vda >&2");
+      $machine->fail("udisksctl info -b /dev/sda1");
+
+      # Attach a USB stick and wait for it to show up.
+      $machine->sendMonitorCommand("drive_add 0 id=stick,if=none,file=$stick,format=raw");
+      $machine->sendMonitorCommand("device_add usb-storage,id=stick,drive=stick");
+      $machine->waitUntilSucceeds("udisksctl info -b /dev/sda1");
+      $machine->succeed("udisksctl info -b /dev/sda1 | grep 'IdLabel:.*USBSTICK'");
+
+      # Mount the stick as a non-root user and do some stuff with it.
+      $machine->succeed("su - alice -c 'udisksctl info -b /dev/sda1'");
+      $machine->succeed("su - alice -c 'udisksctl mount -b /dev/sda1'");
+      $machine->succeed("su - alice -c 'cat /run/media/alice/USBSTICK/test.txt'") =~ /Hello World/ or die;
+      $machine->succeed("su - alice -c 'echo foo > /run/media/alice/USBSTICK/bar.txt'");
+
+      # Unmounting the stick should make the mountpoint disappear.
+      $machine->succeed("su - alice -c 'udisksctl unmount -b /dev/sda1'");
+      $machine->fail("[ -d /run/media/alice/USBSTICK ]");
+
+      # Remove the USB stick.
+      $machine->sendMonitorCommand("device_del stick");
+      $machine->waitUntilFails("udisksctl info -b /dev/sda1");
+      $machine->fail("[ -e /dev/sda ]");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/upnp.nix b/nixpkgs/nixos/tests/upnp.nix
new file mode 100644
index 000000000000..3f2dd13fb560
--- /dev/null
+++ b/nixpkgs/nixos/tests/upnp.nix
@@ -0,0 +1,94 @@
+# This tests whether UPnP port mappings can be created using Miniupnpd
+# and Miniupnpc.
+# It runs a Miniupnpd service on one machine, and verifies
+# a client can indeed create a port mapping using Miniupnpc. If
+# this succeeds an external client will try to connect to the port
+# mapping.
+
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+  internalRouterAddress = "192.168.3.1";
+  internalClient1Address = "192.168.3.2";
+  externalRouterAddress = "80.100.100.1";
+  externalClient2Address = "80.100.100.2";
+in
+{
+  name = "upnp";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bobvanderlinden ];
+  };
+
+  nodes =
+    {
+      router =
+        { pkgs, nodes, ... }:
+        { virtualisation.vlans = [ 1 2 ];
+          networking.nat.enable = true;
+          networking.nat.internalInterfaces = [ "eth2" ];
+          networking.nat.externalInterface = "eth1";
+          networking.firewall.enable = true;
+          networking.firewall.trustedInterfaces = [ "eth2" ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalRouterAddress; prefixLength = 24; }
+          ];
+          networking.interfaces.eth2.ipv4.addresses = [
+            { address = internalRouterAddress; prefixLength = 24; }
+          ];
+          services.miniupnpd = {
+            enable = true;
+            externalInterface = "eth1";
+            internalIPs = [ "eth2" ];
+            appendConfig = ''
+              ext_ip=${externalRouterAddress}
+            '';
+          };
+        };
+
+      client1 =
+        { pkgs, nodes, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpc pkgs.netcat ];
+          virtualisation.vlans = [ 2 ];
+          networking.defaultGateway = internalRouterAddress;
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = internalClient1Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+
+          services.httpd.enable = true;
+          services.httpd.listen = [{ ip = "*"; port = 9000; }];
+          services.httpd.adminAddr = "foo@example.org";
+          services.httpd.documentRoot = "/tmp";
+        };
+
+      client2 =
+        { pkgs, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpc ];
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalClient2Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      startAll;
+
+      # Wait for network and miniupnpd.
+      $router->waitForUnit("network-online.target");
+      # $router->waitForUnit("nat");
+      $router->waitForUnit("firewall.service");
+      $router->waitForUnit("miniupnpd");
+
+      $client1->waitForUnit("network-online.target");
+
+      $client1->succeed("upnpc -a ${internalClient1Address} 9000 9000 TCP");
+
+      $client1->waitForUnit("httpd");
+      $client2->waitUntilSucceeds("curl http://${externalRouterAddress}:9000/");
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/vault.nix b/nixpkgs/nixos/tests/vault.nix
new file mode 100644
index 000000000000..caf0cbb2abfe
--- /dev/null
+++ b/nixpkgs/nixos/tests/vault.nix
@@ -0,0 +1,23 @@
+import ./make-test.nix ({ pkgs, ... }:
+{
+  name = "vault";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lnl7 ];
+  };
+  machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.vault ];
+    environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
+    services.vault.enable = true;
+  };
+
+  testScript =
+    ''
+      startAll;
+
+      $machine->waitForUnit('multi-user.target');
+      $machine->waitForUnit('vault.service');
+      $machine->waitForOpenPort(8200);
+      $machine->succeed('vault operator init');
+      $machine->succeed('vault status | grep Sealed | grep true');
+    '';
+})
diff --git a/nixpkgs/nixos/tests/virtualbox.nix b/nixpkgs/nixos/tests/virtualbox.nix
new file mode 100644
index 000000000000..84d5f3e1530e
--- /dev/null
+++ b/nixpkgs/nixos/tests/virtualbox.nix
@@ -0,0 +1,512 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+  debug ? false,
+  enableUnfree ? false
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  testVMConfig = vmName: attrs: { config, pkgs, lib, ... }: let
+    guestAdditions = pkgs.linuxPackages.virtualboxGuestAdditions;
+
+    miniInit = ''
+      #!${pkgs.stdenv.shell} -xe
+      export PATH="${lib.makeBinPath [ pkgs.coreutils pkgs.utillinux ]}"
+
+      mkdir -p /run/dbus
+      cat > /etc/passwd <<EOF
+      root:x:0:0::/root:/bin/false
+      messagebus:x:1:1::/run/dbus:/bin/false
+      EOF
+      cat > /etc/group <<EOF
+      root:x:0:
+      messagebus:x:1:
+      EOF
+
+      "${pkgs.dbus.daemon}/bin/dbus-daemon" --fork \
+        --config-file="${pkgs.dbus.daemon}/share/dbus-1/system.conf"
+
+      ${guestAdditions}/bin/VBoxService
+      ${(attrs.vmScript or (const "")) pkgs}
+
+      i=0
+      while [ ! -e /mnt-root/shutdown ]; do
+        sleep 10
+        i=$(($i + 10))
+        [ $i -le 120 ] || fail
+      done
+
+      rm -f /mnt-root/boot-done /mnt-root/shutdown
+    '';
+  in {
+    boot.kernelParams = [
+      "console=tty0" "console=ttyS0" "ignore_loglevel"
+      "boot.trace" "panic=1" "boot.panic_on_fail"
+      "init=${pkgs.writeScript "mini-init.sh" miniInit}"
+    ];
+
+    # XXX: Remove this once TSS location detection has been fixed in VirtualBox
+    boot.kernelPackages = pkgs.linuxPackages_4_9;
+
+    fileSystems."/" = {
+      device = "vboxshare";
+      fsType = "vboxsf";
+    };
+
+    virtualisation.virtualbox.guest.enable = true;
+
+    boot.initrd.kernelModules = [
+      "af_packet" "vboxsf"
+      "virtio" "virtio_pci" "virtio_ring" "virtio_net" "vboxguest"
+    ];
+
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs "${guestAdditions}/bin/mount.vboxsf"
+      copy_bin_and_libs "${pkgs.utillinux}/bin/unshare"
+      ${(attrs.extraUtilsCommands or (const "")) pkgs}
+    '';
+
+    boot.initrd.postMountCommands = ''
+      touch /mnt-root/boot-done
+      hostname "${vmName}"
+      mkdir -p /nix/store
+      unshare -m ${escapeShellArg pkgs.stdenv.shell} -c '
+        mount -t vboxsf nixstore /nix/store
+        exec "$stage2Init"
+      '
+      poweroff -f
+    '';
+
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isYes "SERIAL_8250_CONSOLE")
+      (isYes "SERIAL_8250")
+    ];
+  };
+
+  mkLog = logfile: tag: let
+    rotated = map (i: "${logfile}.${toString i}") (range 1 9);
+    all = concatMapStringsSep " " (f: "\"${f}\"") ([logfile] ++ rotated);
+    logcmd = "tail -F ${all} 2> /dev/null | logger -t \"${tag}\"";
+  in optionalString debug "$machine->execute(ru '${logcmd} & disown');";
+
+  testVM = vmName: vmScript: let
+    cfg = (import ../lib/eval-config.nix {
+      system = "i686-linux";
+      modules = [
+        ../modules/profiles/minimal.nix
+        (testVMConfig vmName vmScript)
+      ];
+    }).config;
+  in pkgs.vmTools.runInLinuxVM (pkgs.runCommand "virtualbox-image" {
+    preVM = ''
+      mkdir -p "$out"
+      diskImage="$(pwd)/qimage"
+      ${pkgs.vmTools.qemu}/bin/qemu-img create -f raw "$diskImage" 100M
+    '';
+
+    postVM = ''
+      echo "creating VirtualBox disk image..."
+      ${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -O vdi \
+        "$diskImage" "$out/disk.vdi"
+    '';
+
+    buildInputs = [ pkgs.utillinux pkgs.perl ];
+  } ''
+    ${pkgs.parted}/sbin/parted --script /dev/vda mklabel msdos
+    ${pkgs.parted}/sbin/parted --script /dev/vda -- mkpart primary ext2 1M -1s
+    ${pkgs.e2fsprogs}/sbin/mkfs.ext4 /dev/vda1
+    ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1
+    mkdir /mnt
+    mount /dev/vda1 /mnt
+    cp "${cfg.system.build.kernel}/bzImage" /mnt/linux
+    cp "${cfg.system.build.initialRamdisk}/initrd" /mnt/initrd
+
+    ${pkgs.grub2}/bin/grub-install --boot-directory=/mnt /dev/vda
+
+    cat > /mnt/grub/grub.cfg <<GRUB
+    set root=hd0,1
+    linux /linux ${concatStringsSep " " cfg.boot.kernelParams}
+    initrd /initrd
+    boot
+    GRUB
+    umount /mnt
+  '');
+
+  createVM = name: attrs: let
+    mkFlags = concatStringsSep " ";
+
+    sharePath = "/home/alice/vboxshare-${name}";
+
+    createFlags = mkFlags [
+      "--ostype Linux26"
+      "--register"
+    ];
+
+    vmFlags = mkFlags ([
+      "--uart1 0x3F8 4"
+      "--uartmode1 client /run/virtualbox-log-${name}.sock"
+      "--memory 768"
+      "--audio none"
+    ] ++ (attrs.vmFlags or []));
+
+    controllerFlags = mkFlags [
+      "--name SATA"
+      "--add sata"
+      "--bootable on"
+      "--hostiocache on"
+    ];
+
+    diskFlags = mkFlags [
+      "--storagectl SATA"
+      "--port 0"
+      "--device 0"
+      "--type hdd"
+      "--mtype immutable"
+      "--medium ${testVM name attrs}/disk.vdi"
+    ];
+
+    sharedFlags = mkFlags [
+      "--name vboxshare"
+      "--hostpath ${sharePath}"
+    ];
+
+    nixstoreFlags = mkFlags [
+      "--name nixstore"
+      "--hostpath /nix/store"
+      "--readonly"
+    ];
+  in {
+    machine = {
+      systemd.sockets."vboxtestlog-${name}" = {
+        description = "VirtualBox Test Machine Log Socket For ${name}";
+        wantedBy = [ "sockets.target" ];
+        before = [ "multi-user.target" ];
+        socketConfig.ListenStream = "/run/virtualbox-log-${name}.sock";
+        socketConfig.Accept = true;
+      };
+
+      systemd.services."vboxtestlog-${name}@" = {
+        description = "VirtualBox Test Machine Log For ${name}";
+        serviceConfig.StandardInput = "socket";
+        serviceConfig.StandardOutput = "syslog";
+        serviceConfig.SyslogIdentifier = "GUEST-${name}";
+        serviceConfig.ExecStart = "${pkgs.coreutils}/bin/cat";
+      };
+    };
+
+    testSubs = ''
+      my ${"$" + name}_sharepath = '${sharePath}';
+
+      sub checkRunning_${name} {
+        my $cmd = 'VBoxManage list runningvms | grep -q "^\"${name}\""';
+        my ($status, $out) = $machine->execute(ru $cmd);
+        return $status == 0;
+      }
+
+      sub cleanup_${name} {
+        $machine->execute(ru "VBoxManage controlvm ${name} poweroff")
+          if checkRunning_${name};
+        $machine->succeed("rm -rf ${sharePath}");
+        $machine->succeed("mkdir -p ${sharePath}");
+        $machine->succeed("chown alice.users ${sharePath}");
+      }
+
+      sub createVM_${name} {
+        vbm("createvm --name ${name} ${createFlags}");
+        vbm("modifyvm ${name} ${vmFlags}");
+        vbm("setextradata ${name} VBoxInternal/PDM/HaltOnReset 1");
+        vbm("storagectl ${name} ${controllerFlags}");
+        vbm("storageattach ${name} ${diskFlags}");
+        vbm("sharedfolder add ${name} ${sharedFlags}");
+        vbm("sharedfolder add ${name} ${nixstoreFlags}");
+        cleanup_${name};
+
+        ${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"}
+      }
+
+      sub destroyVM_${name} {
+        cleanup_${name};
+        vbm("unregistervm ${name} --delete");
+      }
+
+      sub waitForVMBoot_${name} {
+        $machine->execute(ru(
+          'set -e; i=0; '.
+          'while ! test -e ${sharePath}/boot-done; do '.
+          'sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; '.
+          'VBoxManage list runningvms | grep -q "^\"${name}\""; '.
+          'done'
+        ));
+      }
+
+      sub waitForIP_${name} ($) {
+        my $property = "/VirtualBox/GuestInfo/Net/$_[0]/V4/IP";
+        my $getip = "VBoxManage guestproperty get ${name} $property | ".
+                    "sed -n -e 's/^Value: //p'";
+        my $ip = $machine->succeed(ru(
+          'for i in $(seq 1000); do '.
+          'if ipaddr="$('.$getip.')" && [ -n "$ipaddr" ]; then '.
+          'echo "$ipaddr"; exit 0; '.
+          'fi; '.
+          'sleep 1; '.
+          'done; '.
+          'echo "Could not get IPv4 address for ${name}!" >&2; '.
+          'exit 1'
+        ));
+        chomp $ip;
+        return $ip;
+      }
+
+      sub waitForStartup_${name} {
+        for (my $i = 0; $i <= 120; $i += 10) {
+          $machine->sleep(10);
+          return if checkRunning_${name};
+          eval { $_[0]->() } if defined $_[0];
+        }
+        die "VirtualBox VM didn't start up within 2 minutes";
+      }
+
+      sub waitForShutdown_${name} {
+        for (my $i = 0; $i <= 120; $i += 10) {
+          $machine->sleep(10);
+          return unless checkRunning_${name};
+        }
+        die "VirtualBox VM didn't shut down within 2 minutes";
+      }
+
+      sub shutdownVM_${name} {
+        $machine->succeed(ru "touch ${sharePath}/shutdown");
+        $machine->execute(
+          'set -e; i=0; '.
+          'while test -e ${sharePath}/shutdown '.
+          '        -o -e ${sharePath}/boot-done; do '.
+          'sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; '.
+          'done'
+        );
+        waitForShutdown_${name};
+      }
+    '';
+  };
+
+  hostonlyVMFlags = [
+    "--nictype1 virtio"
+    "--nictype2 virtio"
+    "--nic2 hostonly"
+    "--hostonlyadapter2 vboxnet0"
+  ];
+
+  # The VirtualBox Oracle Extension Pack lets you use USB 3.0 (xHCI).
+  enableExtensionPackVMFlags = [
+    "--usbxhci on"
+  ];
+
+  dhcpScript = pkgs: ''
+    ${pkgs.dhcp}/bin/dhclient \
+      -lf /run/dhcp.leases \
+      -pf /run/dhclient.pid \
+      -v eth0 eth1
+
+    otherIP="$(${pkgs.netcat}/bin/nc -l 1234 || :)"
+    ${pkgs.iputils}/bin/ping -I eth1 -c1 "$otherIP"
+    echo "$otherIP reachable" | ${pkgs.netcat}/bin/nc -l 5678 || :
+  '';
+
+  sysdDetectVirt = pkgs: ''
+    ${pkgs.systemd}/bin/systemd-detect-virt > /mnt-root/result
+  '';
+
+  vboxVMs = mapAttrs createVM {
+    simple = {};
+
+    detectvirt.vmScript = sysdDetectVirt;
+
+    test1.vmFlags = hostonlyVMFlags;
+    test1.vmScript = dhcpScript;
+
+    test2.vmFlags = hostonlyVMFlags;
+    test2.vmScript = dhcpScript;
+
+    headless.virtualisation.virtualbox.headless = true;
+    headless.services.xserver.enable = false;
+  };
+
+  vboxVMsWithExtpack = mapAttrs createVM {
+    testExtensionPack.vmFlags = enableExtensionPackVMFlags;
+  };
+
+  mkVBoxTest = useExtensionPack: vms: name: testScript: makeTest {
+    name = "virtualbox-${name}";
+
+    machine = { lib, config, ... }: {
+      imports = let
+        mkVMConf = name: val: val.machine // { key = "${name}-config"; };
+        vmConfigs = mapAttrsToList mkVMConf vms;
+      in [ ./common/user-account.nix ./common/x11.nix ] ++ vmConfigs;
+      virtualisation.memorySize = 2048;
+      virtualisation.virtualbox.host.enable = true;
+      services.xserver.displayManager.auto.user = "alice";
+      users.users.alice.extraGroups = let
+        inherit (config.virtualisation.virtualbox.host) enableHardening;
+      in lib.mkIf enableHardening (lib.singleton "vboxusers");
+      virtualisation.virtualbox.host.enableExtensionPack = useExtensionPack;
+      nixpkgs.config.allowUnfree = useExtensionPack;
+    };
+
+    testScript = ''
+      sub ru ($) {
+        my $esc = $_[0] =~ s/'/'\\${"'"}'/gr;
+        return "su - alice -c '$esc'";
+      }
+
+      sub vbm {
+        $machine->succeed(ru("VBoxManage ".$_[0]));
+      };
+
+      sub removeUUIDs {
+        return join("\n", grep { $_ !~ /^UUID:/ } split(/\n/, $_[0]))."\n";
+      }
+
+      ${concatStrings (mapAttrsToList (_: getAttr "testSubs") vms)}
+
+      $machine->waitForX;
+
+      ${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"}
+
+      ${testScript}
+    '';
+
+    meta = with pkgs.stdenv.lib.maintainers; {
+      maintainers = [ aszlig cdepillabout ];
+    };
+  };
+
+  unfreeTests = mapAttrs (mkVBoxTest true vboxVMsWithExtpack) {
+    enable-extension-pack = ''
+      createVM_testExtensionPack;
+      vbm("startvm testExtensionPack");
+      waitForStartup_testExtensionPack;
+      $machine->screenshot("cli_started");
+      waitForVMBoot_testExtensionPack;
+      $machine->screenshot("cli_booted");
+
+      $machine->nest("Checking for privilege escalation", sub {
+        $machine->fail("test -e '/root/VirtualBox VMs'");
+        $machine->fail("test -e '/root/.config/VirtualBox'");
+        $machine->succeed("test -e '/home/alice/VirtualBox VMs'");
+      });
+
+      shutdownVM_testExtensionPack;
+      destroyVM_testExtensionPack;
+    '';
+  };
+
+in mapAttrs (mkVBoxTest false vboxVMs) {
+  simple-gui = ''
+    createVM_simple;
+    $machine->succeed(ru "VirtualBox &");
+    $machine->waitUntilSucceeds(
+      ru "xprop -name 'Oracle VM VirtualBox Manager'"
+    );
+    $machine->sleep(5);
+    $machine->screenshot("gui_manager_started");
+    $machine->sendKeys("ret");
+    $machine->screenshot("gui_manager_sent_startup");
+    waitForStartup_simple (sub {
+      $machine->sendKeys("ret");
+    });
+    $machine->screenshot("gui_started");
+    waitForVMBoot_simple;
+    $machine->screenshot("gui_booted");
+    shutdownVM_simple;
+    $machine->sleep(5);
+    $machine->screenshot("gui_stopped");
+    $machine->sendKeys("ctrl-q");
+    $machine->sleep(5);
+    $machine->screenshot("gui_manager_stopped");
+    destroyVM_simple;
+  '';
+
+  simple-cli = ''
+    createVM_simple;
+    vbm("startvm simple");
+    waitForStartup_simple;
+    $machine->screenshot("cli_started");
+    waitForVMBoot_simple;
+    $machine->screenshot("cli_booted");
+
+    $machine->nest("Checking for privilege escalation", sub {
+      $machine->fail("test -e '/root/VirtualBox VMs'");
+      $machine->fail("test -e '/root/.config/VirtualBox'");
+      $machine->succeed("test -e '/home/alice/VirtualBox VMs'");
+    });
+
+    shutdownVM_simple;
+    destroyVM_simple;
+  '';
+
+  headless = ''
+    createVM_headless;
+    $machine->succeed(ru("VBoxHeadless --startvm headless & disown %1"));
+    waitForStartup_headless;
+    waitForVMBoot_headless;
+    shutdownVM_headless;
+    destroyVM_headless;
+  '';
+
+  host-usb-permissions = ''
+    my $userUSB = removeUUIDs vbm("list usbhost");
+    print STDERR $userUSB;
+    my $rootUSB = removeUUIDs $machine->succeed("VBoxManage list usbhost");
+    print STDERR $rootUSB;
+
+    die "USB host devices differ for root and normal user"
+      if $userUSB ne $rootUSB;
+    die "No USB host devices found" if $userUSB =~ /<none>/;
+  '';
+
+  systemd-detect-virt = ''
+    createVM_detectvirt;
+    vbm("startvm detectvirt");
+    waitForStartup_detectvirt;
+    waitForVMBoot_detectvirt;
+    shutdownVM_detectvirt;
+    my $result = $machine->succeed("cat '$detectvirt_sharepath/result'");
+    chomp $result;
+    destroyVM_detectvirt;
+    die "systemd-detect-virt returned \"$result\" instead of \"oracle\""
+      if $result ne "oracle";
+  '';
+
+  net-hostonlyif = ''
+    createVM_test1;
+    createVM_test2;
+
+    vbm("startvm test1");
+    waitForStartup_test1;
+    waitForVMBoot_test1;
+
+    vbm("startvm test2");
+    waitForStartup_test2;
+    waitForVMBoot_test2;
+
+    $machine->screenshot("net_booted");
+
+    my $test1IP = waitForIP_test1 1;
+    my $test2IP = waitForIP_test2 1;
+
+    $machine->succeed("echo '$test2IP' | nc -N '$test1IP' 1234");
+    $machine->succeed("echo '$test1IP' | nc -N '$test2IP' 1234");
+
+    $machine->waitUntilSucceeds("nc -N '$test1IP' 5678 < /dev/null >&2");
+    $machine->waitUntilSucceeds("nc -N '$test2IP' 5678 < /dev/null >&2");
+
+    shutdownVM_test1;
+    shutdownVM_test2;
+
+    destroyVM_test1;
+    destroyVM_test2;
+  '';
+} // (if enableUnfree then unfreeTests else {})
diff --git a/nixpkgs/nixos/tests/wordpress.nix b/nixpkgs/nixos/tests/wordpress.nix
new file mode 100644
index 000000000000..5003e25a7d5b
--- /dev/null
+++ b/nixpkgs/nixos/tests/wordpress.nix
@@ -0,0 +1,53 @@
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "wordpress";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ grahamc ]; # under duress!
+  };
+
+  nodes =
+    { web =
+        { pkgs, ... }:
+        {
+          services.mysql = {
+            enable = true;
+            package = pkgs.mysql;
+          };
+          services.httpd = {
+            enable = true;
+            logPerVirtualHost = true;
+            adminAddr="js@lastlog.de";
+
+            virtualHosts = [
+              {
+                hostName = "wordpress";
+                extraSubservices =
+                  [
+                    {
+                      serviceType = "wordpress";
+                      dbPassword = "wordpress";
+                      dbHost = "127.0.0.1";
+                      languages = [ "de_DE" "en_GB" ];
+                    }
+                  ];
+              }
+            ];
+          };
+        };
+    };
+
+  testScript =
+    { ... }:
+    ''
+      startAll;
+
+      $web->waitForUnit("mysql");
+      $web->waitForUnit("httpd");
+
+      $web->succeed("curl -L 127.0.0.1:80 | grep 'Welcome to the famous'");
+
+
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/xautolock.nix b/nixpkgs/nixos/tests/xautolock.nix
new file mode 100644
index 000000000000..ee46d9e05b06
--- /dev/null
+++ b/nixpkgs/nixos/tests/xautolock.nix
@@ -0,0 +1,24 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "xautolock";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ];
+
+  nodes.machine = {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+
+    services.xserver.displayManager.auto.user = "bob";
+    services.xserver.xautolock.enable = true;
+    services.xserver.xautolock.time = 1;
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForX;
+    $machine->mustFail("pgrep xlock");
+    $machine->sleep(120);
+    $machine->mustSucceed("pgrep xlock");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xdg-desktop-portal.nix b/nixpkgs/nixos/tests/xdg-desktop-portal.nix
new file mode 100644
index 000000000000..79ebb83c49a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/xdg-desktop-portal.nix
@@ -0,0 +1,17 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "xdg-desktop-portal";
+  meta = {
+    maintainers = pkgs.xdg-desktop-portal.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner -d '${pkgs.xdg-desktop-portal.installedTests}/share'");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xfce.nix b/nixpkgs/nixos/tests/xfce.nix
new file mode 100644
index 000000000000..47717e8cf7d9
--- /dev/null
+++ b/nixpkgs/nixos/tests/xfce.nix
@@ -0,0 +1,42 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "xfce";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eelco chaoflow shlevy ];
+  };
+
+  machine =
+    { 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;
+
+      environment.systemPackages = [ pkgs.xorg.xmessage ];
+    };
+
+  testScript =
+    ''
+      $machine->waitForX;
+      $machine->waitForFile("/home/alice/.Xauthority");
+      $machine->succeed("xauth merge ~alice/.Xauthority");
+      $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");
+
+      # Ensure that the X server does proper access control.
+      $machine->mustFail("su - bob -c 'DISPLAY=:0.0 xmessage Foo'");
+      $machine->mustFail("su - bob -c 'DISPLAY=:0 xmessage Foo'");
+    '';
+})
diff --git a/nixpkgs/nixos/tests/xmonad.nix b/nixpkgs/nixos/tests/xmonad.nix
new file mode 100644
index 000000000000..6d6db6b0ea97
--- /dev/null
+++ b/nixpkgs/nixos/tests/xmonad.nix
@@ -0,0 +1,29 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "xmonad";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  machine = { lib, pkgs, ... }: {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    services.xserver.displayManager.auto.user = "alice";
+    services.xserver.windowManager.default = lib.mkForce "xmonad";
+    services.xserver.windowManager.xmonad = {
+      enable = true;
+      enableContribAndExtras = true;
+      extraPackages = with pkgs.haskellPackages; haskellPackages: [ xmobar ];
+    };
+  };
+
+  testScript = { ... }: ''
+    $machine->waitForX;
+    $machine->waitForFile("/home/alice/.Xauthority");
+    $machine->succeed("xauth merge ~alice/.Xauthority");
+    $machine->waitUntilSucceeds("xmonad --restart");
+    $machine->sleep(3);
+    $machine->sendKeys("alt-shift-ret");
+    $machine->waitForWindow(qr/alice.*machine/);
+    $machine->sleep(1);
+    $machine->screenshot("terminal");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xrdp.nix b/nixpkgs/nixos/tests/xrdp.nix
new file mode 100644
index 000000000000..0106aefe8318
--- /dev/null
+++ b/nixpkgs/nixos/tests/xrdp.nix
@@ -0,0 +1,45 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "xrdp";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ volth ];
+  };
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ ./common/user-account.nix ];
+      services.xrdp.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.xterm}/bin/xterm";
+      networking.firewall.allowedTCPPorts = [ 3389 ];
+    };
+
+    client = { pkgs, ... }: {
+      imports = [ ./common/x11.nix ./common/user-account.nix ];
+      services.xserver.displayManager.auto.user = "alice";
+      environment.systemPackages = [ pkgs.freerdp ];
+      services.xrdp.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.icewm}/bin/icewm";
+    };
+  };
+
+  testScript = { ... }: ''
+    startAll;
+
+    $client->waitForX;
+    $client->waitForFile("/home/alice/.Xauthority");
+    $client->succeed("xauth merge ~alice/.Xauthority");
+
+    $client->sleep(5);
+
+    $client->execute("xterm &");
+    $client->sleep(1);
+    $client->sendChars("xfreerdp /cert-tofu /w:640 /h:480 /v:127.0.0.1 /u:alice /p:foobar\n");
+    $client->sleep(5);
+    $client->screenshot("localrdp");
+
+    $client->execute("xterm &");
+    $client->sleep(1);
+    $client->sendChars("xfreerdp /cert-tofu /w:640 /h:480 /v:server /u:alice /p:foobar\n");
+    $client->sleep(5);
+    $client->screenshot("remoterdp");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xss-lock.nix b/nixpkgs/nixos/tests/xss-lock.nix
new file mode 100644
index 000000000000..b46bb1a8f6e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/xss-lock.nix
@@ -0,0 +1,24 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "xss-lock";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ];
+
+  machine = {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    programs.xss-lock.enable = true;
+    services.xserver.displayManager.auto.user = "alice";
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForX;
+    $machine->waitForUnit("xss-lock.service", "alice");
+
+    $machine->fail("pgrep xlock");
+    $machine->succeed("su -l alice -c 'xset dpms force standby'");
+    $machine->waitUntilSucceeds("pgrep i3lock");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/yabar.nix b/nixpkgs/nixos/tests/yabar.nix
new file mode 100644
index 000000000000..bbc0cf4c7dd7
--- /dev/null
+++ b/nixpkgs/nixos/tests/yabar.nix
@@ -0,0 +1,33 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+{
+  name = "yabar";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  machine = {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+
+    services.xserver.displayManager.auto.user = "bob";
+
+    programs.yabar.enable = true;
+    programs.yabar.bars = {
+      top.indicators.date.exec = "YABAR_DATE";
+    };
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForX;
+
+    # confirm proper startup
+    $machine->waitForUnit("yabar.service", "bob");
+    $machine->sleep(10);
+    $machine->waitForUnit("yabar.service", "bob");
+
+    $machine->screenshot("top_bar");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/zfs.nix b/nixpkgs/nixos/tests/zfs.nix
new file mode 100644
index 000000000000..d7a08268e984
--- /dev/null
+++ b/nixpkgs/nixos/tests/zfs.nix
@@ -0,0 +1,88 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+
+let
+
+  makeTest = import ./make-test.nix;
+
+  makeZfsTest = name:
+    { kernelPackage ? pkgs.linuxPackages_latest
+    , enableUnstable ? false
+    , extraTest ? ""
+    }:
+    makeTest {
+      name = "zfs-" + name;
+      meta = with pkgs.stdenv.lib.maintainers; {
+        maintainers = [ adisbladis ];
+      };
+
+      machine = { pkgs, ... }:
+        {
+          virtualisation.emptyDiskImages = [ 4096 ];
+          networking.hostId = "deadbeef";
+          boot.kernelPackages = kernelPackage;
+          boot.supportedFilesystems = [ "zfs" ];
+          boot.zfs.enableUnstable = enableUnstable;
+
+          environment.systemPackages = with pkgs; [
+            parted
+          ];
+        };
+
+      testScript = ''
+        $machine->succeed("modprobe zfs");
+        $machine->succeed("zpool status");
+
+        $machine->succeed("ls /dev");
+
+        $machine->succeed(
+          "mkdir /tmp/mnt",
+
+          "udevadm settle",
+          "parted --script /dev/vdb mklabel msdos",
+          "parted --script /dev/vdb -- mkpart primary 1024M -1s",
+          "udevadm settle",
+
+          "zpool create rpool /dev/vdb1",
+          "zfs create -o mountpoint=legacy rpool/root",
+          "mount -t zfs rpool/root /tmp/mnt",
+          "udevadm settle",
+
+          "umount /tmp/mnt",
+          "zpool destroy rpool",
+          "udevadm settle"
+
+        );
+
+      '' + extraTest;
+
+    };
+
+
+in {
+
+  stable = makeZfsTest "stable" { };
+
+  unstable = makeZfsTest "unstable" {
+    enableUnstable = true;
+    extraTest = ''
+      $machine->succeed(
+        "echo password | zpool create -o altroot='/tmp/mnt' -O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1",
+        "zfs create -o mountpoint=legacy rpool/root",
+        "mount -t zfs rpool/root /tmp/mnt",
+        "udevadm settle",
+
+        "umount /tmp/mnt",
+        "zpool destroy rpool",
+        "udevadm settle"
+      );
+    '';
+  };
+
+  installer = (import ./installer.nix { }).zfsroot;
+
+}
diff --git a/nixpkgs/nixos/tests/zookeeper.nix b/nixpkgs/nixos/tests/zookeeper.nix
new file mode 100644
index 000000000000..f343ebd39e44
--- /dev/null
+++ b/nixpkgs/nixos/tests/zookeeper.nix
@@ -0,0 +1,28 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "zookeeper";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      services.zookeeper = {
+        enable = true;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 2181 ];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("zookeeper");
+    $server->waitForUnit("network.target");
+    $server->waitForOpenPort(2181);
+
+    $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 create /foo bar");
+    $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 set /foo hello");
+    $server->waitUntilSucceeds("${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 get /foo | grep hello");
+  '';
+})