https://github.com/python/cpython/commit/c07e5ec0a9e5843fc39dec6aa94172faf6354858
commit: c07e5ec0a9e5843fc39dec6aa94172faf6354858
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-08T13:51:38+02:00
summary:
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.
files:
A Misc/NEWS.d/next/Tests/2026-01-08-11-50-06.gh-issue-143553.KyyNTt.rst
M Doc/library/test.rst
M Lib/test/libregrtest/cmdline.py
M Lib/test/libregrtest/main.py
M Lib/test/libregrtest/runtests.py
M Lib/test/libregrtest/utils.py
M Lib/test/support/__init__.py
M Lib/test/test_regrtest.py
M Lib/test/test_xpickle.py
diff --git a/Doc/library/test.rst b/Doc/library/test.rst
index 395cde21ccf449..44b1d395a27d13 100644
--- a/Doc/library/test.rst
+++ b/Doc/library/test.rst
@@ -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``.
diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py
index 42edb73496c752..d784506703461b 100644
--- a/Lib/test/libregrtest/cmdline.py
+++ b/Lib/test/libregrtest/cmdline.py
@@ -162,7 +162,7 @@ def __init__(self, **kwargs) -> None:
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:
diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py
index 0fc2548789e2e1..d8b9605ea49843 100644
--- a/Lib/test/libregrtest/main.py
+++ b/Lib/test/libregrtest/main.py
@@ -118,7 +118,7 @@ def __init__(self, ns: Namespace, _add_python_opts: bool =
False):
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 759f24fc25e38c..e6d34d8e6a3be5 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 @@ def bisect_cmd_args(self) -> list[str]:
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/utils.py b/Lib/test/libregrtest/utils.py
index 1daa9c7baf8211..4479f336b1ee53 100644
--- a/Lib/test/libregrtest/utils.py
+++ b/Lib/test/libregrtest/utils.py
@@ -12,7 +12,7 @@
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())
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 84fd43fd396914..847d9074eb82cd 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -30,7 +30,8 @@
"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):
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index c27b3c862924d1..fc6694d489fb0f 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -279,26 +279,56 @@ def test_use(self):
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 @@ def check_ci_mode(self, args, use_resources,
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 @@ def test_fast_ci_python_cmd(self):
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 @@ def test_format_resources(self):
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):
diff --git a/Lib/test/test_xpickle.py b/Lib/test/test_xpickle.py
index 659d3e38389860..158f27dce4fdc2 100644
--- a/Lib/test/test_xpickle.py
+++ b/Lib/test/test_xpickle.py
@@ -230,11 +230,15 @@ def add_tests(py_version):
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
diff --git
a/Misc/NEWS.d/next/Tests/2026-01-08-11-50-06.gh-issue-143553.KyyNTt.rst
b/Misc/NEWS.d/next/Tests/2026-01-08-11-50-06.gh-issue-143553.KyyNTt.rst
new file mode 100644
index 00000000000000..e9509057601979
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2026-01-08-11-50-06.gh-issue-143553.KyyNTt.rst
@@ -0,0 +1 @@
+Add support for parametrized resources, such as ``-u xpickle=2.7``.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]