diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index fef7b02f47cdb7..68c0b1ecc508bd 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -7,6 +7,7 @@ on: - main pull_request: paths: + - "Lib/test/libregrtest/**" - "Tools/clinic/**" - "Tools/cases_generator/**" - "Tools/peg_generator/**" @@ -30,11 +31,11 @@ jobs: mypy: strategy: matrix: - target: [ - "Tools/cases_generator", - "Tools/clinic", - "Tools/peg_generator", - ] + target: + - "Lib/test/libregrtest" + - "Tools/cases_generator" + - "Tools/clinic" + - "Tools/peg_generator" name: Run mypy on ${{ matrix.target }} runs-on: ubuntu-latest timeout-minutes: 10 @@ -46,4 +47,13 @@ jobs: cache: pip cache-dependency-path: Tools/requirements-dev.txt - run: pip install -r Tools/requirements-dev.txt - - run: mypy --config-file ${{ matrix.target }}/mypy.ini + - if: ${{ matrix.target != 'Lib/test/libregrtest' }} + run: mypy --config-file ${{ matrix.target }}/mypy.ini + - name: Run mypy on libregrtest + if: ${{ matrix.target == 'Lib/test/libregrtest' }} + # Mypy can't be run on libregrtest from the repo root, + # or it (amusingly) thinks that the entire Lib directory is "shadowing the stdlib", + # and refuses to do any type-checking at all + run: | + cd Lib/test + mypy --config-file libregrtest/mypy.ini diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py index ab8efb427a14a5..a03a33c7b6451c 100644 --- a/Lib/test/libregrtest/cmdline.py +++ b/Lib/test/libregrtest/cmdline.py @@ -3,6 +3,7 @@ import shlex import sys from test.support import os_helper +from typing import Literal USAGE = """\ @@ -157,11 +158,11 @@ def __init__(self, **kwargs) -> None: self.randomize = False self.fromfile = None self.fail_env_changed = False - self.use_resources = None + self.use_resources: list[str] | None = None self.trace = False self.coverdir = 'coverage' self.runleaks = False - self.huntrleaks = False + self.huntrleaks: tuple[int, int, str] | Literal[False] = False self.rerun = False self.verbose3 = False self.print_slow = False diff --git a/Lib/test/libregrtest/findtests.py b/Lib/test/libregrtest/findtests.py index f4a8b9ae26ae65..a5ec33000ce9d8 100644 --- a/Lib/test/libregrtest/findtests.py +++ b/Lib/test/libregrtest/findtests.py @@ -24,7 +24,7 @@ } -def findtestdir(path=None): +def findtestdir(path: StrPath | None = None) -> StrPath: return path or os.path.dirname(os.path.dirname(__file__)) or os.curdir diff --git a/Lib/test/libregrtest/logger.py b/Lib/test/libregrtest/logger.py index f74bdff6322f13..2ab8ebc8033da8 100644 --- a/Lib/test/libregrtest/logger.py +++ b/Lib/test/libregrtest/logger.py @@ -14,7 +14,7 @@ def __init__(self, results: TestResults, quiet: bool, pgo: bool): self.start_time = time.perf_counter() self.test_count_text = '' self.test_count_width = 3 - self.win_load_tracker = None + self.win_load_tracker: WindowsLoadTracker | None = None self._results: TestResults = results self._quiet: bool = quiet self._pgo: bool = pgo @@ -32,9 +32,9 @@ def log(self, line: str = '') -> None: mins, secs = divmod(int(test_time), 60) hours, mins = divmod(mins, 60) - test_time = "%d:%02d:%02d" % (hours, mins, secs) + formatted_test_time = "%d:%02d:%02d" % (hours, mins, secs) - line = f"{test_time} {line}" + line = f"{formatted_test_time} {line}" if empty: line = line[:-1] diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index ba493ae1796fe0..b18d3405811e76 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -3,6 +3,7 @@ import re import sys import time +from typing import cast from test import support from test.support import os_helper @@ -71,11 +72,11 @@ def __init__(self, ns: Namespace): # Select tests if ns.match_tests: - self.match_tests: FilterTuple = tuple(ns.match_tests) + self.match_tests: FilterTuple | None = tuple(ns.match_tests) else: self.match_tests = None if ns.ignore_tests: - self.ignore_tests: FilterTuple = tuple(ns.ignore_tests) + self.ignore_tests: FilterTuple | None = tuple(ns.ignore_tests) else: self.ignore_tests = None self.exclude: bool = ns.exclude @@ -105,16 +106,16 @@ def __init__(self, ns: Namespace): if ns.huntrleaks: warmups, runs, filename = ns.huntrleaks filename = os.path.abspath(filename) - self.hunt_refleak: HuntRefleak = HuntRefleak(warmups, runs, filename) + self.hunt_refleak: HuntRefleak | None = HuntRefleak(warmups, runs, filename) else: self.hunt_refleak = None self.test_dir: StrPath | None = ns.testdir self.junit_filename: StrPath | None = ns.xmlpath self.memory_limit: str | None = ns.memlimit self.gc_threshold: int | None = ns.threshold - self.use_resources: tuple[str] = tuple(ns.use_resources) + self.use_resources: tuple[str, ...] = tuple(ns.use_resources or ()) if ns.python: - self.python_cmd: tuple[str] = tuple(ns.python) + self.python_cmd: tuple[str, ...] | None = tuple(ns.python) else: self.python_cmd = None self.coverage: bool = ns.trace @@ -139,6 +140,7 @@ def log(self, line=''): self.logger.log(line) def find_tests(self, tests: TestList | None = None) -> tuple[TestTuple, TestList | None]: + assert self.tmp_dir is not None if self.single_test_run: self.next_single_filename = os.path.join(self.tmp_dir, 'pynexttest') try: @@ -183,6 +185,7 @@ def find_tests(self, tests: TestList | None = None) -> tuple[TestTuple, TestList else: selected = alltests else: + assert tests is not None selected = tests if self.single_test_run: @@ -389,7 +392,7 @@ def create_run_tests(self, tests: TestTuple): match_tests=self.match_tests, ignore_tests=self.ignore_tests, match_tests_dict=None, - rerun=None, + rerun=False, forever=self.forever, pgo=self.pgo, pgo_extended=self.pgo_extended, @@ -457,6 +460,7 @@ def _run_tests(self, selected: TestTuple, tests: TestList | None) -> int: self.fail_rerun) def run_tests(self, selected: TestTuple, tests: TestList | None) -> int: + assert self.tmp_dir is not None os.makedirs(self.tmp_dir, exist_ok=True) work_dir = get_work_dir(self.tmp_dir) diff --git a/Lib/test/libregrtest/mypy.ini b/Lib/test/libregrtest/mypy.ini new file mode 100644 index 00000000000000..24ba6a41ce916b --- /dev/null +++ b/Lib/test/libregrtest/mypy.ini @@ -0,0 +1,44 @@ +# Config file for running mypy on libregrtest. +# +# Note: mypy can't be run on libregrtest from the CPython repo root. +# If you try to do so, mypy will complain +# about the entire `Lib/` directory "shadowing the stdlib". +# Instead, `cd` into `Lib/test`, then run `mypy --config-file libregrtest/mypy.ini`. + +[mypy] +packages = libregrtest +python_version = 3.11 +platform = linux +pretty = True + +# Enable most stricter settings +enable_error_code = ignore-without-code +strict = True + +# Various stricter settings that we can't yet enable +# Try to enable these in the following order: +disallow_any_generics = False +disallow_incomplete_defs = False +disallow_untyped_calls = False +disallow_untyped_defs = False +check_untyped_defs = False +warn_return_any = False + +# Various internal modules that typeshed deliberately doesn't have stubs for: +[mypy-_abc.*] +ignore_missing_imports = True + +[mypy-_opcode.*] +ignore_missing_imports = True + +[mypy-_overlapped.*] +ignore_missing_imports = True + +[mypy-_testcapi.*] +ignore_missing_imports = True + +[mypy-_testinternalcapi.*] +ignore_missing_imports = True + +[mypy-test.*] +ignore_missing_imports = True diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index edf8569a8f95fd..d52800a39d83c0 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -1,6 +1,8 @@ import sys import warnings +from collections.abc import Callable from inspect import isabstract +from typing import Any from test import support from test.support import os_helper @@ -45,12 +47,13 @@ def runtest_refleak(test_name, test_func, fs = warnings.filters[:] ps = copyreg.dispatch_table.copy() pic = sys.path_importer_cache.copy() + zdc: dict[str, Any] | None try: import zipimport except ImportError: zdc = None # Run unmodified on platforms without zipimport support else: - zdc = zipimport._zip_directory_cache.copy() + zdc = zipimport._zip_directory_cache.copy() # type: ignore[attr-defined] abcs = {} for abc in [getattr(collections.abc, a) for a in collections.abc.__all__]: if not isabstract(abc): @@ -78,7 +81,7 @@ def get_pooled_int(value): fd_deltas = [0] * repcount getallocatedblocks = sys.getallocatedblocks gettotalrefcount = sys.gettotalrefcount - getunicodeinternedsize = sys.getunicodeinternedsize + getunicodeinternedsize: Callable[[], int] = sys.getunicodeinternedsize # type: ignore[attr-defined] fd_count = os_helper.fd_count # initialize variables to make pyflakes quiet rc_before = alloc_before = fd_before = interned_before = 0 @@ -175,6 +178,7 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs): except ImportError: pass # Run unmodified on platforms without zipimport support else: + assert zdc is not None zipimport._zip_directory_cache.clear() zipimport._zip_directory_cache.update(zdc) diff --git a/Lib/test/libregrtest/results.py b/Lib/test/libregrtest/results.py index 94654fd6ab23f9..f13b143180f4b8 100644 --- a/Lib/test/libregrtest/results.py +++ b/Lib/test/libregrtest/results.py @@ -1,5 +1,6 @@ import sys from test.support import TestStats +from typing import TYPE_CHECKING from .runtests import RunTests from .result import State, TestResult @@ -8,6 +9,13 @@ printlist, count, format_duration) +if TYPE_CHECKING: + # Needed to annotate `TestResults.testsuite_xml` accurately + # Delay the runtime import until later, + # so that we only import it if we actually have to + import xml.etree.ElementTree as ET + + EXITCODE_BAD_TEST = 2 EXITCODE_ENV_CHANGED = 3 EXITCODE_NO_TESTS_RAN = 4 @@ -31,7 +39,7 @@ def __init__(self): self.test_times: list[tuple[float, TestName]] = [] self.stats = TestStats() # used by --junit-xml - self.testsuite_xml: list[str] = [] + self.testsuite_xml: list["ET.Element"] = [] def get_executed(self): return (set(self.good) | set(self.bad) | set(self.skipped) @@ -98,6 +106,7 @@ def accumulate_result(self, result: TestResult, runtests: RunTests): raise ValueError(f"invalid test state: {result.state!r}") if result.has_meaningful_duration() and not rerun: + assert result.duration is not None self.test_times.append((result.duration, test_name)) if result.stats is not None: self.stats.accumulate(result.stats) @@ -111,7 +120,7 @@ def accumulate_result(self, result: TestResult, runtests: RunTests): def need_rerun(self): return bool(self.bad_results) - def prepare_rerun(self) -> (TestTuple, FilterDict): + def prepare_rerun(self) -> tuple[TestTuple, FilterDict]: tests: TestList = [] match_tests_dict = {} for result in self.bad_results: @@ -130,6 +139,8 @@ def prepare_rerun(self) -> (TestTuple, FilterDict): return (tuple(tests), match_tests_dict) def add_junit(self, xml_data: list[str]): + # Local import, so that we only import this if we actually need the xml module. + # It's best to import as few things as possible when running a test. import xml.etree.ElementTree as ET for e in xml_data: try: @@ -231,8 +242,7 @@ def display_summary(self, first_runtests: RunTests, filtered: bool): report.append(f'failures={stats.failures:,}') if stats.skipped: report.append(f'skipped={stats.skipped:,}') - report = ' '.join(report) - print(f"Total tests: {report}") + print(f"Total tests: {' '.join(report)}") # Total test files all_tests = [self.good, self.bad, self.rerun, @@ -256,5 +266,4 @@ def display_summary(self, first_runtests: RunTests, filtered: bool): ): if tests: report.append(f'{name}={len(tests)}') - report = ' '.join(report) - print(f"Total test files: {report}") + print(f"Total test files: {' '.join(report)}") diff --git a/Lib/test/libregrtest/run_workers.py b/Lib/test/libregrtest/run_workers.py index 45b2f424ce4e5d..0b446cb0d0e861 100644 --- a/Lib/test/libregrtest/run_workers.py +++ b/Lib/test/libregrtest/run_workers.py @@ -104,9 +104,9 @@ def __init__(self, worker_id: int, runner: "RunWorkers") -> None: self.output = runner.output self.timeout = runner.worker_timeout self.log = runner.log - self.test_name = None - self.start_time = None - self._popen = None + self.test_name: TestName | None = None + self.start_time: float | None = None + self._popen: subprocess.Popen[str] | None = None self._killed = False self._stopped = False @@ -121,8 +121,9 @@ def __repr__(self) -> str: info.append(f'test={test}') popen = self._popen if popen is not None: + assert self.start_time is not None dt = time.monotonic() - self.start_time - info.extend((f'pid={self._popen.pid}', + info.extend((f'pid={popen.pid}', f'time={format_duration(dt)}')) return '<%s>' % ' '.join(info) @@ -160,7 +161,7 @@ def stop(self) -> None: self._kill() def _run_process(self, runtests: RunTests, output_fd: int, - tmp_dir: StrPath | None = None) -> int: + tmp_dir: StrPath | None = None) -> int | None: popen = create_worker_process(runtests, output_fd, tmp_dir) self._popen = popen self._killed = False @@ -192,6 +193,7 @@ def _run_process(self, runtests: RunTests, output_fd: int, # bpo-38207: Don't attempt to call communicate() again: on it # can hang until all child processes using stdout # pipes completes. + return retcode except OSError: if self._stopped: # kill() has been called: communicate() fails @@ -235,7 +237,7 @@ def create_json_file(self, stack: contextlib.ExitStack) -> tuple[JsonFile, TextI json_fd = json_tmpfile.fileno() if MS_WINDOWS: - json_handle = msvcrt.get_osfhandle(json_fd) + json_handle = msvcrt.get_osfhandle(json_fd) # type: ignore[attr-defined] json_file = JsonFile(json_handle, JsonFileType.WINDOWS_HANDLE) else: @@ -260,7 +262,7 @@ def create_worker_runtests(self, test_name: TestName, json_file: JsonFile) -> Ru **kwargs) def run_tmp_files(self, worker_runtests: RunTests, - stdout_fd: int) -> (int, list[StrPath]): + stdout_fd: int) -> tuple[int | None, list[StrPath]]: # gh-93353: Check for leaked temporary files in the parent process, # since the deletion of temporary files can happen late during # Python finalization: too late for libregrtest. @@ -283,6 +285,7 @@ def run_tmp_files(self, worker_runtests: RunTests, return (retcode, tmp_files) def read_stdout(self, stdout_file: TextIO) -> str: + assert self.test_name is not None stdout_file.seek(0) try: return stdout_file.read().strip() @@ -294,16 +297,18 @@ def read_stdout(self, stdout_file: TextIO) -> str: def read_json(self, json_file: JsonFile, json_tmpfile: TextIO | None, stdout: str) -> tuple[TestResult, str]: + assert self.test_name is not None + worker_json: StrJSON try: if json_tmpfile is not None: json_tmpfile.seek(0) - worker_json: StrJSON = json_tmpfile.read() + worker_json = json_tmpfile.read() elif json_file.file_type == JsonFileType.STDOUT: stdout, _, worker_json = stdout.rpartition("\n") stdout = stdout.rstrip() else: with json_file.open(encoding='utf8') as json_fp: - worker_json: StrJSON = json_fp.read() + worker_json = json_fp.read() except Exception as exc: # gh-101634: Catch UnicodeDecodeError if stdout cannot be # decoded from encoding @@ -337,9 +342,9 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult: stdout = self.read_stdout(stdout_file) if retcode is None: - raise WorkerError(self.test_name, None, stdout, state=State.TIMEOUT) + raise WorkerError(test_name, None, stdout, state=State.TIMEOUT) if retcode != 0: - raise WorkerError(self.test_name, f"Exit code {retcode}", stdout) + raise WorkerError(test_name, f"Exit code {retcode}", stdout) result, stdout = self.read_json(json_file, json_tmpfile, stdout) @@ -383,6 +388,7 @@ def run(self) -> None: def _wait_completed(self) -> None: popen = self._popen + assert popen is not None try: popen.wait(JOIN_TIMEOUT) @@ -414,12 +420,13 @@ def wait_stopped(self, start_time: float) -> None: break -def get_running(workers: list[WorkerThread]) -> list[str]: - running = [] +def get_running(workers: list[WorkerThread]) -> str | None: + running: list[str] = [] for worker in workers: test_name = worker.test_name if not test_name: continue + assert worker.start_time is not None dt = time.monotonic() - worker.start_time if dt >= PROGRESS_MIN_TIME: text = f'{test_name} ({format_duration(dt)})' @@ -431,7 +438,7 @@ def get_running(workers: list[WorkerThread]) -> list[str]: class RunWorkers: def __init__(self, num_workers: int, runtests: RunTests, - logger: Logger, results: TestResult) -> None: + logger: Logger, results: TestResults) -> None: self.num_workers = num_workers self.runtests = runtests self.log = logger.log @@ -446,10 +453,10 @@ def __init__(self, num_workers: int, runtests: RunTests, # Rely on faulthandler to kill a worker process. This timouet is # when faulthandler fails to kill a worker process. Give a maximum # of 5 minutes to faulthandler to kill the worker. - self.worker_timeout = min(self.timeout * 1.5, self.timeout + 5 * 60) + self.worker_timeout: float | None = min(self.timeout * 1.5, self.timeout + 5 * 60) else: self.worker_timeout = None - self.workers = None + self.workers: list[WorkerThread] | None = None jobs = self.runtests.get_jobs() if jobs is not None: @@ -478,6 +485,7 @@ def start_workers(self) -> None: worker.start() def stop_workers(self) -> None: + assert self.workers start_time = time.monotonic() for worker in self.workers: worker.stop() @@ -487,6 +495,7 @@ def stop_workers(self) -> None: def _get_result(self) -> QueueOutput | None: pgo = self.runtests.pgo use_faulthandler = (self.timeout is not None) + assert self.workers # bpo-46205: check the status of workers every iteration to avoid # waiting forever on an empty queue. @@ -521,15 +530,18 @@ def display_result(self, mp_result: MultiprocessResult) -> None: if mp_result.err_msg: # MULTIPROCESSING_ERROR text += ' (%s)' % mp_result.err_msg - elif (result.duration >= PROGRESS_MIN_TIME and not pgo): - text += ' (%s)' % format_duration(result.duration) + else: + assert isinstance(result.duration, float) + if (result.duration >= PROGRESS_MIN_TIME and not pgo): + text += ' (%s)' % format_duration(result.duration) + assert self.workers if not pgo: running = get_running(self.workers) if running: text += f' -- {running}' self.display_progress(self.test_index, text) - def _process_result(self, item: QueueOutput) -> bool: + def _process_result(self, item: QueueOutput) -> TestResult: """Returns True if test runner must stop.""" if item[0]: # Thread got an exception diff --git a/Lib/test/libregrtest/runtests.py b/Lib/test/libregrtest/runtests.py index aee0ab6fd6e38f..e37eb58a3334c0 100644 --- a/Lib/test/libregrtest/runtests.py +++ b/Lib/test/libregrtest/runtests.py @@ -33,7 +33,7 @@ def configure_subprocess(self, popen_kwargs: dict) -> None: popen_kwargs['pass_fds'] = [self.file] case JsonFileType.WINDOWS_HANDLE: # Windows handle - startupinfo = subprocess.STARTUPINFO() + startupinfo = subprocess.STARTUPINFO() # type: ignore[attr-defined] startupinfo.lpAttributeList = {"handle_list": [self.file]} popen_kwargs['startupinfo'] = startupinfo @@ -88,8 +88,8 @@ class RunTests: use_junit: bool memory_limit: str | None gc_threshold: int | None - use_resources: tuple[str] - python_cmd: tuple[str] | None + use_resources: tuple[str, ...] | None + python_cmd: tuple[str, ...] | None randomize: bool random_seed: int | None json_file: JsonFile | None diff --git a/Lib/test/libregrtest/setup.py b/Lib/test/libregrtest/setup.py index 1c40b7c7b3bbfd..efe93edf694cf8 100644 --- a/Lib/test/libregrtest/setup.py +++ b/Lib/test/libregrtest/setup.py @@ -1,4 +1,5 @@ import faulthandler +import gc import os import random import signal @@ -6,10 +7,6 @@ import unittest from test import support from test.support.os_helper import TESTFN_UNDECODABLE, FS_NONASCII -try: - import gc -except ImportError: - gc = None from .runtests import RunTests from .utils import ( @@ -124,7 +121,7 @@ def setup_tests(runtests: RunTests): support.LONG_TIMEOUT = min(support.LONG_TIMEOUT, timeout) if runtests.hunt_refleak: - unittest.BaseTestSuite._cleanup = False + unittest.BaseTestSuite._cleanup = False # type: ignore[attr-defined] if runtests.gc_threshold is not None: gc.set_threshold(runtests.gc_threshold) diff --git a/Lib/test/libregrtest/single.py b/Lib/test/libregrtest/single.py index de6056628738bc..842416f081829a 100644 --- a/Lib/test/libregrtest/single.py +++ b/Lib/test/libregrtest/single.py @@ -7,6 +7,7 @@ import time import traceback import unittest +from typing import cast from test import support from test.support import TestStats @@ -51,8 +52,12 @@ def regrtest_runner(result: TestResult, test_func, runtests: RunTests) -> None: if refleak: result.state = State.REFLEAK + stats: TestStats | None + match test_result: - case TestStats(): + # TestResults is imported from test.support, so mypy isn't aware that it's a class + # mypy error: 'Expected type in class pattern; found "Any" [misc]' + case TestStats(): # type: ignore[misc] stats = test_result case unittest.TestResult(): stats = TestStats.from_unittest(test_result) @@ -134,14 +139,14 @@ def _runtest_env_changed_exc(result: TestResult, runtests: RunTests, with saved_test_environment(test_name, runtests.verbose, quiet, pgo=pgo): _load_run_test(result, runtests) - except support.ResourceDenied as msg: + except support.ResourceDenied as exc: if not quiet and not pgo: - print(f"{test_name} skipped -- {msg}", flush=True) + print(f"{test_name} skipped -- {exc}", flush=True) result.state = State.RESOURCE_DENIED return - except unittest.SkipTest as msg: + except unittest.SkipTest as exc: if not quiet and not pgo: - print(f"{test_name} skipped -- {msg}", flush=True) + print(f"{test_name} skipped -- {exc}", flush=True) result.state = State.SKIPPED return except support.TestFailedWithDetails as exc: @@ -195,7 +200,7 @@ def _runtest(result: TestResult, runtests: RunTests) -> None: timeout is not None and threading_helper.can_start_thread ) if use_timeout: - faulthandler.dump_traceback_later(timeout, exit=True) + faulthandler.dump_traceback_later(cast(float, timeout), exit=True) try: setup_tests(runtests) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 880cec5cc50f4a..b1fecf246f834b 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -10,6 +10,7 @@ import sysconfig import tempfile import textwrap +from collections.abc import Callable from test import support from test.support import os_helper @@ -67,7 +68,7 @@ def format_duration(seconds): return ' '.join(parts) -def strip_py_suffix(names: list[str]): +def strip_py_suffix(names: list[str] | None) -> None: if not names: return for idx, name in enumerate(names): @@ -372,6 +373,7 @@ def get_temp_dir(tmp_dir: StrPath | None = None) -> StrPath: else: # WASI platform tmp_dir = sysconfig.get_config_var('projectbase') + assert tmp_dir is not None tmp_dir = os.path.join(tmp_dir, 'build') # When get_temp_dir() is called in a worker process, @@ -441,6 +443,7 @@ def remove_testfn(test_name: TestName, verbose: int) -> None: if not os.path.exists(name): return + nuker: Callable[[str], None] if os.path.isdir(name): import shutil kind, nuker = "directory", shutil.rmtree diff --git a/Lib/test/libregrtest/worker.py b/Lib/test/libregrtest/worker.py index 168803c5d9451f..32a89bc92b073a 100644 --- a/Lib/test/libregrtest/worker.py +++ b/Lib/test/libregrtest/worker.py @@ -1,7 +1,7 @@ import subprocess import sys import os -from typing import NoReturn +from typing import Any, NoReturn from test import support from test.support import os_helper @@ -25,7 +25,7 @@ def create_worker_process(runtests: RunTests, output_fd: int, if python_cmd is not None: executable = python_cmd else: - executable = [sys.executable] + executable = (sys.executable,) cmd = [*executable, *support.args_from_interpreter_flags(), '-u', # Unbuffered stdout and stderr '-m', 'test.libregrtest.worker', @@ -45,7 +45,7 @@ def create_worker_process(runtests: RunTests, output_fd: int, # Running the child from the same working directory as regrtest's original # invocation ensures that TEMPDIR for the child is the same when # sysconfig.is_python_build() is true. See issue 15300. - kwargs = dict( + kwargs: dict[str, Any] = dict( env=env, stdout=output_fd, # bpo-45410: Write stderr into stdout to keep messages order @@ -57,6 +57,7 @@ def create_worker_process(runtests: RunTests, output_fd: int, # Pass json_file to the worker process json_file = runtests.json_file + assert json_file is not None json_file.configure_subprocess(kwargs) with json_file.inherit_subprocess(): @@ -67,7 +68,8 @@ def worker_process(worker_json: StrJSON) -> NoReturn: runtests = RunTests.from_json(worker_json) test_name = runtests.tests[0] match_tests: FilterTuple | None = runtests.match_tests - json_file: JsonFile = runtests.json_file + json_file = runtests.json_file + assert isinstance(json_file, JsonFile) setup_test_dir(runtests.test_dir) setup_process()