From 4e5b779a751ed42eeb168c661a00e69b9c6b1a7f Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Mon, 1 Jun 2026 18:46:47 -0700 Subject: [PATCH] nixos/test-driver: rework timeout for wait_for_console_text Before this commit, `wait_for_console_text` would read one line every 900ms before trying again to read the next line. This commit fixes that behavior by greedily reading all buffer that is available for match trying to find a match rather than reading one per retry iteration. In effect this moves the loop inside the `console_matches` helper and makes the behavior of `wait_for_console_text` with timeout behave the same way as with timeout. This also provides a test that showcases the problem. --- .../src/test_driver/machine/__init__.py | 21 ++++++++------- nixos/tests/all-tests.nix | 1 + .../nixos-test-driver/console-timeout.nix | 26 +++++++++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 nixos/tests/nixos-test-driver/console-timeout.nix 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 db96445af912..0331e297a8a6 100644 --- a/nixos/lib/test-driver/src/test_driver/machine/__init__.py +++ b/nixos/lib/test-driver/src/test_driver/machine/__init__.py @@ -1164,24 +1164,25 @@ class QemuMachine(BaseMachine): # to match multiline regexes. console = io.StringIO() - def console_matches(_last_try: bool) -> bool: + def console_matches(_last_try: bool, block: bool = False) -> bool: nonlocal console try: - # This will return as soon as possible and - # sleep 1 second. - console.write(self.last_lines.get(block=False)) + while True: + # This will return as soon as possible and + # sleep 1 second. + console.write(self.last_lines.get(block=block)) + console.seek(0) + matches = re.search(regex, console.read()) + if matches is not None: + return True except queue.Empty: - pass - console.seek(0) - matches = re.search(regex, console.read()) - return matches is not None + return False with self.nested(f"waiting for {regex} to appear on console"): if timeout is not None: retry(console_matches, timeout) else: - while not console_matches(False): - pass + console_matches(False, block=True) def get_console_log(self) -> str: """ diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index a4c63fcbc368..32f63661803f 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -153,6 +153,7 @@ in console-log = runTest ./nixos-test-driver/console-log.nix; containers = runTest ./nixos-test-driver/containers.nix; skip-typecheck = runTest ./nixos-test-driver/skip-typecheck.nix; + console-timeout = runTest ./nixos-test-driver/console-timeout.nix; options-doc-regression = import ./nixos-test-driver/options-doc-regression.nix { inherit pkgs; }; driver-timeout = pkgs.runCommand "ensure-timeout-induced-failure" diff --git a/nixos/tests/nixos-test-driver/console-timeout.nix b/nixos/tests/nixos-test-driver/console-timeout.nix new file mode 100644 index 000000000000..504f4504bf9d --- /dev/null +++ b/nixos/tests/nixos-test-driver/console-timeout.nix @@ -0,0 +1,26 @@ +{ pkgs, lib, ... }: +{ + name = "console-timeout"; + + nodes.machine = { + systemd.services.generate-output.script = '' + echo "match that" + sleep 1 + + for i in $(seq 15); do + echo "line $i" + done + + echo "match this" + ''; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("multi-user.target") + + machine.systemctl("start generate-output") + machine.wait_for_console_text("match that") + machine.wait_for_console_text("match this", timeout=10) + ''; +}