From 5931105883d87a9f4512ec633e491414072b83f1 Mon Sep 17 00:00:00 2001 From: Will Fancher Date: Sun, 20 Sep 2026 13:11:15 -0400 Subject: [PATCH 1/2] nixos/test-driver: Add timeout to wait_for_shutdown --- .../src/test_driver/machine/__init__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/nixos/lib/test-driver/src/test_driver/machine/__init__.py b/nixos/lib/test-driver/src/test_driver/machine/__init__.py index ffc7c7517165..596627a79491 100644 --- a/nixos/lib/test-driver/src/test_driver/machine/__init__.py +++ b/nixos/lib/test-driver/src/test_driver/machine/__init__.py @@ -335,7 +335,7 @@ class BaseMachine(ABC): ... @abstractmethod - def wait_for_shutdown(self) -> None: + def wait_for_shutdown(self, timeout: dt.timedelta | None = None) -> None: """Wait for the machine to power off. This does *not* initiate a shutdown; that's usually done via `shutdown()`. """ @@ -1061,7 +1061,7 @@ class QemuMachine(BaseMachine): break self.send_console(char.decode()) - def wait_for_shutdown(self) -> None: + def wait_for_shutdown(self, timeout: dt.timedelta | None = None) -> None: """ Wait for the VM to power off. This does *not* initiate a shutdown; that's usually done via `shutdown()`. @@ -1072,7 +1072,9 @@ class QemuMachine(BaseMachine): with self.nested("waiting for the VM to power off"): sys.stdout.flush() assert self.process - self.process.wait() + self.process.wait( + timeout=timeout.total_seconds() if timeout is not None else None + ) self.pid = None self.booted = False @@ -1903,7 +1905,7 @@ class NspawnMachine(BaseMachine): self.systemctl("poweroff") self.wait_for_shutdown() - def wait_for_shutdown(self) -> None: + def wait_for_shutdown(self, timeout: dt.timedelta | None = None) -> None: """ Wait for the container to power off. This does *not* initiate a shutdown; that's usually done via `shutdown()`. @@ -1912,7 +1914,9 @@ class NspawnMachine(BaseMachine): return with self.nested("waiting for the container to power off"): - self.process.wait() + self.process.wait( + timeout=timeout.total_seconds() if timeout is not None else None + ) self.process = None From c3c2644b50a29473ca04c05c1234050bce56fb45 Mon Sep 17 00:00:00 2001 From: Will Fancher Date: Sun, 20 Sep 2026 01:20:52 -0400 Subject: [PATCH 2/2] nixos/tests/systemd-shutdown: Test pre-exitrd shutdown scripts --- nixos/tests/systemd-shutdown.nix | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/nixos/tests/systemd-shutdown.nix b/nixos/tests/systemd-shutdown.nix index 7439c9331182..76870b2e8562 100644 --- a/nixos/tests/systemd-shutdown.nix +++ b/nixos/tests/systemd-shutdown.nix @@ -13,24 +13,32 @@ in nodes.machine = { imports = [ ../modules/profiles/minimal.nix ]; + systemd.shutdown.pre-exitrd = pkgs.writeShellScript "pre-exitrd" '' + echo pre-exitrd > /run/initramfs/test.txt + ''; systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/shutdown-message".source = pkgs.writeShellScript "shutdown-message" '' - echo "${msg}" > /dev/kmsg + if test -e /test.txt; then + # Test should only pass if both scripts run. + echo "${msg}" > /dev/kmsg + fi ''; boot.initrd.systemd.enable = systemdStage1; }; testScript = '' + import datetime as dt + # Check that 'generate-shutdown-ramfs.service' is started # automatically and that 'systemd-shutdown' runs our script. machine.wait_for_unit("multi-user.target") # .shutdown() would wait for the machine to power off machine.execute("systemctl poweroff", check_return=False) # Message printed by systemd-shutdown - machine.wait_for_console_text("Unmounting '/oldroot'") - machine.wait_for_console_text("${msg}") + machine.wait_for_console_text("Unmounting '/oldroot'", timeout=dt.timedelta(seconds=60)) + machine.wait_for_console_text("${msg}", timeout=dt.timedelta(seconds=5)) # Don't try to sync filesystems - machine.wait_for_shutdown() + machine.wait_for_shutdown(timeout=dt.timedelta(seconds=5)) # In a separate boot, start 'generate-shutdown-ramfs.service' # manually in order to check the permissions on '/run/initramfs'.