Skip to content

Commit

Permalink
pythongh-111165: Move test running code from test.support to libregrt…
Browse files Browse the repository at this point in the history
…est (pythonGH-111166)

Remove no longer used functions run_unittest() and run_doctest() from
the test.support module.
  • Loading branch information
serhiy-storchaka authored Oct 25, 2023
1 parent a8a89fc commit f6a45a0
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 342 deletions.
28 changes: 0 additions & 28 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -508,34 +508,6 @@ The :mod:`test.support` module defines the following functions:
Define match patterns on test filenames and test method names for filtering tests.


.. function:: run_unittest(*classes)

Execute :class:`unittest.TestCase` subclasses passed to the function. The
function scans the classes for methods starting with the prefix ``test_``
and executes the tests individually.

It is also legal to pass strings as parameters; these should be keys in
``sys.modules``. Each associated module will be scanned by
``unittest.TestLoader.loadTestsFromModule()``. This is usually seen in the
following :func:`test_main` function::

def test_main():
support.run_unittest(__name__)

This will run all tests defined in the named module.


.. function:: run_doctest(module, verbosity=None, optionflags=0)

Run :func:`doctest.testmod` on the given *module*. Return
``(failure_count, test_count)``.

If *verbosity* is ``None``, :func:`doctest.testmod` is run with verbosity
set to :data:`verbose`. Otherwise, it is run with verbosity set to
``None``. *optionflags* is passed as ``optionflags`` to
:func:`doctest.testmod`.


.. function:: get_pagesize()

Get size of a page in bytes.
Expand Down
72 changes: 72 additions & 0 deletions Lib/test/libregrtest/filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import itertools
import operator
import re


# By default, don't filter tests
_test_matchers = ()
_test_patterns = ()


def match_test(test):
# Function used by support.run_unittest() and regrtest --list-cases
result = False
for matcher, result in reversed(_test_matchers):
if matcher(test.id()):
return result
return not result


def _is_full_match_test(pattern):
# If a pattern contains at least one dot, it's considered
# as a full test identifier.
# Example: 'test.test_os.FileTests.test_access'.
#
# ignore patterns which contain fnmatch patterns: '*', '?', '[...]'
# or '[!...]'. For example, ignore 'test_access*'.
return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern))


def set_match_tests(patterns):
global _test_matchers, _test_patterns

if not patterns:
_test_matchers = ()
_test_patterns = ()
else:
itemgetter = operator.itemgetter
patterns = tuple(patterns)
if patterns != _test_patterns:
_test_matchers = [
(_compile_match_function(map(itemgetter(0), it)), result)
for result, it in itertools.groupby(patterns, itemgetter(1))
]
_test_patterns = patterns


def _compile_match_function(patterns):
patterns = list(patterns)

if all(map(_is_full_match_test, patterns)):
# Simple case: all patterns are full test identifier.
# The test.bisect_cmd utility only uses such full test identifiers.
return set(patterns).__contains__
else:
import fnmatch
regex = '|'.join(map(fnmatch.translate, patterns))
# The search *is* case sensitive on purpose:
# don't use flags=re.IGNORECASE
regex_match = re.compile(regex).match

def match_test_regex(test_id, regex_match=regex_match):
if regex_match(test_id):
# The regex matches the whole identifier, for example
# 'test.test_os.FileTests.test_access'.
return True
else:
# Try to match parts of the test identifier.
# For example, split 'test.test_os.FileTests.test_access'
# into: 'test', 'test_os', 'FileTests' and 'test_access'.
return any(map(regex_match, test_id.split(".")))

return match_test_regex
5 changes: 3 additions & 2 deletions Lib/test/libregrtest/findtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from test import support

from .filter import match_test, set_match_tests
from .utils import (
StrPath, TestName, TestTuple, TestList, TestFilter,
abs_module_name, count, printlist)
Expand Down Expand Up @@ -79,14 +80,14 @@ def _list_cases(suite):
if isinstance(test, unittest.TestSuite):
_list_cases(test)
elif isinstance(test, unittest.TestCase):
if support.match_test(test):
if match_test(test):
print(test.id())

def list_cases(tests: TestTuple, *,
match_tests: TestFilter | None = None,
test_dir: StrPath | None = None):
support.verbose = False
support.set_match_tests(match_tests)
set_match_tests(match_tests)

skipped = []
for test_name in tests:
Expand Down
26 changes: 24 additions & 2 deletions Lib/test/libregrtest/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,35 @@
import json
from typing import Any

from test.support import TestStats

from .utils import (
StrJSON, TestName, FilterTuple,
format_duration, normalize_test_name, print_warning)


@dataclasses.dataclass(slots=True)
class TestStats:
tests_run: int = 0
failures: int = 0
skipped: int = 0

@staticmethod
def from_unittest(result):
return TestStats(result.testsRun,
len(result.failures),
len(result.skipped))

@staticmethod
def from_doctest(results):
return TestStats(results.attempted,
results.failed,
results.skipped)

def accumulate(self, stats):
self.tests_run += stats.tests_run
self.failures += stats.failures
self.skipped += stats.skipped


# Avoid enum.Enum to reduce the number of imports when tests are run
class State:
PASSED = "PASSED"
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/libregrtest/results.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import sys
from test.support import TestStats

from .runtests import RunTests
from .result import State, TestResult
from .result import State, TestResult, TestStats
from .utils import (
StrPath, TestName, TestTuple, TestList, FilterDict,
printlist, count, format_duration)
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/libregrtest/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from test import support
from test.support.os_helper import TESTFN_UNDECODABLE, FS_NONASCII

from .filter import set_match_tests
from .runtests import RunTests
from .utils import (
setup_unraisable_hook, setup_threading_excepthook, fix_umask,
Expand Down Expand Up @@ -92,11 +93,11 @@ def setup_tests(runtests: RunTests):
support.PGO = runtests.pgo
support.PGO_EXTENDED = runtests.pgo_extended

support.set_match_tests(runtests.match_tests)
set_match_tests(runtests.match_tests)

if runtests.use_junit:
support.junit_xml_list = []
from test.support.testresult import RegressionTestResult
from .testresult import RegressionTestResult
RegressionTestResult.USE_XML = True
else:
support.junit_xml_list = None
Expand Down
47 changes: 44 additions & 3 deletions Lib/test/libregrtest/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
import unittest

from test import support
from test.support import TestStats
from test.support import threading_helper

from .result import State, TestResult
from .filter import match_test
from .result import State, TestResult, TestStats
from .runtests import RunTests
from .save_env import saved_test_environment
from .setup import setup_tests
from .testresult import get_test_runner
from .utils import (
TestName,
clear_caches, remove_testfn, abs_module_name, print_warning)
Expand All @@ -33,7 +34,47 @@ def run_unittest(test_mod):
print(error, file=sys.stderr)
if loader.errors:
raise Exception("errors while loading tests")
return support.run_unittest(tests)
_filter_suite(tests, match_test)
return _run_suite(tests)

def _filter_suite(suite, pred):
"""Recursively filter test cases in a suite based on a predicate."""
newtests = []
for test in suite._tests:
if isinstance(test, unittest.TestSuite):
_filter_suite(test, pred)
newtests.append(test)
else:
if pred(test):
newtests.append(test)
suite._tests = newtests

def _run_suite(suite):
"""Run tests from a unittest.TestSuite-derived class."""
runner = get_test_runner(sys.stdout,
verbosity=support.verbose,
capture_output=(support.junit_xml_list is not None))

result = runner.run(suite)

if support.junit_xml_list is not None:
support.junit_xml_list.append(result.get_xml_element())

if not result.testsRun and not result.skipped and not result.errors:
raise support.TestDidNotRun
if not result.wasSuccessful():
stats = TestStats.from_unittest(result)
if len(result.errors) == 1 and not result.failures:
err = result.errors[0][1]
elif len(result.failures) == 1 and not result.errors:
err = result.failures[0][1]
else:
err = "multiple errors occurred"
if not verbose: err += "; run in verbose mode for details"
errors = [(str(tc), exc_str) for tc, exc_str in result.errors]
failures = [(str(tc), exc_str) for tc, exc_str in result.failures]
raise support.TestFailedWithDetails(err, errors, failures, stats=stats)
return result


def regrtest_runner(result: TestResult, test_func, runtests: RunTests) -> None:
Expand Down
File renamed without changes.
Loading

0 comments on commit f6a45a0

Please sign in to comment.