diff --git a/Lib/test/libregrtest/__init__.py b/Lib/test/libregrtest/__init__.py index 8b1378917..e69de29bb 100644 --- a/Lib/test/libregrtest/__init__.py +++ b/Lib/test/libregrtest/__init__.py @@ -1 +0,0 @@ - diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py index e7a12e4d0..2c404f6d8 100644 --- a/Lib/test/libregrtest/cmdline.py +++ b/Lib/test/libregrtest/cmdline.py @@ -130,6 +130,13 @@ resources to test. Currently only the following are defined: tzdata - Run tests that require timezone data. + xpickle - Test pickle and _pickle against Python 3.6, 3.7, 3.8 + and 3.9 to test backwards compatibility. These tests + may take very long to complete. + + wantobjects - Allows to run Tkinter tests with the specified value of + tkinter.wantobjects. + To enable all resources except one, use '-uall,-'. For example, to run all the tests except for the gui tests, give the option '-uall,-gui'. @@ -158,7 +165,7 @@ class Namespace(argparse.Namespace): self.randomize = False self.fromfile = None self.fail_env_changed = False - self.use_resources: list[str] = [] + self.use_resources: dict[str, str | None] = {} self.trace = False self.coverdir = 'coverage' self.runleaks = False @@ -305,7 +312,7 @@ def _create_parser(): group.add_argument('-G', '--failfast', action='store_true', help='fail as soon as a test fails (only with -v or -W)') group.add_argument('-u', '--use', metavar='RES1,RES2,...', - action='append', type=resources_list, + action='extend', type=resources_list, help='specify which special resource intensive tests ' 'to run.' + more_details) group.add_argument('-M', '--memlimit', metavar='LIMIT', @@ -410,11 +417,18 @@ def huntrleaks(string): def resources_list(string): - u = [x.lower() for x in string.split(',')] - for r in u: + u = [] + for x in string.split(','): + r, eq, v = x.partition('=') + r = r.lower() + u.append((r, v if eq else None)) if r == 'all' or r == 'none': + if eq: + raise argparse.ArgumentTypeError('invalid resource: ' + x) continue if r[0] == '-': + if eq: + raise argparse.ArgumentTypeError('invalid resource: ' + x) r = r[1:] if r not in RESOURCE_NAMES: raise argparse.ArgumentTypeError('invalid resource: ' + r) @@ -482,14 +496,14 @@ def _parse_args(args, **kwargs): # Similar to: -u "all" --timeout=1200 if ns.use is None: ns.use = [] - ns.use.insert(0, ['all']) + ns.use[:0] = [('all', None)] if ns.timeout is None: ns.timeout = 1200 # 20 minutes elif ns.fast_ci: # Similar to: -u "all,-cpu" --timeout=600 if ns.use is None: ns.use = [] - ns.use.insert(0, ['all', '-cpu']) + ns.use[:0] = [('all', None), ('-cpu', None)] if ns.timeout is None: ns.timeout = 600 # 10 minutes @@ -527,23 +541,17 @@ def _parse_args(args, **kwargs): if ns.timeout <= 0: ns.timeout = None if ns.use: - for a in ns.use: - for r in a: - if r == 'all': - ns.use_resources[:] = ALL_RESOURCES - continue - if r == 'none': - del ns.use_resources[:] - continue - remove = False - if r[0] == '-': - remove = True - r = r[1:] - if remove: - if r in ns.use_resources: - ns.use_resources.remove(r) - elif r not in ns.use_resources: - ns.use_resources.append(r) + for r, v in ns.use: + if r == 'all': + for r in ALL_RESOURCES: + ns.use_resources[r] = None + elif r == 'none': + ns.use_resources.clear() + elif r[0] == '-': + r = r[1:] + ns.use_resources.pop(r, None) + else: + ns.use_resources[r] = v if ns.random_seed is not None: ns.randomize = True if ns.no_randomize: diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index 0fc254878..d8b9605ea 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -118,7 +118,7 @@ class Regrtest: 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: dict[str, str | None] = dict(ns.use_resources) if ns.python: self.python_cmd: tuple[str, ...] | None = tuple(ns.python) else: diff --git a/Lib/test/libregrtest/runtests.py b/Lib/test/libregrtest/runtests.py index 759f24fc2..e6d34d8e6 100644 --- a/Lib/test/libregrtest/runtests.py +++ b/Lib/test/libregrtest/runtests.py @@ -96,7 +96,7 @@ class RunTests: coverage: bool memory_limit: str | None gc_threshold: int | None - use_resources: tuple[str, ...] + use_resources: dict[str, str | None] python_cmd: tuple[str, ...] | None randomize: bool random_seed: int | str @@ -179,7 +179,14 @@ class RunTests: if self.gc_threshold: args.append(f"--threshold={self.gc_threshold}") if self.use_resources: - args.extend(("-u", ','.join(self.use_resources))) + simple = ','.join(resource + for resource, value in self.use_resources.items() + if value is None) + if simple: + args.extend(("-u", simple)) + for resource, value in self.use_resources.items(): + if value is not None: + args.extend(("-u", f"{resource}={value}")) if self.python_cmd: cmd = shlex.join(self.python_cmd) args.extend(("--python", cmd)) diff --git a/Lib/test/libregrtest/save_env.py b/Lib/test/libregrtest/save_env.py index eeb1f17d7..138465012 100644 --- a/Lib/test/libregrtest/save_env.py +++ b/Lib/test/libregrtest/save_env.py @@ -227,9 +227,7 @@ class saved_test_environment: # to track reference leaks. def get_threading__dangling(self): # This copies the weakrefs without making any strong reference - # XXX: RUSTPYTHON - filter out dead threads since gc doesn't clean WeakSet. Revert this line when we have a GC - # return threading._dangling.copy() - return {t for t in threading._dangling if t.is_alive()} + return threading._dangling.copy() def restore_threading__dangling(self, saved): threading._dangling.clear() threading._dangling.update(saved) @@ -240,9 +238,7 @@ class saved_test_environment: # Unjoined process objects can survive after process exits multiprocessing_process._cleanup() # This copies the weakrefs without making any strong reference - # TODO: RUSTPYTHON - filter out dead processes since gc doesn't clean WeakSet. Revert this line when we have a GC - # return multiprocessing_process._dangling.copy() - return {p for p in multiprocessing_process._dangling if p.is_alive()} + return multiprocessing_process._dangling.copy() def restore_multiprocessing_process__dangling(self, saved): multiprocessing_process = self.get_module('multiprocessing.process') multiprocessing_process._dangling.clear() diff --git a/Lib/test/libregrtest/single.py b/Lib/test/libregrtest/single.py index 3dfb0b01d..d0759d262 100644 --- a/Lib/test/libregrtest/single.py +++ b/Lib/test/libregrtest/single.py @@ -145,7 +145,7 @@ def regrtest_runner(result: TestResult, test_func, runtests: RunTests) -> None: # Storage of uncollectable GC objects (gc.garbage) -GC_GARBAGE = [] +GC_GARBAGE: list[object] = [] def _load_run_test(result: TestResult, runtests: RunTests) -> None: @@ -172,16 +172,15 @@ def _load_run_test(result: TestResult, runtests: RunTests) -> None: remove_testfn(test_name, runtests.verbose) - # XXX: RUSTPYTHON, build a functional garbage collector into the interpreter - # if gc.garbage: - # support.environment_altered = True - # print_warning(f"{test_name} created {len(gc.garbage)} " - # f"uncollectable object(s)") + if gc.garbage: + support.environment_altered = True + print_warning(f"{test_name} created {len(gc.garbage)} " + f"uncollectable object(s)") - # # move the uncollectable objects somewhere, - # # so we don't see them again - # GC_GARBAGE.extend(gc.garbage) - # gc.garbage.clear() + # move the uncollectable objects somewhere, + # so we don't see them again + GC_GARBAGE.extend(gc.garbage) + gc.garbage.clear() support.reap_children() diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index d94fb84a7..15caba75a 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -12,7 +12,7 @@ import sys import sysconfig import tempfile import textwrap -from collections.abc import Callable, Iterable +from collections.abc import Callable from test import support from test.support import os_helper @@ -41,7 +41,7 @@ ALL_RESOURCES = ('audio', 'console', 'curses', 'largefile', 'network', # - tzdata: while needed to validate fully test_datetime, it makes # test_datetime too slow (15-20 min on some buildbots) and so is disabled by # default (see bpo-30822). -RESOURCE_NAMES = ALL_RESOURCES + ('extralargefile', 'tzdata') +RESOURCE_NAMES = ALL_RESOURCES + ('extralargefile', 'tzdata', 'xpickle', 'wantobjects') # Types for types hints @@ -150,7 +150,7 @@ def setup_unraisable_hook() -> None: sys.unraisablehook = regrtest_unraisable_hook -orig_threading_excepthook: Callable[..., None] | None = None +orig_threading_excepthook: Callable[..., object] | None = None def regrtest_threading_excepthook(args) -> None: @@ -433,12 +433,6 @@ def get_temp_dir(tmp_dir: StrPath | None = None) -> StrPath: f"unexpectedly returned {tmp_dir!r} on WASI" ) tmp_dir = os.path.join(tmp_dir, 'build') - - # When get_temp_dir() is called in a worker process, - # get_temp_dir() path is different than in the parent process - # which is not a WASI process. So the parent does not create - # the same "tmp_dir" than the test worker process. - os.makedirs(tmp_dir, exist_ok=True) else: tmp_dir = tempfile.gettempdir() @@ -588,21 +582,30 @@ def is_cross_compiled() -> bool: return ('_PYTHON_HOST_PLATFORM' in os.environ) -def format_resources(use_resources: Iterable[str]) -> str: - use_resources = set(use_resources) +def format_resources(use_resources: dict[str, str | None]) -> str: all_resources = set(ALL_RESOURCES) + values = [] + for name in sorted(use_resources): + if use_resources[name] is not None: + values.append(f'{name}={use_resources[name]}') + # Express resources relative to "all" relative_all = ['all'] - for name in sorted(all_resources - use_resources): + for name in sorted(all_resources - set(use_resources)): relative_all.append(f'-{name}') - for name in sorted(use_resources - all_resources): - relative_all.append(f'{name}') - all_text = ','.join(relative_all) + for name in sorted(set(use_resources) - all_resources): + if use_resources[name] is None: + relative_all.append(name) + all_text = ','.join(relative_all + values) all_text = f"resources: {all_text}" # List of enabled resources - text = ','.join(sorted(use_resources)) + resources = [] + for name in sorted(use_resources): + if use_resources[name] is None: + resources.append(name) + text = ','.join(resources + values) text = f"resources ({len(use_resources)}): {text}" # Pick the shortest string (prefer relative to all if lengths are equal) @@ -612,7 +615,7 @@ def format_resources(use_resources: Iterable[str]) -> str: return text -def display_header(use_resources: tuple[str, ...], +def display_header(use_resources: dict[str, str | None], python_cmd: tuple[str, ...] | None) -> None: # Print basic platform information print("==", platform.python_implementation(), *sys.version.split()) diff --git a/Lib/test/libregrtest/worker.py b/Lib/test/libregrtest/worker.py index 1ad67e1ce..4e69ab9d8 100644 --- a/Lib/test/libregrtest/worker.py +++ b/Lib/test/libregrtest/worker.py @@ -127,6 +127,9 @@ def main() -> NoReturn: worker_json = sys.argv[1] tmp_dir = get_temp_dir() + # get_temp_dir() can be different in the worker and the parent process. + # For example, if --tempdir option is used. + os.makedirs(tmp_dir, exist_ok=True) work_dir = get_work_dir(tmp_dir, worker=True) with exit_timeout():