mirror of
https://github.com/python/cpython.git
synced 2026-01-27 05:05:50 +00:00
gh-143553: Add support for parametrized resources in regrtests (GH-143554)
For example, "-u xpickle=2.7" will run test_xpickle only against Python 2.7.
This commit is contained in:
parent
6c9f7b4406
commit
c07e5ec0a9
@ -492,6 +492,12 @@ The :mod:`test.support` module defines the following functions:
|
||||
tests.
|
||||
|
||||
|
||||
.. function:: get_resource_value(resource)
|
||||
|
||||
Return the value specified for *resource* (as :samp:`-u {resource}={value}`).
|
||||
Return ``None`` if *resource* is disabled or no value is specified.
|
||||
|
||||
|
||||
.. function:: python_is_optimized()
|
||||
|
||||
Return ``True`` if Python was not built with ``-O0`` or ``-Og``.
|
||||
|
||||
@ -162,7 +162,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
|
||||
@ -309,7 +309,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',
|
||||
@ -414,11 +414,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)
|
||||
@ -486,14 +493,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
|
||||
|
||||
@ -531,23 +538,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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
@ -607,21 +607,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)
|
||||
@ -631,7 +640,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())
|
||||
|
||||
@ -30,7 +30,8 @@ __all__ = [
|
||||
"record_original_stdout", "get_original_stdout", "captured_stdout",
|
||||
"captured_stdin", "captured_stderr", "captured_output",
|
||||
# unittest
|
||||
"is_resource_enabled", "requires", "requires_freebsd_version",
|
||||
"is_resource_enabled", "get_resource_value", "requires", "requires_resource",
|
||||
"requires_freebsd_version",
|
||||
"requires_gil_enabled", "requires_linux_version", "requires_mac_ver",
|
||||
"check_syntax_error",
|
||||
"requires_gzip", "requires_bz2", "requires_lzma", "requires_zstd",
|
||||
@ -185,7 +186,7 @@ def get_attribute(obj, name):
|
||||
return attribute
|
||||
|
||||
verbose = 1 # Flag set to 0 by regrtest.py
|
||||
use_resources = None # Flag set to [] by regrtest.py
|
||||
use_resources = None # Flag set to {} by regrtest.py
|
||||
max_memuse = 0 # Disable bigmem tests (they will still be run with
|
||||
# small sizes, to make sure they work.)
|
||||
real_max_memuse = 0
|
||||
@ -300,6 +301,16 @@ def is_resource_enabled(resource):
|
||||
"""
|
||||
return use_resources is None or resource in use_resources
|
||||
|
||||
def get_resource_value(resource):
|
||||
"""Test whether a resource is enabled.
|
||||
|
||||
Known resources are set by regrtest.py. If not running under regrtest.py,
|
||||
all resources are assumed enabled unless use_resources has been set.
|
||||
"""
|
||||
if use_resources is None:
|
||||
return None
|
||||
return use_resources.get(resource)
|
||||
|
||||
def requires(resource, msg=None):
|
||||
"""Raise ResourceDenied if the specified resource is not available."""
|
||||
if not is_resource_enabled(resource):
|
||||
|
||||
@ -279,26 +279,56 @@ class ParseArgsTestCase(unittest.TestCase):
|
||||
for opt in '-u', '--use':
|
||||
with self.subTest(opt=opt):
|
||||
ns = self.parse_args([opt, 'gui,network'])
|
||||
self.assertEqual(ns.use_resources, ['gui', 'network'])
|
||||
self.assertEqual(ns.use_resources, {'gui': None, 'network': None})
|
||||
ns = self.parse_args([opt, 'gui', opt, 'network'])
|
||||
self.assertEqual(ns.use_resources, {'gui': None, 'network': None})
|
||||
|
||||
ns = self.parse_args([opt, 'gui,none,network'])
|
||||
self.assertEqual(ns.use_resources, ['network'])
|
||||
self.assertEqual(ns.use_resources, {'network': None})
|
||||
ns = self.parse_args([opt, 'gui', opt, 'none', opt, 'network'])
|
||||
self.assertEqual(ns.use_resources, {'network': None})
|
||||
|
||||
expected = list(cmdline.ALL_RESOURCES)
|
||||
expected.remove('gui')
|
||||
expected = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
del expected['gui']
|
||||
ns = self.parse_args([opt, 'all,-gui'])
|
||||
self.assertEqual(ns.use_resources, expected)
|
||||
|
||||
self.checkError([opt], 'expected one argument')
|
||||
self.checkError([opt, 'foo'], 'invalid resource')
|
||||
|
||||
# all + a resource not part of "all"
|
||||
expected = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
expected['tzdata'] = None
|
||||
ns = self.parse_args([opt, 'all,tzdata'])
|
||||
self.assertEqual(ns.use_resources,
|
||||
list(cmdline.ALL_RESOURCES) + ['tzdata'])
|
||||
self.assertEqual(ns.use_resources, expected)
|
||||
ns = self.parse_args([opt, 'all', opt, 'tzdata'])
|
||||
self.assertEqual(ns.use_resources, expected)
|
||||
|
||||
# test another resource which is not part of "all"
|
||||
ns = self.parse_args([opt, 'extralargefile'])
|
||||
self.assertEqual(ns.use_resources, ['extralargefile'])
|
||||
self.assertEqual(ns.use_resources, {'extralargefile': None})
|
||||
|
||||
# test resource with value
|
||||
ns = self.parse_args([opt, 'xpickle=2.7'])
|
||||
self.assertEqual(ns.use_resources, {'xpickle': '2.7'})
|
||||
ns = self.parse_args([opt, 'xpickle=2.7,xpickle=3.3'])
|
||||
self.assertEqual(ns.use_resources, {'xpickle': '3.3'})
|
||||
ns = self.parse_args([opt, 'xpickle=2.7,none'])
|
||||
self.assertEqual(ns.use_resources, {})
|
||||
ns = self.parse_args([opt, 'xpickle=2.7,-xpickle'])
|
||||
self.assertEqual(ns.use_resources, {})
|
||||
|
||||
expected = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
expected['xpickle'] = '2.7'
|
||||
ns = self.parse_args([opt, 'all,xpickle=2.7'])
|
||||
self.assertEqual(ns.use_resources, expected)
|
||||
ns = self.parse_args([opt, 'all', opt, 'xpickle=2.7'])
|
||||
self.assertEqual(ns.use_resources, expected)
|
||||
|
||||
# test invalid resources with value
|
||||
self.checkError([opt, 'all=0'], 'invalid resource: all=0')
|
||||
self.checkError([opt, 'none=0'], 'invalid resource: none=0')
|
||||
self.checkError([opt, 'all,-gui=0'], 'invalid resource: -gui=0')
|
||||
|
||||
def test_memlimit(self):
|
||||
for opt in '-M', '--memlimit':
|
||||
@ -459,20 +489,20 @@ class ParseArgsTestCase(unittest.TestCase):
|
||||
self.assertTrue(regrtest.fail_env_changed)
|
||||
self.assertTrue(regrtest.print_slowest)
|
||||
self.assertEqual(regrtest.output_on_failure, output_on_failure)
|
||||
self.assertEqual(sorted(regrtest.use_resources), sorted(use_resources))
|
||||
self.assertEqual(regrtest.use_resources, use_resources)
|
||||
return regrtest
|
||||
|
||||
def test_fast_ci(self):
|
||||
args = ['--fast-ci']
|
||||
use_resources = sorted(cmdline.ALL_RESOURCES)
|
||||
use_resources.remove('cpu')
|
||||
use_resources = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
del use_resources['cpu']
|
||||
regrtest = self.check_ci_mode(args, use_resources)
|
||||
self.assertEqual(regrtest.timeout, 10 * 60)
|
||||
|
||||
def test_fast_ci_python_cmd(self):
|
||||
args = ['--fast-ci', '--python', 'python -X dev']
|
||||
use_resources = sorted(cmdline.ALL_RESOURCES)
|
||||
use_resources.remove('cpu')
|
||||
use_resources = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
del use_resources['cpu']
|
||||
regrtest = self.check_ci_mode(args, use_resources, rerun=False)
|
||||
self.assertEqual(regrtest.timeout, 10 * 60)
|
||||
self.assertEqual(regrtest.python_cmd, ('python', '-X', 'dev'))
|
||||
@ -480,32 +510,33 @@ class ParseArgsTestCase(unittest.TestCase):
|
||||
def test_fast_ci_resource(self):
|
||||
# it should be possible to override resources individually
|
||||
args = ['--fast-ci', '-u-network']
|
||||
use_resources = sorted(cmdline.ALL_RESOURCES)
|
||||
use_resources.remove('cpu')
|
||||
use_resources.remove('network')
|
||||
use_resources = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
del use_resources['cpu']
|
||||
del use_resources['network']
|
||||
self.check_ci_mode(args, use_resources)
|
||||
|
||||
def test_fast_ci_verbose(self):
|
||||
args = ['--fast-ci', '--verbose']
|
||||
use_resources = sorted(cmdline.ALL_RESOURCES)
|
||||
use_resources.remove('cpu')
|
||||
use_resources = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
del use_resources['cpu']
|
||||
regrtest = self.check_ci_mode(args, use_resources,
|
||||
output_on_failure=False)
|
||||
self.assertEqual(regrtest.verbose, True)
|
||||
|
||||
def test_slow_ci(self):
|
||||
args = ['--slow-ci']
|
||||
use_resources = sorted(cmdline.ALL_RESOURCES)
|
||||
use_resources = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
regrtest = self.check_ci_mode(args, use_resources)
|
||||
self.assertEqual(regrtest.timeout, 20 * 60)
|
||||
|
||||
def test_ci_no_randomize(self):
|
||||
all_resources = set(cmdline.ALL_RESOURCES)
|
||||
use_resources = dict.fromkeys(cmdline.ALL_RESOURCES)
|
||||
self.check_ci_mode(
|
||||
["--slow-ci", "--no-randomize"], all_resources, randomize=False
|
||||
["--slow-ci", "--no-randomize"], use_resources, randomize=False
|
||||
)
|
||||
del use_resources['cpu']
|
||||
self.check_ci_mode(
|
||||
["--fast-ci", "--no-randomize"], all_resources - {'cpu'}, randomize=False
|
||||
["--fast-ci", "--no-randomize"], use_resources, randomize=False
|
||||
)
|
||||
|
||||
def test_dont_add_python_opts(self):
|
||||
@ -2435,20 +2466,20 @@ class TestUtils(unittest.TestCase):
|
||||
format_resources = utils.format_resources
|
||||
ALL_RESOURCES = utils.ALL_RESOURCES
|
||||
self.assertEqual(
|
||||
format_resources(("network",)),
|
||||
format_resources({"network": None}),
|
||||
'resources (1): network')
|
||||
self.assertEqual(
|
||||
format_resources(("audio", "decimal", "network")),
|
||||
format_resources(dict.fromkeys(("audio", "decimal", "network"))),
|
||||
'resources (3): audio,decimal,network')
|
||||
self.assertEqual(
|
||||
format_resources(ALL_RESOURCES),
|
||||
format_resources(dict.fromkeys(ALL_RESOURCES)),
|
||||
'resources: all')
|
||||
self.assertEqual(
|
||||
format_resources(tuple(name for name in ALL_RESOURCES
|
||||
if name != "cpu")),
|
||||
format_resources({name: None for name in ALL_RESOURCES
|
||||
if name != "cpu"}),
|
||||
'resources: all,-cpu')
|
||||
self.assertEqual(
|
||||
format_resources((*ALL_RESOURCES, "tzdata")),
|
||||
format_resources({**dict.fromkeys(ALL_RESOURCES), "tzdata": None}),
|
||||
'resources: all,tzdata')
|
||||
|
||||
def test_match_test(self):
|
||||
|
||||
@ -230,11 +230,15 @@ def load_tests(loader, tests, pattern):
|
||||
test_class = make_test(py_version, CPicklePythonCompat)
|
||||
tests.addTest(loader.loadTestsFromTestCase(test_class))
|
||||
|
||||
major = sys.version_info.major
|
||||
assert major == 3
|
||||
add_tests((2, 7))
|
||||
for minor in range(2, sys.version_info.minor):
|
||||
add_tests((major, minor))
|
||||
value = support.get_resource_value('xpickle')
|
||||
if value is None:
|
||||
major = sys.version_info.major
|
||||
assert major == 3
|
||||
add_tests((2, 7))
|
||||
for minor in range(2, sys.version_info.minor):
|
||||
add_tests((major, minor))
|
||||
else:
|
||||
add_tests(tuple(map(int, value.split('.'))))
|
||||
return tests
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
Add support for parametrized resources, such as ``-u xpickle=2.7``.
|
||||
Loading…
x
Reference in New Issue
Block a user