about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/tests')
-rw-r--r--nixpkgs/nixos/tests/all-tests.nix5
-rw-r--r--nixpkgs/nixos/tests/armagetronad.nix272
-rw-r--r--nixpkgs/nixos/tests/boot.nix82
-rw-r--r--nixpkgs/nixos/tests/common/ec2.nix2
-rw-r--r--nixpkgs/nixos/tests/consul.nix4
-rw-r--r--nixpkgs/nixos/tests/docker-tools.nix22
-rw-r--r--nixpkgs/nixos/tests/incus/container.nix13
-rw-r--r--nixpkgs/nixos/tests/installer.nix46
-rw-r--r--nixpkgs/nixos/tests/k3s/default.nix5
-rw-r--r--nixpkgs/nixos/tests/k3s/etcd.nix100
-rw-r--r--nixpkgs/nixos/tests/kernel-generic.nix1
-rw-r--r--nixpkgs/nixos/tests/lomiri-system-settings.nix99
-rw-r--r--nixpkgs/nixos/tests/mealie.nix24
-rw-r--r--nixpkgs/nixos/tests/monado.nix39
-rw-r--r--nixpkgs/nixos/tests/plasma6.nix64
-rw-r--r--nixpkgs/nixos/tests/searx.nix6
-rw-r--r--nixpkgs/nixos/tests/systemd-boot.nix115
17 files changed, 835 insertions, 64 deletions
diff --git a/nixpkgs/nixos/tests/all-tests.nix b/nixpkgs/nixos/tests/all-tests.nix
index 1144a5611dcf..231767ca2b97 100644
--- a/nixpkgs/nixos/tests/all-tests.nix
+++ b/nixpkgs/nixos/tests/all-tests.nix
@@ -128,6 +128,7 @@ in {
   appliance-repart-image = runTest ./appliance-repart-image.nix;
   apparmor = handleTest ./apparmor.nix {};
   archi = handleTest ./archi.nix {};
+  armagetronad = handleTest ./armagetronad.nix {};
   atd = handleTest ./atd.nix {};
   atop = handleTest ./atop.nix {};
   atuin = handleTest ./atuin.nix {};
@@ -497,6 +498,7 @@ in {
   lxd = pkgs.recurseIntoAttrs (handleTest ./lxd { inherit handleTestOn; });
   lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
+  lomiri-system-settings = handleTest ./lomiri-system-settings.nix {};
   lorri = handleTest ./lorri/default.nix {};
   maddy = discoverTests (import ./maddy { inherit handleTest; });
   maestral = handleTest ./maestral.nix {};
@@ -516,6 +518,7 @@ in {
   matrix-synapse = handleTest ./matrix/synapse.nix {};
   matrix-synapse-workers = handleTest ./matrix/synapse-workers.nix {};
   mattermost = handleTest ./mattermost.nix {};
+  mealie = handleTest ./mealie.nix {};
   mediamtx = handleTest ./mediamtx.nix {};
   mediatomb = handleTest ./mediatomb.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
@@ -535,6 +538,7 @@ in {
   mobilizon = handleTest ./mobilizon.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
   molly-brown = handleTest ./molly-brown.nix {};
+  monado = handleTest ./monado.nix {};
   monica = handleTest ./web-apps/monica.nix {};
   mongodb = handleTest ./mongodb.nix {};
   moodle = handleTest ./moodle.nix {};
@@ -692,6 +696,7 @@ in {
   plantuml-server = handleTest ./plantuml-server.nix {};
   plasma-bigscreen = handleTest ./plasma-bigscreen.nix {};
   plasma5 = handleTest ./plasma5.nix {};
+  plasma6 = handleTest ./plasma6.nix {};
   plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {};
   plausible = handleTest ./plausible.nix {};
   please = handleTest ./please.nix {};
diff --git a/nixpkgs/nixos/tests/armagetronad.nix b/nixpkgs/nixos/tests/armagetronad.nix
new file mode 100644
index 000000000000..ff2841dedd21
--- /dev/null
+++ b/nixpkgs/nixos/tests/armagetronad.nix
@@ -0,0 +1,272 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  user = "alice";
+
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/user-account.nix ./common/x11.nix ];
+      hardware.opengl.driSupport = true;
+      virtualisation.memorySize = 256;
+      environment = {
+        systemPackages = [ pkgs.armagetronad ];
+        variables.XAUTHORITY = "/home/${user}/.Xauthority";
+      };
+      test-support.displayManager.auto.user = user;
+    };
+
+in {
+  name = "armagetronad";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ numinit ];
+  };
+
+  enableOCR = true;
+
+  nodes =
+    {
+      server = {
+        services.armagetronad.servers = {
+          high-rubber = {
+            enable = true;
+            name = "Smoke Test High Rubber Server";
+            port = 4534;
+            settings = {
+              SERVER_OPTIONS = "High Rubber server made to run smoke tests.";
+              CYCLE_RUBBER = 40;
+              SIZE_FACTOR = 0.5;
+            };
+            roundSettings = {
+              SAY = [
+                "NixOS Smoke Test Server"
+                "https://nixos.org"
+              ];
+            };
+          };
+          sty = {
+            enable = true;
+            name = "Smoke Test sty+ct+ap Server";
+            package = pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated;
+            port = 4535;
+            settings = {
+              SERVER_OPTIONS = "sty+ct+ap server made to run smoke tests.";
+              CYCLE_RUBBER = 20;
+              SIZE_FACTOR = 0.5;
+            };
+            roundSettings = {
+              SAY = [
+                "NixOS Smoke Test sty+ct+ap Server"
+                "https://nixos.org"
+              ];
+            };
+          };
+          trunk = {
+            enable = true;
+            name = "Smoke Test trunk Server";
+            package = pkgs.armagetronad."0.4".dedicated;
+            port = 4536;
+            settings = {
+              SERVER_OPTIONS = "0.4 server made to run smoke tests.";
+              CYCLE_RUBBER = 20;
+              SIZE_FACTOR = 0.5;
+            };
+            roundSettings = {
+              SAY = [
+                "NixOS Smoke Test 0.4 Server"
+                "https://nixos.org"
+              ];
+            };
+          };
+        };
+      };
+
+      client1 = client;
+      client2 = client;
+    };
+
+  testScript = let
+    xdo = name: text: let
+      xdoScript = pkgs.writeText "${name}.xdo" text;
+    in "${pkgs.xdotool}/bin/xdotool ${xdoScript}";
+  in
+    ''
+      import shlex
+      import threading
+      from collections import namedtuple
+
+      class Client(namedtuple('Client', ('node', 'name'))):
+        def send(self, *keys):
+          for key in keys:
+            self.node.send_key(key)
+
+        def send_on(self, text, *keys):
+          self.node.wait_for_text(text)
+          self.send(*keys)
+
+      Server = namedtuple('Server', ('node', 'name', 'address', 'port', 'welcome', 'attacker', 'victim', 'coredump_delay'))
+
+      # Clients and their in-game names
+      clients = (
+        Client(client1, 'Arduino'),
+        Client(client2, 'SmOoThIcE')
+      )
+
+      # Server configs.
+      servers = (
+        Server(server, 'high-rubber', 'server', 4534, 'NixOS Smoke Test Server', 'SmOoThIcE', 'Arduino', 8),
+        Server(server, 'sty', 'server', 4535, 'NixOS Smoke Test sty+ct+ap Server', 'Arduino', 'SmOoThIcE', 8),
+        Server(server, 'trunk', 'server', 4536, 'NixOS Smoke Test 0.4 Server', 'Arduino', 'SmOoThIcE', 8)
+      )
+
+      """
+      Runs a command as the client user.
+      """
+      def run(cmd):
+        return "su - ${user} -c " + shlex.quote(cmd)
+
+      screenshot_idx = 1
+
+      """
+      Takes screenshots on all clients.
+      """
+      def take_screenshots(screenshot_idx):
+        for client in clients:
+          client.node.screenshot(f"screen_{client.name}_{screenshot_idx}")
+        return screenshot_idx + 1
+
+      # Wait for the servers to come up.
+      start_all()
+      for srv in servers:
+        srv.node.wait_for_unit(f"armagetronad-{srv.name}")
+        srv.node.wait_until_succeeds(f"ss --numeric --udp --listening | grep -q {srv.port}")
+
+      # Make sure console commands work through the named pipe we created.
+      for srv in servers:
+        srv.node.succeed(
+          f"echo 'say Testing!' >> /var/lib/armagetronad/{srv.name}/input"
+        )
+        srv.node.succeed(
+          f"echo 'say Testing again!' >> /var/lib/armagetronad/{srv.name}/input"
+        )
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: Testing!'"
+        )
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: Testing again!'"
+        )
+
+      """
+      Sets up a client, waiting for the given barrier on completion.
+      """
+      def client_setup(client, servers, barrier):
+        client.node.wait_for_x()
+
+        # Configure Armagetron.
+        client.node.succeed(
+          run("mkdir -p ~/.armagetronad/var"),
+          run(f"echo 'PLAYER_1 {client.name}' >> ~/.armagetronad/var/autoexec.cfg")
+        )
+        for idx, srv in enumerate(servers):
+          client.node.succeed(
+            run(f"echo 'BOOKMARK_{idx+1}_ADDRESS {srv.address}' >> ~/.armagetronad/var/autoexec.cfg"),
+            run(f"echo 'BOOKMARK_{idx+1}_NAME {srv.name}' >> ~/.armagetronad/var/autoexec.cfg"),
+            run(f"echo 'BOOKMARK_{idx+1}_PORT {srv.port}' >> ~/.armagetronad/var/autoexec.cfg")
+          )
+
+        # Start Armagetron.
+        client.node.succeed(run("ulimit -c unlimited; armagetronad >&2 & disown"))
+        client.node.wait_until_succeeds(
+          run(
+            "${xdo "create_new_win-select_main_window" ''
+              search --onlyvisible --name "Armagetron Advanced"
+              windowfocus --sync
+              windowactivate --sync
+            ''}"
+          )
+        )
+
+        # Get through the tutorial.
+        client.send_on('Language Settings', 'ret')
+        client.send_on('First Setup', 'ret')
+        client.send_on('Welcome to Armagetron Advanced', 'ret')
+        client.send_on('round 1', 'esc')
+        client.send_on('Menu', 'up', 'up', 'ret')
+        client.send_on('We hope you', 'ret')
+        client.send_on('Armagetron Advanced', 'ret')
+        client.send_on('Play Game', 'ret')
+
+        # Online > LAN > Network Setup > Mates > Server Bookmarks
+        client.send_on('Multiplayer', 'down', 'down', 'down', 'down', 'ret')
+
+        barrier.wait()
+
+      # Get to the Server Bookmarks screen on both clients. This takes a while so do it asynchronously.
+      barrier = threading.Barrier(3, timeout=120)
+      for client in clients:
+        threading.Thread(target=client_setup, args=(client, servers, barrier)).start()
+      barrier.wait()
+
+      # Main testing loop. Iterates through each server bookmark and connects to them in sequence.
+      # Assumes that the game is currently on the Server Bookmarks screen.
+      for srv in servers:
+        screenshot_idx = take_screenshots(screenshot_idx)
+
+        # Connect both clients at once, one second apart.
+        for client in clients:
+          client.send('ret')
+          client.node.sleep(1)
+
+        # Wait for clients to connect
+        for client in clients:
+          srv.node.wait_until_succeeds(
+            f"journalctl -u armagetronad-{srv.name} -e | grep -q '{client.name}.*entered the game'"
+          )
+
+        # Wait for the match to start
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: {srv.welcome}'"
+        )
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: https://nixos.org'"
+        )
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Go (round 1 of 10)'"
+        )
+
+        # Wait a bit
+        srv.node.sleep(srv.coredump_delay)
+
+        # Turn the attacker player's lightcycle left
+        attacker = next(client for client in clients if client.name == srv.attacker)
+        victim = next(client for client in clients if client.name == srv.victim)
+        attacker.send('left')
+        screenshot_idx = take_screenshots(screenshot_idx)
+
+        # Wait for coredump.
+        srv.node.wait_until_succeeds(
+          f"journalctl -u armagetronad-{srv.name} -e | grep -q '{attacker.name} core dumped {victim.name}'"
+        )
+        screenshot_idx = take_screenshots(screenshot_idx)
+
+        # Disconnect both clients from the server
+        for client in clients:
+          client.send('esc')
+          client.send_on('Menu', 'up', 'up', 'ret')
+          srv.node.wait_until_succeeds(
+            f"journalctl -u armagetronad-{srv.name} -e | grep -q '{client.name}.*left the game'"
+          )
+
+        # Next server.
+        for client in clients:
+          client.send_on('Server Bookmarks', 'down')
+
+      # Stop the servers
+      for srv in servers:
+        srv.node.succeed(
+          f"systemctl stop armagetronad-{srv.name}"
+        )
+        srv.node.wait_until_fails(f"ss --numeric --udp --listening | grep -q {srv.port}")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/boot.nix b/nixpkgs/nixos/tests/boot.nix
index ec2a9f6527c9..56f72dddf526 100644
--- a/nixpkgs/nixos/tests/boot.nix
+++ b/nixpkgs/nixos/tests/boot.nix
@@ -4,10 +4,41 @@
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
-with pkgs.lib;
 
 let
-  qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
+  lib = pkgs.lib;
+  qemu-common = import ../lib/qemu-common.nix { inherit lib pkgs; };
+
+  mkStartCommand = {
+    memory ? 2048,
+    cdrom ? null,
+    usb ? null,
+    pxe ? null,
+    uboot ? false,
+    uefi ? false,
+    extraFlags ? [],
+  }: let
+    qemu = qemu-common.qemuBinary pkgs.qemu_test;
+
+    flags = [
+      "-m" (toString memory)
+      "-netdev" ("user,id=net0" + (lib.optionalString (pxe != null) ",tftp=${pxe},bootfile=netboot.ipxe"))
+      "-device" ("virtio-net-pci,netdev=net0" + (lib.optionalString (pxe != null && uefi) ",romfile=${pkgs.ipxe}/ipxe.efirom"))
+    ] ++ lib.optionals (cdrom != null) [
+      "-cdrom" cdrom
+    ] ++ lib.optionals (usb != null) [
+      "-device" "usb-ehci"
+      "-drive" "id=usbdisk,file=${usb},if=none,readonly"
+      "-device" "usb-storage,drive=usbdisk"
+    ] ++ lib.optionals (pxe != null) [
+      "-boot" "order=n"
+    ] ++ lib.optionals uefi [
+      "-drive" "if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware}"
+      "-drive" "if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}"
+    ] ++ extraFlags;
+
+    flagsStr = lib.concatStringsSep " " flags;
+  in "${qemu} ${flagsStr}";
 
   iso =
     (import ../lib/eval-config.nix {
@@ -28,21 +59,16 @@ let
       ];
     }).config.system.build.sdImage;
 
-  pythonDict = params: "\n    {\n        ${concatStringsSep ",\n        " (mapAttrsToList (name: param: "\"${name}\": \"${param}\"") params)},\n    }\n";
-
-  makeBootTest = name: extraConfig:
+  makeBootTest = name: config:
     let
-      machineConfig = pythonDict ({
-        qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
-        qemuFlags = "-m 768";
-      } // extraConfig);
+      startCommand = mkStartCommand config;
     in
       makeTest {
         name = "boot-" + name;
         nodes = { };
         testScript =
           ''
-            machine = create_machine(${machineConfig})
+            machine = create_machine("${startCommand}")
             machine.start()
             machine.wait_for_unit("multi-user.target")
             machine.succeed("nix store verify --no-trust -r --option experimental-features nix-command /run/current-system")
@@ -73,43 +99,35 @@ let
           config.system.build.netbootIpxeScript
         ];
       };
-      machineConfig = pythonDict ({
-        qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
-        qemuFlags = "-boot order=n -m 2000";
-        netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe";
+      startCommand = mkStartCommand ({
+        pxe = ipxeBootDir;
       } // extraConfig);
     in
       makeTest {
         name = "boot-netboot-" + name;
         nodes = { };
         testScript = ''
-            machine = create_machine(${machineConfig})
+            machine = create_machine("${startCommand}")
             machine.start()
             machine.wait_for_unit("multi-user.target")
             machine.shutdown()
           '';
       };
-  uefiBinary = {
-    x86_64-linux = "${pkgs.OVMF.fd}/FV/OVMF.fd";
-    aarch64-linux = "${pkgs.OVMF.fd}/FV/QEMU_EFI.fd";
-  }.${pkgs.stdenv.hostPlatform.system};
 in {
     uefiCdrom = makeBootTest "uefi-cdrom" {
+      uefi = true;
       cdrom = "${iso}/iso/${iso.isoName}";
-      bios = uefiBinary;
     };
 
     uefiUsb = makeBootTest "uefi-usb" {
+      uefi = true;
       usb = "${iso}/iso/${iso.isoName}";
-      bios = uefiBinary;
     };
 
     uefiNetboot = makeNetbootTest "uefi" {
-      bios = uefiBinary;
-      # Custom ROM is needed for EFI PXE boot. I failed to understand exactly why, because QEMU should still use iPXE for EFI.
-      netFrontendArgs = "romfile=${pkgs.ipxe}/ipxe.efirom";
+      uefi = true;
     };
-} // optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
+} // lib.optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
     biosCdrom = makeBootTest "bios-cdrom" {
       cdrom = "${iso}/iso/${iso.isoName}";
     };
@@ -124,9 +142,12 @@ in {
       sdImage = "${sd}/sd-image/${sd.imageName}";
       mutableImage = "/tmp/linked-image.qcow2";
 
-      machineConfig = pythonDict {
-        bios = "${pkgs.ubootQemuX86}/u-boot.rom";
-        qemuFlags = "-m 768 -machine type=pc,accel=tcg -drive file=${mutableImage},if=ide,format=qcow2";
+      startCommand = mkStartCommand {
+        extraFlags = [
+          "-bios" "${pkgs.ubootQemuX86}/u-boot.rom"
+          "-machine" "type=pc,accel=tcg"
+          "-drive" "file=${mutableImage},if=virtio"
+        ];
       };
     in makeTest {
       name = "boot-uboot-extlinux";
@@ -138,11 +159,14 @@ in {
         if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0:
             raise RuntimeError("Could not create mutable linked image")
 
-        machine = create_machine(${machineConfig})
+        machine = create_machine("${startCommand}")
         machine.start()
         machine.wait_for_unit("multi-user.target")
         machine.succeed("nix store verify -r --no-trust --option experimental-features nix-command /run/current-system")
         machine.shutdown()
       '';
+
+      # kernel can't find rootfs after boot - investigate?
+      meta.broken = true;
     };
 }
diff --git a/nixpkgs/nixos/tests/common/ec2.nix b/nixpkgs/nixos/tests/common/ec2.nix
index 1a64c464039b..82922102f07b 100644
--- a/nixpkgs/nixos/tests/common/ec2.nix
+++ b/nixpkgs/nixos/tests/common/ec2.nix
@@ -61,7 +61,7 @@ with pkgs.lib;
             + " $QEMU_OPTS"
         )
 
-        machine = create_machine({"startCommand": start_command})
+        machine = create_machine(start_command)
         try:
       '' + indentLines script + ''
         finally:
diff --git a/nixpkgs/nixos/tests/consul.nix b/nixpkgs/nixos/tests/consul.nix
index 6233234ff083..c819312068dc 100644
--- a/nixpkgs/nixos/tests/consul.nix
+++ b/nixpkgs/nixos/tests/consul.nix
@@ -42,6 +42,8 @@ let
         ];
         networking.firewall = firewallSettings;
 
+        nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ];
+
         services.consul = {
           enable = true;
           inherit webUi;
@@ -65,6 +67,8 @@ let
         ];
         networking.firewall = firewallSettings;
 
+        nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "consul" ];
+
         services.consul =
           assert builtins.elem thisConsensusServerHost allConsensusServerHosts;
           {
diff --git a/nixpkgs/nixos/tests/docker-tools.nix b/nixpkgs/nixos/tests/docker-tools.nix
index f252eb9ff61e..7d91076600f9 100644
--- a/nixpkgs/nixos/tests/docker-tools.nix
+++ b/nixpkgs/nixos/tests/docker-tools.nix
@@ -58,6 +58,20 @@ let
       '';
       config.Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "/testfile" ];
     };
+
+  nonRootTestImage =
+    pkgs.dockerTools.streamLayeredImage rec {
+      name = "non-root-test";
+      tag = "latest";
+      uid = 1000;
+      gid = 1000;
+      uname = "user";
+      gname = "user";
+      config = {
+        User = "user";
+        Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "${pkgs.coreutils}/bin/stat" ];
+      };
+    };
 in {
   name = "docker-tools";
   meta = with pkgs.lib.maintainers; {
@@ -181,7 +195,7 @@ in {
     ):
         docker.succeed(
             "docker load --input='${examples.bashLayeredWithUser}'",
-            "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 555 == $(stat --format=%a /nix) && test 555 == $(stat --format=%a /nix/store)'",
+            "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 755 == $(stat --format=%a /nix) && test 755 == $(stat --format=%a /nix/store)'",
             "docker rmi ${examples.bashLayeredWithUser.imageName}",
         )
 
@@ -604,5 +618,11 @@ in {
             "${chownTestImage} | docker load",
             "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)"
         )
+
+    with subtest("streamLayeredImage: with non-root user"):
+        docker.succeed(
+            "${nonRootTestImage} | docker load",
+            "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)"
+        )
   '';
 })
diff --git a/nixpkgs/nixos/tests/incus/container.nix b/nixpkgs/nixos/tests/incus/container.nix
index 0f42d16f133d..eb00429e53fe 100644
--- a/nixpkgs/nixos/tests/incus/container.nix
+++ b/nixpkgs/nixos/tests/incus/container.nix
@@ -5,6 +5,8 @@ let
     configuration = {
       # Building documentation makes the test unnecessarily take a longer time:
       documentation.enable = lib.mkForce false;
+
+      boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
     } // extra;
   };
 
@@ -40,6 +42,12 @@ in
         with machine.nested("Waiting for instance to start and be usable"):
           retry(instance_is_up)
 
+    def check_sysctl(instance):
+        with subtest("systemd sysctl settings are applied"):
+            machine.succeed(f"incus exec {instance} -- systemctl status systemd-sysctl")
+            sysctl = machine.succeed(f"incus exec {instance} -- sysctl net.ipv4.ip_forward").strip().split(" ")[-1]
+            assert "1" == sysctl, f"systemd-sysctl configuration not correctly applied, {sysctl} != 1"
+
     machine.wait_for_unit("incus.service")
 
     # no preseed should mean no service
@@ -83,6 +91,7 @@ in
     with subtest("lxc-container generator configures plain container"):
         # reuse the existing container to save some time
         machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+        check_sysctl("container")
 
     with subtest("lxc-container generator configures nested container"):
         machine.execute("incus delete --force container")
@@ -94,6 +103,8 @@ in
         target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip()
         assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service"
 
+        check_sysctl("container")
+
     with subtest("lxc-container generator configures privileged container"):
         machine.execute("incus delete --force container")
         machine.succeed("incus launch nixos container --config security.privileged=true")
@@ -101,5 +112,7 @@ in
           retry(instance_is_up)
 
         machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+
+        check_sysctl("container")
   '';
 })
diff --git a/nixpkgs/nixos/tests/installer.nix b/nixpkgs/nixos/tests/installer.nix
index b6cb6a0c6d45..97bb7f8def59 100644
--- a/nixpkgs/nixos/tests/installer.nix
+++ b/nixpkgs/nixos/tests/installer.nix
@@ -83,46 +83,34 @@ let
                   , postInstallCommands, preBootCommands, postBootCommands, extraConfig
                   , testSpecialisationConfig, testFlakeSwitch, clevisTest, clevisFallbackTest
                   }:
-    let iface = "virtio";
-        isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
-        bios  = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd";
+    let
+      qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
+      isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
+      qemu = qemu-common.qemuBinary pkgs.qemu_test;
     in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
       machine.succeed("true")
     '' else ''
+      import os
       import subprocess
-      tpm_folder = os.environ['NIX_BUILD_TOP']
-      def assemble_qemu_flags():
-          flags = "-cpu max"
-          ${if (system == "x86_64-linux" || system == "i686-linux")
-            then ''flags += " -m 1024"''
-            else ''flags += " -m 768 -enable-kvm -machine virt,gic-version=host"''
-          }
-          ${optionalString clevisTest ''flags += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"''}
-          ${optionalString clevisTest ''flags += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""''}
-          return flags
 
+      tpm_folder = os.environ['NIX_BUILD_TOP']
 
-      qemu_flags = {"qemuFlags": assemble_qemu_flags()}
+      startcommand = "${qemu} -m 2048"
 
-      import os
+      ${optionalString clevisTest ''
+        startcommand += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"
+        startcommand += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""
+      ''}
+      ${optionalString isEfi ''
+        startcommand +=" -drive if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware} -drive if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}"
+      ''}
 
       image_dir = machine.state_dir
       disk_image = os.path.join(image_dir, "machine.qcow2")
-
-      hd_flags = {
-          "hdaInterface": "${iface}",
-          "hda": disk_image,
-      }
-      ${optionalString isEfi ''
-        hd_flags.update(
-            bios="${pkgs.OVMF.fd}/FV/${bios}"
-        )''
-      }
-      default_flags = {**hd_flags, **qemu_flags}
-
+      startcommand += f" -drive file={disk_image},if=virtio,werror=report"
 
       def create_machine_named(name):
-          return create_machine({**default_flags, "name": name})
+          return create_machine(startcommand, name=name)
 
       class Tpm:
             def __init__(self):
@@ -471,7 +459,7 @@ let
           # builds stuff in the VM, needs more juice
           virtualisation.diskSize = 8 * 1024;
           virtualisation.cores = 8;
-          virtualisation.memorySize = 1536;
+          virtualisation.memorySize = 2048;
 
           boot.initrd.systemd.enable = systemdStage1;
 
diff --git a/nixpkgs/nixos/tests/k3s/default.nix b/nixpkgs/nixos/tests/k3s/default.nix
index e168f8233c76..512dc06ee77e 100644
--- a/nixpkgs/nixos/tests/k3s/default.nix
+++ b/nixpkgs/nixos/tests/k3s/default.nix
@@ -6,6 +6,11 @@ let
   allK3s = lib.filterAttrs (n: _: lib.strings.hasPrefix "k3s_" n) pkgs;
 in
 {
+  # Testing K3s with Etcd backend
+  etcd = lib.mapAttrs (_: k3s: import ./etcd.nix {
+    inherit system pkgs k3s;
+    inherit (pkgs) etcd;
+  }) allK3s;
   # Run a single node k3s cluster and verify a pod can run
   single-node = lib.mapAttrs (_: k3s: import ./single-node.nix { inherit system pkgs k3s; }) allK3s;
   # Run a multi-node k3s cluster and verify pod networking works across nodes
diff --git a/nixpkgs/nixos/tests/k3s/etcd.nix b/nixpkgs/nixos/tests/k3s/etcd.nix
new file mode 100644
index 000000000000..d6e9a294adb1
--- /dev/null
+++ b/nixpkgs/nixos/tests/k3s/etcd.nix
@@ -0,0 +1,100 @@
+import ../make-test-python.nix ({ pkgs, lib, k3s, etcd, ... }:
+
+{
+  name = "${k3s.name}-etcd";
+
+  nodes = {
+
+    etcd = { ... }: {
+      services.etcd = {
+        enable = true;
+        openFirewall = true;
+        listenClientUrls = [ "http://192.168.1.1:2379" "http://127.0.0.1:2379" ];
+        listenPeerUrls = [ "http://192.168.1.1:2380" ];
+        initialAdvertisePeerUrls = [ "http://192.168.1.1:2380" ];
+        initialCluster = [ "etcd=http://192.168.1.1:2380" ];
+      };
+      networking = {
+        useDHCP = false;
+        defaultGateway = "192.168.1.1";
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+    };
+
+    k3s = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ jq ];
+      # k3s uses enough resources the default vm fails.
+      virtualisation.memorySize = 1536;
+      virtualisation.diskSize = 4096;
+
+      services.k3s = {
+        enable = true;
+        role = "server";
+        extraFlags = builtins.toString [
+          "--datastore-endpoint=\"http://192.168.1.1:2379\""
+          "--disable" "coredns"
+          "--disable" "local-storage"
+          "--disable" "metrics-server"
+          "--disable" "servicelb"
+          "--disable" "traefik"
+          "--node-ip" "192.168.1.2"
+        ];
+      };
+
+      networking = {
+        firewall = {
+          allowedTCPPorts = [ 2379 2380 6443 ];
+          allowedUDPPorts = [ 8472 ];
+        };
+        useDHCP = false;
+        defaultGateway = "192.168.1.2";
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+    };
+
+  };
+
+  testScript = ''
+    with subtest("should start etcd"):
+        etcd.start()
+        etcd.wait_for_unit("etcd.service")
+
+    with subtest("should wait for etcdctl endpoint status to succeed"):
+        etcd.wait_until_succeeds("etcdctl endpoint status")
+
+    with subtest("should start k3s"):
+        k3s.start()
+        k3s.wait_for_unit("k3s")
+
+    with subtest("should test if kubectl works"):
+        k3s.wait_until_succeeds("k3s kubectl get node")
+
+    with subtest("should wait for service account to show up; takes a sec"):
+        k3s.wait_until_succeeds("k3s kubectl get serviceaccount default")
+
+    with subtest("should create a sample secret object"):
+        k3s.succeed("k3s kubectl create secret generic nixossecret --from-literal thesecret=abacadabra")
+
+    with subtest("should check if secret is correct"):
+        k3s.wait_until_succeeds("[[ $(kubectl get secrets nixossecret -o json | jq -r .data.thesecret | base64 -d) == abacadabra ]]")
+
+    with subtest("should have a secret in database"):
+        etcd.wait_until_succeeds("[[ $(etcdctl get /registry/secrets/default/nixossecret | head -c1 | wc -c) -ne 0 ]]")
+
+    with subtest("should delete the secret"):
+        k3s.succeed("k3s kubectl delete secret nixossecret")
+
+    with subtest("should not have a secret in database"):
+        etcd.wait_until_fails("[[ $(etcdctl get /registry/secrets/default/nixossecret | head -c1 | wc -c) -ne 0 ]]")
+
+    with subtest("should shutdown k3s and etcd"):
+        k3s.shutdown()
+        etcd.shutdown()
+  '';
+
+  meta.maintainers = etcd.meta.maintainers ++ k3s.meta.maintainers;
+})
diff --git a/nixpkgs/nixos/tests/kernel-generic.nix b/nixpkgs/nixos/tests/kernel-generic.nix
index 0dcab39f3fad..9714a94382ee 100644
--- a/nixpkgs/nixos/tests/kernel-generic.nix
+++ b/nixpkgs/nixos/tests/kernel-generic.nix
@@ -30,7 +30,6 @@ let
       linux_5_10_hardened
       linux_5_15_hardened
       linux_6_1_hardened
-      linux_6_5_hardened
       linux_6_6_hardened
       linux_6_7_hardened
       linux_rt_5_4
diff --git a/nixpkgs/nixos/tests/lomiri-system-settings.nix b/nixpkgs/nixos/tests/lomiri-system-settings.nix
new file mode 100644
index 000000000000..867fc14797e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/lomiri-system-settings.nix
@@ -0,0 +1,99 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lomiri-system-settings-standalone";
+  meta.maintainers = lib.teams.lomiri.members;
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+
+    environment = {
+      systemPackages = with pkgs.lomiri; [
+        suru-icon-theme
+        lomiri-system-settings
+      ];
+      variables = {
+        UITK_ICON_THEME = "suru";
+      };
+    };
+
+    i18n.supportedLocales = [ "all" ];
+
+    fonts.packages = with pkgs; [
+      # Intended font & helps with OCR
+      ubuntu_font_family
+    ];
+
+    services.upower.enable = true;
+  };
+
+  enableOCR = true;
+
+  testScript = let
+    settingsPages = [
+      # Base pages
+      { name = "wifi"; type = "internal"; element = "networks"; }
+      { name = "bluetooth"; type = "internal"; element = "discoverable|None detected"; }
+      # only text we can really look for with VPN is on a button, OCR on CI struggles with it
+      { name = "vpn"; type = "internal"; element = "Add|Manual|Configuration"; skipOCR = true; }
+      { name = "appearance"; type = "internal"; element = "Background image|blur effects"; }
+      { name = "desktop"; type = "internal"; element = "workspaces|Icon size"; }
+      { name = "sound"; type = "internal"; element = "Silent Mode|Message sound"; }
+      { name = "language"; type = "internal"; element = "Display language|External keyboard"; }
+      { name = "notification"; type = "internal"; element = "Apps that notify"; }
+      { name = "gestures"; type = "internal"; element = "Edge drag"; }
+      { name = "mouse"; type = "internal"; element = "Cursor speed|Wheel scrolling speed"; }
+      { name = "timedate"; type = "internal"; element = "Time zone|Set the time and date"; }
+
+      # External plugins
+      { name = "security-privacy"; type = "external"; element = "Locking|unlocking|permissions"; elementLocalised = "Sperren|Entsperren|Berechtigungen"; }
+    ];
+  in
+  ''
+    machine.wait_for_x()
+
+    with subtest("lomiri system settings launches"):
+        machine.execute("lomiri-system-settings >&2 &")
+        machine.wait_for_text("System Settings")
+        machine.screenshot("lss_open")
+
+    # Move focus to start of plugins list for following list of tests
+    machine.send_key("tab")
+    machine.send_key("tab")
+    machine.screenshot("lss_focus")
+
+    # tab through & open all sub-menus, to make sure none of them fail
+  '' + (lib.strings.concatMapStringsSep "\n" (page: ''
+    machine.send_key("tab")
+    machine.send_key("kp_enter")
+  ''
+  + lib.optionalString (!(page.skipOCR or false)) ''
+    with subtest("lomiri system settings ${page.name} works"):
+        machine.wait_for_text(r"(${page.element})")
+        machine.screenshot("lss_page_${page.name}")
+  '') settingsPages) + ''
+
+    machine.execute("pkill -f lomiri-system-settings")
+
+    with subtest("lomiri system settings localisation works"):
+        machine.execute("env LANG=de_DE.UTF-8 lomiri-system-settings >&2 &")
+        machine.wait_for_text("Systemeinstellungen")
+        machine.screenshot("lss_localised_open")
+
+    # Move focus to start of plugins list for following list of tests
+    machine.send_key("tab")
+    machine.send_key("tab")
+    machine.screenshot("lss_focus_localised")
+
+  '' + (lib.strings.concatMapStringsSep "\n" (page: ''
+    machine.send_key("tab")
+    machine.send_key("kp_enter")
+  '' + lib.optionalString (page.type == "external") ''
+    with subtest("lomiri system settings ${page.name} localisation works"):
+        machine.wait_for_text(r"(${page.elementLocalised})")
+        machine.screenshot("lss_localised_page_${page.name}")
+  '') settingsPages) + ''
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mealie.nix b/nixpkgs/nixos/tests/mealie.nix
new file mode 100644
index 000000000000..88f749c71294
--- /dev/null
+++ b/nixpkgs/nixos/tests/mealie.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "mealie";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ litchipi ];
+  };
+
+  nodes = {
+    server = {
+      services.mealie = {
+        enable = true;
+        port = 9001;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("mealie.service")
+    server.wait_for_open_port(9001)
+    server.succeed("curl --fail http://localhost:9001")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/monado.nix b/nixpkgs/nixos/tests/monado.nix
new file mode 100644
index 000000000000..8368950951e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/monado.nix
@@ -0,0 +1,39 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "monado";
+
+  nodes.machine =
+    { pkgs, ... }:
+
+    {
+      hardware.opengl.enable = true;
+      users.users.alice = {
+        isNormalUser = true;
+        uid = 1000;
+      };
+
+      services.monado = {
+        enable = true;
+        defaultRuntime = true;
+      };
+      # Stop Monado from probing for any hardware
+      systemd.user.services.monado.environment.SIMULATED_ENABLE = "1";
+
+      environment.systemPackages = with pkgs; [ openxr-loader ];
+    };
+
+  testScript = { nodes, ... }:
+    let
+      userId = toString nodes.machine.users.users.alice.uid;
+      runtimePath = "/run/user/${userId}";
+    in
+    ''
+      machine.succeed("loginctl enable-linger alice")
+      machine.wait_for_unit("user@${userId}.service")
+
+      machine.wait_for_unit("monado.socket", "alice")
+      machine.systemctl("start monado.service", "alice")
+      machine.wait_for_unit("monado.service", "alice")
+
+      machine.succeed("su -- alice -c env XDG_RUNTIME_DIR=${runtimePath} openxr_runtime_list")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/plasma6.nix b/nixpkgs/nixos/tests/plasma6.nix
new file mode 100644
index 000000000000..ec5b3f24ef74
--- /dev/null
+++ b/nixpkgs/nixos/tests/plasma6.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "plasma6";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ k900 ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.sddm.enable = true;
+    # FIXME: this should be testing Wayland
+    services.xserver.displayManager.defaultSession = "plasmax11";
+    services.xserver.desktopManager.plasma6.enable = true;
+    environment.plasma6.excludePackages = [ pkgs.kdePackages.elisa ];
+    services.xserver.displayManager.autoLogin = {
+      enable = true;
+      user = "alice";
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    xdo = "${pkgs.xdotool}/bin/xdotool";
+  in ''
+    with subtest("Wait for login"):
+        start_all()
+        machine.wait_for_file("/tmp/xauth_*")
+        machine.succeed("xauth merge /tmp/xauth_*")
+
+    with subtest("Check plasmashell started"):
+        machine.wait_until_succeeds("pgrep plasmashell")
+        machine.wait_for_window("^Desktop ")
+
+    with subtest("Check that KDED is running"):
+        machine.succeed("pgrep kded6")
+
+    with subtest("Ensure Elisa is not installed"):
+        machine.fail("which elisa")
+
+    machine.succeed("su - ${user.name} -c 'xauth merge /tmp/xauth_*'")
+
+    with subtest("Run Dolphin"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 dolphin >&2 &'")
+        machine.wait_for_window(" Dolphin")
+
+    with subtest("Run Konsole"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 konsole >&2 &'")
+        machine.wait_for_window("Konsole")
+
+    with subtest("Run systemsettings"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 systemsettings >&2 &'")
+        machine.wait_for_window("Settings")
+
+    with subtest("Wait to get a screenshot"):
+        machine.execute(
+            "${xdo} key Alt+F1 sleep 10"
+        )
+        machine.screenshot("screen")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/searx.nix b/nixpkgs/nixos/tests/searx.nix
index 2f808cb65266..02a88f690db7 100644
--- a/nixpkgs/nixos/tests/searx.nix
+++ b/nixpkgs/nixos/tests/searx.nix
@@ -36,7 +36,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
   };
 
   # fancy setup: run in uWSGI and use nginx as proxy
-  nodes.fancy = { ... }: {
+  nodes.fancy = { config, ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
 
     services.searx = {
@@ -65,7 +65,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
           include ${pkgs.nginx}/conf/uwsgi_params;
           uwsgi_pass unix:/run/searx/uwsgi.sock;
         '';
-      locations."/searx/static/".alias = "${pkgs.searx}/share/static/";
+      locations."/searx/static/".alias = "${config.services.searx.package}/share/static/";
     };
 
     # allow nginx access to the searx socket
@@ -108,7 +108,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
               "${pkgs.curl}/bin/curl --fail http://localhost/searx >&2"
           )
           fancy.succeed(
-              "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/themes/oscar/js/bootstrap.min.js >&2"
+              "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/themes/simple/js/leaflet.js >&2"
           )
     '';
 })
diff --git a/nixpkgs/nixos/tests/systemd-boot.nix b/nixpkgs/nixos/tests/systemd-boot.nix
index ce3245f3d862..1b7e83253e59 100644
--- a/nixpkgs/nixos/tests/systemd-boot.nix
+++ b/nixpkgs/nixos/tests/systemd-boot.nix
@@ -14,6 +14,72 @@ let
     boot.loader.efi.canTouchEfiVariables = true;
     environment.systemPackages = [ pkgs.efibootmgr ];
   };
+
+  commonXbootldr = { config, lib, pkgs, ... }:
+    let
+      diskImage = import ../lib/make-disk-image.nix {
+        inherit config lib pkgs;
+        label = "nixos";
+        format = "qcow2";
+        partitionTableType = "efixbootldr";
+        touchEFIVars = true;
+        installBootLoader = true;
+      };
+    in
+    {
+      imports = [ common ];
+      virtualisation.useBootLoader = lib.mkForce false; # Only way to tell qemu-vm not to create the default system image
+      virtualisation.directBoot.enable = false; # But don't direct boot either because we're testing systemd-boot
+
+      system.build.diskImage = diskImage; # Use custom disk image with an XBOOTLDR partition
+      virtualisation.efi.variables = "${diskImage}/efi-vars.fd";
+
+      virtualisation.useDefaultFilesystems = false; # Needs custom setup for `diskImage`
+      virtualisation.bootPartition = null;
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/vda3";
+          fsType = "ext4";
+        };
+        "/boot" = {
+          device = "/dev/vda2";
+          fsType = "vfat";
+          noCheck = true;
+        };
+        "/efi" = {
+          device = "/dev/vda1";
+          fsType = "vfat";
+          noCheck = true;
+        };
+      };
+
+      boot.loader.systemd-boot.enable = true;
+      boot.loader.efi.efiSysMountPoint = "/efi";
+      boot.loader.systemd-boot.xbootldrMountPoint = "/boot";
+    };
+
+  customDiskImage = nodes: ''
+    import os
+    import subprocess
+    import tempfile
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    subprocess.run([
+      "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img",
+      "create",
+      "-f",
+      "qcow2",
+      "-b",
+      "${nodes.machine.system.build.diskImage}/nixos.qcow2",
+      "-F",
+      "qcow2",
+      tmp_disk_image.name,
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+  '';
 in
 {
   basic = makeTest {
@@ -65,6 +131,32 @@ in
     '';
   };
 
+  basicXbootldr = makeTest {
+    name = "systemd-boot-xbootldr";
+    meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ];
+
+    nodes.machine = commonXbootldr;
+
+    testScript = { nodes, ... }: ''
+      ${customDiskImage nodes}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi")
+      machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+
+      # Ensure we actually booted using systemd-boot
+      # Magic number is the vendor UUID used by systemd-boot.
+      machine.succeed(
+          "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+      )
+
+      # "bootctl install" should have created an EFI entry
+      machine.succeed('efibootmgr | grep "Linux Boot Manager"')
+    '';
+  };
+
   # Check that specialisations create corresponding boot entries.
   specialisation = makeTest {
     name = "systemd-boot-specialisation";
@@ -184,6 +276,29 @@ in
     '';
   };
 
+  entryFilenameXbootldr = makeTest {
+    name = "systemd-boot-entry-filename-xbootldr";
+    meta.maintainers = with pkgs.lib.maintainers; [ sdht0 ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ commonXbootldr ];
+      boot.loader.systemd-boot.memtest86.enable = true;
+      boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf";
+    };
+
+    testScript = { nodes, ... }: ''
+      ${customDiskImage nodes}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /efi/EFI/systemd/systemd-bootx64.efi")
+      machine.fail("test -e /boot/loader/entries/memtest86.conf")
+      machine.succeed("test -e /boot/loader/entries/apple.conf")
+      machine.succeed("test -e /boot/EFI/memtest86/memtest.efi")
+    '';
+  };
+
   extraEntries = makeTest {
     name = "systemd-boot-extra-entries";
     meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];