Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
/nixos/modules/system @dasJ

# NixOS integration test driver
/nixos/lib/test-driver @tfc
/nixos/lib/test-driver @tfc @synthetica9

# Updaters
## update.nix
Expand Down
7 changes: 6 additions & 1 deletion nixos/lib/test-driver/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

python3Packages.buildPythonApplication rec {
pname = "nixos-test-driver";
version = "1.1";
version = "1.2";
src = ./.;

propagatedBuildInputs = [ coreutils netpbm python3Packages.colorama python3Packages.ptpython qemu_pkg socat vde2 ]
Expand All @@ -29,4 +29,9 @@ python3Packages.buildPythonApplication rec {
pylint --errors-only --enable=unused-import ${src}/test_driver
black --check --diff ${src}/test_driver
'';

meta = {
maintainers = with lib.maintainers; [ synthetica tfc ];
homepage = "https://nixos.org/manual/nixos/unstable/index.html#sec-nixos-tests";
};
}
25 changes: 25 additions & 0 deletions nixos/lib/test-driver/test_driver/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path
from typing import Any, Dict, Iterator, List, Union, Optional, Callable, ContextManager
import os
import re
import tempfile

from test_driver.logger import rootlog
Expand All @@ -10,6 +11,29 @@
from test_driver.polling_condition import PollingCondition


@contextmanager
def must_raise(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like this isn't used anywhere, or ist it just too early in the morning for me?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it was too late at night last night I guess. I packaged all prerequisites for the I wrote for #155622 into one pr, should've mentioned that. I also wrote a similar local function in #154168, and it seems like it would be more generally useful so I thought it would be best to just provide it within the test driver

regex: str,
exception: type[BaseException] = Exception,
) -> Iterator[None]:
short_desc = f"{exception.__name__} {regex!r}"
msg = f"Expected {short_desc}"

with rootlog.nested(f"Waiting for {short_desc}"):
try:
yield
except exception as e:
e_str = str(e)
if re.search(regex, str(e)):
return
raise Exception(f"{msg}, but string {str(e)!r} did not match") from e
except Exception as e:
raise Exception(
f"{msg}, but {e!r} is not an instance of {exception!r}"
) from e
raise Exception(f"{msg}, but no exception was raised")


class Driver:
"""A handle to the driver that sets up the environment
and runs the tests"""
Expand Down Expand Up @@ -91,6 +115,7 @@ def subtest(name: str) -> Iterator[None]:
serial_stdout_on=self.serial_stdout_on,
polling_condition=self.polling_condition,
Machine=Machine, # for typing
must_raise=must_raise,
)
machine_symbols = {m.name: m for m in self.machines}
# If there's exactly one machine, make it available under the name
Expand Down
7 changes: 4 additions & 3 deletions nixos/lib/test-driver/test_driver/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,21 +805,22 @@ def screen_matches(last: bool) -> bool:
with self.nested("waiting for {} to appear on screen".format(regex)):
retry(screen_matches)

def wait_for_console_text(self, regex: str) -> None:
def wait_for_console_text(self, regex: str) -> re.Match[str]:
with self.nested("waiting for {} to appear on console".format(regex)):
# Buffer the console output, this is needed
# to match multiline regexes.
console = io.StringIO()
while True:
try:
console.write(self.last_lines.get())
console.write(self.last_lines.get(timeout=1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is possibly a breaking change: any reason why you're making it non-blocking?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because otherwise it will wait forever and never run the scheduled callbacks. When it times out it throws Empty, which was already caught, so it basically just runs callbacks and then retries (we could probably also get rid of the sleep here)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'm not sure how you imagine this to fail, but I personally don't see it)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(just realized Machine.sleep also indirectly runs callbacks, so I guess just setting the timeout would be enough here)

except queue.Empty:
self.run_callbacks()
self.sleep(1)
continue
console.seek(0)
matches = re.search(regex, console.read())
if matches is not None:
return
return matches

def send_key(self, key: str) -> None:
key = CHAR_TO_KEY.get(key, key)
Expand Down