nixos/test-driver: apply ruff check suggestions (#358150)

This commit is contained in:
Martin Weinelt
2024-11-22 17:02:27 +01:00
committed by GitHub
7 changed files with 121 additions and 104 deletions
+49 -33
View File
@@ -1,17 +1,18 @@
{ lib
, python3Packages
, enableOCR ? false
, qemu_pkg ? qemu_test
, coreutils
, imagemagick_light
, netpbm
, qemu_test
, socat
, ruff
, tesseract4
, vde2
, extraPythonPackages ? (_ : [])
, nixosTests
{
lib,
python3Packages,
enableOCR ? false,
qemu_pkg ? qemu_test,
coreutils,
imagemagick_light,
netpbm,
qemu_test,
socat,
ruff,
tesseract4,
vde2,
extraPythonPackages ? (_: [ ]),
nixosTests,
}:
let
fs = lib.fileset;
@@ -19,6 +20,8 @@ in
python3Packages.buildPythonApplication {
pname = "nixos-test-driver";
version = "1.1";
pyproject = true;
src = fs.toSource {
root = ./.;
fileset = fs.unions [
@@ -27,37 +30,50 @@ python3Packages.buildPythonApplication {
./extract-docstrings.py
];
};
pyproject = true;
propagatedBuildInputs = [
coreutils
netpbm
python3Packages.colorama
python3Packages.junit-xml
python3Packages.ptpython
qemu_pkg
socat
vde2
]
++ (lib.optionals enableOCR [ imagemagick_light tesseract4 ])
build-system = with python3Packages; [
setuptools
];
dependencies =
with python3Packages;
[
colorama
junit-xml
ptpython
]
++ extraPythonPackages python3Packages;
nativeBuildInputs = [
python3Packages.setuptools
];
propagatedBuildInputs =
[
coreutils
netpbm
qemu_pkg
socat
vde2
]
++ lib.optionals enableOCR [
imagemagick_light
tesseract4
];
passthru.tests = {
inherit (nixosTests.nixos-test-driver) driver-timeout;
};
doCheck = true;
nativeCheckInputs = with python3Packages; [ mypy ruff black ];
nativeCheckInputs = with python3Packages; [
mypy
ruff
];
checkPhase = ''
echo -e "\x1b[32m## run mypy\x1b[0m"
mypy test_driver extract-docstrings.py
echo -e "\x1b[32m## run ruff\x1b[0m"
echo -e "\x1b[32m## run ruff check\x1b[0m"
ruff check .
echo -e "\x1b[32m## run black\x1b[0m"
black --check --diff .
echo -e "\x1b[32m## run ruff format\x1b[0m"
ruff format --check --diff .
'';
}
+1 -5
View File
@@ -17,6 +17,7 @@ find = {}
test_driver = ["py.typed"]
[tool.ruff]
target-version = "py312"
line-length = 88
lint.select = ["E", "F", "I", "U", "N"]
@@ -35,11 +36,6 @@ ignore_missing_imports = true
module = "junit_xml.*"
ignore_missing_imports = true
[tool.black]
line-length = 88
target-version = ['py39']
include = '\.pyi?$'
[tool.mypy]
warn_redundant_casts = true
disallow_untyped_calls = true
+14 -13
View File
@@ -3,9 +3,10 @@ import re
import signal
import tempfile
import threading
from contextlib import contextmanager
from collections.abc import Callable, Iterator
from contextlib import AbstractContextManager, contextmanager
from pathlib import Path
from typing import Any, Callable, ContextManager, Dict, Iterator, List, Optional, Union
from typing import Any
from colorama import Fore, Style
@@ -44,17 +45,17 @@ class Driver:
and runs the tests"""
tests: str
vlans: List[VLan]
machines: List[Machine]
polling_conditions: List[PollingCondition]
vlans: list[VLan]
machines: list[Machine]
polling_conditions: list[PollingCondition]
global_timeout: int
race_timer: threading.Timer
logger: AbstractLogger
def __init__(
self,
start_scripts: List[str],
vlans: List[int],
start_scripts: list[str],
vlans: list[int],
tests: str,
out_dir: Path,
logger: AbstractLogger,
@@ -73,7 +74,7 @@ class Driver:
vlans = list(set(vlans))
self.vlans = [VLan(nr, tmp_dir, self.logger) for nr in vlans]
def cmd(scripts: List[str]) -> Iterator[NixStartScript]:
def cmd(scripts: list[str]) -> Iterator[NixStartScript]:
for s in scripts:
yield NixStartScript(s)
@@ -119,7 +120,7 @@ class Driver:
self.logger.error(f'Test "{name}" failed with error: "{e}"')
raise e
def test_symbols(self) -> Dict[str, Any]:
def test_symbols(self) -> dict[str, Any]:
@contextmanager
def subtest(name: str) -> Iterator[None]:
return self.subtest(name)
@@ -207,7 +208,7 @@ class Driver:
self,
start_command: str | dict,
*,
name: Optional[str] = None,
name: str | None = None,
keep_vm_state: bool = False,
) -> Machine:
# Legacy args handling
@@ -273,11 +274,11 @@ class Driver:
def polling_condition(
self,
fun_: Optional[Callable] = None,
fun_: Callable | None = None,
*,
seconds_interval: float = 2.0,
description: Optional[str] = None,
) -> Union[Callable[[Callable], ContextManager], ContextManager]:
description: str | None = None,
) -> Callable[[Callable], AbstractContextManager] | AbstractContextManager:
driver = self
class Poll:
+22 -21
View File
@@ -5,10 +5,11 @@ import sys
import time
import unicodedata
from abc import ABC, abstractmethod
from collections.abc import Iterator
from contextlib import ExitStack, contextmanager
from pathlib import Path
from queue import Empty, Queue
from typing import Any, Dict, Iterator, List
from typing import Any
from xml.sax.saxutils import XMLGenerator
from xml.sax.xmlreader import AttributesImpl
@@ -18,17 +19,17 @@ from junit_xml import TestCase, TestSuite
class AbstractLogger(ABC):
@abstractmethod
def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
def log(self, message: str, attributes: dict[str, str] = {}) -> None:
pass
@abstractmethod
@contextmanager
def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]:
pass
@abstractmethod
@contextmanager
def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]:
pass
@abstractmethod
@@ -68,11 +69,11 @@ class JunitXMLLogger(AbstractLogger):
self._print_serial_logs = True
atexit.register(self.close)
def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
def log(self, message: str, attributes: dict[str, str] = {}) -> None:
self.tests[self.currentSubtest].stdout += message + os.linesep
@contextmanager
def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]:
old_test = self.currentSubtest
self.tests.setdefault(name, self.TestCaseState())
self.currentSubtest = name
@@ -82,7 +83,7 @@ class JunitXMLLogger(AbstractLogger):
self.currentSubtest = old_test
@contextmanager
def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]:
self.log(message)
yield
@@ -123,25 +124,25 @@ class JunitXMLLogger(AbstractLogger):
class CompositeLogger(AbstractLogger):
def __init__(self, logger_list: List[AbstractLogger]) -> None:
def __init__(self, logger_list: list[AbstractLogger]) -> None:
self.logger_list = logger_list
def add_logger(self, logger: AbstractLogger) -> None:
self.logger_list.append(logger)
def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
def log(self, message: str, attributes: dict[str, str] = {}) -> None:
for logger in self.logger_list:
logger.log(message, attributes)
@contextmanager
def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]:
with ExitStack() as stack:
for logger in self.logger_list:
stack.enter_context(logger.subtest(name, attributes))
yield
@contextmanager
def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]:
with ExitStack() as stack:
for logger in self.logger_list:
stack.enter_context(logger.nested(message, attributes))
@@ -173,7 +174,7 @@ class TerminalLogger(AbstractLogger):
def __init__(self) -> None:
self._print_serial_logs = True
def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str:
def maybe_prefix(self, message: str, attributes: dict[str, str]) -> str:
if "machine" in attributes:
return f"{attributes['machine']}: {message}"
return message
@@ -182,16 +183,16 @@ class TerminalLogger(AbstractLogger):
def _eprint(*args: object, **kwargs: Any) -> None:
print(*args, file=sys.stderr, **kwargs)
def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
def log(self, message: str, attributes: dict[str, str] = {}) -> None:
self._eprint(self.maybe_prefix(message, attributes))
@contextmanager
def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]:
with self.nested("subtest: " + name, attributes):
yield
@contextmanager
def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]:
self._eprint(
self.maybe_prefix(
Style.BRIGHT + Fore.GREEN + message + Style.RESET_ALL, attributes
@@ -241,12 +242,12 @@ class XMLLogger(AbstractLogger):
def sanitise(self, message: str) -> str:
return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C")
def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str:
def maybe_prefix(self, message: str, attributes: dict[str, str]) -> str:
if "machine" in attributes:
return f"{attributes['machine']}: {message}"
return message
def log_line(self, message: str, attributes: Dict[str, str]) -> None:
def log_line(self, message: str, attributes: dict[str, str]) -> None:
self.xml.startElement("line", attrs=AttributesImpl(attributes))
self.xml.characters(message)
self.xml.endElement("line")
@@ -260,7 +261,7 @@ class XMLLogger(AbstractLogger):
def error(self, *args, **kwargs) -> None: # type: ignore
self.log(*args, **kwargs)
def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
def log(self, message: str, attributes: dict[str, str] = {}) -> None:
self.drain_log_queue()
self.log_line(message, attributes)
@@ -273,7 +274,7 @@ class XMLLogger(AbstractLogger):
self.enqueue({"msg": message, "machine": machine, "type": "serial"})
def enqueue(self, item: Dict[str, str]) -> None:
def enqueue(self, item: dict[str, str]) -> None:
self.queue.put(item)
def drain_log_queue(self) -> None:
@@ -287,12 +288,12 @@ class XMLLogger(AbstractLogger):
pass
@contextmanager
def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]:
with self.nested("subtest: " + name, attributes):
yield
@contextmanager
def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]:
self.xml.startElement("nest", attrs=AttributesImpl({}))
self.xml.startElement("head", attrs=AttributesImpl(attributes))
self.xml.characters(message)
+29 -28
View File
@@ -12,10 +12,11 @@ import sys
import tempfile
import threading
import time
from collections.abc import Callable, Iterable
from contextlib import _GeneratorContextManager, nullcontext
from pathlib import Path
from queue import Queue
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
from typing import Any
from test_driver.logger import AbstractLogger
@@ -91,7 +92,7 @@ def make_command(args: list) -> str:
def _perform_ocr_on_screenshot(
screenshot_path: str, model_ids: Iterable[int]
) -> List[str]:
) -> list[str]:
if shutil.which("tesseract") is None:
raise Exception("OCR requested but enableOCR is false")
@@ -248,19 +249,19 @@ class Machine:
start_command: StartCommand
keep_vm_state: bool
process: Optional[subprocess.Popen]
pid: Optional[int]
monitor: Optional[socket.socket]
qmp_client: Optional[QMPSession]
shell: Optional[socket.socket]
serial_thread: Optional[threading.Thread]
process: subprocess.Popen | None
pid: int | None
monitor: socket.socket | None
qmp_client: QMPSession | None
shell: socket.socket | None
serial_thread: threading.Thread | None
booted: bool
connected: bool
# Store last serial console lines for use
# of wait_for_console_text
last_lines: Queue = Queue()
callbacks: List[Callable]
callbacks: list[Callable]
def __repr__(self) -> str:
return f"<Machine '{self.name}'>"
@@ -273,7 +274,7 @@ class Machine:
logger: AbstractLogger,
name: str = "machine",
keep_vm_state: bool = False,
callbacks: Optional[List[Callable]] = None,
callbacks: list[Callable] | None = None,
) -> None:
self.out_dir = out_dir
self.tmp_dir = tmp_dir
@@ -314,7 +315,7 @@ class Machine:
def log_serial(self, msg: str) -> None:
self.logger.log_serial(msg, self.name)
def nested(self, msg: str, attrs: Dict[str, str] = {}) -> _GeneratorContextManager:
def nested(self, msg: str, attrs: dict[str, str] = {}) -> _GeneratorContextManager:
my_attrs = {"machine": self.name}
my_attrs.update(attrs)
return self.logger.nested(msg, my_attrs)
@@ -343,7 +344,7 @@ class Machine:
return self.wait_for_monitor_prompt()
def wait_for_unit(
self, unit: str, user: Optional[str] = None, timeout: int = 900
self, unit: str, user: str | None = None, timeout: int = 900
) -> None:
"""
Wait for a systemd unit to get into "active" state.
@@ -373,7 +374,7 @@ class Machine:
):
retry(check_active, timeout)
def get_unit_info(self, unit: str, user: Optional[str] = None) -> Dict[str, str]:
def get_unit_info(self, unit: str, user: str | None = None) -> dict[str, str]:
status, lines = self.systemctl(f'--no-pager show "{unit}"', user)
if status != 0:
raise Exception(
@@ -384,7 +385,7 @@ class Machine:
line_pattern = re.compile(r"^([^=]+)=(.*)$")
def tuple_from_line(line: str) -> Tuple[str, str]:
def tuple_from_line(line: str) -> tuple[str, str]:
match = line_pattern.match(line)
assert match is not None
return match[1], match[2]
@@ -399,7 +400,7 @@ class Machine:
self,
unit: str,
property: str,
user: Optional[str] = None,
user: str | None = None,
) -> str:
status, lines = self.systemctl(
f'--no-pager show "{unit}" --property="{property}"',
@@ -424,7 +425,7 @@ class Machine:
assert match[1] == property, invalid_output_message
return match[2]
def systemctl(self, q: str, user: Optional[str] = None) -> Tuple[int, str]:
def systemctl(self, q: str, user: str | None = None) -> tuple[int, str]:
"""
Runs `systemctl` commands with optional support for
`systemctl --user`
@@ -480,8 +481,8 @@ class Machine:
command: str,
check_return: bool = True,
check_output: bool = True,
timeout: Optional[int] = 900,
) -> Tuple[int, str]:
timeout: int | None = 900,
) -> tuple[int, str]:
"""
Execute a shell command, returning a list `(status, stdout)`.
@@ -548,7 +549,7 @@ class Machine:
return (rc, output.decode(errors="replace"))
def shell_interact(self, address: Optional[str] = None) -> None:
def shell_interact(self, address: str | None = None) -> None:
"""
Allows you to directly interact with the guest shell. This should
only be used during test development, not in production tests.
@@ -595,7 +596,7 @@ class Machine:
break
self.send_console(char.decode())
def succeed(self, *commands: str, timeout: Optional[int] = None) -> str:
def succeed(self, *commands: str, timeout: int | None = None) -> str:
"""
Execute a shell command, raising an exception if the exit status is
not zero, otherwise returning the standard output. Similar to `execute`,
@@ -612,7 +613,7 @@ class Machine:
output += out
return output
def fail(self, *commands: str, timeout: Optional[int] = None) -> str:
def fail(self, *commands: str, timeout: int | None = None) -> str:
"""
Like `succeed`, but raising an exception if the command returns a zero
status.
@@ -724,7 +725,7 @@ class Machine:
with self.nested(f"waiting for {regexp} to appear on tty {tty}"):
retry(tty_matches, timeout)
def send_chars(self, chars: str, delay: Optional[float] = 0.01) -> None:
def send_chars(self, chars: str, delay: float | None = 0.01) -> None:
"""
Simulate typing a sequence of characters on the virtual keyboard,
e.g., `send_chars("foobar\n")` will type the string `foobar`
@@ -798,10 +799,10 @@ class Machine:
with self.nested(f"waiting for TCP port {port} on {addr} to be closed"):
retry(port_is_closed, timeout)
def start_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]:
def start_job(self, jobname: str, user: str | None = None) -> tuple[int, str]:
return self.systemctl(f"start {jobname}", user)
def stop_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]:
def stop_job(self, jobname: str, user: str | None = None) -> tuple[int, str]:
return self.systemctl(f"stop {jobname}", user)
def wait_for_job(self, jobname: str) -> None:
@@ -942,13 +943,13 @@ class Machine:
"""Debugging: Dump the contents of the TTY<n>"""
self.execute(f"fold -w 80 /dev/vcs{tty} | systemd-cat")
def _get_screen_text_variants(self, model_ids: Iterable[int]) -> List[str]:
def _get_screen_text_variants(self, model_ids: Iterable[int]) -> list[str]:
with tempfile.TemporaryDirectory() as tmpdir:
screenshot_path = os.path.join(tmpdir, "ppm")
self.send_monitor_command(f"screendump {screenshot_path}")
return _perform_ocr_on_screenshot(screenshot_path, model_ids)
def get_screen_text_variants(self) -> List[str]:
def get_screen_text_variants(self) -> list[str]:
"""
Return a list of different interpretations of what is currently
visible on the machine's screen using optical character
@@ -1028,7 +1029,7 @@ class Machine:
pass
def send_key(
self, key: str, delay: Optional[float] = 0.01, log: Optional[bool] = True
self, key: str, delay: float | None = 0.01, log: bool | None = True
) -> None:
"""
Simulate pressing keys on the virtual keyboard, e.g.,
@@ -1168,7 +1169,7 @@ class Machine:
with self.nested("waiting for the X11 server"):
retry(check_x, timeout)
def get_window_names(self) -> List[str]:
def get_window_names(self) -> list[str]:
return self.succeed(
r"xwininfo -root -tree | sed 's/.*0x[0-9a-f]* \"\([^\"]*\)\".*/\1/; t; d'"
).splitlines()
@@ -1,6 +1,6 @@
import time
from collections.abc import Callable
from math import isfinite
from typing import Callable, Optional
from test_driver.logger import AbstractLogger
@@ -12,7 +12,7 @@ class PollingConditionError(Exception):
class PollingCondition:
condition: Callable[[], bool]
seconds_interval: float
description: Optional[str]
description: str | None
logger: AbstractLogger
last_called: float
@@ -20,10 +20,10 @@ class PollingCondition:
def __init__(
self,
condition: Callable[[], Optional[bool]],
condition: Callable[[], bool | None],
logger: AbstractLogger,
seconds_interval: float = 2.0,
description: Optional[str] = None,
description: str | None = None,
):
self.condition = condition # type: ignore
self.seconds_interval = seconds_interval
+2
View File
@@ -11,6 +11,7 @@
nix-update-script,
versionCheckHook,
libiconv,
nixosTests,
}:
python3Packages.buildPythonPackage rec {
@@ -76,6 +77,7 @@ python3Packages.buildPythonPackage rec {
passthru = {
tests = {
inherit ruff-lsp;
nixos-test-driver-busybox = nixosTests.nixos-test-driver.busybox;
};
updateScript = nix-update-script { };
};