about summary refs log tree commit diff
path: root/nixos/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/tests')
-rw-r--r--nixos/tests/3proxy.nix162
-rw-r--r--nixos/tests/all-tests.nix7
-rw-r--r--nixos/tests/common/x11.nix8
-rw-r--r--nixos/tests/gnome3-xorg.nix2
-rw-r--r--nixos/tests/hadoop/hdfs.nix26
-rw-r--r--nixos/tests/hadoop/yarn.nix24
-rw-r--r--nixos/tests/haproxy.nix16
-rw-r--r--nixos/tests/hitch/default.nix12
-rw-r--r--nixos/tests/i3wm.nix2
-rw-r--r--nixos/tests/initrd-network.nix8
-rw-r--r--nixos/tests/leaps.nix12
-rw-r--r--nixos/tests/lidarr.nix10
-rw-r--r--nixos/tests/lightdm.nix3
-rw-r--r--nixos/tests/mailcatcher.nix16
-rw-r--r--nixos/tests/mutable-users.nix28
-rw-r--r--nixos/tests/mxisd.nix17
-rw-r--r--nixos/tests/nesting.nix36
-rw-r--r--nixos/tests/networking.nix473
-rw-r--r--nixos/tests/nfs.nix90
-rw-r--r--nixos/tests/nfs/default.nix9
-rw-r--r--nixos/tests/nfs/kerberos.nix133
-rw-r--r--nixos/tests/nfs/simple.nix94
-rw-r--r--nixos/tests/nghttpx.nix10
-rw-r--r--nixos/tests/novacomd.nix32
-rw-r--r--nixos/tests/nzbget.nix18
-rw-r--r--nixos/tests/orangefs.nix52
-rw-r--r--nixos/tests/osrm-backend.nix14
-rw-r--r--nixos/tests/overlayfs.nix77
-rw-r--r--nixos/tests/paperless.nix29
-rw-r--r--nixos/tests/pdns-recursor.nix6
-rw-r--r--nixos/tests/peerflix.nix8
-rw-r--r--nixos/tests/pgmanage.nix12
-rw-r--r--nixos/tests/php-pcre.nix9
-rw-r--r--nixos/tests/plasma5.nix2
-rw-r--r--nixos/tests/postgis.nix12
-rw-r--r--nixos/tests/quagga.nix28
-rw-r--r--nixos/tests/rspamd.nix159
-rw-r--r--nixos/tests/sddm.nix6
-rw-r--r--nixos/tests/sonarr.nix8
-rw-r--r--nixos/tests/switch-test.nix10
-rw-r--r--nixos/tests/systemd-timesyncd.nix24
-rw-r--r--nixos/tests/wireguard/namespaces.nix18
-rw-r--r--nixos/tests/xmonad.nix8
-rw-r--r--nixos/tests/zsh-history.nix35
44 files changed, 1093 insertions, 672 deletions
diff --git a/nixos/tests/3proxy.nix b/nixos/tests/3proxy.nix
new file mode 100644
index 000000000000..b8e1dac0e89e
--- /dev/null
+++ b/nixos/tests/3proxy.nix
@@ -0,0 +1,162 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "3proxy";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ misuzu ];
+  };
+
+  nodes = {
+    peer0 = { lib, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.1";
+            prefixLength = 24;
+          }
+          {
+            address = "216.58.211.111";
+            prefixLength = 24;
+          }
+        ];
+      };
+    };
+
+    peer1 = { lib, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.2";
+            prefixLength = 24;
+          }
+          {
+            address = "216.58.211.112";
+            prefixLength = 24;
+          }
+        ];
+      };
+      # test that binding to [::] is working when ipv6 is disabled
+      networking.enableIPv6 = false;
+      services._3proxy = {
+        enable = true;
+        services = [
+          {
+            type = "admin";
+            bindPort = 9999;
+            auth = [ "none" ];
+          }
+          {
+            type = "proxy";
+            bindPort = 3128;
+            auth = [ "none" ];
+          }
+        ];
+      };
+      networking.firewall.allowedTCPPorts = [ 3128 9999 ];
+    };
+
+    peer2 = { lib, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.3";
+            prefixLength = 24;
+          }
+          {
+            address = "216.58.211.113";
+            prefixLength = 24;
+          }
+        ];
+      };
+      services._3proxy = {
+        enable = true;
+        services = [
+          {
+            type = "admin";
+            bindPort = 9999;
+            auth = [ "none" ];
+          }
+          {
+            type = "proxy";
+            bindPort = 3128;
+            auth = [ "iponly" ];
+            acl = [
+              {
+                rule = "allow";
+              }
+            ];
+          }
+        ];
+      };
+      networking.firewall.allowedTCPPorts = [ 3128 9999 ];
+    };
+
+    peer3 = { lib, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.4";
+            prefixLength = 24;
+          }
+          {
+            address = "216.58.211.114";
+            prefixLength = 24;
+          }
+        ];
+      };
+      services._3proxy = {
+        enable = true;
+        usersFile = pkgs.writeText "3proxy.passwd" ''
+          admin:CR:$1$.GUV4Wvk$WnEVQtaqutD9.beO5ar1W/
+        '';
+        services = [
+          {
+            type = "admin";
+            bindPort = 9999;
+            auth = [ "none" ];
+          }
+          {
+            type = "proxy";
+            bindPort = 3128;
+            auth = [ "strong" ];
+            acl = [
+              {
+                rule = "allow";
+              }
+            ];
+          }
+        ];
+      };
+      networking.firewall.allowedTCPPorts = [ 3128 9999 ];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $peer1->waitForUnit("3proxy.service");
+
+    # test none auth
+    $peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://216.58.211.112:9999");
+    $peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://192.168.0.2:9999");
+    $peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://127.0.0.1:9999");
+
+    $peer2->waitForUnit("3proxy.service");
+
+    # test iponly auth
+    $peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://216.58.211.113:9999");
+    $peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://192.168.0.3:9999");
+    $peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://127.0.0.1:9999");
+
+    $peer3->waitForUnit("3proxy.service");
+
+    # test strong auth
+    $peer0->succeed("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999");
+    $peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999");
+    $peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999");
+    $peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999");
+    $peer0->fail("${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://127.0.0.1:9999");
+  '';
+})
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 39ee3206d806..6413895c4c5e 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -21,6 +21,7 @@ let
     else {};
 in
 {
+  _3proxy = handleTest ./3proxy.nix {};
   acme = handleTestOn ["x86_64-linux"] ./acme.nix {};
   atd = handleTest ./atd.nix {};
   automysqlbackup = handleTest ./automysqlbackup.nix {};
@@ -190,8 +191,9 @@ in
   networkingProxy = handleTest ./networking-proxy.nix {};
   nextcloud = handleTest ./nextcloud {};
   nexus = handleTest ./nexus.nix {};
-  nfs3 = handleTest ./nfs.nix { version = 3; };
-  nfs4 = handleTest ./nfs.nix { version = 4; };
+  # TODO: Test nfsv3 + Kerberos
+  nfs3 = handleTest ./nfs { version = 3; };
+  nfs4 = handleTest ./nfs { version = 4; };
   nghttpx = handleTest ./nghttpx.nix {};
   nginx = handleTest ./nginx.nix {};
   nginx-sso = handleTest ./nginx-sso.nix {};
@@ -294,5 +296,6 @@ in
   xss-lock = handleTest ./xss-lock.nix {};
   yabar = handleTest ./yabar.nix {};
   yggdrasil = handleTest ./yggdrasil.nix {};
+  zsh-history = handleTest ./zsh-history.nix {};
   zookeeper = handleTest ./zookeeper.nix {};
 }
diff --git a/nixos/tests/common/x11.nix b/nixos/tests/common/x11.nix
index c5a7c165d126..5ad0ac20fac8 100644
--- a/nixos/tests/common/x11.nix
+++ b/nixos/tests/common/x11.nix
@@ -1,12 +1,12 @@
+{ lib, ... }:
+
 { 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";
+  services.xserver.displayManager.defaultSession = lib.mkDefault "none+icewm";
+  services.xserver.windowManager.icewm.enable = true;
 }
diff --git a/nixos/tests/gnome3-xorg.nix b/nixos/tests/gnome3-xorg.nix
index eb4c376319be..aa03501f6a55 100644
--- a/nixos/tests/gnome3-xorg.nix
+++ b/nixos/tests/gnome3-xorg.nix
@@ -16,7 +16,7 @@ import ./make-test.nix ({ pkgs, ...} : {
       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";
+      services.xserver.displayManager.defaultSession = "gnome-xorg";
 
       virtualisation.memorySize = 1024;
     };
diff --git a/nixos/tests/hadoop/hdfs.nix b/nixos/tests/hadoop/hdfs.nix
index e7d72a56e1e7..85aaab34b158 100644
--- a/nixos/tests/hadoop/hdfs.nix
+++ b/nixos/tests/hadoop/hdfs.nix
@@ -1,4 +1,4 @@
-import ../make-test.nix ({...}: {
+import ../make-test-python.nix ({...}: {
   nodes = {
     namenode = {pkgs, ...}: {
       services.hadoop = {
@@ -35,20 +35,20 @@ import ../make-test.nix ({...}: {
   };
 
   testScript = ''
-    startAll
+    start_all()
 
-    $namenode->waitForUnit("hdfs-namenode");
-    $namenode->waitForUnit("network.target");
-    $namenode->waitForOpenPort(8020);
-    $namenode->waitForOpenPort(9870);
+    namenode.wait_for_unit("hdfs-namenode")
+    namenode.wait_for_unit("network.target")
+    namenode.wait_for_open_port(8020)
+    namenode.wait_for_open_port(9870)
 
-    $datanode->waitForUnit("hdfs-datanode");
-    $datanode->waitForUnit("network.target");
-    $datanode->waitForOpenPort(9864);
-    $datanode->waitForOpenPort(9866);
-    $datanode->waitForOpenPort(9867);
+    datanode.wait_for_unit("hdfs-datanode")
+    datanode.wait_for_unit("network.target")
+    datanode.wait_for_open_port(9864)
+    datanode.wait_for_open_port(9866)
+    datanode.wait_for_open_port(9867)
 
-    $namenode->succeed("curl http://namenode:9870");
-    $datanode->succeed("curl http://datanode:9864");
+    namenode.succeed("curl http://namenode:9870")
+    datanode.succeed("curl http://datanode:9864")
   '';
 })
diff --git a/nixos/tests/hadoop/yarn.nix b/nixos/tests/hadoop/yarn.nix
index 031592301f17..2264ecaff155 100644
--- a/nixos/tests/hadoop/yarn.nix
+++ b/nixos/tests/hadoop/yarn.nix
@@ -1,4 +1,4 @@
-import ../make-test.nix ({...}: {
+import ../make-test-python.nix ({...}: {
   nodes = {
     resourcemanager = {pkgs, ...}: {
       services.hadoop.package = pkgs.hadoop_3_1;
@@ -28,19 +28,19 @@ import ../make-test.nix ({...}: {
   };
 
   testScript = ''
-    startAll;
+    start_all()
 
-    $resourcemanager->waitForUnit("yarn-resourcemanager");
-    $resourcemanager->waitForUnit("network.target");
-    $resourcemanager->waitForOpenPort(8031);
-    $resourcemanager->waitForOpenPort(8088);
+    resourcemanager.wait_for_unit("yarn-resourcemanager")
+    resourcemanager.wait_for_unit("network.target")
+    resourcemanager.wait_for_open_port(8031)
+    resourcemanager.wait_for_open_port(8088)
 
-    $nodemanager->waitForUnit("yarn-nodemanager");
-    $nodemanager->waitForUnit("network.target");
-    $nodemanager->waitForOpenPort(8042);
-    $nodemanager->waitForOpenPort(8041);
+    nodemanager.wait_for_unit("yarn-nodemanager")
+    nodemanager.wait_for_unit("network.target")
+    nodemanager.wait_for_open_port(8042)
+    nodemanager.wait_for_open_port(8041)
 
-    $resourcemanager->succeed("curl http://localhost:8088");
-    $nodemanager->succeed("curl http://localhost:8042");
+    resourcemanager.succeed("curl http://localhost:8088")
+    nodemanager.succeed("curl http://localhost:8042")
   '';
 })
diff --git a/nixos/tests/haproxy.nix b/nixos/tests/haproxy.nix
index 72e77a68193e..b6fed3e2108f 100644
--- a/nixos/tests/haproxy.nix
+++ b/nixos/tests/haproxy.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ...}: {
+import ./make-test-python.nix ({ pkgs, ...}: {
   name = "haproxy";
   nodes = {
     machine = { ... }: {
@@ -33,11 +33,13 @@ import ./make-test.nix ({ pkgs, ...}: {
     };
   };
   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!"');
-    $machine->succeed('curl -k http://localhost:80/metrics | grep haproxy_process_pool_allocated_bytes');
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("haproxy.service")
+    machine.wait_for_unit("httpd.service")
+    assert "We are all good!" in machine.succeed("curl -k http://localhost:80/index.txt")
+    assert "haproxy_process_pool_allocated_bytes" in machine.succeed(
+        "curl -k http://localhost:80/metrics"
+    )
   '';
 })
diff --git a/nixos/tests/hitch/default.nix b/nixos/tests/hitch/default.nix
index cb24c4dcffc2..106120256412 100644
--- a/nixos/tests/hitch/default.nix
+++ b/nixos/tests/hitch/default.nix
@@ -1,4 +1,4 @@
-import ../make-test.nix ({ pkgs, ... }:
+import ../make-test-python.nix ({ pkgs, ... }:
 {
   name = "hitch";
   meta = with pkgs.stdenv.lib.maintainers; {
@@ -23,11 +23,11 @@ import ../make-test.nix ({ pkgs, ... }:
 
   testScript =
     ''
-      startAll;
+      start_all()
 
-      $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!"');
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("hitch.service")
+      machine.wait_for_open_port(443)
+      assert "We are all good!" in machine.succeed("curl -k https://localhost:443/index.txt")
     '';
 })
diff --git a/nixos/tests/i3wm.nix b/nixos/tests/i3wm.nix
index 8afa845f1e21..126178d11879 100644
--- a/nixos/tests/i3wm.nix
+++ b/nixos/tests/i3wm.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   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.displayManager.defaultSession = lib.mkForce "none+i3";
     services.xserver.windowManager.i3.enable = true;
   };
 
diff --git a/nixos/tests/initrd-network.nix b/nixos/tests/initrd-network.nix
index ed9b82e2da77..4796ff9b7c8d 100644
--- a/nixos/tests/initrd-network.nix
+++ b/nixos/tests/initrd-network.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "initrd-network";
 
   meta.maintainers = [ pkgs.stdenv.lib.maintainers.eelco ];
@@ -15,8 +15,8 @@ import ./make-test.nix ({ pkgs, ...} : {
 
   testScript =
     ''
-      startAll;
-      $machine->waitForUnit("multi-user.target");
-      $machine->succeed("ip link >&2");
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("ip link >&2")
     '';
 })
diff --git a/nixos/tests/leaps.nix b/nixos/tests/leaps.nix
index 6163fed56b6f..65b475d734ec 100644
--- a/nixos/tests/leaps.nix
+++ b/nixos/tests/leaps.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs,  ... }:
+import ./make-test-python.nix ({ pkgs,  ... }:
 
 {
   name = "leaps";
@@ -22,9 +22,11 @@ import ./make-test.nix ({ pkgs,  ... }:
 
   testScript =
     ''
-      startAll;
-      $server->waitForOpenPort(6666);
-      $client->waitForUnit("network.target");
-      $client->succeed("${pkgs.curl}/bin/curl http://server:6666/leaps/ | grep -i 'leaps'");
+      start_all()
+      server.wait_for_open_port(6666)
+      client.wait_for_unit("network.target")
+      assert "leaps" in client.succeed(
+          "${pkgs.curl}/bin/curl http://server:6666/leaps/"
+      )
     '';
 })
diff --git a/nixos/tests/lidarr.nix b/nixos/tests/lidarr.nix
index 85fcbd21d8c0..d3f83e5d9145 100644
--- a/nixos/tests/lidarr.nix
+++ b/nixos/tests/lidarr.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ lib, ... }:
+import ./make-test-python.nix ({ lib, ... }:
 
 with lib;
 
@@ -11,8 +11,10 @@ with lib;
     { services.lidarr.enable = true; };
 
   testScript = ''
-    $machine->waitForUnit('lidarr.service');
-    $machine->waitForOpenPort('8686');
-    $machine->succeed("curl --fail http://localhost:8686/");
+    start_all()
+
+    machine.wait_for_unit("lidarr.service")
+    machine.wait_for_open_port("8686")
+    machine.succeed("curl --fail http://localhost:8686/")
   '';
 })
diff --git a/nixos/tests/lightdm.nix b/nixos/tests/lightdm.nix
index ef30f7741e23..46c2ed7ccc59 100644
--- a/nixos/tests/lightdm.nix
+++ b/nixos/tests/lightdm.nix
@@ -8,9 +8,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     imports = [ ./common/user-account.nix ];
     services.xserver.enable = true;
     services.xserver.displayManager.lightdm.enable = true;
-    services.xserver.windowManager.default = "icewm";
+    services.xserver.displayManager.defaultSession = "none+icewm";
     services.xserver.windowManager.icewm.enable = true;
-    services.xserver.desktopManager.default = "none";
   };
 
   enableOCR = true;
diff --git a/nixos/tests/mailcatcher.nix b/nixos/tests/mailcatcher.nix
index eb5b606ecc84..2ef38544fe0a 100644
--- a/nixos/tests/mailcatcher.nix
+++ b/nixos/tests/mailcatcher.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ lib, ... }:
+import ./make-test-python.nix ({ lib, ... }:
 
 {
   name = "mailcatcher";
@@ -16,11 +16,15 @@ import ./make-test.nix ({ lib, ... }:
     };
 
   testScript = ''
-    startAll;
+    start_all()
 
-    $machine->waitForUnit('mailcatcher.service');
-    $machine->waitForOpenPort('1025');
-    $machine->succeed('echo "this is the body of the email" | mail -s "subject" root@example.org');
-    $machine->succeed('curl http://localhost:1080/messages/1.source') =~ /this is the body of the email/ or die;
+    machine.wait_for_unit("mailcatcher.service")
+    machine.wait_for_open_port("1025")
+    machine.succeed(
+        'echo "this is the body of the email" | mail -s "subject" root@example.org'
+    )
+    assert "this is the body of the email" in machine.succeed(
+        "curl http://localhost:1080/messages/1.source"
+    )
   '';
 })
diff --git a/nixos/tests/mutable-users.nix b/nixos/tests/mutable-users.nix
index e590703ab2f4..49c7f78b82ed 100644
--- a/nixos/tests/mutable-users.nix
+++ b/nixos/tests/mutable-users.nix
@@ -1,6 +1,6 @@
 # Mutable users tests.
 
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "mutable-users";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ gleber ];
@@ -19,21 +19,27 @@ import ./make-test.nix ({ pkgs, ...} : {
     immutableSystem = nodes.machine.config.system.build.toplevel;
     mutableSystem = nodes.mutable.config.system.build.toplevel;
   in ''
-    $machine->start();
-    $machine->waitForUnit("default.target");
+    machine.start()
+    machine.wait_for_unit("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:");
+    with subtest("Machine in immutable mode"):
+        assert "foobar" not in machine.succeed("cat /etc/passwd")
+        machine.succeed("sudo useradd foobar")
+        assert "foobar" in machine.succeed("cat /etc/passwd")
+        machine.succeed(
+            "${immutableSystem}/bin/switch-to-configuration test"
+        )
+        assert "foobar" not in machine.succeed("cat /etc/passwd")
 
     # 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/');
+    with subtest("Password is wrapped in mutable mode"):
+        assert "/run/current-system/" in machine.succeed("which passwd")
+        machine.succeed(
+            "${mutableSystem}/bin/switch-to-configuration test"
+        )
+        assert "/run/wrappers/" in machine.succeed("which passwd")
   '';
 })
diff --git a/nixos/tests/mxisd.nix b/nixos/tests/mxisd.nix
index 0039256f5861..b2b60db4d822 100644
--- a/nixos/tests/mxisd.nix
+++ b/nixos/tests/mxisd.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ... } : {
+import ./make-test-python.nix ({ pkgs, ... } : {
 
   name = "mxisd";
   meta = with pkgs.stdenv.lib.maintainers; {
@@ -19,13 +19,12 @@ import ./make-test.nix ({ pkgs, ... } : {
   };
 
   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\"");
-    $server_ma1sd->waitForUnit("mxisd.service");
-    $server_ma1sd->waitForOpenPort(8090);
-    $server_ma1sd->succeed("curl -Ssf \"http://127.0.0.1:8090/_matrix/identity/api/v1\"")
-
+    start_all()
+    server_mxisd.wait_for_unit("mxisd.service")
+    server_mxisd.wait_for_open_port(8090)
+    server_mxisd.succeed("curl -Ssf 'http://127.0.0.1:8090/_matrix/identity/api/v1'")
+    server_ma1sd.wait_for_unit("mxisd.service")
+    server_ma1sd.wait_for_open_port(8090)
+    server_ma1sd.succeed("curl -Ssf 'http://127.0.0.1:8090/_matrix/identity/api/v1'")
   '';
 })
diff --git a/nixos/tests/nesting.nix b/nixos/tests/nesting.nix
index 1306d6f8e0c5..6388b67a6e40 100644
--- a/nixos/tests/nesting.nix
+++ b/nixos/tests/nesting.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix {
+import ./make-test-python.nix {
   name = "nesting";
   nodes =  {
     clone = { pkgs, ... }: {
@@ -19,24 +19,26 @@ import ./make-test.nix {
     };
   };
   testScript = ''
-    $clone->waitForUnit("default.target");
-    $clone->succeed("cowsay hey");
-    $clone->fail("hello");
+    clone.wait_for_unit("default.target")
+    clone.succeed("cowsay hey")
+    clone.fail("hello")
 
-    # Nested clones do inherit from parent
-    $clone->succeed("/run/current-system/fine-tune/child-1/bin/switch-to-configuration test");
-    $clone->succeed("cowsay hey");
-    $clone->succeed("hello");
+    with subtest("Nested clones do inherit from parent"):
+        clone.succeed(
+            "/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
+        )
+        clone.succeed("cowsay hey")
+        clone.succeed("hello")
     
+        children.wait_for_unit("default.target")
+        children.succeed("cowsay hey")
+        children.fail("hello")
 
-    $children->waitForUnit("default.target");
-    $children->succeed("cowsay hey");
-    $children->fail("hello");
-
-    # Nested children do not inherit from parent
-    $children->succeed("/run/current-system/fine-tune/child-1/bin/switch-to-configuration test");
-    $children->fail("cowsay hey");
-    $children->succeed("hello");
-
+    with subtest("Nested children do not inherit from parent"):
+        children.succeed(
+            "/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"
+        )
+        children.fail("cowsay hey")
+        children.succeed("hello")
   '';
 }
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index e0585d8f1bb4..9448a104073f 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -4,7 +4,7 @@
 # bool: whether to use networkd in the tests
 , networkd }:
 
-with import ../lib/testing.nix { inherit system pkgs; };
+with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
@@ -75,10 +75,11 @@ let
       machine.networking.useDHCP = false;
       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 '");
+        start_all()
+        machine.wait_for_unit("network.target")
+        loopback_addresses = machine.succeed("ip addr show lo")
+        assert "inet 127.0.0.1/8" in loopback_addresses
+        assert "inet6 ::1/128" in loopback_addresses
       '';
     };
     static = {
@@ -102,35 +103,35 @@ let
       };
       testScript = { ... }:
         ''
-          startAll;
+          start_all()
 
-          $client->waitForUnit("network.target");
-          $router->waitForUnit("network-online.target");
+          client.wait_for_unit("network.target")
+          router.wait_for_unit("network-online.target")
 
-          # Make sure dhcpcd is not started
-          $client->fail("systemctl status dhcpcd.service");
+          with subtest("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");
+          with subtest("Test vlan 1"):
+              client.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client.wait_until_succeeds("ping -c 1 192.168.1.3")
+              client.wait_until_succeeds("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");
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+              router.wait_until_succeeds("ping -c 1 192.168.1.3")
+              router.wait_until_succeeds("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");
+          with subtest("Test vlan 2"):
+              client.wait_until_succeeds("ping -c 1 192.168.2.1")
+              client.wait_until_succeeds("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");
+              router.wait_until_succeeds("ping -c 1 192.168.2.1")
+              router.wait_until_succeeds("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");
+          with subtest("Test default gateway"):
+              router.wait_until_succeeds("ping -c 1 192.168.3.1")
+              client.wait_until_succeeds("ping -c 1 192.168.3.1")
         '';
     };
     dhcpSimple = {
@@ -155,38 +156,38 @@ let
       };
       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");
+          start_all()
+
+          client.wait_for_unit("network.target")
+          router.wait_for_unit("network-online.target")
+
+          with subtest("Wait until we have an ip address on each interface"):
+              client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
+              client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
+              client.wait_until_succeeds("ip addr show dev eth2 | grep -q '192.168.2'")
+              client.wait_until_succeeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'")
+
+          with subtest("Test vlan 1"):
+              client.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
+
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
+
+          with subtest("Test vlan 2"):
+              client.wait_until_succeeds("ping -c 1 192.168.2.1")
+              client.wait_until_succeeds("ping -c 1 192.168.2.2")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
+
+              router.wait_until_succeeds("ping -c 1 192.168.2.1")
+              router.wait_until_succeeds("ping -c 1 192.168.2.2")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
         '';
     };
     dhcpOneIf = {
@@ -206,28 +207,28 @@ let
       };
       testScript = { ... }:
         ''
-          startAll;
+          start_all()
 
-          # Wait for networking to come up
-          $client->waitForUnit("network.target");
-          $router->waitForUnit("network.target");
+          with subtest("Wait for networking to come up"):
+              client.wait_for_unit("network.target")
+              router.wait_for_unit("network.target")
 
-          # Wait until we have an ip address on each interface
-          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
+          with subtest("Wait until we have an ip address on each interface"):
+              client.wait_until_succeeds("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");
+          with subtest("Test vlan 1"):
+              client.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client.wait_until_succeeds("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");
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("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");
+          with subtest("Test vlan 2"):
+              client.wait_until_succeeds("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");
+              router.wait_until_succeeds("ping -c 1 192.168.2.1")
+              router.fail("ping -c 1 192.168.2.2")
         '';
     };
     bond = let
@@ -252,18 +253,18 @@ let
       nodes.client2 = node "192.168.1.2";
       testScript = { ... }:
         ''
-          startAll;
+          start_all()
 
-          # Wait for networking to come up
-          $client1->waitForUnit("network.target");
-          $client2->waitForUnit("network.target");
+          with subtest("Wait for networking to come up"):
+              client1.wait_for_unit("network.target")
+              client2.wait_for_unit("network.target")
 
-          # Test bonding
-          $client1->waitUntilSucceeds("ping -c 2 192.168.1.1");
-          $client1->waitUntilSucceeds("ping -c 2 192.168.1.2");
+          with subtest("Test bonding"):
+              client1.wait_until_succeeds("ping -c 2 192.168.1.1")
+              client1.wait_until_succeeds("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");
+              client2.wait_until_succeeds("ping -c 2 192.168.1.1")
+              client2.wait_until_succeeds("ping -c 2 192.168.1.2")
         '';
     };
     bridge = let
@@ -294,25 +295,24 @@ let
       };
       testScript = { ... }:
         ''
-          startAll;
+          start_all()
 
-          # Wait for networking to come up
-          $client1->waitForUnit("network.target");
-          $client2->waitForUnit("network.target");
-          $router->waitForUnit("network.target");
+          with subtest("Wait for networking to come up"):
+              for machine in client1, client2, router:
+                  machine.wait_for_unit("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");
+          with subtest("Test bridging"):
+              client1.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client1.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client1.wait_until_succeeds("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");
+              client2.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client2.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client2.wait_until_succeeds("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");
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+              router.wait_until_succeeds("ping -c 1 192.168.1.3")
         '';
     };
     macvlan = {
@@ -340,35 +340,35 @@ let
       };
       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");
+          start_all()
+
+          with subtest("Wait for networking to come up"):
+              client.wait_for_unit("network.target")
+              router.wait_for_unit("network.target")
+
+          with subtest("Wait until we have an ip address on each interface"):
+              client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
+              client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'")
+
+          with subtest("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("##############################################")
+
+          with subtest("Test macvlan creates routable ips"):
+              client.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client.wait_until_succeeds("ping -c 1 192.168.1.3")
+
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+              router.wait_until_succeeds("ping -c 1 192.168.1.3")
         '';
     };
     sit = let
@@ -395,22 +395,22 @@ let
       nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
       testScript = { ... }:
         ''
-          startAll;
+          start_all()
 
-          # Wait for networking to be configured
-          $client1->waitForUnit("network.target");
-          $client2->waitForUnit("network.target");
+          with subtest("Wait for networking to be configured"):
+              client1.wait_for_unit("network.target")
+              client2.wait_for_unit("network.target")
 
-          # Print diagnostic information
-          $client1->succeed("ip addr >&2");
-          $client2->succeed("ip addr >&2");
+              # 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");
+          with subtest("Test ipv6"):
+              client1.wait_until_succeeds("ping -c 1 fc00::1")
+              client1.wait_until_succeeds("ping -c 1 fc00::2")
 
-          $client2->waitUntilSucceeds("ping -c 1 fc00::1");
-          $client2->waitUntilSucceeds("ping -c 1 fc00::2");
+              client2.wait_until_succeeds("ping -c 1 fc00::1")
+              client2.wait_until_succeeds("ping -c 1 fc00::2")
         '';
     };
     vlan = let
@@ -435,15 +435,15 @@ let
       nodes.client2 = node "192.168.1.2";
       testScript = { ... }:
         ''
-          startAll;
+          start_all()
 
-          # Wait for networking to be configured
-          $client1->waitForUnit("network.target");
-          $client2->waitForUnit("network.target");
+          with subtest("Wait for networking to be configured"):
+              client1.wait_for_unit("network.target")
+              client2.wait_for_unit("network.target")
 
-          # Test vlan is setup
-          $client1->succeed("ip addr show dev vlan >&2");
-          $client2->succeed("ip addr show dev vlan >&2");
+          with subtest("Test vlan is setup"):
+              client1.succeed("ip addr show dev vlan >&2")
+              client2.succeed("ip addr show dev vlan >&2")
         '';
     };
     virtual = {
@@ -464,33 +464,38 @@ let
       };
 
       testScript = ''
-        my $targetList = <<'END';
+        targetList = """
         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"
-        );
+        """.strip()
+
+        with subtest("Wait for networking to come up"):
+            machine.start()
+            machine.wait_for_unit("network-online.target")
+
+        with subtest("Test interfaces set up"):
+            list = machine.succeed("ip tuntap list | sort").strip()
+            assert (
+                list == targetList
+            ), """
+            The list of virtual interfaces does not match the expected one:
+            Result:
+              {}
+            Expected:
+              {}
+            """.format(
+                list, targetList
+            )
+
+        with subtest("Test interfaces clean up"):
+            machine.succeed("systemctl stop network-addresses-tap0")
+            machine.sleep(10)
+            machine.succeed("systemctl stop network-addresses-tun0")
+            machine.sleep(10)
+            residue = machine.succeed("ip tuntap list")
+            assert (
+                residue is ""
+            ), "Some virtual interface has not been properly cleaned:\n{}".format(residue)
       '';
     };
     privacy = {
@@ -522,7 +527,7 @@ let
           '';
         };
       };
-      nodes.clientWithPrivacy = { pkgs, ... }: with pkgs.lib; {
+      nodes.client_with_privacy = { pkgs, ... }: with pkgs.lib; {
         virtualisation.vlans = [ 1 ];
         networking = {
           useNetworkd = networkd;
@@ -550,25 +555,31 @@ let
       };
       testScript = { ... }:
         ''
-          startAll;
-
-          $client->waitForUnit("network.target");
-          $clientWithPrivacy->waitForUnit("network.target");
-          $router->waitForUnit("network-online.target");
-
-          # Wait until we have an ip address
-          $clientWithPrivacy->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
-          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
-
-          # Test vlan 1
-          $clientWithPrivacy->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
-          $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
-
-          # Test address used is temporary
-          $clientWithPrivacy->waitUntilSucceeds("! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'");
-
-          # Test address used is EUI-64
-          $client->waitUntilSucceeds("ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'");
+          start_all()
+
+          client.wait_for_unit("network.target")
+          client_with_privacy.wait_for_unit("network.target")
+          router.wait_for_unit("network-online.target")
+
+          with subtest("Wait until we have an ip address"):
+              client_with_privacy.wait_until_succeeds(
+                  "ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'"
+              )
+              client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
+
+          with subtest("Test vlan 1"):
+              client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+
+          with subtest("Test address used is temporary"):
+              client_with_privacy.wait_until_succeeds(
+                  "! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
+              )
+
+          with subtest("Test address used is EUI-64"):
+              client.wait_until_succeeds(
+                  "ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
+              )
         '';
     };
     routes = {
@@ -591,47 +602,57 @@ let
       };
 
       testScript = ''
-        my $targetIPv4Table = <<'END';
+        targetIPv4Table = """
         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
+        """.strip()
 
-        my $targetIPv6Table = <<'END';
+        targetIPv6Table = """
         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"
-        );
+        """.strip()
+
+        machine.start()
+        machine.wait_for_unit("network.target")
+
+        with subtest("test routing tables"):
+            ipv4Table = machine.succeed("ip -4 route list dev eth0 | head -n3").strip()
+            ipv6Table = machine.succeed("ip -6 route list dev eth0 | head -n3").strip()
+            assert (
+                ipv4Table == targetIPv4Table
+            ), """
+              The IPv4 routing table does not match the expected one:
+                Result:
+                  {}
+                Expected:
+                  {}
+              """.format(
+                ipv4Table, targetIPv4Table
+            )
+            assert (
+                ipv6Table == targetIPv6Table
+            ), """
+              The IPv6 routing table does not match the expected one:
+                Result:
+                  {}
+                Expected:
+                  {}
+              """.format(
+                ipv6Table, targetIPv6Table
+            )
+
+        with subtest("test clean-up of the tables"):
+            machine.succeed("systemctl stop network-addresses-eth0")
+            ipv4Residue = machine.succeed("ip -4 route list dev eth0 | head -n-3").strip()
+            ipv6Residue = machine.succeed("ip -6 route list dev eth0 | head -n-3").strip()
+            assert (
+                ipv4Residue is ""
+            ), "The IPv4 routing table has not been properly cleaned:\n{}".format(ipv4Residue)
+            assert (
+                ipv6Residue is ""
+            ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue)
       '';
     };
   };
diff --git a/nixos/tests/nfs.nix b/nixos/tests/nfs.nix
deleted file mode 100644
index 2f655336e757..000000000000
--- a/nixos/tests/nfs.nix
+++ /dev/null
@@ -1,90 +0,0 @@
-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 ];
-  };
-
-  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/nixos/tests/nfs/default.nix b/nixos/tests/nfs/default.nix
new file mode 100644
index 000000000000..6bc803c91b46
--- /dev/null
+++ b/nixos/tests/nfs/default.nix
@@ -0,0 +1,9 @@
+{ version ? 4
+, system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}: {
+  simple = import ./simple.nix { inherit version system pkgs; };
+} // pkgs.lib.optionalAttrs (version == 4) {
+  # TODO: Test kerberos + nfsv3
+  kerberos = import ./kerberos.nix { inherit version system pkgs; };
+}
diff --git a/nixos/tests/nfs/kerberos.nix b/nixos/tests/nfs/kerberos.nix
new file mode 100644
index 000000000000..1f2d0d453ea0
--- /dev/null
+++ b/nixos/tests/nfs/kerberos.nix
@@ -0,0 +1,133 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+
+with lib;
+
+let
+  krb5 = 
+    { enable = true;
+      domain_realm."nfs.test"   = "NFS.TEST";
+      libdefaults.default_realm = "NFS.TEST";
+      realms."NFS.TEST" =
+        { admin_server = "server.nfs.test";
+          kdc = "server.nfs.test";
+        };
+    };
+
+  hosts =
+    ''
+      192.168.1.1 client.nfs.test
+      192.168.1.2 server.nfs.test
+    '';
+
+  users = {
+    users.alice = {
+        isNormalUser = true;
+        name = "alice";
+        uid = 1000;
+      };
+  };
+
+in
+
+{
+  name = "nfsv4-with-kerberos";
+ 
+  nodes = {
+    client = { lib, ... }:
+      { inherit krb5 users;
+
+        networking.extraHosts = hosts;
+        networking.domain = "nfs.test";
+        networking.hostName = "client";
+
+        fileSystems = lib.mkVMOverride
+          { "/data" = {
+              device  = "server.nfs.test:/";
+              fsType  = "nfs";
+              options = [ "nfsvers=4" "sec=krb5p" "noauto" ];
+            };
+          };
+      };
+
+    server = { lib, ...}:
+      { inherit krb5 users;
+
+        networking.extraHosts = hosts;
+        networking.domain = "nfs.test";
+        networking.hostName = "server";
+
+        networking.firewall.allowedTCPPorts = [
+          111  # rpc
+          2049 # nfs
+          88   # kerberos
+          749  # kerberos admin
+        ];
+
+        services.kerberos_server.enable = true;
+        services.kerberos_server.realms =
+          { "NFS.TEST".acl =
+            [ { access = "all"; principal = "admin/admin"; } ];
+          };
+
+        services.nfs.server.enable = true;
+        services.nfs.server.createMountPoints = true;
+        services.nfs.server.exports =
+          ''
+            /data *(rw,no_root_squash,fsid=0,sec=krb5p)
+          '';
+      };
+  };
+
+  testScript =
+    ''
+      server.succeed("mkdir -p /data/alice")
+      server.succeed("chown alice:users /data/alice")
+
+      # set up kerberos database
+      server.succeed(
+          "kdb5_util create -s -r NFS.TEST -P master_key",
+          "systemctl restart kadmind.service kdc.service",
+      )
+      server.wait_for_unit(f"kadmind.service")
+      server.wait_for_unit(f"kdc.service")
+
+      # create principals
+      server.succeed(
+          "kadmin.local add_principal -randkey nfs/server.nfs.test",
+          "kadmin.local add_principal -randkey nfs/client.nfs.test",
+          "kadmin.local add_principal -pw admin_pw admin/admin",
+          "kadmin.local add_principal -pw alice_pw alice",
+      )
+
+      # add principals to server keytab
+      server.succeed("kadmin.local ktadd nfs/server.nfs.test")
+      server.succeed("systemctl start rpc-gssd.service rpc-svcgssd.service")
+      server.wait_for_unit(f"rpc-gssd.service")
+      server.wait_for_unit(f"rpc-svcgssd.service")
+
+      client.wait_for_unit("network-online.target")
+
+      # add principals to client keytab
+      client.succeed("echo admin_pw | kadmin -p admin/admin ktadd nfs/client.nfs.test")
+      client.succeed("systemctl start rpc-gssd.service")
+      client.wait_for_unit("rpc-gssd.service")
+
+      with subtest("nfs share mounts"):
+          client.succeed("systemctl restart data.mount")
+          client.wait_for_unit("data.mount")
+
+      with subtest("permissions on nfs share are enforced"):
+          client.fail("su alice -c 'ls /data'")
+          client.succeed("su alice -c 'echo alice_pw | kinit'")
+          client.succeed("su alice -c 'ls /data'")
+
+          client.fail("su alice -c 'echo bla >> /data/foo'")
+          client.succeed("su alice -c 'echo bla >> /data/alice/foo'")
+          server.succeed("test -e /data/alice/foo")
+
+      with subtest("uids/gids are mapped correctly on nfs share"):
+          ids = client.succeed("stat -c '%U %G' /data/alice").split()
+          expected = ["alice", "users"]
+          assert ids == expected, f"ids incorrect: got {ids} expected {expected}"
+    '';
+})
diff --git a/nixos/tests/nfs/simple.nix b/nixos/tests/nfs/simple.nix
new file mode 100644
index 000000000000..a1a09ee0f45c
--- /dev/null
+++ b/nixos/tests/nfs/simple.nix
@@ -0,0 +1,94 @@
+import ../make-test-python.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 ];
+  };
+
+  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 =
+    ''
+      import time
+
+      server.wait_for_unit("nfs-server")
+      server.succeed("systemctl start network-online.target")
+      server.wait_for_unit("network-online.target")
+
+      start_all()
+
+      client1.wait_for_unit("data.mount")
+      client1.succeed("echo bla > /data/foo")
+      server.succeed("test -e /data/foo")
+
+      client2.wait_for_unit("data.mount")
+      client2.succeed("echo bla > /data/bar")
+      server.succeed("test -e /data/bar")
+
+      with subtest("restarting 'nfs-server' works correctly"):
+          server.succeed("systemctl restart nfs-server")
+          # will take 90 seconds due to the NFS grace period
+          client2.succeed("echo bla >> /data/bar")
+
+      with subtest("can get a lock"):
+          client2.succeed("time flock -n -s /data/lock true")
+
+      with subtest("client 2 fails to acquire lock held by client 1"):
+          client1.succeed("flock -x /data/lock -c 'touch locked; sleep 100000' &")
+          client1.wait_for_file("locked")
+          client2.fail("flock -n -s /data/lock true")
+
+      with subtest("client 2 obtains lock after resetting client 1"):
+          client2.succeed(
+              "flock -x /data/lock -c 'echo acquired; touch locked; sleep 100000' >&2 &"
+          )
+          client1.crash()
+          client1.start()
+          client2.wait_for_file("locked")
+
+      with subtest("locks survive server reboot"):
+          client1.wait_for_unit("data.mount")
+          server.shutdown()
+          server.start()
+          client1.succeed("touch /data/xyzzy")
+          client1.fail("time flock -n -s /data/lock true")
+
+      with subtest("unmounting during shutdown happens quickly"):
+          t1 = time.monotonic()
+          client1.shutdown()
+          duration = time.monotonic() - t1
+          assert duration < 30, f"shutdown took too long ({duration} seconds)"
+    '';
+})
diff --git a/nixos/tests/nghttpx.nix b/nixos/tests/nghttpx.nix
index 11611bfe1063..d83c1c4cae63 100644
--- a/nixos/tests/nghttpx.nix
+++ b/nixos/tests/nghttpx.nix
@@ -1,7 +1,7 @@
 let
   nginxRoot = "/run/nginx";
 in
-  import ./make-test.nix ({...}: {
+  import ./make-test-python.nix ({...}: {
     name  = "nghttpx";
     nodes = {
       webserver = {
@@ -52,10 +52,10 @@ in
     };
 
     testScript = ''
-      startAll;
+      start_all()
 
-      $webserver->waitForOpenPort("80");
-      $proxy->waitForOpenPort("80");
-      $client->waitUntilSucceeds("curl -s --fail http://proxy/hello-world.txt");
+      webserver.wait_for_open_port("80")
+      proxy.wait_for_open_port("80")
+      client.wait_until_succeeds("curl -s --fail http://proxy/hello-world.txt")
     '';
   })
diff --git a/nixos/tests/novacomd.nix b/nixos/tests/novacomd.nix
index 4eb60c0feb5c..940210dee235 100644
--- a/nixos/tests/novacomd.nix
+++ b/nixos/tests/novacomd.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "novacomd";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ dtzWill ];
@@ -9,26 +9,20 @@ import ./make-test.nix ({ pkgs, ...} : {
   };
 
   testScript = ''
-    $machine->waitForUnit("multi-user.target");
+    machine.wait_for_unit("novacomd.service")
 
-    # multi-user.target wants novacomd.service, but let's make sure
-    $machine->waitForUnit("novacomd.service");
+    with subtest("Make sure the daemon is really listening"):
+        machine.wait_for_open_port(6968)
+        machine.succeed("novacom -l")
 
-    # 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");
+    with subtest("Stop the daemon, double-check novacom fails if daemon isn't working"):
+        machine.stop_job("novacomd")
+        machine.fail("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");
+    with subtest("Make sure the daemon starts back up again"):
+        machine.start_job("novacomd")
+        # make sure the daemon is really listening
+        machine.wait_for_open_port(6968)
+        machine.succeed("novacom -l")
   '';
 })
diff --git a/nixos/tests/nzbget.nix b/nixos/tests/nzbget.nix
index 042ccec98cf6..12d8ed6ea8da 100644
--- a/nixos/tests/nzbget.nix
+++ b/nixos/tests/nzbget.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "nzbget";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ aanderse flokli ];
@@ -15,12 +15,16 @@ import ./make-test.nix ({ pkgs, ...} : {
   };
 
   testScript = ''
-    startAll;
+    start_all()
 
-    $server->waitForUnit("nzbget.service");
-    $server->waitForUnit("network.target");
-    $server->waitForOpenPort(6789);
-    $server->succeed("curl -s -u nzbget:tegbzn6789 http://127.0.0.1:6789 | grep -q 'This file is part of nzbget'");
-    $server->succeed("${pkgs.nzbget}/bin/nzbget -n -o ControlIP=127.0.0.1 -o ControlPort=6789 -o ControlPassword=tegbzn6789 -V");
+    server.wait_for_unit("nzbget.service")
+    server.wait_for_unit("network.target")
+    server.wait_for_open_port(6789)
+    assert "This file is part of nzbget" in server.succeed(
+        "curl -s -u nzbget:tegbzn6789 http://127.0.0.1:6789"
+    )
+    server.succeed(
+        "${pkgs.nzbget}/bin/nzbget -n -o Control_iP=127.0.0.1 -o Control_port=6789 -o Control_password=tegbzn6789 -V"
+    )
   '';
 })
diff --git a/nixos/tests/orangefs.nix b/nixos/tests/orangefs.nix
index bdf4fc10c447..46d7a6a72f89 100644
--- a/nixos/tests/orangefs.nix
+++ b/nixos/tests/orangefs.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ ... } :
+import ./make-test-python.nix ({ ... } :
 
 let
   server = { pkgs, ... } : {
@@ -52,37 +52,31 @@ in {
 
   testScript = ''
     # format storage
-    foreach my $server  (($server1,$server2))
-    {
-      $server->start();
-      $server->waitForUnit("multi-user.target");
-      $server->succeed("mkdir -p /data/storage /data/meta");
-      $server->succeed("chown orangefs:orangefs /data/storage /data/meta");
-      $server->succeed("chmod 0770 /data/storage /data/meta");
-      $server->succeed("sudo -g orangefs -u orangefs pvfs2-server -f /etc/orangefs/server.conf");
-    }
+    for server in server1, server2:
+        server.start()
+        server.wait_for_unit("multi-user.target")
+        server.succeed("mkdir -p /data/storage /data/meta")
+        server.succeed("chown orangefs:orangefs /data/storage /data/meta")
+        server.succeed("chmod 0770 /data/storage /data/meta")
+        server.succeed(
+            "sudo -g orangefs -u orangefs pvfs2-server -f /etc/orangefs/server.conf"
+        )
 
     # start services after storage is formated on all machines
-    foreach my $server  (($server1,$server2))
-    {
-      $server->succeed("systemctl start orangefs-server.service");
-    }
+    for server in server1, server2:
+        server.succeed("systemctl start orangefs-server.service")
 
-    # Check if clients can reach and mount the FS
-    foreach my $client  (($client1,$client2))
-    {
-      $client->start();
-      $client->waitForUnit("orangefs-client.service");
-      # Both servers need to be reachable
-      $client->succeed("pvfs2-check-server -h server1 -f orangefs -n tcp -p 3334");
-      $client->succeed("pvfs2-check-server -h server2 -f orangefs -n tcp -p 3334");
-      $client->waitForUnit("orangefs.mount");
-
-    }
-
-    # R/W test between clients
-    $client1->succeed("echo test > /orangefs/file1");
-    $client2->succeed("grep test /orangefs/file1");
+    with subtest("clients can reach and mount the FS"):
+        for client in client1, client2:
+            client.start()
+            client.wait_for_unit("orangefs-client.service")
+            # Both servers need to be reachable
+            client.succeed("pvfs2-check-server -h server1 -f orangefs -n tcp -p 3334")
+            client.succeed("pvfs2-check-server -h server2 -f orangefs -n tcp -p 3334")
+            client.wait_for_unit("orangefs.mount")
 
+    with subtest("R/W test between clients"):
+        client1.succeed("echo test > /orangefs/file1")
+        client2.succeed("grep test /orangefs/file1")
   '';
 })
diff --git a/nixos/tests/osrm-backend.nix b/nixos/tests/osrm-backend.nix
index 6e2d098d4adb..db67a5a589f9 100644
--- a/nixos/tests/osrm-backend.nix
+++ b/nixos/tests/osrm-backend.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, lib, ... }:
+import ./make-test-python.nix ({ pkgs, lib, ... }:
 let
   port = 5000;
 in {
@@ -45,9 +45,13 @@ in {
   testScript = let
     query = "http://localhost:${toString port}/route/v1/driving/7.41720,43.73304;7.42463,43.73886?steps=true";
   in ''
-    $machine->waitForUnit("osrm.service");
-    $machine->waitForOpenPort(${toString port});
-    $machine->succeed("curl --silent '${query}' | jq .waypoints[0].name | grep -F 'Boulevard Rainier III'");
-    $machine->succeed("curl --silent '${query}' | jq .waypoints[1].name | grep -F 'Avenue de la Costa'");
+    machine.wait_for_unit("osrm.service")
+    machine.wait_for_open_port(${toString port})
+    assert "Boulevard Rainier III" in machine.succeed(
+        "curl --silent '${query}' | jq .waypoints[0].name"
+    )
+    assert "Avenue de la Costa" in machine.succeed(
+        "curl --silent '${query}' | jq .waypoints[1].name"
+    )
   '';
 })
diff --git a/nixos/tests/overlayfs.nix b/nixos/tests/overlayfs.nix
index 99bb6b0f5531..33794deb9ed8 100644
--- a/nixos/tests/overlayfs.nix
+++ b/nixos/tests/overlayfs.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ... }: {
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "overlayfs";
   meta.maintainers = with pkgs.stdenv.lib.maintainers; [ bachp ];
 
@@ -9,49 +9,42 @@ import ./make-test.nix ({ pkgs, ... }: {
   };
 
   testScript = ''
-    $machine->succeed("ls /dev");
+    machine.succeed("ls /dev")
 
-    $machine->succeed("mkdir -p /tmp/mnt");
+    machine.succeed("mkdir -p /tmp/mnt")
 
     # Test ext4 + overlayfs
-    $machine->succeed(
-
-      "mkfs.ext4 -F -L overlay-ext4 /dev/vdb",
-      "mount -t ext4 /dev/vdb /tmp/mnt",
-
-      "mkdir -p /tmp/mnt/upper /tmp/mnt/lower /tmp/mnt/work /tmp/mnt/merged",
-
-      # Setup some existing files
-      "echo 'Replace' > /tmp/mnt/lower/replace.txt",
-      "echo 'Append' > /tmp/mnt/lower/append.txt",
-      "echo 'Overwrite' > /tmp/mnt/lower/overwrite.txt",
-
-      "mount -t overlay overlay -o lowerdir=/tmp/mnt/lower,upperdir=/tmp/mnt/upper,workdir=/tmp/mnt/work /tmp/mnt/merged",
-
-      # Test new
-      "echo 'New' > /tmp/mnt/merged/new.txt",
-      "[[ \"\$(cat /tmp/mnt/merged/new.txt)\" == \"New\" ]]",
-
-      # Test replace
-      "[[ \"\$(cat /tmp/mnt/merged/replace.txt)\" == \"Replace\" ]]",
-      "echo 'Replaced' > /tmp/mnt/merged/replace-tmp.txt",
-      "mv /tmp/mnt/merged/replace-tmp.txt /tmp/mnt/merged/replace.txt",
-      "[[ \"\$(cat /tmp/mnt/merged/replace.txt)\" == \"Replaced\" ]]",
-
-      # Overwrite
-      "[[ \"\$(cat /tmp/mnt/merged/overwrite.txt)\" == \"Overwrite\" ]]",
-      "echo 'Overwritten' > /tmp/mnt/merged/overwrite.txt",
-      "[[ \"\$(cat /tmp/mnt/merged/overwrite.txt)\" == \"Overwritten\" ]]",
-
-      # Test append
-      "[[ \"\$(cat /tmp/mnt/merged/append.txt)\" == \"Append\" ]]",
-      "echo 'ed' >> /tmp/mnt/merged/append.txt",
-      #"cat /tmp/mnt/merged/append.txt && exit 1",
-      "[[ \"\$(cat /tmp/mnt/merged/append.txt)\" == \"Append\ned\" ]]",
-
-      "umount /tmp/mnt/merged",
-      "umount /tmp/mnt",
-      "udevadm settle"
-    );
+    machine.succeed(
+        """
+          mkfs.ext4 -F -L overlay-ext4 /dev/vdb
+          mount -t ext4 /dev/vdb /tmp/mnt
+          mkdir -p /tmp/mnt/upper /tmp/mnt/lower /tmp/mnt/work /tmp/mnt/merged
+          # Setup some existing files
+          echo 'Replace' > /tmp/mnt/lower/replace.txt
+          echo 'Append' > /tmp/mnt/lower/append.txt
+          echo 'Overwrite' > /tmp/mnt/lower/overwrite.txt
+          mount -t overlay overlay -o lowerdir=/tmp/mnt/lower,upperdir=/tmp/mnt/upper,workdir=/tmp/mnt/work /tmp/mnt/merged
+          # Test new
+          echo 'New' > /tmp/mnt/merged/new.txt
+          [[ "\$(cat /tmp/mnt/merged/new.txt)" == "New" ]]
+          # Test replace
+          [[ "\$(cat /tmp/mnt/merged/replace.txt)" == "Replace" ]]
+          echo 'Replaced' > /tmp/mnt/merged/replace-tmp.txt
+          mv /tmp/mnt/merged/replace-tmp.txt /tmp/mnt/merged/replace.txt
+          [[ "\$(cat /tmp/mnt/merged/replace.txt)" == "Replaced" ]]
+          # Overwrite
+          [[ "\$(cat /tmp/mnt/merged/overwrite.txt)" == "Overwrite" ]]
+          echo 'Overwritten' > /tmp/mnt/merged/overwrite.txt
+          [[ "\$(cat /tmp/mnt/merged/overwrite.txt)" == "Overwritten" ]]
+          # Test append
+          [[ "\$(cat /tmp/mnt/merged/append.txt)" == "Append" ]]
+          echo 'ed' >> /tmp/mnt/merged/append.txt
+          #"cat /tmp/mnt/merged/append.txt && exit 1
+          [[ "\$(cat /tmp/mnt/merged/append.txt)" == "Append\ned" ]]
+          umount /tmp/mnt/merged
+          umount /tmp/mnt
+          udevadm settle
+      """
+    )
   '';
 })
diff --git a/nixos/tests/paperless.nix b/nixos/tests/paperless.nix
index 860ad0a6218f..355e7041d3fe 100644
--- a/nixos/tests/paperless.nix
+++ b/nixos/tests/paperless.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ lib, ... } : {
+import ./make-test-python.nix ({ lib, ... } : {
   name = "paperless";
   meta = with lib.maintainers; {
     maintainers = [ earvstedt ];
@@ -13,17 +13,24 @@ import ./make-test.nix ({ lib, ... } : {
   };
 
   testScript = ''
-    $machine->waitForUnit("paperless-consumer.service");
+    machine.wait_for_unit("paperless-consumer.service")
+
     # Create test doc
-    $machine->succeed('convert -size 400x40 xc:white -font "DejaVu-Sans" -pointsize 20 -fill black \
-      -annotate +5+20 "hello world 16-10-2005" /var/lib/paperless/consume/doc.png');
+    machine.succeed(
+        "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black -annotate +5+20 'hello world 16-10-2005' /var/lib/paperless/consume/doc.png"
+    )
+
+    with subtest("Service gets ready"):
+        machine.wait_for_unit("paperless-server.service")
+        # Wait until server accepts connections
+        machine.wait_until_succeeds("curl -s localhost:28981")
 
-    $machine->waitForUnit("paperless-server.service");
-    # Wait until server accepts connections
-    $machine->waitUntilSucceeds("curl -s localhost:28981");
-    # Wait until document is consumed
-    $machine->waitUntilSucceeds('(($(curl -s localhost:28981/api/documents/ | jq .count) == 1))');
-    $machine->succeed("curl -s localhost:28981/api/documents/ | jq '.results | .[0] | .created'")
-      =~ /2005-10-16/ or die;
+    with subtest("Test document is consumed"):
+        machine.wait_until_succeeds(
+            "(($(curl -s localhost:28981/api/documents/ | jq .count) == 1))"
+        )
+        assert "2005-10-16" in machine.succeed(
+            "curl -s localhost:28981/api/documents/ | jq '.results | .[0] | .created'"
+        )
   '';
 })
diff --git a/nixos/tests/pdns-recursor.nix b/nixos/tests/pdns-recursor.nix
index bf6e6093d69c..de1b60e0b1c7 100644
--- a/nixos/tests/pdns-recursor.nix
+++ b/nixos/tests/pdns-recursor.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ... }: {
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "powerdns";
 
   nodes.server = { ... }: {
@@ -6,7 +6,7 @@ import ./make-test.nix ({ pkgs, ... }: {
   };
 
   testScript = ''
-    $server->waitForUnit("pdns-recursor");
-    $server->waitForOpenPort("53");
+    server.wait_for_unit("pdns-recursor")
+    server.wait_for_open_port("53")
   '';
 })
diff --git a/nixos/tests/peerflix.nix b/nixos/tests/peerflix.nix
index fae37fedaac7..37628604d49b 100644
--- a/nixos/tests/peerflix.nix
+++ b/nixos/tests/peerflix.nix
@@ -1,6 +1,6 @@
 # This test runs peerflix and checks if peerflix starts
 
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "peerflix";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ offline ];
@@ -15,9 +15,9 @@ import ./make-test.nix ({ pkgs, ...} : {
     };
 
   testScript = ''
-    startAll;
+    start_all()
 
-    $peerflix->waitForUnit("peerflix.service");
-    $peerflix->waitUntilSucceeds("curl localhost:9000");
+    peerflix.wait_for_unit("peerflix.service")
+    peerflix.wait_until_succeeds("curl localhost:9000")
   '';
 })
diff --git a/nixos/tests/pgmanage.nix b/nixos/tests/pgmanage.nix
index bacaf3f41588..4f5dbed24a97 100644
--- a/nixos/tests/pgmanage.nix
+++ b/nixos/tests/pgmanage.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ... } :
+import ./make-test-python.nix ({ pkgs, ... } :
 let
   role     = "test";
   password = "secret";
@@ -29,11 +29,13 @@ in
   };
 
   testScript = ''
-    startAll;
-    $one->waitForUnit("default.target");
-    $one->requireActiveUnit("pgmanage.service");
+    start_all()
+    one.wait_for_unit("default.target")
+    one.require_unit_state("pgmanage.service", "active")
 
     # Test if we can log in.
-    $one->waitUntilSucceeds("curl 'http://localhost:8080/pgmanage/auth' --data 'action=login&connname=${conn}&username=${role}&password=${password}' --fail");
+    one.wait_until_succeeds(
+        "curl 'http://localhost:8080/pgmanage/auth' --data 'action=login&connname=${conn}&username=${role}&password=${password}' --fail"
+    )
   '';
 })
diff --git a/nixos/tests/php-pcre.nix b/nixos/tests/php-pcre.nix
index ae44aec7944f..d5c22e0582a0 100644
--- a/nixos/tests/php-pcre.nix
+++ b/nixos/tests/php-pcre.nix
@@ -1,7 +1,7 @@
 
 let testString = "can-use-subgroups"; in
 
-import ./make-test.nix ({ ...}: {
+import ./make-test-python.nix ({ ...}: {
   name = "php-httpd-pcre-jit-test";
   machine = { lib, pkgs, ... }: {
     time.timeZone = "UTC";
@@ -31,9 +31,10 @@ import ./make-test.nix ({ ...}: {
   };
   testScript = { ... }:
   ''
-    $machine->waitForUnit('httpd.service');
+    machine.wait_for_unit("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}\""');
+    assert 'string(${toString (builtins.stringLength testString)}) "${testString}"' in machine.succeed(
+        "curl -vvv -s http://127.0.0.1:80/index.php"
+    )
   '';
 })
diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
index 6884f17aabbe..2eccfdf47f59 100644
--- a/nixos/tests/plasma5.nix
+++ b/nixos/tests/plasma5.nix
@@ -12,8 +12,8 @@ import ./make-test-python.nix ({ pkgs, ...} :
     imports = [ ./common/user-account.nix ];
     services.xserver.enable = true;
     services.xserver.displayManager.sddm.enable = true;
+    services.xserver.displayManager.defaultSession = "plasma5";
     services.xserver.desktopManager.plasma5.enable = true;
-    services.xserver.desktopManager.default = "plasma5";
     services.xserver.displayManager.sddm.autoLogin = {
       enable = true;
       user = "alice";
diff --git a/nixos/tests/postgis.nix b/nixos/tests/postgis.nix
index 294eb50b5fe5..84bbb0bc8ec6 100644
--- a/nixos/tests/postgis.nix
+++ b/nixos/tests/postgis.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "postgis";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ lsix ];
@@ -20,10 +20,10 @@ import ./make-test.nix ({ pkgs, ...} : {
   };
 
   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;'");
+    start_all()
+    master.wait_for_unit("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/nixos/tests/quagga.nix b/nixos/tests/quagga.nix
index 6aee7ea57f03..04590aa0eb38 100644
--- a/nixos/tests/quagga.nix
+++ b/nixos/tests/quagga.nix
@@ -5,7 +5,7 @@
 #
 # All interfaces are in OSPF Area 0.
 
-import ./make-test.nix ({ pkgs, ... }:
+import ./make-test-python.nix ({ pkgs, ... }:
   let
 
     ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ipv4.addresses).address;
@@ -74,23 +74,23 @@ import ./make-test.nix ({ pkgs, ... }:
       testScript =
         { ... }:
         ''
-          startAll;
+          start_all()
 
           # Wait for the networking to start on all machines
-          $_->waitForUnit("network.target") foreach values %vms;
+          for machine in client, router1, router2, server:
+              machine.wait_for_unit("network.target")
 
-          # 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>'");
-          }
+          with subtest("Wait for OSPF to form adjacencies"):
+              for gw in router1, router2:
+                  gw.wait_for_unit("ospfd")
+                  gw.wait_until_succeeds("vtysh -c 'show ip ospf neighbor' | grep Full")
+                  gw.wait_until_succeeds("vtysh -c 'show ip route' | grep '^O>'")
 
-          # Test ICMP.
-          $client->succeed("ping -c 3 server >&2");
+          with subtest("Test ICMP"):
+              client.wait_until_succeeds("ping -c 3 server >&2")
 
-          # Test whether HTTP works.
-          $server->waitForUnit("httpd");
-          $client->succeed("curl --fail http://server/ >&2");
+          with subtest("Test whether HTTP works"):
+              server.wait_for_unit("httpd")
+              client.succeed("curl --fail http://server/ >&2")
         '';
     })
diff --git a/nixos/tests/rspamd.nix b/nixos/tests/rspamd.nix
index 0cc94728f80a..bf3f0de62044 100644
--- a/nixos/tests/rspamd.nix
+++ b/nixos/tests/rspamd.nix
@@ -3,20 +3,20 @@
   pkgs ? import ../.. { inherit system config; }
 }:
 
-with import ../lib/testing.nix { inherit system pkgs; };
+with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
   initMachine = ''
-    startAll
-    $machine->waitForUnit("rspamd.service");
-    $machine->succeed("id \"rspamd\" >/dev/null");
+    start_all()
+    machine.wait_for_unit("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}\" ]]");
+    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}";
@@ -25,22 +25,23 @@ let
       networking.enableIPv6 = enableIPv6;
     };
     testScript = ''
-      startAll
-      $machine->waitForUnit("multi-user.target");
-      $machine->waitForOpenPort(11334);
-      $machine->waitForUnit("rspamd.service");
-      $machine->succeed("id \"rspamd\" >/dev/null");
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_open_port(11334)
+      machine.wait_for_unit("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"));
-      ''}
+      machine.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"))''}
+      # would not reformat
     '';
   };
 in
@@ -69,14 +70,18 @@ in
 
     testScript = ''
       ${initMachine}
-      $machine->waitForFile("/run/rspamd.sock");
+      machine.wait_for_file("/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"));
+      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")
+      )
     '';
   };
 
@@ -111,18 +116,32 @@ in
 
     testScript = ''
       ${initMachine}
-      $machine->waitForFile("/run/rspamd.sock");
+      machine.wait_for_file("/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"));
+      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.wait_until_succeeds(
+          "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 {
@@ -199,22 +218,34 @@ in
     };
     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"));
+      machine.wait_for_open_port(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");
+      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");
+      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"));
+      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.wait_until_succeeds("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 {
@@ -250,16 +281,24 @@ in
     };
     testScript = ''
       ${initMachine}
-      $machine->waitForOpenPort(11334);
-      $machine->waitForOpenPort(25);
+      machine.wait_for_open_port(11334)
+      machine.wait_for_open_port(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.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");
+      machine.wait_until_fails('[ "$(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/nixos/tests/sddm.nix b/nixos/tests/sddm.nix
index 4bdcd701dcf1..a145705250f7 100644
--- a/nixos/tests/sddm.nix
+++ b/nixos/tests/sddm.nix
@@ -16,9 +16,8 @@ let
         imports = [ ./common/user-account.nix ];
         services.xserver.enable = true;
         services.xserver.displayManager.sddm.enable = true;
-        services.xserver.windowManager.default = "icewm";
+        services.xserver.displayManager.defaultSession = "none+icewm";
         services.xserver.windowManager.icewm.enable = true;
-        services.xserver.desktopManager.default = "none";
       };
 
       enableOCR = true;
@@ -52,9 +51,8 @@ let
             user = "alice";
           };
         };
-        services.xserver.windowManager.default = "icewm";
+        services.xserver.displayManager.defaultSession = "none+icewm";
         services.xserver.windowManager.icewm.enable = true;
-        services.xserver.desktopManager.default = "none";
       };
 
       testScript = { nodes, ... }: let
diff --git a/nixos/tests/sonarr.nix b/nixos/tests/sonarr.nix
index 3e84445099ab..764a4d05b381 100644
--- a/nixos/tests/sonarr.nix
+++ b/nixos/tests/sonarr.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ lib, ... }:
+import ./make-test-python.nix ({ lib, ... }:
 
 with lib;
 
@@ -11,8 +11,8 @@ with lib;
     { services.sonarr.enable = true; };
 
   testScript = ''
-    $machine->waitForUnit('sonarr.service');
-    $machine->waitForOpenPort('8989');
-    $machine->succeed("curl --fail http://localhost:8989/");
+    machine.wait_for_unit("sonarr.service")
+    machine.wait_for_open_port("8989")
+    machine.succeed("curl --fail http://localhost:8989/")
   '';
 })
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index 0dba3697980f..7076bd77b770 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -1,6 +1,6 @@
 # Test configuration switching.
 
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ...} : {
   name = "switch-test";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ gleber ];
@@ -28,7 +28,11 @@ import ./make-test.nix ({ pkgs, ...} : {
       exec env -i "$@" | tee /dev/stderr
     '';
   in ''
-    $machine->succeed("${stderrRunner} ${originalSystem}/bin/switch-to-configuration test");
-    $machine->succeed("${stderrRunner} ${otherSystem}/bin/switch-to-configuration test");
+    machine.succeed(
+        "${stderrRunner} ${originalSystem}/bin/switch-to-configuration test"
+    )
+    machine.succeed(
+        "${stderrRunner} ${otherSystem}/bin/switch-to-configuration test"
+    )
   '';
 })
diff --git a/nixos/tests/systemd-timesyncd.nix b/nixos/tests/systemd-timesyncd.nix
index d12b8eb2bf7e..ad5b9a47383b 100644
--- a/nixos/tests/systemd-timesyncd.nix
+++ b/nixos/tests/systemd-timesyncd.nix
@@ -1,7 +1,7 @@
 # Regression test for systemd-timesync having moved the state directory without
 # upstream providing a migration path. https://github.com/systemd/systemd/issues/12131
 
-import ./make-test.nix (let
+import ./make-test-python.nix (let
   common = { lib, ... }: {
     # override the `false` value from the qemu-vm base profile
     services.timesyncd.enable = lib.mkForce true;
@@ -25,28 +25,28 @@ in {
   };
 
   testScript = ''
-    startAll;
-    $current->succeed('systemctl status systemd-timesyncd.service');
+    start_all()
+    current.succeed("systemctl status systemd-timesyncd.service")
     # on a new install with a recent systemd there should not be any
     # leftovers from the dynamic user mess
-    $current->succeed('test -e /var/lib/systemd/timesync');
-    $current->succeed('test ! -L /var/lib/systemd/timesync');
+    current.succeed("test -e /var/lib/systemd/timesync")
+    current.succeed("test ! -L /var/lib/systemd/timesync")
 
     # timesyncd should be running on the upgrading system since we fixed the
     # file bits in the activation script
-    $pre1909->succeed('systemctl status systemd-timesyncd.service');
+    pre1909.succeed("systemctl status systemd-timesyncd.service")
 
     # the path should be gone after the migration
-    $pre1909->succeed('test ! -e /var/lib/private/systemd/timesync');
+    pre1909.succeed("test ! -e /var/lib/private/systemd/timesync")
 
     # and the new path should no longer be a symlink
-    $pre1909->succeed('test -e /var/lib/systemd/timesync');
-    $pre1909->succeed('test ! -L /var/lib/systemd/timesync');
+    pre1909.succeed("test -e /var/lib/systemd/timesync")
+    pre1909.succeed("test ! -L /var/lib/systemd/timesync")
 
     # after a restart things should still work and not fail in the activation
     # scripts and cause the boot to fail..
-    $pre1909->shutdown;
-    $pre1909->start;
-    $pre1909->succeed('systemctl status systemd-timesyncd.service');
+    pre1909.shutdown()
+    pre1909.start()
+    pre1909.succeed("systemctl status systemd-timesyncd.service")
   '';
 })
diff --git a/nixos/tests/wireguard/namespaces.nix b/nixos/tests/wireguard/namespaces.nix
index 94f993d9475d..c8a4e3bb52a1 100644
--- a/nixos/tests/wireguard/namespaces.nix
+++ b/nixos/tests/wireguard/namespaces.nix
@@ -13,7 +13,7 @@ let
 
 in
 
-import ../make-test.nix ({ pkgs, ...} : {
+import ../make-test-python.nix ({ pkgs, ...} : {
   name = "wireguard-with-namespaces";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ asymmetric ];
@@ -65,16 +65,14 @@ import ../make-test.nix ({ pkgs, ...} : {
   };
 
   testScript = ''
-    startAll();
+    start_all()
 
-    $peer0->waitForUnit("wireguard-wg0.service");
-    $peer1->waitForUnit("wireguard-wg0.service");
-    $peer2->waitForUnit("wireguard-wg0.service");
-    $peer3->waitForUnit("wireguard-wg0.service");
+    for machine in peer0, peer1, peer2, peer3:
+        machine.wait_for_unit("wireguard-wg0.service")
 
-    $peer0->succeed("ip -n ${socketNamespace} link show wg0");
-    $peer1->succeed("ip -n ${interfaceNamespace} link show wg0");
-    $peer2->succeed("ip -n ${interfaceNamespace} link show wg0");
-    $peer3->succeed("ip link show wg0");
+    peer0.succeed("ip -n ${socketNamespace} link show wg0")
+    peer1.succeed("ip -n ${interfaceNamespace} link show wg0")
+    peer2.succeed("ip -n ${interfaceNamespace} link show wg0")
+    peer3.succeed("ip link show wg0")
   '';
 })
diff --git a/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix
index c2e5ba60d7bc..ef711f8dcf6a 100644
--- a/nixos/tests/xmonad.nix
+++ b/nixos/tests/xmonad.nix
@@ -4,10 +4,10 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ nequissimus ];
   };
 
-  machine = { lib, pkgs, ... }: {
+  machine = { pkgs, ... }: {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
     services.xserver.displayManager.auto.user = "alice";
-    services.xserver.windowManager.default = lib.mkForce "xmonad";
+    services.xserver.displayManager.defaultSession = "none+xmonad";
     services.xserver.windowManager.xmonad = {
       enable = true;
       enableContribAndExtras = true;
@@ -27,13 +27,13 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     machine.wait_for_x()
     machine.wait_for_file("${user.home}/.Xauthority")
     machine.succeed("xauth merge ${user.home}/.Xauthority")
-    machine.send_chars("alt-ctrl-x")
+    machine.send_key("alt-ctrl-x")
     machine.wait_for_window("${user.name}.*machine")
     machine.sleep(1)
     machine.screenshot("terminal")
     machine.wait_until_succeeds("xmonad --restart")
     machine.sleep(3)
-    machine.send_chars("alt-shift-ret")
+    machine.send_key("alt-shift-ret")
     machine.wait_for_window("${user.name}.*machine")
     machine.sleep(1)
     machine.screenshot("terminal")
diff --git a/nixos/tests/zsh-history.nix b/nixos/tests/zsh-history.nix
new file mode 100644
index 000000000000..4380ec9adfd2
--- /dev/null
+++ b/nixos/tests/zsh-history.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "zsh-history";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ kampka ];
+  };
+
+  nodes.default = { ... }: {
+    programs = {
+      zsh.enable = true;
+    };
+    environment.systemPackages = [ pkgs.zsh-history ];
+    programs.zsh.interactiveShellInit = ''
+      source ${pkgs.zsh-history.out}/share/zsh/init.zsh
+    '';
+    users.users.root.shell = "${pkgs.zsh}/bin/zsh";
+  };
+
+  testScript = ''
+    start_all()
+    default.wait_for_unit("multi-user.target")
+    default.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+    # Login
+    default.wait_until_tty_matches(1, "login: ")
+    default.send_chars("root\n")
+    default.wait_until_tty_matches(1, "root@default>")
+
+    # Generate some history
+    default.send_chars("echo foobar\n")
+    default.wait_until_tty_matches(1, "foobar")
+
+    # Ensure that command was recorded in history
+    default.succeed("/run/current-system/sw/bin/history list | grep -q foobar")
+  '';
+})