Skip to content

Commit

Permalink
Merge branch 'main' into fraction-format-d-presentation-type
Browse files Browse the repository at this point in the history
  • Loading branch information
mdickinson committed Oct 25, 2023
2 parents 5225f1b + f6a45a0 commit ef3ad07
Show file tree
Hide file tree
Showing 32 changed files with 527 additions and 471 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.0
rev: v0.1.2
hooks:
- id: ruff
name: Run Ruff on Lib/test/
Expand Down
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
1 change: 1 addition & 0 deletions Include/cpython/dictobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) {

PyAPI_FUNC(int) PyDict_ContainsString(PyObject *mp, const char *key);

PyAPI_FUNC(PyObject *) _PyDict_Pop(PyObject *dict, PyObject *key, PyObject *default_value);

/* Dictionary watchers */

Expand Down
42 changes: 42 additions & 0 deletions Include/cpython/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,45 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnicodeObject(PyObject *u, int base);
PyAPI_FUNC(int) PyUnstable_Long_IsCompact(const PyLongObject* op);
PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op);

/* _PyLong_FromByteArray: View the n unsigned bytes as a binary integer in
base 256, and return a Python int with the same numeric value.
If n is 0, the integer is 0. Else:
If little_endian is 1/true, bytes[n-1] is the MSB and bytes[0] the LSB;
else (little_endian is 0/false) bytes[0] is the MSB and bytes[n-1] the
LSB.
If is_signed is 0/false, view the bytes as a non-negative integer.
If is_signed is 1/true, view the bytes as a 2's-complement integer,
non-negative if bit 0x80 of the MSB is clear, negative if set.
Error returns:
+ Return NULL with the appropriate exception set if there's not
enough memory to create the Python int.
*/
PyAPI_FUNC(PyObject *) _PyLong_FromByteArray(
const unsigned char* bytes, size_t n,
int little_endian, int is_signed);

/* _PyLong_AsByteArray: Convert the least-significant 8*n bits of long
v to a base-256 integer, stored in array bytes. Normally return 0,
return -1 on error.
If little_endian is 1/true, store the MSB at bytes[n-1] and the LSB at
bytes[0]; else (little_endian is 0/false) store the MSB at bytes[0] and
the LSB at bytes[n-1].
If is_signed is 0/false, it's an error if v < 0; else (v >= 0) n bytes
are filled and there's nothing special about bit 0x80 of the MSB.
If is_signed is 1/true, bytes is filled with the 2's-complement
representation of v's value. Bit 0x80 of the MSB is the sign bit.
Error returns (-1):
+ is_signed is 0 and v < 0. TypeError is set in this case, and bytes
isn't altered.
+ n isn't big enough to hold the full mathematical value of v. For
example, if is_signed is 0 and there are more digits in the v than
fit in n; or if is_signed is 1, v < 0, and n is just 1 bit shy of
being large enough to hold a sign bit. OverflowError is set in this
case, but bytes holds the least-significant n bytes of the true value.
*/
PyAPI_FUNC(int) _PyLong_AsByteArray(PyLongObject* v,
unsigned char* bytes, size_t n,
int little_endian, int is_signed);

/* For use by the gcd function in mathmodule.c */
PyAPI_FUNC(PyObject *) _PyLong_GCD(PyObject *, PyObject *);
3 changes: 0 additions & 3 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ extern PyObject* _PyDict_NewPresized(Py_ssize_t minused);
// Export for '_ctypes' shared extension
PyAPI_FUNC(Py_ssize_t) _PyDict_SizeOf(PyDictObject *);

// Export for '_socket' shared extension (Windows remove_unusable_flags())
PyAPI_FUNC(PyObject*) _PyDict_Pop(PyObject *, PyObject *, PyObject *);

#define _PyDict_HasSplitTable(d) ((d)->ma_values != NULL)

/* Like PyDict_Merge, but override can be 0, 1 or 2. If override is 0,
Expand Down
46 changes: 0 additions & 46 deletions Include/internal/pycore_long.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,57 +128,11 @@ extern PyObject* _PyLong_FromBytes(const char *, Py_ssize_t, int);
// Export for '_datetime' shared extension.
PyAPI_DATA(PyObject*) _PyLong_DivmodNear(PyObject *, PyObject *);

// _PyLong_FromByteArray: View the n unsigned bytes as a binary integer in
// base 256, and return a Python int with the same numeric value.
// If n is 0, the integer is 0. Else:
// If little_endian is 1/true, bytes[n-1] is the MSB and bytes[0] the LSB;
// else (little_endian is 0/false) bytes[0] is the MSB and bytes[n-1] the
// LSB.
// If is_signed is 0/false, view the bytes as a non-negative integer.
// If is_signed is 1/true, view the bytes as a 2's-complement integer,
// non-negative if bit 0x80 of the MSB is clear, negative if set.
// Error returns:
// + Return NULL with the appropriate exception set if there's not
// enough memory to create the Python int.
//
// Export for '_multibytecodec' shared extension.
PyAPI_DATA(PyObject*) _PyLong_FromByteArray(
const unsigned char* bytes, size_t n,
int little_endian, int is_signed);

// _PyLong_AsByteArray: Convert the least-significant 8*n bits of long
// v to a base-256 integer, stored in array bytes. Normally return 0,
// return -1 on error.
// If little_endian is 1/true, store the MSB at bytes[n-1] and the LSB at
// bytes[0]; else (little_endian is 0/false) store the MSB at bytes[0] and
// the LSB at bytes[n-1].
// If is_signed is 0/false, it's an error if v < 0; else (v >= 0) n bytes
// are filled and there's nothing special about bit 0x80 of the MSB.
// If is_signed is 1/true, bytes is filled with the 2's-complement
// representation of v's value. Bit 0x80 of the MSB is the sign bit.
// Error returns (-1):
// + is_signed is 0 and v < 0. TypeError is set in this case, and bytes
// isn't altered.
// + n isn't big enough to hold the full mathematical value of v. For
// example, if is_signed is 0 and there are more digits in the v than
// fit in n; or if is_signed is 1, v < 0, and n is just 1 bit shy of
// being large enough to hold a sign bit. OverflowError is set in this
// case, but bytes holds the least-significant n bytes of the true value.
//
// Export for '_struct' shared extension.
PyAPI_DATA(int) _PyLong_AsByteArray(PyLongObject* v,
unsigned char* bytes, size_t n,
int little_endian, int is_signed);

// _PyLong_Format: Convert the long to a string object with given base,
// appending a base prefix of 0[box] if base is 2, 8 or 16.
// Export for '_tkinter' shared extension.
PyAPI_DATA(PyObject*) _PyLong_Format(PyObject *obj, int base);

// For use by the math.gcd() function.
// Export for 'math' shared extension.
PyAPI_DATA(PyObject*) _PyLong_GCD(PyObject *, PyObject *);

// Export for 'math' shared extension
PyAPI_DATA(PyObject*) _PyLong_Rshift(PyObject *, size_t);

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 ef3ad07

Please sign in to comment.