| import faulthandler | |
| import functools | |
| import gc | |
| import importlib | |
| import io | |
| import os | |
| import sys | |
| import time | |
| import traceback | |
| import unittest | |
| from test import support | |
| from test.support import os_helper | |
| from test.libregrtest.cmdline import Namespace | |
| from test.libregrtest.save_env import saved_test_environment | |
| from test.libregrtest.utils import clear_caches, format_duration, print_warning | |
| class TestResult: | |
| def __init__( | |
| self, | |
| name: str, | |
| duration_sec: float = 0.0, | |
| xml_data: list[str] | None = None, | |
| ) -> None: | |
| self.name = name | |
| self.duration_sec = duration_sec | |
| self.xml_data = xml_data | |
| def __str__(self) -> str: | |
| return f"{self.name} finished" | |
| class Passed(TestResult): | |
| def __str__(self) -> str: | |
| return f"{self.name} passed" | |
| class Failed(TestResult): | |
| def __init__( | |
| self, | |
| name: str, | |
| duration_sec: float = 0.0, | |
| xml_data: list[str] | None = None, | |
| errors: list[tuple[str, str]] | None = None, | |
| failures: list[tuple[str, str]] | None = None, | |
| ) -> None: | |
| super().__init__(name, duration_sec=duration_sec, xml_data=xml_data) | |
| self.errors = errors | |
| self.failures = failures | |
| def __str__(self) -> str: | |
| if self.errors and self.failures: | |
| le = len(self.errors) | |
| lf = len(self.failures) | |
| error_s = "error" + ("s" if le > 1 else "") | |
| failure_s = "failure" + ("s" if lf > 1 else "") | |
| return f"{self.name} failed ({le} {error_s}, {lf} {failure_s})" | |
| if self.errors: | |
| le = len(self.errors) | |
| error_s = "error" + ("s" if le > 1 else "") | |
| return f"{self.name} failed ({le} {error_s})" | |
| if self.failures: | |
| lf = len(self.failures) | |
| failure_s = "failure" + ("s" if lf > 1 else "") | |
| return f"{self.name} failed ({lf} {failure_s})" | |
| return f"{self.name} failed" | |
| class UncaughtException(Failed): | |
| def __str__(self) -> str: | |
| return f"{self.name} failed (uncaught exception)" | |
| class EnvChanged(Failed): | |
| def __str__(self) -> str: | |
| return f"{self.name} failed (env changed)" | |
| class RefLeak(Failed): | |
| def __str__(self) -> str: | |
| return f"{self.name} failed (reference leak)" | |
| class Skipped(TestResult): | |
| def __str__(self) -> str: | |
| return f"{self.name} skipped" | |
| class ResourceDenied(Skipped): | |
| def __str__(self) -> str: | |
| return f"{self.name} skipped (resource denied)" | |
| class Interrupted(TestResult): | |
| def __str__(self) -> str: | |
| return f"{self.name} interrupted" | |
| class ChildError(Failed): | |
| def __str__(self) -> str: | |
| return f"{self.name} crashed" | |
| class DidNotRun(TestResult): | |
| def __str__(self) -> str: | |
| return f"{self.name} ran no tests" | |
| class Timeout(Failed): | |
| def __str__(self) -> str: | |
| return f"{self.name} timed out ({format_duration(self.duration_sec)})" | |
| # Minimum duration of a test to display its duration or to mention that | |
| # the test is running in background | |
| PROGRESS_MIN_TIME = 30.0 # seconds | |
| # small set of tests to determine if we have a basically functioning interpreter | |
| # (i.e. if any of these fail, then anything else is likely to follow) | |
| STDTESTS = [ | |
| 'test_grammar', | |
| 'test_opcodes', | |
| 'test_dict', | |
| 'test_builtin', | |
| 'test_exceptions', | |
| 'test_types', | |
| 'test_unittest', | |
| 'test_doctest', | |
| 'test_doctest2', | |
| 'test_support' | |
| ] | |
| # set of tests that we don't want to be executed when using regrtest | |
| NOTTESTS = set() | |
| # used by --findleaks, store for gc.garbage | |
| FOUND_GARBAGE = [] | |
| def is_failed(result: TestResult, ns: Namespace) -> bool: | |
| if isinstance(result, EnvChanged): | |
| return ns.fail_env_changed | |
| return isinstance(result, Failed) | |
| def findtestdir(path=None): | |
| return path or os.path.dirname(os.path.dirname(__file__)) or os.curdir | |
| def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS): | |
| """Return a list of all applicable test modules.""" | |
| testdir = findtestdir(testdir) | |
| names = os.listdir(testdir) | |
| tests = [] | |
| others = set(stdtests) | nottests | |
| for name in names: | |
| mod, ext = os.path.splitext(name) | |
| if mod[:5] == "test_" and ext in (".py", "") and mod not in others: | |
| tests.append(mod) | |
| return stdtests + sorted(tests) | |
| def get_abs_module(ns: Namespace, test_name: str) -> str: | |
| if test_name.startswith('test.') or ns.testdir: | |
| return test_name | |
| else: | |
| # Import it from the test package | |
| return 'test.' + test_name | |
| def _runtest(ns: Namespace, test_name: str) -> TestResult: | |
| # Handle faulthandler timeout, capture stdout+stderr, XML serialization | |
| # and measure time. | |
| output_on_failure = ns.verbose3 | |
| use_timeout = (ns.timeout is not None) | |
| if use_timeout: | |
| faulthandler.dump_traceback_later(ns.timeout, exit=True) | |
| start_time = time.perf_counter() | |
| try: | |
| support.set_match_tests(ns.match_tests, ns.ignore_tests) | |
| support.junit_xml_list = xml_list = [] if ns.xmlpath else None | |
| if ns.failfast: | |
| support.failfast = True | |
| if output_on_failure: | |
| support.verbose = True | |
| stream = io.StringIO() | |
| orig_stdout = sys.stdout | |
| orig_stderr = sys.stderr | |
| try: | |
| sys.stdout = stream | |
| sys.stderr = stream | |
| result = _runtest_inner(ns, test_name, | |
| display_failure=False) | |
| if not isinstance(result, Passed): | |
| output = stream.getvalue() | |
| orig_stderr.write(output) | |
| orig_stderr.flush() | |
| finally: | |
| sys.stdout = orig_stdout | |
| sys.stderr = orig_stderr | |
| else: | |
| # Tell tests to be moderately quiet | |
| support.verbose = ns.verbose | |
| result = _runtest_inner(ns, test_name, | |
| display_failure=not ns.verbose) | |
| if xml_list: | |
| import xml.etree.ElementTree as ET | |
| result.xml_data = [ | |
| ET.tostring(x).decode('us-ascii') | |
| for x in xml_list | |
| ] | |
| result.duration_sec = time.perf_counter() - start_time | |
| return result | |
| finally: | |
| if use_timeout: | |
| faulthandler.cancel_dump_traceback_later() | |
| support.junit_xml_list = None | |
| def runtest(ns: Namespace, test_name: str) -> TestResult: | |
| """Run a single test. | |
| ns -- regrtest namespace of options | |
| test_name -- the name of the test | |
| Returns a TestResult sub-class depending on the kind of result received. | |
| If ns.xmlpath is not None, xml_data is a list containing each | |
| generated testsuite element. | |
| """ | |
| try: | |
| return _runtest(ns, test_name) | |
| except: | |
| if not ns.pgo: | |
| msg = traceback.format_exc() | |
| print(f"test {test_name} crashed -- {msg}", | |
| file=sys.stderr, flush=True) | |
| return Failed(test_name) | |
| def _test_module(the_module): | |
| loader = unittest.TestLoader() | |
| tests = loader.loadTestsFromModule(the_module) | |
| for error in loader.errors: | |
| print(error, file=sys.stderr) | |
| if loader.errors: | |
| raise Exception("errors while loading tests") | |
| support.run_unittest(tests) | |
| def save_env(ns: Namespace, test_name: str): | |
| return saved_test_environment(test_name, ns.verbose, ns.quiet, pgo=ns.pgo) | |
| def _runtest_inner2(ns: Namespace, test_name: str) -> bool: | |
| # Load the test function, run the test function, handle huntrleaks | |
| # and findleaks to detect leaks | |
| abstest = get_abs_module(ns, test_name) | |
| # remove the module from sys.module to reload it if it was already imported | |
| try: | |
| del sys.modules[abstest] | |
| except KeyError: | |
| pass | |
| the_module = importlib.import_module(abstest) | |
| if ns.huntrleaks: | |
| from test.libregrtest.refleak import dash_R | |
| # If the test has a test_main, that will run the appropriate | |
| # tests. If not, use normal unittest test loading. | |
| test_runner = getattr(the_module, "test_main", None) | |
| if test_runner is None: | |
| test_runner = functools.partial(_test_module, the_module) | |
| try: | |
| with save_env(ns, test_name): | |
| if ns.huntrleaks: | |
| # Return True if the test leaked references | |
| refleak = dash_R(ns, test_name, test_runner) | |
| else: | |
| test_runner() | |
| refleak = False | |
| finally: | |
| cleanup_test_droppings(test_name, ns.verbose) | |
| support.gc_collect() | |
| 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 | |
| FOUND_GARBAGE.extend(gc.garbage) | |
| gc.garbage.clear() | |
| support.reap_children() | |
| return refleak | |
| def _runtest_inner( | |
| ns: Namespace, test_name: str, display_failure: bool = True | |
| ) -> TestResult: | |
| # Detect environment changes, handle exceptions. | |
| # Reset the environment_altered flag to detect if a test altered | |
| # the environment | |
| support.environment_altered = False | |
| if ns.pgo: | |
| display_failure = False | |
| try: | |
| clear_caches() | |
| with save_env(ns, test_name): | |
| refleak = _runtest_inner2(ns, test_name) | |
| except support.ResourceDenied as msg: | |
| if not ns.quiet and not ns.pgo: | |
| print(f"{test_name} skipped -- {msg}", flush=True) | |
| return ResourceDenied(test_name) | |
| except unittest.SkipTest as msg: | |
| if not ns.quiet and not ns.pgo: | |
| print(f"{test_name} skipped -- {msg}", flush=True) | |
| return Skipped(test_name) | |
| except support.TestFailedWithDetails as exc: | |
| msg = f"test {test_name} failed" | |
| if display_failure: | |
| msg = f"{msg} -- {exc}" | |
| print(msg, file=sys.stderr, flush=True) | |
| return Failed(test_name, errors=exc.errors, failures=exc.failures) | |
| except support.TestFailed as exc: | |
| msg = f"test {test_name} failed" | |
| if display_failure: | |
| msg = f"{msg} -- {exc}" | |
| print(msg, file=sys.stderr, flush=True) | |
| return Failed(test_name) | |
| except support.TestDidNotRun: | |
| return DidNotRun(test_name) | |
| except KeyboardInterrupt: | |
| print() | |
| return Interrupted(test_name) | |
| except: | |
| if not ns.pgo: | |
| msg = traceback.format_exc() | |
| print(f"test {test_name} crashed -- {msg}", | |
| file=sys.stderr, flush=True) | |
| return UncaughtException(test_name) | |
| if refleak: | |
| return RefLeak(test_name) | |
| if support.environment_altered: | |
| return EnvChanged(test_name) | |
| return Passed(test_name) | |
| def cleanup_test_droppings(test_name: str, verbose: int) -> None: | |
| # First kill any dangling references to open files etc. | |
| # This can also issue some ResourceWarnings which would otherwise get | |
| # triggered during the following test run, and possibly produce failures. | |
| support.gc_collect() | |
| # Try to clean up junk commonly left behind. While tests shouldn't leave | |
| # any files or directories behind, when a test fails that can be tedious | |
| # for it to arrange. The consequences can be especially nasty on Windows, | |
| # since if a test leaves a file open, it cannot be deleted by name (while | |
| # there's nothing we can do about that here either, we can display the | |
| # name of the offending test, which is a real help). | |
| for name in (os_helper.TESTFN,): | |
| if not os.path.exists(name): | |
| continue | |
| if os.path.isdir(name): | |
| import shutil | |
| kind, nuker = "directory", shutil.rmtree | |
| elif os.path.isfile(name): | |
| kind, nuker = "file", os.unlink | |
| else: | |
| raise RuntimeError(f"os.path says {name!r} exists but is neither " | |
| f"directory nor file") | |
| if verbose: | |
| print_warning(f"{test_name} left behind {kind} {name!r}") | |
| support.environment_altered = True | |
| try: | |
| import stat | |
| # fix possible permissions problems that might prevent cleanup | |
| os.chmod(name, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) | |
| nuker(name) | |
| except Exception as exc: | |
| print_warning(f"{test_name} left behind {kind} {name!r} " | |
| f"and it couldn't be removed: {exc}") |