Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,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``.
Expand Down
47 changes: 24 additions & 23 deletions Lib/test/libregrtest/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,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
Expand Down Expand Up @@ -293,7 +293,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',
Expand Down Expand Up @@ -391,11 +391,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)
Expand Down Expand Up @@ -459,14 +466,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

Expand Down Expand Up @@ -504,23 +511,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:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/libregrtest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 9 additions & 2 deletions Lib/test/libregrtest/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -178,7 +178,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))
Expand Down
27 changes: 18 additions & 9 deletions Lib/test/libregrtest/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,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
Expand Down Expand Up @@ -632,21 +632,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)
Expand All @@ -656,7 +665,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())
Expand Down
15 changes: 13 additions & 2 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,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",
Expand Down Expand Up @@ -179,7 +180,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
Expand Down Expand Up @@ -294,6 +295,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):
Expand Down
85 changes: 58 additions & 27 deletions Lib/test/test_regrtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,26 +276,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':
Expand Down Expand Up @@ -456,53 +486,54 @@ 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'))

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):
Expand Down Expand Up @@ -2445,20 +2476,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):
Expand Down
Loading