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

authored by

Martin Weinelt and committed by
GitHub
b9dec6d5 9069a281

+121 -104
+49 -33
nixos/lib/test-driver/default.nix
··· 1 - { lib 2 - , python3Packages 3 - , enableOCR ? false 4 - , qemu_pkg ? qemu_test 5 - , coreutils 6 - , imagemagick_light 7 - , netpbm 8 - , qemu_test 9 - , socat 10 - , ruff 11 - , tesseract4 12 - , vde2 13 - , extraPythonPackages ? (_ : []) 14 - , nixosTests 1 + { 2 + lib, 3 + python3Packages, 4 + enableOCR ? false, 5 + qemu_pkg ? qemu_test, 6 + coreutils, 7 + imagemagick_light, 8 + netpbm, 9 + qemu_test, 10 + socat, 11 + ruff, 12 + tesseract4, 13 + vde2, 14 + extraPythonPackages ? (_: [ ]), 15 + nixosTests, 15 16 }: 16 17 let 17 18 fs = lib.fileset; ··· 19 20 python3Packages.buildPythonApplication { 20 21 pname = "nixos-test-driver"; 21 22 version = "1.1"; 23 + pyproject = true; 24 + 22 25 src = fs.toSource { 23 26 root = ./.; 24 27 fileset = fs.unions [ ··· 27 30 ./extract-docstrings.py 28 31 ]; 29 32 }; 30 - pyproject = true; 31 33 32 - propagatedBuildInputs = [ 33 - coreutils 34 - netpbm 35 - python3Packages.colorama 36 - python3Packages.junit-xml 37 - python3Packages.ptpython 38 - qemu_pkg 39 - socat 40 - vde2 41 - ] 42 - ++ (lib.optionals enableOCR [ imagemagick_light tesseract4 ]) 34 + build-system = with python3Packages; [ 35 + setuptools 36 + ]; 37 + 38 + dependencies = 39 + with python3Packages; 40 + [ 41 + colorama 42 + junit-xml 43 + ptpython 44 + ] 43 45 ++ extraPythonPackages python3Packages; 44 46 45 - nativeBuildInputs = [ 46 - python3Packages.setuptools 47 - ]; 47 + propagatedBuildInputs = 48 + [ 49 + coreutils 50 + netpbm 51 + qemu_pkg 52 + socat 53 + vde2 54 + ] 55 + ++ lib.optionals enableOCR [ 56 + imagemagick_light 57 + tesseract4 58 + ]; 48 59 49 60 passthru.tests = { 50 61 inherit (nixosTests.nixos-test-driver) driver-timeout; 51 62 }; 52 63 53 64 doCheck = true; 54 - nativeCheckInputs = with python3Packages; [ mypy ruff black ]; 65 + 66 + nativeCheckInputs = with python3Packages; [ 67 + mypy 68 + ruff 69 + ]; 70 + 55 71 checkPhase = '' 56 72 echo -e "\x1b[32m## run mypy\x1b[0m" 57 73 mypy test_driver extract-docstrings.py 58 - echo -e "\x1b[32m## run ruff\x1b[0m" 74 + echo -e "\x1b[32m## run ruff check\x1b[0m" 59 75 ruff check . 60 - echo -e "\x1b[32m## run black\x1b[0m" 61 - black --check --diff . 76 + echo -e "\x1b[32m## run ruff format\x1b[0m" 77 + ruff format --check --diff . 62 78 ''; 63 79 }
+1 -5
nixos/lib/test-driver/pyproject.toml
··· 17 17 test_driver = ["py.typed"] 18 18 19 19 [tool.ruff] 20 + target-version = "py312" 20 21 line-length = 88 21 22 22 23 lint.select = ["E", "F", "I", "U", "N"] ··· 34 35 [[tool.mypy.overrides]] 35 36 module = "junit_xml.*" 36 37 ignore_missing_imports = true 37 - 38 - [tool.black] 39 - line-length = 88 40 - target-version = ['py39'] 41 - include = '\.pyi?$' 42 38 43 39 [tool.mypy] 44 40 warn_redundant_casts = true
+14 -13
nixos/lib/test-driver/test_driver/driver.py
··· 3 3 import signal 4 4 import tempfile 5 5 import threading 6 - from contextlib import contextmanager 6 + from collections.abc import Callable, Iterator 7 + from contextlib import AbstractContextManager, contextmanager 7 8 from pathlib import Path 8 - from typing import Any, Callable, ContextManager, Dict, Iterator, List, Optional, Union 9 + from typing import Any 9 10 10 11 from colorama import Fore, Style 11 12 ··· 44 45 and runs the tests""" 45 46 46 47 tests: str 47 - vlans: List[VLan] 48 - machines: List[Machine] 49 - polling_conditions: List[PollingCondition] 48 + vlans: list[VLan] 49 + machines: list[Machine] 50 + polling_conditions: list[PollingCondition] 50 51 global_timeout: int 51 52 race_timer: threading.Timer 52 53 logger: AbstractLogger 53 54 54 55 def __init__( 55 56 self, 56 - start_scripts: List[str], 57 - vlans: List[int], 57 + start_scripts: list[str], 58 + vlans: list[int], 58 59 tests: str, 59 60 out_dir: Path, 60 61 logger: AbstractLogger, ··· 73 74 vlans = list(set(vlans)) 74 75 self.vlans = [VLan(nr, tmp_dir, self.logger) for nr in vlans] 75 76 76 - def cmd(scripts: List[str]) -> Iterator[NixStartScript]: 77 + def cmd(scripts: list[str]) -> Iterator[NixStartScript]: 77 78 for s in scripts: 78 79 yield NixStartScript(s) 79 80 ··· 119 120 self.logger.error(f'Test "{name}" failed with error: "{e}"') 120 121 raise e 121 122 122 - def test_symbols(self) -> Dict[str, Any]: 123 + def test_symbols(self) -> dict[str, Any]: 123 124 @contextmanager 124 125 def subtest(name: str) -> Iterator[None]: 125 126 return self.subtest(name) ··· 207 208 self, 208 209 start_command: str | dict, 209 210 *, 210 - name: Optional[str] = None, 211 + name: str | None = None, 211 212 keep_vm_state: bool = False, 212 213 ) -> Machine: 213 214 # Legacy args handling ··· 273 274 274 275 def polling_condition( 275 276 self, 276 - fun_: Optional[Callable] = None, 277 + fun_: Callable | None = None, 277 278 *, 278 279 seconds_interval: float = 2.0, 279 - description: Optional[str] = None, 280 - ) -> Union[Callable[[Callable], ContextManager], ContextManager]: 280 + description: str | None = None, 281 + ) -> Callable[[Callable], AbstractContextManager] | AbstractContextManager: 281 282 driver = self 282 283 283 284 class Poll:
+22 -21
nixos/lib/test-driver/test_driver/logger.py
··· 5 5 import time 6 6 import unicodedata 7 7 from abc import ABC, abstractmethod 8 + from collections.abc import Iterator 8 9 from contextlib import ExitStack, contextmanager 9 10 from pathlib import Path 10 11 from queue import Empty, Queue 11 - from typing import Any, Dict, Iterator, List 12 + from typing import Any 12 13 from xml.sax.saxutils import XMLGenerator 13 14 from xml.sax.xmlreader import AttributesImpl 14 15 ··· 18 19 19 20 class AbstractLogger(ABC): 20 21 @abstractmethod 21 - def log(self, message: str, attributes: Dict[str, str] = {}) -> None: 22 + def log(self, message: str, attributes: dict[str, str] = {}) -> None: 22 23 pass 23 24 24 25 @abstractmethod 25 26 @contextmanager 26 - def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 27 + def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]: 27 28 pass 28 29 29 30 @abstractmethod 30 31 @contextmanager 31 - def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 32 + def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]: 32 33 pass 33 34 34 35 @abstractmethod ··· 68 69 self._print_serial_logs = True 69 70 atexit.register(self.close) 70 71 71 - def log(self, message: str, attributes: Dict[str, str] = {}) -> None: 72 + def log(self, message: str, attributes: dict[str, str] = {}) -> None: 72 73 self.tests[self.currentSubtest].stdout += message + os.linesep 73 74 74 75 @contextmanager 75 - def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 76 + def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]: 76 77 old_test = self.currentSubtest 77 78 self.tests.setdefault(name, self.TestCaseState()) 78 79 self.currentSubtest = name ··· 82 83 self.currentSubtest = old_test 83 84 84 85 @contextmanager 85 - def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 86 + def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]: 86 87 self.log(message) 87 88 yield 88 89 ··· 123 124 124 125 125 126 class CompositeLogger(AbstractLogger): 126 - def __init__(self, logger_list: List[AbstractLogger]) -> None: 127 + def __init__(self, logger_list: list[AbstractLogger]) -> None: 127 128 self.logger_list = logger_list 128 129 129 130 def add_logger(self, logger: AbstractLogger) -> None: 130 131 self.logger_list.append(logger) 131 132 132 - def log(self, message: str, attributes: Dict[str, str] = {}) -> None: 133 + def log(self, message: str, attributes: dict[str, str] = {}) -> None: 133 134 for logger in self.logger_list: 134 135 logger.log(message, attributes) 135 136 136 137 @contextmanager 137 - def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 138 + def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]: 138 139 with ExitStack() as stack: 139 140 for logger in self.logger_list: 140 141 stack.enter_context(logger.subtest(name, attributes)) 141 142 yield 142 143 143 144 @contextmanager 144 - def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 145 + def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]: 145 146 with ExitStack() as stack: 146 147 for logger in self.logger_list: 147 148 stack.enter_context(logger.nested(message, attributes)) ··· 173 174 def __init__(self) -> None: 174 175 self._print_serial_logs = True 175 176 176 - def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str: 177 + def maybe_prefix(self, message: str, attributes: dict[str, str]) -> str: 177 178 if "machine" in attributes: 178 179 return f"{attributes['machine']}: {message}" 179 180 return message ··· 182 183 def _eprint(*args: object, **kwargs: Any) -> None: 183 184 print(*args, file=sys.stderr, **kwargs) 184 185 185 - def log(self, message: str, attributes: Dict[str, str] = {}) -> None: 186 + def log(self, message: str, attributes: dict[str, str] = {}) -> None: 186 187 self._eprint(self.maybe_prefix(message, attributes)) 187 188 188 189 @contextmanager 189 - def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 190 + def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]: 190 191 with self.nested("subtest: " + name, attributes): 191 192 yield 192 193 193 194 @contextmanager 194 - def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 195 + def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]: 195 196 self._eprint( 196 197 self.maybe_prefix( 197 198 Style.BRIGHT + Fore.GREEN + message + Style.RESET_ALL, attributes ··· 241 242 def sanitise(self, message: str) -> str: 242 243 return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C") 243 244 244 - def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str: 245 + def maybe_prefix(self, message: str, attributes: dict[str, str]) -> str: 245 246 if "machine" in attributes: 246 247 return f"{attributes['machine']}: {message}" 247 248 return message 248 249 249 - def log_line(self, message: str, attributes: Dict[str, str]) -> None: 250 + def log_line(self, message: str, attributes: dict[str, str]) -> None: 250 251 self.xml.startElement("line", attrs=AttributesImpl(attributes)) 251 252 self.xml.characters(message) 252 253 self.xml.endElement("line") ··· 260 261 def error(self, *args, **kwargs) -> None: # type: ignore 261 262 self.log(*args, **kwargs) 262 263 263 - def log(self, message: str, attributes: Dict[str, str] = {}) -> None: 264 + def log(self, message: str, attributes: dict[str, str] = {}) -> None: 264 265 self.drain_log_queue() 265 266 self.log_line(message, attributes) 266 267 ··· 273 274 274 275 self.enqueue({"msg": message, "machine": machine, "type": "serial"}) 275 276 276 - def enqueue(self, item: Dict[str, str]) -> None: 277 + def enqueue(self, item: dict[str, str]) -> None: 277 278 self.queue.put(item) 278 279 279 280 def drain_log_queue(self) -> None: ··· 287 288 pass 288 289 289 290 @contextmanager 290 - def subtest(self, name: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 291 + def subtest(self, name: str, attributes: dict[str, str] = {}) -> Iterator[None]: 291 292 with self.nested("subtest: " + name, attributes): 292 293 yield 293 294 294 295 @contextmanager 295 - def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]: 296 + def nested(self, message: str, attributes: dict[str, str] = {}) -> Iterator[None]: 296 297 self.xml.startElement("nest", attrs=AttributesImpl({})) 297 298 self.xml.startElement("head", attrs=AttributesImpl(attributes)) 298 299 self.xml.characters(message)
+29 -28
nixos/lib/test-driver/test_driver/machine.py
··· 12 12 import tempfile 13 13 import threading 14 14 import time 15 + from collections.abc import Callable, Iterable 15 16 from contextlib import _GeneratorContextManager, nullcontext 16 17 from pathlib import Path 17 18 from queue import Queue 18 - from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple 19 + from typing import Any 19 20 20 21 from test_driver.logger import AbstractLogger 21 22 ··· 91 92 92 93 def _perform_ocr_on_screenshot( 93 94 screenshot_path: str, model_ids: Iterable[int] 94 - ) -> List[str]: 95 + ) -> list[str]: 95 96 if shutil.which("tesseract") is None: 96 97 raise Exception("OCR requested but enableOCR is false") 97 98 ··· 248 249 start_command: StartCommand 249 250 keep_vm_state: bool 250 251 251 - process: Optional[subprocess.Popen] 252 - pid: Optional[int] 253 - monitor: Optional[socket.socket] 254 - qmp_client: Optional[QMPSession] 255 - shell: Optional[socket.socket] 256 - serial_thread: Optional[threading.Thread] 252 + process: subprocess.Popen | None 253 + pid: int | None 254 + monitor: socket.socket | None 255 + qmp_client: QMPSession | None 256 + shell: socket.socket | None 257 + serial_thread: threading.Thread | None 257 258 258 259 booted: bool 259 260 connected: bool 260 261 # Store last serial console lines for use 261 262 # of wait_for_console_text 262 263 last_lines: Queue = Queue() 263 - callbacks: List[Callable] 264 + callbacks: list[Callable] 264 265 265 266 def __repr__(self) -> str: 266 267 return f"<Machine '{self.name}'>" ··· 273 274 logger: AbstractLogger, 274 275 name: str = "machine", 275 276 keep_vm_state: bool = False, 276 - callbacks: Optional[List[Callable]] = None, 277 + callbacks: list[Callable] | None = None, 277 278 ) -> None: 278 279 self.out_dir = out_dir 279 280 self.tmp_dir = tmp_dir ··· 314 315 def log_serial(self, msg: str) -> None: 315 316 self.logger.log_serial(msg, self.name) 316 317 317 - def nested(self, msg: str, attrs: Dict[str, str] = {}) -> _GeneratorContextManager: 318 + def nested(self, msg: str, attrs: dict[str, str] = {}) -> _GeneratorContextManager: 318 319 my_attrs = {"machine": self.name} 319 320 my_attrs.update(attrs) 320 321 return self.logger.nested(msg, my_attrs) ··· 343 344 return self.wait_for_monitor_prompt() 344 345 345 346 def wait_for_unit( 346 - self, unit: str, user: Optional[str] = None, timeout: int = 900 347 + self, unit: str, user: str | None = None, timeout: int = 900 347 348 ) -> None: 348 349 """ 349 350 Wait for a systemd unit to get into "active" state. ··· 373 374 ): 374 375 retry(check_active, timeout) 375 376 376 - def get_unit_info(self, unit: str, user: Optional[str] = None) -> Dict[str, str]: 377 + def get_unit_info(self, unit: str, user: str | None = None) -> dict[str, str]: 377 378 status, lines = self.systemctl(f'--no-pager show "{unit}"', user) 378 379 if status != 0: 379 380 raise Exception( ··· 384 385 385 386 line_pattern = re.compile(r"^([^=]+)=(.*)$") 386 387 387 - def tuple_from_line(line: str) -> Tuple[str, str]: 388 + def tuple_from_line(line: str) -> tuple[str, str]: 388 389 match = line_pattern.match(line) 389 390 assert match is not None 390 391 return match[1], match[2] ··· 399 400 self, 400 401 unit: str, 401 402 property: str, 402 - user: Optional[str] = None, 403 + user: str | None = None, 403 404 ) -> str: 404 405 status, lines = self.systemctl( 405 406 f'--no-pager show "{unit}" --property="{property}"', ··· 424 425 assert match[1] == property, invalid_output_message 425 426 return match[2] 426 427 427 - def systemctl(self, q: str, user: Optional[str] = None) -> Tuple[int, str]: 428 + def systemctl(self, q: str, user: str | None = None) -> tuple[int, str]: 428 429 """ 429 430 Runs `systemctl` commands with optional support for 430 431 `systemctl --user` ··· 480 481 command: str, 481 482 check_return: bool = True, 482 483 check_output: bool = True, 483 - timeout: Optional[int] = 900, 484 - ) -> Tuple[int, str]: 484 + timeout: int | None = 900, 485 + ) -> tuple[int, str]: 485 486 """ 486 487 Execute a shell command, returning a list `(status, stdout)`. 487 488 ··· 548 549 549 550 return (rc, output.decode(errors="replace")) 550 551 551 - def shell_interact(self, address: Optional[str] = None) -> None: 552 + def shell_interact(self, address: str | None = None) -> None: 552 553 """ 553 554 Allows you to directly interact with the guest shell. This should 554 555 only be used during test development, not in production tests. ··· 595 596 break 596 597 self.send_console(char.decode()) 597 598 598 - def succeed(self, *commands: str, timeout: Optional[int] = None) -> str: 599 + def succeed(self, *commands: str, timeout: int | None = None) -> str: 599 600 """ 600 601 Execute a shell command, raising an exception if the exit status is 601 602 not zero, otherwise returning the standard output. Similar to `execute`, ··· 612 613 output += out 613 614 return output 614 615 615 - def fail(self, *commands: str, timeout: Optional[int] = None) -> str: 616 + def fail(self, *commands: str, timeout: int | None = None) -> str: 616 617 """ 617 618 Like `succeed`, but raising an exception if the command returns a zero 618 619 status. ··· 724 725 with self.nested(f"waiting for {regexp} to appear on tty {tty}"): 725 726 retry(tty_matches, timeout) 726 727 727 - def send_chars(self, chars: str, delay: Optional[float] = 0.01) -> None: 728 + def send_chars(self, chars: str, delay: float | None = 0.01) -> None: 728 729 """ 729 730 Simulate typing a sequence of characters on the virtual keyboard, 730 731 e.g., `send_chars("foobar\n")` will type the string `foobar` ··· 798 799 with self.nested(f"waiting for TCP port {port} on {addr} to be closed"): 799 800 retry(port_is_closed, timeout) 800 801 801 - def start_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]: 802 + def start_job(self, jobname: str, user: str | None = None) -> tuple[int, str]: 802 803 return self.systemctl(f"start {jobname}", user) 803 804 804 - def stop_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]: 805 + def stop_job(self, jobname: str, user: str | None = None) -> tuple[int, str]: 805 806 return self.systemctl(f"stop {jobname}", user) 806 807 807 808 def wait_for_job(self, jobname: str) -> None: ··· 942 943 """Debugging: Dump the contents of the TTY<n>""" 943 944 self.execute(f"fold -w 80 /dev/vcs{tty} | systemd-cat") 944 945 945 - def _get_screen_text_variants(self, model_ids: Iterable[int]) -> List[str]: 946 + def _get_screen_text_variants(self, model_ids: Iterable[int]) -> list[str]: 946 947 with tempfile.TemporaryDirectory() as tmpdir: 947 948 screenshot_path = os.path.join(tmpdir, "ppm") 948 949 self.send_monitor_command(f"screendump {screenshot_path}") 949 950 return _perform_ocr_on_screenshot(screenshot_path, model_ids) 950 951 951 - def get_screen_text_variants(self) -> List[str]: 952 + def get_screen_text_variants(self) -> list[str]: 952 953 """ 953 954 Return a list of different interpretations of what is currently 954 955 visible on the machine's screen using optical character ··· 1028 1029 pass 1029 1030 1030 1031 def send_key( 1031 - self, key: str, delay: Optional[float] = 0.01, log: Optional[bool] = True 1032 + self, key: str, delay: float | None = 0.01, log: bool | None = True 1032 1033 ) -> None: 1033 1034 """ 1034 1035 Simulate pressing keys on the virtual keyboard, e.g., ··· 1168 1169 with self.nested("waiting for the X11 server"): 1169 1170 retry(check_x, timeout) 1170 1171 1171 - def get_window_names(self) -> List[str]: 1172 + def get_window_names(self) -> list[str]: 1172 1173 return self.succeed( 1173 1174 r"xwininfo -root -tree | sed 's/.*0x[0-9a-f]* \"\([^\"]*\)\".*/\1/; t; d'" 1174 1175 ).splitlines()
+4 -4
nixos/lib/test-driver/test_driver/polling_condition.py
··· 1 1 import time 2 + from collections.abc import Callable 2 3 from math import isfinite 3 - from typing import Callable, Optional 4 4 5 5 from test_driver.logger import AbstractLogger 6 6 ··· 12 12 class PollingCondition: 13 13 condition: Callable[[], bool] 14 14 seconds_interval: float 15 - description: Optional[str] 15 + description: str | None 16 16 logger: AbstractLogger 17 17 18 18 last_called: float ··· 20 20 21 21 def __init__( 22 22 self, 23 - condition: Callable[[], Optional[bool]], 23 + condition: Callable[[], bool | None], 24 24 logger: AbstractLogger, 25 25 seconds_interval: float = 2.0, 26 - description: Optional[str] = None, 26 + description: str | None = None, 27 27 ): 28 28 self.condition = condition # type: ignore 29 29 self.seconds_interval = seconds_interval
+2
pkgs/by-name/ru/ruff/package.nix
··· 11 11 nix-update-script, 12 12 versionCheckHook, 13 13 libiconv, 14 + nixosTests, 14 15 }: 15 16 16 17 python3Packages.buildPythonPackage rec { ··· 76 77 passthru = { 77 78 tests = { 78 79 inherit ruff-lsp; 80 + nixos-test-driver-busybox = nixosTests.nixos-test-driver.busybox; 79 81 }; 80 82 updateScript = nix-update-script { }; 81 83 };