From 19b6241ef9fce0f5c48dd87b5940da2663b27f0f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 10:53:43 +0300 Subject: [PATCH 01/15] Update changed files from 3.13.7 -> 3.13.8 --- Lib/_android_support.py | 16 +- Lib/_collections_abc.py | 16 +- Lib/html/parser.py | 24 +- Lib/multiprocessing/forkserver.py | 15 +- Lib/multiprocessing/popen_spawn_posix.py | 4 + Lib/sysconfig/__init__.py | 24 +- Lib/test/_test_multiprocessing.py | 41 ++++ Lib/test/list_tests.py | 27 ++- Lib/test/mp_preload_main.py | 14 ++ Lib/test/support/__init__.py | 269 +++------------------ Lib/test/test_bytes.py | 126 ++++++---- Lib/test/test_difflib.py | 39 ++++ Lib/test/test_exceptions.py | 121 +++++----- Lib/test/test_genericalias.py | 228 +++++++++++++++--- Lib/test/test_htmlparser.py | 88 +++++-- Lib/test/test_locale.py | 5 +- Lib/test/test_long.py | 25 +- Lib/test/test_posix.py | 142 +++++++++--- Lib/test/test_pyexpat.py | 282 ++++++++++++++--------- Lib/test/test_site.py | 128 ++++++++-- Lib/test/test_sysconfig.py | 36 ++- Lib/test/test_typing.py | 82 +++---- 22 files changed, 1110 insertions(+), 642 deletions(-) create mode 100644 Lib/test/mp_preload_main.py diff --git a/Lib/_android_support.py b/Lib/_android_support.py index ae506f6a4..a439d03a1 100644 --- a/Lib/_android_support.py +++ b/Lib/_android_support.py @@ -29,15 +29,19 @@ def init_streams(android_log_write, stdout_prio, stderr_prio): global logcat logcat = Logcat(android_log_write) - - sys.stdout = TextLogStream( - stdout_prio, "python.stdout", sys.stdout.fileno()) - sys.stderr = TextLogStream( - stderr_prio, "python.stderr", sys.stderr.fileno()) + sys.stdout = TextLogStream(stdout_prio, "python.stdout", sys.stdout) + sys.stderr = TextLogStream(stderr_prio, "python.stderr", sys.stderr) class TextLogStream(io.TextIOWrapper): - def __init__(self, prio, tag, fileno=None, **kwargs): + def __init__(self, prio, tag, original=None, **kwargs): + # Respect the -u option. + if original: + kwargs.setdefault("write_through", original.write_through) + fileno = original.fileno() + else: + fileno = None + # The default is surrogateescape for stdout and backslashreplace for # stderr, but in the context of an Android log, readability is more # important than reversibility. diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index de624f2e5..6e224d360 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -512,10 +512,6 @@ class _CallableGenericAlias(GenericAlias): new_args = (t_args, t_result) return _CallableGenericAlias(Callable, tuple(new_args)) - # TODO: RUSTPYTHON patch for common call - def __or__(self, other): - super().__or__(other) - def _is_param_expr(obj): """Checks if obj matches either a list of types, ``...``, ``ParamSpec`` or ``_ConcatenateGenericAlias`` from typing.py @@ -1087,7 +1083,7 @@ class _DeprecateByteStringMeta(ABCMeta): warnings._deprecated( "collections.abc.ByteString", - remove=(3, 14), + remove=(3, 17), ) return super().__new__(cls, name, bases, namespace, **kwargs) @@ -1096,14 +1092,18 @@ class _DeprecateByteStringMeta(ABCMeta): warnings._deprecated( "collections.abc.ByteString", - remove=(3, 14), + remove=(3, 17), ) return super().__instancecheck__(instance) class ByteString(Sequence, metaclass=_DeprecateByteStringMeta): - """This unifies bytes and bytearray. + """Deprecated ABC serving as a common supertype of ``bytes`` and ``bytearray``. - XXX Should add all their methods. + This ABC is scheduled for removal in Python 3.17. + Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj`` + implements the buffer protocol at runtime. For use in type annotations, + either use ``Buffer`` or a union that explicitly specifies the types your + code supports (e.g., ``bytes | bytearray | memoryview``). """ __slots__ = () diff --git a/Lib/html/parser.py b/Lib/html/parser.py index 5d03c98df..5d7050dad 100644 --- a/Lib/html/parser.py +++ b/Lib/html/parser.py @@ -146,6 +146,7 @@ class HTMLParser(_markupbase.ParserBase): self.lasttag = '???' self.interesting = interesting_normal self.cdata_elem = None + self._support_cdata = True self._escapable = True super().reset() @@ -183,6 +184,19 @@ class HTMLParser(_markupbase.ParserBase): self.cdata_elem = None self._escapable = True + def _set_support_cdata(self, flag=True): + """Enable or disable support of the CDATA sections. + If enabled, "<[CDATA[" starts a CDATA section which ends with "]]>". + If disabled, "<[CDATA[" starts a bogus comments which ends with ">". + + This method is not called by default. Its purpose is to be called + in custom handle_starttag() and handle_endtag() methods, with + value that depends on the adjusted current node. + See https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state + for details. + """ + self._support_cdata = flag + # Internal -- handle data as far as reasonable. May leave state # and data to be processed by a subsequent call. If 'end' is # true, force handling all data as if followed by EOF marker. @@ -257,7 +271,7 @@ class HTMLParser(_markupbase.ParserBase): j -= len(suffix) break self.handle_comment(rawdata[i+4:j]) - elif startswith("', i+9) + if j < 0: + return -1 + self.unknown_decl(rawdata[i+3: j]) + return j + 3 elif rawdata[i:i+9].lower() == ' gtpos = rawdata.find('>', i+9) diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py index bff7fb91d..e243442e7 100644 --- a/Lib/multiprocessing/forkserver.py +++ b/Lib/multiprocessing/forkserver.py @@ -127,12 +127,13 @@ class ForkServer(object): cmd = ('from multiprocessing.forkserver import main; ' + 'main(%d, %d, %r, **%r)') + main_kws = {} if self._preload_modules: - desired_keys = {'main_path', 'sys_path'} data = spawn.get_preparation_data('ignore') - data = {x: y for x, y in data.items() if x in desired_keys} - else: - data = {} + if 'sys_path' in data: + main_kws['sys_path'] = data['sys_path'] + if 'init_main_from_path' in data: + main_kws['main_path'] = data['init_main_from_path'] with socket.socket(socket.AF_UNIX) as listener: address = connection.arbitrary_address('AF_UNIX') @@ -147,7 +148,7 @@ class ForkServer(object): try: fds_to_pass = [listener.fileno(), alive_r] cmd %= (listener.fileno(), alive_r, self._preload_modules, - data) + main_kws) exe = spawn.get_executable() args = [exe] + util._args_from_interpreter_flags() args += ['-c', cmd] @@ -182,6 +183,10 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None): except ImportError: pass + # gh-135335: flush stdout/stderr in case any of the preloaded modules + # wrote to them, otherwise children might inherit buffered data + util._flush_std_streams() + util._close_stdin() sig_r, sig_w = os.pipe() diff --git a/Lib/multiprocessing/popen_spawn_posix.py b/Lib/multiprocessing/popen_spawn_posix.py index 24b863452..cccd659ae 100644 --- a/Lib/multiprocessing/popen_spawn_posix.py +++ b/Lib/multiprocessing/popen_spawn_posix.py @@ -57,6 +57,10 @@ class Popen(popen_fork.Popen): self._fds.extend([child_r, child_w]) self.pid = util.spawnv_passfds(spawn.get_executable(), cmd, self._fds) + os.close(child_r) + child_r = None + os.close(child_w) + child_w = None self.sentinel = parent_r with open(parent_w, 'wb', closefd=False) as f: f.write(fp.getbuffer()) diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 3ad9df603..f7bd675bb 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -107,7 +107,7 @@ else: _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv'] def _get_implementation(): - return 'RustPython' # XXX: For site-packages + return 'Python' # NOTE: site.py has copy of this function. # Sync it when modify this function. @@ -596,18 +596,22 @@ def get_platform(): isn't particularly important. Examples of returned values: - linux-i586 - linux-alpha (?) - solaris-2.6-sun4u - Windows will return one of: - win-amd64 (64-bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) - win-arm64 (64-bit Windows on ARM64 (aka AArch64) - win32 (all others - specifically, sys.platform is returned) - For other non-POSIX platforms, currently just returns 'sys.platform'. + Windows: - """ + - win-amd64 (64-bit Windows on AMD64, aka x86_64, Intel64, and EM64T) + - win-arm64 (64-bit Windows on ARM64, aka AArch64) + - win32 (all others - specifically, sys.platform is returned) + + POSIX based OS: + + - linux-x86_64 + - macosx-15.5-arm64 + - macosx-26.0-universal2 (macOS on Apple Silicon or Intel) + - android-24-arm64_v8a + + For other non-POSIX platforms, currently just returns :data:`sys.platform`.""" if os.name == 'nt': if 'amd64' in sys.version.lower(): return 'win-amd64' diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 0b8de96f1..c22ce769c 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -6451,6 +6451,35 @@ class _TestSpawnedSysPath(BaseTestCase): self.assertEqual(child_sys_path[1:], sys.path[1:]) self.assertIsNone(import_error, msg=f"child could not import {self._mod_name}") + def test_std_streams_flushed_after_preload(self): + # gh-135335: Check fork server flushes standard streams after + # preloading modules + if multiprocessing.get_start_method() != "forkserver": + self.skipTest("forkserver specific test") + + # Create a test module in the temporary directory on the child's path + # TODO: This can all be simplified once gh-126631 is fixed and we can + # use __main__ instead of a module. + dirname = os.path.join(self._temp_dir, 'preloaded_module') + init_name = os.path.join(dirname, '__init__.py') + os.mkdir(dirname) + with open(init_name, "w") as f: + cmd = '''if 1: + import sys + print('stderr', end='', file=sys.stderr) + print('stdout', end='', file=sys.stdout) + ''' + f.write(cmd) + + name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py') + env = {'PYTHONPATH': self._temp_dir} + _, out, err = test.support.script_helper.assert_python_ok(name, **env) + + # Check stderr first, as it is more likely to be useful to see in the + # event of a failure. + self.assertEqual(err.decode().rstrip(), 'stderr') + self.assertEqual(out.decode().rstrip(), 'stdout') + class MiscTestCase(unittest.TestCase): def test__all__(self): @@ -6516,6 +6545,18 @@ class MiscTestCase(unittest.TestCase): self.assertEqual(q.get_nowait(), "done") close_queue(q) + def test_preload_main(self): + # gh-126631: Check that __main__ can be pre-loaded + if multiprocessing.get_start_method() != "forkserver": + self.skipTest("forkserver specific test") + + name = os.path.join(os.path.dirname(__file__), 'mp_preload_main.py') + _, out, err = test.support.script_helper.assert_python_ok(name) + self.assertEqual(err, b'') + + # The trailing empty string comes from split() on output ending with \n + out = out.decode().split("\n") + self.assertEqual(out, ['__main__', '__mp_main__', 'f', 'f', '']) # # Mixins diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py index 0e11af6f3..65dfa41b2 100644 --- a/Lib/test/list_tests.py +++ b/Lib/test/list_tests.py @@ -2,13 +2,11 @@ Tests common to list and UserList.UserList """ -import unittest import sys -import os from functools import cmp_to_key -from test import support, seq_tests -from test.support import ALWAYS_EQ, NEVER_EQ +from test import seq_tests +from test.support import ALWAYS_EQ, NEVER_EQ, get_c_recursion_limit class CommonTest(seq_tests.CommonTest): @@ -33,13 +31,13 @@ class CommonTest(seq_tests.CommonTest): self.assertEqual(a, b) def test_getitem_error(self): - a = [] + a = self.type2test([]) msg = "list indices must be integers or slices" with self.assertRaisesRegex(TypeError, msg): a['a'] def test_setitem_error(self): - a = [] + a = self.type2test([]) msg = "list indices must be integers or slices" with self.assertRaisesRegex(TypeError, msg): a['a'] = "python" @@ -63,7 +61,7 @@ class CommonTest(seq_tests.CommonTest): def test_repr_deep(self): a = self.type2test([]) - for i in range(sys.getrecursionlimit() + 100): + for i in range(get_c_recursion_limit() + 1): a = self.type2test([a]) self.assertRaises(RecursionError, repr, a) @@ -193,6 +191,14 @@ class CommonTest(seq_tests.CommonTest): self.assertRaises(TypeError, a.__setitem__) + def test_slice_assign_iterator(self): + x = self.type2test(range(5)) + x[0:3] = reversed(range(3)) + self.assertEqual(x, self.type2test([2, 1, 0, 3, 4])) + + x[:] = reversed(range(3)) + self.assertEqual(x, self.type2test([2, 1, 0])) + def test_delslice(self): a = self.type2test([0, 1]) del a[1:2] @@ -552,7 +558,7 @@ class CommonTest(seq_tests.CommonTest): class F(object): def __iter__(self): raise KeyboardInterrupt - self.assertRaises(KeyboardInterrupt, list, F()) + self.assertRaises(KeyboardInterrupt, self.type2test, F()) def test_exhausted_iterator(self): a = self.type2test([1, 2, 3]) @@ -564,3 +570,8 @@ class CommonTest(seq_tests.CommonTest): self.assertEqual(list(exhit), []) self.assertEqual(list(empit), [9]) self.assertEqual(a, self.type2test([1, 2, 3, 9])) + + # gh-115733: Crash when iterating over exhausted iterator + exhit = iter(self.type2test([1, 2, 3])) + for _ in exhit: + next(exhit, 1) diff --git a/Lib/test/mp_preload_main.py b/Lib/test/mp_preload_main.py new file mode 100644 index 000000000..acb342822 --- /dev/null +++ b/Lib/test/mp_preload_main.py @@ -0,0 +1,14 @@ +import multiprocessing + +print(f"{__name__}") + +def f(): + print("f") + +if __name__ == "__main__": + ctx = multiprocessing.get_context("forkserver") + ctx.set_forkserver_preload(['__main__']) + for _ in range(2): + p = ctx.Process(target=f) + p.start() + p.join() diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 88369e25c..4605938b8 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -305,6 +305,16 @@ def requires(resource, msg=None): if resource == 'gui' and not _is_gui_available(): raise ResourceDenied(_is_gui_available.reason) +def _get_kernel_version(sysname="Linux"): + import platform + if platform.system() != sysname: + return None + version_txt = platform.release().split('-', 1)[0] + try: + return tuple(map(int, version_txt.split('.'))) + except ValueError: + return None + def _requires_unix_version(sysname, min_version): """Decorator raising SkipTest if the OS is `sysname` and the version is less than `min_version`. @@ -501,8 +511,6 @@ def requires_lzma(reason='requires lzma'): import lzma except ImportError: lzma = None - # XXX: RUSTPYTHON; xz is not supported yet - lzma = None return unittest.skipUnless(lzma, reason) def has_no_debug_ranges(): @@ -521,25 +529,43 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): reason = e.args[0] if e.args else reason return unittest.skipIf(skip, reason) -@contextlib.contextmanager -def suppress_immortalization(suppress=True): - """Suppress immortalization of deferred objects.""" + +def can_use_suppress_immortalization(suppress=True): + """Check if suppress_immortalization(suppress) can be used. + + Use this helper in code where SkipTest must be eagerly handled. + """ + if not suppress: + return True try: import _testinternalcapi except ImportError: - yield - return + return False + return True + +@contextlib.contextmanager +def suppress_immortalization(suppress=True): + """Suppress immortalization of deferred objects. + + If _testinternalcapi is not available, the decorated test or class + is skipped. Use can_use_suppress_immortalization() outside test cases + to check if this decorator can be used. + """ if not suppress: - yield + yield # no-op return + from .import_helper import import_module + + _testinternalcapi = import_module("_testinternalcapi") _testinternalcapi.suppress_immortalization(True) try: yield finally: _testinternalcapi.suppress_immortalization(False) + def skip_if_suppress_immortalization(): try: import _testinternalcapi @@ -815,8 +841,6 @@ def gc_collect(): longer than expected. This function tries its best to force all garbage objects to disappear. """ - return # TODO: RUSTPYTHON - import gc gc.collect() gc.collect() @@ -824,13 +848,6 @@ def gc_collect(): @contextlib.contextmanager def disable_gc(): - # TODO: RUSTPYTHON; GC is not supported yet - try: - yield - finally: - pass - return - import gc have_gc = gc.isenabled() gc.disable() @@ -842,13 +859,6 @@ def disable_gc(): @contextlib.contextmanager def gc_threshold(*args): - # TODO: RUSTPYTHON; GC is not supported yet - try: - yield - finally: - pass - return - import gc old_threshold = gc.get_threshold() gc.set_threshold(*args) @@ -1645,7 +1655,7 @@ def check__all__(test_case, module, name_of_module=None, extra=(), 'module'. The 'name_of_module' argument can specify (as a string or tuple thereof) - what module(s) an API could be defined in in order to be detected as a + what module(s) an API could be defined in order to be detected as a public API. One case for this is when 'module' imports part of its public API from other modules, possibly a C backend (like 'csv' and its '_csv'). @@ -1911,10 +1921,6 @@ def _check_tracemalloc(): def check_free_after_iterating(test, iter, cls, args=()): - # TODO: RUSTPYTHON; GC is not supported yet - test.assertTrue(False) - return - done = False def wrapper(): class A(cls): @@ -2296,6 +2302,7 @@ def check_disallow_instantiation(testcase, tp, *args, **kwds): qualname = f"{name}" msg = f"cannot create '{re.escape(qualname)}' instances" testcase.assertRaisesRegex(TypeError, msg, tp, *args, **kwds) + testcase.assertRaisesRegex(TypeError, msg, tp.__new__, tp, *args, **kwds) def get_recursion_depth(): """Get the recursion depth of the caller function. @@ -2757,7 +2764,7 @@ def no_color(): from .os_helper import EnvironmentVarGuard with ( - swap_attr(_colorize, "can_colorize", lambda file=None: False), + swap_attr(_colorize, "can_colorize", lambda *, file=None: False), EnvironmentVarGuard() as env, ): env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS") @@ -2838,205 +2845,3 @@ def linked_to_musl(): except (OSError, subprocess.CalledProcessError): return False return ('musl' in stdout) - - -# TODO: RUSTPYTHON -# Every line of code below allowed us to update `Lib/test/support/__init__.py` without -# needing to update `libregtest` and its dependencies. -# Ideally we want to remove all code below and update `libregtest`. -# -# Code below was copied from: https://github.com/RustPython/RustPython/blob/9499d39f55b73535e2405bf208d5380241f79ada/Lib/test/support/__init__.py - -from .testresult import get_test_runner - -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 - -# By default, don't filter tests -_match_test_func = None - -_accept_test_patterns = None -_ignore_test_patterns = None - -def match_test(test): - # Function used by support.run_unittest() and regrtest --list-cases - if _match_test_func is None: - return True - else: - return _match_test_func(test.id()) - -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(accept_patterns=None, ignore_patterns=None): - global _match_test_func, _accept_test_patterns, _ignore_test_patterns - - if accept_patterns is None: - accept_patterns = () - if ignore_patterns is None: - ignore_patterns = () - - accept_func = ignore_func = None - - if accept_patterns != _accept_test_patterns: - accept_patterns, accept_func = _compile_match_function(accept_patterns) - if ignore_patterns != _ignore_test_patterns: - ignore_patterns, ignore_func = _compile_match_function(ignore_patterns) - - # Create a copy since patterns can be mutable and so modified later - _accept_test_patterns = tuple(accept_patterns) - _ignore_test_patterns = tuple(ignore_patterns) - - if accept_func is not None or ignore_func is not None: - def match_function(test_id): - accept = True - ignore = False - if accept_func: - accept = accept_func(test_id) - if ignore_func: - ignore = ignore_func(test_id) - return accept and not ignore - - _match_test_func = match_function - -def _compile_match_function(patterns): - if not patterns: - func = None - # set_match_tests(None) behaves as set_match_tests(()) - patterns = () - elif 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. - func = 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): - 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("."))) - - func = match_test_regex - - return patterns, func - -def run_unittest(*classes): - """Run tests from unittest.TestCase-derived classes.""" - valid_types = (unittest.TestSuite, unittest.TestCase) - loader = unittest.TestLoader() - suite = unittest.TestSuite() - for cls in classes: - if isinstance(cls, str): - if cls in sys.modules: - suite.addTest(loader.loadTestsFromModule(sys.modules[cls])) - else: - raise ValueError("str arguments must be keys in sys.modules") - elif isinstance(cls, valid_types): - suite.addTest(cls) - else: - suite.addTest(loader.loadTestsFromTestCase(cls)) - _filter_suite(suite, match_test) - return _run_suite(suite) - -def _run_suite(suite): - """Run tests from a unittest.TestSuite-derived class.""" - runner = get_test_runner(sys.stdout, - verbosity=verbose, - capture_output=(junit_xml_list is not None)) - - result = runner.run(suite) - - if junit_xml_list is not None: - junit_xml_list.append(result.get_xml_element()) - - if not result.testsRun and not result.skipped and not result.errors: - raise 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 TestFailedWithDetails(err, errors, failures, stats=stats) - return result - -@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) - - def accumulate(self, stats): - self.tests_run += stats.tests_run - self.failures += stats.failures - self.skipped += stats.skipped - - -def run_doctest(module, verbosity=None, optionflags=0): - """Run doctest on the given module. Return (#failures, #tests). - - If optional argument verbosity is not specified (or is None), pass - support's belief about verbosity on to doctest. Else doctest's - usual behavior is used (it searches sys.argv for -v). - """ - - import doctest - - if verbosity is None: - verbosity = verbose - else: - verbosity = None - - results = doctest.testmod(module, - verbose=verbosity, - optionflags=optionflags) - if results.failed: - stats = TestStats.from_doctest(results) - raise TestFailed(f"{results.failed} of {results.attempted} " - f"doctests failed", - stats=stats) - if verbose: - print('doctest (%s) ... %d tests with zero failures' % - (module.__name__, results.attempted)) - return results diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index e84df546a..0847afe01 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -201,8 +201,7 @@ class BaseBytesTest: self.assertRaises(ValueError, self.type2test, [sys.maxsize+1]) self.assertRaises(ValueError, self.type2test, [10**100]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @bigaddrspacetest def test_constructor_overflow(self): size = MAX_Py_ssize_t @@ -326,8 +325,7 @@ class BaseBytesTest: # Default encoding is utf-8 self.assertEqual(self.type2test(b'\xe2\x98\x83').decode(), '\u2603') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_check_encoding_errors(self): # bpo-37388: bytes(str) and bytes.encode() must check encoding # and errors arguments in dev mode @@ -972,8 +970,7 @@ class BaseBytesTest: self.assertRaises(ValueError, method, 256) self.assertRaises(ValueError, method, 9999) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_find_etc_raise_correct_error_messages(self): # issue 11828 b = self.type2test(b'hello') @@ -993,8 +990,7 @@ class BaseBytesTest: self.assertRaisesRegex(TypeError, r'\bendswith\b', b.endswith, x, None, None, None) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_free_after_iterating(self): test.support.check_free_after_iterating(self, iter, self.type2test) test.support.check_free_after_iterating(self, reversed, self.type2test) @@ -1583,11 +1579,6 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): self.assertEqual(b, b1) self.assertIs(b, b1) - # NOTE: RUSTPYTHON: - # - # The second instance of self.assertGreater was replaced with - # self.assertGreaterEqual since, in RustPython, the underlying storage - # is a Vec which doesn't require trailing null byte. def test_alloc(self): b = bytearray() alloc = b.__alloc__() @@ -1596,15 +1587,10 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): for i in range(100): b += b"x" alloc = b.__alloc__() - self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched + self.assertGreater(alloc, len(b)) # including trailing null byte if alloc not in seq: seq.append(alloc) - # NOTE: RUSTPYTHON: - # - # The usages of self.assertGreater were replaced with - # self.assertGreaterEqual since, in RustPython, the underlying storage - # is a Vec which doesn't require trailing null byte. def test_init_alloc(self): b = bytearray() def g(): @@ -1615,12 +1601,12 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): self.assertEqual(len(b), len(a)) self.assertLessEqual(len(b), i) alloc = b.__alloc__() - self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched + self.assertGreater(alloc, len(b)) # including trailing null byte b.__init__(g()) self.assertEqual(list(b), list(range(1, 100))) self.assertEqual(len(b), 99) alloc = b.__alloc__() - self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched + self.assertGreater(alloc, len(b)) def test_extend(self): orig = b'hello' @@ -1845,6 +1831,8 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): self.assertEqual(b3, b'xcxcxc') def test_mutating_index(self): + # bytearray slice assignment can call into python code + # that reallocates the internal buffer # See gh-91153 class Boom: @@ -1862,22 +1850,82 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): with self.assertRaises(IndexError): self._testlimitedcapi.sequence_setitem(b, 0, Boom()) + def test_mutating_index_inbounds(self): + # gh-91153 continued + # Ensure buffer is not broken even if length is correct + + class MutatesOnIndex: + def __init__(self): + self.ba = bytearray(0x180) + + def __index__(self): + self.ba.clear() + self.new_ba = bytearray(0x180) # to catch out-of-bounds writes + self.ba.extend([0] * 0x180) # to check bounds checks + return 0 + + with self.subTest("skip_bounds_safety"): + instance = MutatesOnIndex() + instance.ba[instance] = ord("?") + self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered") + self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered") + + with self.subTest("skip_bounds_safety_capi"): + instance = MutatesOnIndex() + instance.ba[instance] = ord("?") + self._testlimitedcapi.sequence_setitem(instance.ba, instance, ord("?")) + self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered") + self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered") + + with self.subTest("skip_bounds_safety_slice"): + instance = MutatesOnIndex() + instance.ba[instance:1] = [ord("?")] + self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered") + self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered") + class AssortedBytesTest(unittest.TestCase): # # Test various combinations of bytes and bytearray # + def test_bytes_repr(self, f=repr): + self.assertEqual(f(b''), "b''") + self.assertEqual(f(b"abc"), "b'abc'") + self.assertEqual(f(bytes([92])), r"b'\\'") + self.assertEqual(f(bytes([0, 1, 254, 255])), r"b'\x00\x01\xfe\xff'") + self.assertEqual(f(b'\a\b\t\n\v\f\r'), r"b'\x07\x08\t\n\x0b\x0c\r'") + self.assertEqual(f(b'"'), """b'"'""") # '"' + self.assertEqual(f(b"'"), '''b"'"''') # "'" + self.assertEqual(f(b"'\""), r"""b'\'"'""") # '\'"' + self.assertEqual(f(b"\"'\""), r"""b'"\'"'""") # '"\'"' + self.assertEqual(f(b"'\"'"), r"""b'\'"\''""") # '\'"\'' + self.assertEqual(f(BytesSubclass(b"abc")), "b'abc'") + + def test_bytearray_repr(self, f=repr): + self.assertEqual(f(bytearray()), "bytearray(b'')") + self.assertEqual(f(bytearray(b'abc')), "bytearray(b'abc')") + self.assertEqual(f(bytearray([92])), r"bytearray(b'\\')") + self.assertEqual(f(bytearray([0, 1, 254, 255])), + r"bytearray(b'\x00\x01\xfe\xff')") + self.assertEqual(f(bytearray([7, 8, 9, 10, 11, 12, 13])), + r"bytearray(b'\x07\x08\t\n\x0b\x0c\r')") + self.assertEqual(f(bytearray(b'"')), """bytearray(b'"')""") # '"' + self.assertEqual(f(bytearray(b"'")), r'''bytearray(b"\'")''') # "\'" + self.assertEqual(f(bytearray(b"'\"")), r"""bytearray(b'\'"')""") # '\'"' + self.assertEqual(f(bytearray(b"\"'\"")), r"""bytearray(b'"\'"')""") # '"\'"' + self.assertEqual(f(bytearray(b'\'"\'')), r"""bytearray(b'\'"\'')""") # '\'"\'' + self.assertEqual(f(ByteArraySubclass(b"abc")), "ByteArraySubclass(b'abc')") + self.assertEqual(f(ByteArraySubclass.Nested(b"abc")), "Nested(b'abc')") + self.assertEqual(f(ByteArraySubclass.Ŭñıçöđë(b"abc")), "Ŭñıçöđë(b'abc')") + @check_bytes_warnings - def test_repr_str(self): - for f in str, repr: - self.assertEqual(f(bytearray()), "bytearray(b'')") - self.assertEqual(f(bytearray([0])), "bytearray(b'\\x00')") - self.assertEqual(f(bytearray([0, 1, 254, 255])), - "bytearray(b'\\x00\\x01\\xfe\\xff')") - self.assertEqual(f(b"abc"), "b'abc'") - self.assertEqual(f(b"'"), '''b"'"''') # ''' - self.assertEqual(f(b"'\""), r"""b'\'"'""") # ' + def test_bytes_str(self): + self.test_bytes_repr(str) + + @check_bytes_warnings + def test_bytearray_str(self): + self.test_bytearray_repr(str) @check_bytes_warnings def test_format(self): @@ -1930,15 +1978,6 @@ class AssortedBytesTest(unittest.TestCase): b = bytearray(buf) self.assertEqual(b, bytearray(sample)) - @check_bytes_warnings - def test_to_str(self): - self.assertEqual(str(b''), "b''") - self.assertEqual(str(b'x'), "b'x'") - self.assertEqual(str(b'\x80'), "b'\\x80'") - self.assertEqual(str(bytearray(b'')), "bytearray(b'')") - self.assertEqual(str(bytearray(b'x')), "bytearray(b'x')") - self.assertEqual(str(bytearray(b'\x80')), "bytearray(b'\\x80')") - def test_literal(self): tests = [ (b"Wonderful spam", "Wonderful spam"), @@ -2089,7 +2128,7 @@ class SubclassTest: s3 = s1.join([b"abcd"]) self.assertIs(type(s3), self.basetype) - @unittest.skip("TODO: RUSTPYTHON, Fails on ByteArraySubclassWithSlotsTest") + @unittest.skip('TODO: RUSTPYTHON; Fails on ByteArraySubclassWithSlotsTest') def test_pickle(self): a = self.type2test(b"abcd") a.x = 10 @@ -2104,7 +2143,7 @@ class SubclassTest: self.assertEqual(type(a.z), type(b.z)) self.assertFalse(hasattr(b, 'y')) - @unittest.skip("TODO: RUSTPYTHON, Fails on ByteArraySubclassWithSlotsTest") + @unittest.skip('TODO: RUSTPYTHON; Fails on ByteArraySubclassWithSlotsTest') def test_copy(self): a = self.type2test(b"abcd") a.x = 10 @@ -2148,7 +2187,10 @@ class SubclassTest: class ByteArraySubclass(bytearray): - pass + class Nested(bytearray): + pass + class Ŭñıçöđë(bytearray): + pass class ByteArraySubclassWithSlots(bytearray): __slots__ = ('x', 'y', '__dict__') diff --git a/Lib/test/test_difflib.py b/Lib/test/test_difflib.py index 6afd90af8..943d7a659 100644 --- a/Lib/test/test_difflib.py +++ b/Lib/test/test_difflib.py @@ -29,6 +29,16 @@ class TestWithAscii(unittest.TestCase): ('delete', 40, 41, 40, 40), ('equal', 41, 81, 40, 80)]) + def test_opcode_caching(self): + sm = difflib.SequenceMatcher(None, 'b' * 100, 'a' + 'b' * 100) + opcode = sm.get_opcodes() + self.assertEqual(opcode, + [ ('insert', 0, 0, 0, 1), + ('equal', 0, 100, 1, 101)]) + # Implementation detail: opcodes are cached; + # `get_opcodes()` returns the same object + self.assertIs(opcode, sm.get_opcodes()) + def test_bjunk(self): sm = difflib.SequenceMatcher(isjunk=lambda x: x == ' ', a='a' * 40 + 'b' * 40, b='a' * 44 + 'b' * 40) @@ -273,6 +283,15 @@ class TestSFpatches(unittest.TestCase): self.assertIn('ımplıcıt', output) + def test_one_insert(self): + m = difflib.Differ().compare('b' * 2, 'a' + 'b' * 2) + self.assertEqual(list(m), ['+ a', ' b', ' b']) + + def test_one_delete(self): + m = difflib.Differ().compare('a' + 'b' * 2, 'b' * 2) + self.assertEqual(list(m), ['- a', ' b', ' b']) + + class TestOutputFormat(unittest.TestCase): def test_tab_delimiter(self): args = ['one', 'two', 'Original', 'Current', @@ -547,6 +566,26 @@ class TestFindLongest(unittest.TestCase): self.assertFalse(self.longer_match_exists(a, b, match.size)) +class TestCloseMatches(unittest.TestCase): + # Happy paths are tested in the doctests of `difflib.get_close_matches`. + + def test_invalid_inputs(self): + self.assertRaises(ValueError, difflib.get_close_matches, "spam", ['egg'], n=0) + self.assertRaises(ValueError, difflib.get_close_matches, "spam", ['egg'], n=-1) + self.assertRaises(ValueError, difflib.get_close_matches, "spam", ['egg'], cutoff=1.1) + self.assertRaises(ValueError, difflib.get_close_matches, "spam", ['egg'], cutoff=-0.1) + + +class TestRestore(unittest.TestCase): + # Happy paths are tested in the doctests of `difflib.restore`. + + def test_invalid_input(self): + with self.assertRaises(ValueError): + ''.join(difflib.restore([], 0)) + with self.assertRaises(ValueError): + ''.join(difflib.restore([], 3)) + + def setUpModule(): difflib.HtmlDiff._default_prefix = 0 diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 57afb6ec6..eee8c1338 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -60,8 +60,7 @@ class ExceptionTests(unittest.TestCase): self.assertEqual(buf1, buf2) self.assertEqual(exc.__name__, excname) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testRaising(self): self.raise_catch(AttributeError, "AttributeError") self.assertRaises(AttributeError, getattr, sys, "undefined_attribute") @@ -146,8 +145,7 @@ class ExceptionTests(unittest.TestCase): self.raise_catch(StopAsyncIteration, "StopAsyncIteration") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testSyntaxErrorMessage(self): # make sure the right exception message is raised for each of # these code fragments @@ -172,8 +170,7 @@ class ExceptionTests(unittest.TestCase): ckmsg("continue\n", "'continue' not properly in loop") ckmsg("f'{6 0}'", "invalid syntax. Perhaps you forgot a comma?") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testSyntaxErrorMissingParens(self): def ckmsg(src, msg, exception=SyntaxError): try: @@ -232,14 +229,12 @@ class ExceptionTests(unittest.TestCase): line = src.split('\n')[lineno-1] self.assertIn(line, cm.exception.text) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_error_offset_continuation_characters(self): check = self.check check('"\\\n"(1 for c in I,\\\n\\', 2, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testSyntaxErrorOffset(self): check = self.check check('def fact(x):\n\treturn x!\n', 2, 10) @@ -443,8 +438,7 @@ class ExceptionTests(unittest.TestCase): with self.assertRaisesRegex(OSError, 'Windows Error 0x%x' % code): ctypes.pythonapi.PyErr_SetFromWindowsErr(code) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def testAttributes(self): # test that exception attributes are happy @@ -607,8 +601,7 @@ class ExceptionTests(unittest.TestCase): with self.assertRaisesRegex(TypeError, "state is not a dictionary"): e.__setstate__(42) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_notes(self): for e in [BaseException(1), Exception(2), ValueError(3)]: with self.subTest(e=e): @@ -665,8 +658,7 @@ class ExceptionTests(unittest.TestCase): else: self.fail("No exception raised") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_setattr(self): TE = TypeError exc = Exception() @@ -679,8 +671,7 @@ class ExceptionTests(unittest.TestCase): msg = "exception context must be None or derive from BaseException" self.assertRaisesRegex(TE, msg, setattr, exc, '__context__', 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_delattr(self): TE = TypeError try: @@ -752,8 +743,8 @@ class ExceptionTests(unittest.TestCase): x = DerivedException(fancy_arg=42) self.assertEqual(x.fancy_arg, 42) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; Windows') @no_tracing - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows') def testInfiniteRecursion(self): def f(): return f() @@ -1161,8 +1152,6 @@ class ExceptionTests(unittest.TestCase): self.assertIs(c.__context__, b) self.assertIsNone(b.__context__) - # TODO: RUSTPYTHON - @unittest.skip("Infinite loop") def test_no_hang_on_context_chain_cycle1(self): # See issue 25782. Cycle in context chain. @@ -1218,8 +1207,6 @@ class ExceptionTests(unittest.TestCase): self.assertIs(b.__context__, a) self.assertIs(a.__context__, c) - # TODO: RUSTPYTHON - @unittest.skip("Infinite loop") def test_no_hang_on_context_chain_cycle3(self): # See issue 25782. Longer context chain with cycle. @@ -1317,8 +1304,7 @@ class ExceptionTests(unittest.TestCase): self.assertIs(exc, oe) self.assertIs(exc.__context__, ve) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unicode_change_attributes(self): # See issue 7309. This was a crasher. @@ -1362,8 +1348,7 @@ class ExceptionTests(unittest.TestCase): for klass in klasses: self.assertEqual(str(klass.__new__(klass)), "") - # TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust usize - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust usize def test_unicode_error_str_does_not_crash(self): # Test that str(UnicodeError(...)) does not crash. # See https://github.com/python/cpython/issues/123378. @@ -1387,8 +1372,8 @@ class ExceptionTests(unittest.TestCase): exc = UnicodeDecodeError('utf-8', encoded, start, end, '') self.assertIsInstance(str(exc), str) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; Windows') @no_tracing - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows') def test_badisinstance(self): # Bug #2542: if issubclass(e, MyException) raises an exception, # it should be ignored @@ -1669,7 +1654,7 @@ class ExceptionTests(unittest.TestCase): gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) - @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON Windows') + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; Windows') @no_tracing def test_recursion_error_cleanup(self): # Same test as above, but with "recursion exceeded" errors @@ -1691,7 +1676,6 @@ class ExceptionTests(unittest.TestCase): gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) - @unittest.skipIf(sys.platform == 'win32', 'error specific to cpython') def test_errno_ENOTDIR(self): # Issue #12802: "not a directory" errors are ENOTDIR even on Windows with self.assertRaises(OSError) as cm: @@ -1714,8 +1698,7 @@ class ExceptionTests(unittest.TestCase): self.assertEqual(cm.unraisable.object, BrokenDel.__del__) self.assertIsNotNone(cm.unraisable.exc_traceback) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unhandled(self): # Check for sensible reporting of unhandled exceptions for exc_type in (ValueError, BrokenStrException): @@ -1814,8 +1797,7 @@ class ExceptionTests(unittest.TestCase): next(i) next(i) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(__debug__, "Won't work if __debug__ is False") def test_assert_shadowing(self): # Shadowing AssertionError would cause the assert statement to @@ -1877,6 +1859,38 @@ class ExceptionTests(unittest.TestCase): rc, _, err = script_helper.assert_python_ok("-c", code) self.assertIn(b'MemoryError', err) + @cpython_only + # Python built with Py_TRACE_REFS fail with a fatal error in + # _PyRefchain_Trace() on memory allocation error. + @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + def test_exec_set_nomemory_hang(self): + import_module("_testcapi") + # gh-134163: A MemoryError inside code that was wrapped by a try/except + # block would lead to an infinite loop. + + # The frame_lasti needs to be greater than 257 to prevent + # PyLong_FromLong() from returning cached integers, which + # don't require a memory allocation. Prepend some dummy code + # to artificially increase the instruction index. + warmup_code = "a = list(range(0, 1))\n" * 20 + user_input = warmup_code + dedent(""" + try: + import _testcapi + _testcapi.set_nomemory(0) + b = list(range(1000, 2000)) + except Exception as e: + import traceback + traceback.print_exc() + """) + with SuppressCrashReport(): + with script_helper.spawn_python('-c', user_input) as p: + p.wait() + output = p.stdout.read() + + self.assertIn(p.returncode, (0, 1)) + self.assertGreater(len(output), 0) # At minimum, should not hang + self.assertIn(b"MemoryError", output) + class NameErrorTests(unittest.TestCase): def test_name_error_has_name(self): @@ -1973,8 +1987,7 @@ class AttributeErrorTests(unittest.TestCase): class ImportErrorTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_attributes(self): # Setting 'name' and 'path' should not be a problem. exc = ImportError('test') @@ -2064,8 +2077,7 @@ class AssertionErrorTests(unittest.TestCase): def tearDown(self): unlink(TESTFN) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @force_not_colorized def test_assertion_error_location(self): cases = [ @@ -2164,8 +2176,7 @@ class AssertionErrorTests(unittest.TestCase): result = run_script(source) self.assertEqual(result[-3:], expected) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @force_not_colorized def test_multiline_not_highlighted(self): cases = [ @@ -2202,8 +2213,7 @@ class AssertionErrorTests(unittest.TestCase): class SyntaxErrorTests(unittest.TestCase): maxDiff = None - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @force_not_colorized def test_range_of_offsets(self): cases = [ @@ -2310,8 +2320,7 @@ class SyntaxErrorTests(unittest.TestCase): ^^^^^ """, err.getvalue()) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_encodings(self): self.addCleanup(unlink, TESTFN) source = ( @@ -2328,16 +2337,14 @@ class SyntaxErrorTests(unittest.TestCase): self.assertEqual(err[-3], ' (') self.assertEqual(err[-2], ' ^') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_non_utf8(self): # Check non utf-8 characters self.addCleanup(unlink, TESTFN) err = run_script(b"\x89") self.assertIn("SyntaxError: Non-UTF-8 code starting with '\\x89' in file", err[-1]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_string_source(self): def try_compile(source): with self.assertRaises(SyntaxError) as cm: @@ -2380,8 +2387,7 @@ class SyntaxErrorTests(unittest.TestCase): self.assertEqual(exc.offset, 1) self.assertEqual(exc.end_offset, 12) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_file_source(self): self.addCleanup(unlink, TESTFN) err = run_script('return "ä"') @@ -2444,8 +2450,7 @@ class SyntaxErrorTests(unittest.TestCase): self.assertEqual(error, the_exception.text) self.assertEqual("bad bad", the_exception.msg) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_incorrect_constructor(self): args = ("bad.py", 1, 2) self.assertRaises(TypeError, SyntaxError, "bad bad", args) @@ -2507,8 +2512,7 @@ class PEP626Tests(unittest.TestCase): pass self.lineno_after_raise(in_except, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_lineno_after_other_except(self): def other_except(): try: @@ -2526,8 +2530,7 @@ class PEP626Tests(unittest.TestCase): pass self.lineno_after_raise(in_named_except, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_lineno_in_try(self): def in_try(): try: @@ -2554,8 +2557,7 @@ class PEP626Tests(unittest.TestCase): pass self.lineno_after_raise(in_finally_except, 4) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_lineno_after_with(self): class Noop: def __enter__(self): @@ -2568,8 +2570,7 @@ class PEP626Tests(unittest.TestCase): pass self.lineno_after_raise(after_with, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_missing_lineno_shows_as_none(self): def f(): 1/0 diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 0daaff099..4a87e2bf7 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -2,6 +2,7 @@ import unittest import pickle +from array import array import copy from collections import ( defaultdict, deque, OrderedDict, Counter, UserDict, UserList @@ -11,8 +12,11 @@ from concurrent.futures import Future from concurrent.futures.thread import _WorkItem from contextlib import AbstractContextManager, AbstractAsyncContextManager from contextvars import ContextVar, Token +from csv import DictReader, DictWriter from dataclasses import Field from functools import partial, partialmethod, cached_property +from graphlib import TopologicalSorter +from logging import LoggerAdapter, StreamHandler from mailbox import Mailbox, _PartialFile try: import ctypes @@ -23,29 +27,73 @@ from filecmp import dircmp from fileinput import FileInput from itertools import chain from http.cookies import Morsel -from multiprocessing.managers import ValueProxy -from multiprocessing.pool import ApplyResult +try: + from multiprocessing.managers import ValueProxy, DictProxy, ListProxy + from multiprocessing.pool import ApplyResult + from multiprocessing.queues import SimpleQueue as MPSimpleQueue + from multiprocessing.queues import Queue as MPQueue + from multiprocessing.queues import JoinableQueue as MPJoinableQueue +except ImportError: + # _multiprocessing module is optional + ValueProxy = None + DictProxy = None + ListProxy = None + ApplyResult = None + MPSimpleQueue = None + MPQueue = None + MPJoinableQueue = None try: from multiprocessing.shared_memory import ShareableList except ImportError: # multiprocessing.shared_memory is not available on e.g. Android ShareableList = None -from multiprocessing.queues import SimpleQueue as MPSimpleQueue from os import DirEntry from re import Pattern, Match -from types import GenericAlias, MappingProxyType, AsyncGeneratorType +from types import GenericAlias, MappingProxyType, AsyncGeneratorType, CoroutineType, GeneratorType from tempfile import TemporaryDirectory, SpooledTemporaryFile from urllib.parse import SplitResult, ParseResult from unittest.case import _AssertRaisesContext from queue import Queue, SimpleQueue from weakref import WeakSet, ReferenceType, ref import typing +from typing import Unpack from typing import TypeVar T = TypeVar('T') K = TypeVar('K') V = TypeVar('V') +_UNPACKED_TUPLES = [ + # Unpacked tuple using `*` + (*tuple[int],)[0], + (*tuple[T],)[0], + (*tuple[int, str],)[0], + (*tuple[int, ...],)[0], + (*tuple[T, ...],)[0], + tuple[*tuple[int, ...]], + tuple[*tuple[T, ...]], + tuple[str, *tuple[int, ...]], + tuple[*tuple[int, ...], str], + tuple[float, *tuple[int, ...], str], + tuple[*tuple[*tuple[int, ...]]], + # Unpacked tuple using `Unpack` + Unpack[tuple[int]], + Unpack[tuple[T]], + Unpack[tuple[int, str]], + Unpack[tuple[int, ...]], + Unpack[tuple[T, ...]], + tuple[Unpack[tuple[int, ...]]], + tuple[Unpack[tuple[T, ...]]], + tuple[str, Unpack[tuple[int, ...]]], + tuple[Unpack[tuple[int, ...]], str], + tuple[float, Unpack[tuple[int, ...]], str], + tuple[Unpack[tuple[Unpack[tuple[int, ...]]]]], + # Unpacked tuple using `*` AND `Unpack` + tuple[Unpack[tuple[*tuple[int, ...]]]], + tuple[*tuple[Unpack[tuple[int, ...]]]], +] + + class BaseTest(unittest.TestCase): """Test basics.""" generic_types = [type, tuple, list, dict, set, frozenset, enumerate, @@ -56,6 +104,7 @@ class BaseTest(unittest.TestCase): OrderedDict, Counter, UserDict, UserList, Pattern, Match, partial, partialmethod, cached_property, + TopologicalSorter, AbstractContextManager, AbstractAsyncContextManager, Awaitable, Coroutine, AsyncIterable, AsyncIterator, @@ -71,19 +120,25 @@ class BaseTest(unittest.TestCase): KeysView, ItemsView, ValuesView, Sequence, MutableSequence, MappingProxyType, AsyncGeneratorType, + GeneratorType, CoroutineType, DirEntry, chain, + LoggerAdapter, StreamHandler, TemporaryDirectory, SpooledTemporaryFile, Queue, SimpleQueue, _AssertRaisesContext, SplitResult, ParseResult, - ValueProxy, ApplyResult, WeakSet, ReferenceType, ref, - ShareableList, MPSimpleQueue, + ShareableList, Future, _WorkItem, - Morsel] + Morsel, + DictReader, DictWriter, + array] if ctypes is not None: generic_types.extend((ctypes.Array, ctypes.LibraryLoader)) + if ValueProxy is not None: + generic_types.extend((ValueProxy, DictProxy, ListProxy, ApplyResult, + MPSimpleQueue, MPQueue, MPJoinableQueue)) def test_subscriptable(self): for t in self.generic_types: @@ -100,7 +155,7 @@ class BaseTest(unittest.TestCase): for t in int, str, float, Sized, Hashable: tname = t.__name__ with self.subTest(f"Testing {tname}"): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, tname): t[int] def test_instantiate(self): @@ -157,12 +212,26 @@ class BaseTest(unittest.TestCase): def test_repr(self): class MyList(list): pass + class MyGeneric: + __class_getitem__ = classmethod(GenericAlias) + self.assertEqual(repr(list[str]), 'list[str]') self.assertEqual(repr(list[()]), 'list[()]') self.assertEqual(repr(tuple[int, ...]), 'tuple[int, ...]') + x1 = tuple[*tuple[int]] + self.assertEqual(repr(x1), 'tuple[*tuple[int]]') + x2 = tuple[*tuple[int, str]] + self.assertEqual(repr(x2), 'tuple[*tuple[int, str]]') + x3 = tuple[*tuple[int, ...]] + self.assertEqual(repr(x3), 'tuple[*tuple[int, ...]]') self.assertTrue(repr(MyList[int]).endswith('.BaseTest.test_repr..MyList[int]')) self.assertEqual(repr(list[str]()), '[]') # instances should keep their normal repr + # gh-105488 + self.assertTrue(repr(MyGeneric[int]).endswith('MyGeneric[int]')) + self.assertTrue(repr(MyGeneric[[]]).endswith('MyGeneric[[]]')) + self.assertTrue(repr(MyGeneric[[int, str]]).endswith('MyGeneric[[int, str]]')) + def test_exposed_type(self): import types a = types.GenericAlias(list, int) @@ -173,6 +242,7 @@ class BaseTest(unittest.TestCase): def test_parameters(self): from typing import List, Dict, Callable + D0 = dict[str, int] self.assertEqual(D0.__args__, (str, int)) self.assertEqual(D0.__parameters__, ()) @@ -188,6 +258,7 @@ class BaseTest(unittest.TestCase): D2b = dict[T, T] self.assertEqual(D2b.__args__, (T, T)) self.assertEqual(D2b.__parameters__, (T,)) + L0 = list[str] self.assertEqual(L0.__args__, (str,)) self.assertEqual(L0.__parameters__, ()) @@ -210,6 +281,27 @@ class BaseTest(unittest.TestCase): self.assertEqual(L5.__args__, (Callable[[K, V], K],)) self.assertEqual(L5.__parameters__, (K, V)) + T1 = tuple[*tuple[int]] + self.assertEqual( + T1.__args__, + (*tuple[int],), + ) + self.assertEqual(T1.__parameters__, ()) + + T2 = tuple[*tuple[T]] + self.assertEqual( + T2.__args__, + (*tuple[T],), + ) + self.assertEqual(T2.__parameters__, (T,)) + + T4 = tuple[*tuple[int, str]] + self.assertEqual( + T4.__args__, + (*tuple[int, str],), + ) + self.assertEqual(T4.__parameters__, ()) + def test_parameter_chaining(self): from typing import List, Dict, Union, Callable self.assertEqual(list[T][int], list[int]) @@ -233,16 +325,23 @@ class BaseTest(unittest.TestCase): with self.assertRaises(TypeError): list[int][int] + with self.assertRaises(TypeError): dict[T, int][str, int] + with self.assertRaises(TypeError): dict[str, T][str, int] + with self.assertRaises(TypeError): dict[T, T][str, int] def test_equality(self): self.assertEqual(list[int], list[int]) self.assertEqual(dict[str, int], dict[str, int]) + self.assertEqual((*tuple[int],)[0], (*tuple[int],)[0]) + self.assertEqual(tuple[*tuple[int]], tuple[*tuple[int]]) self.assertNotEqual(dict[str, int], dict[str, str]) self.assertNotEqual(list, list[int]) self.assertNotEqual(list[int], list) + self.assertNotEqual(list[int], tuple[int]) + self.assertNotEqual((*tuple[int],)[0], tuple[int]) def test_isinstance(self): self.assertTrue(isinstance([], list)) @@ -266,17 +365,20 @@ class BaseTest(unittest.TestCase): def test_type_subclass_generic(self): class MyType(type): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, 'MyType'): MyType[int] def test_pickle(self): - alias = GenericAlias(list, T) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - s = pickle.dumps(alias, proto) - loaded = pickle.loads(s) - self.assertEqual(loaded.__origin__, alias.__origin__) - self.assertEqual(loaded.__args__, alias.__args__) - self.assertEqual(loaded.__parameters__, alias.__parameters__) + aliases = [GenericAlias(list, T)] + _UNPACKED_TUPLES + for alias in aliases: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(alias=alias, proto=proto): + s = pickle.dumps(alias, proto) + loaded = pickle.loads(s) + self.assertEqual(loaded.__origin__, alias.__origin__) + self.assertEqual(loaded.__args__, alias.__args__) + self.assertEqual(loaded.__parameters__, alias.__parameters__) + self.assertEqual(type(loaded), type(alias)) def test_copy(self): class X(list): @@ -285,16 +387,30 @@ class BaseTest(unittest.TestCase): def __deepcopy__(self, memo): return self - for origin in list, deque, X: - alias = GenericAlias(origin, T) - copied = copy.copy(alias) - self.assertEqual(copied.__origin__, alias.__origin__) - self.assertEqual(copied.__args__, alias.__args__) - self.assertEqual(copied.__parameters__, alias.__parameters__) - copied = copy.deepcopy(alias) - self.assertEqual(copied.__origin__, alias.__origin__) - self.assertEqual(copied.__args__, alias.__args__) - self.assertEqual(copied.__parameters__, alias.__parameters__) + aliases = [ + GenericAlias(list, T), + GenericAlias(deque, T), + GenericAlias(X, T), + X[T], + list[T], + deque[T], + ] + _UNPACKED_TUPLES + for alias in aliases: + with self.subTest(alias=alias): + copied = copy.copy(alias) + self.assertEqual(copied.__origin__, alias.__origin__) + self.assertEqual(copied.__args__, alias.__args__) + self.assertEqual(copied.__parameters__, alias.__parameters__) + copied = copy.deepcopy(alias) + self.assertEqual(copied.__origin__, alias.__origin__) + self.assertEqual(copied.__args__, alias.__args__) + self.assertEqual(copied.__parameters__, alias.__parameters__) + + def test_unpack(self): + alias = tuple[str, ...] + self.assertIs(alias.__unpacked__, False) + unpacked = (*alias,)[0] + self.assertIs(unpacked.__unpacked__, True) def test_union(self): a = typing.Union[list[int], list[str]] @@ -307,10 +423,26 @@ class BaseTest(unittest.TestCase): self.assertEqual(a.__parameters__, (T,)) def test_dir(self): - dir_of_gen_alias = set(dir(list[int])) + ga = list[int] + dir_of_gen_alias = set(dir(ga)) self.assertTrue(dir_of_gen_alias.issuperset(dir(list))) - for generic_alias_property in ("__origin__", "__args__", "__parameters__"): - self.assertIn(generic_alias_property, dir_of_gen_alias) + for generic_alias_property in ( + "__origin__", "__args__", "__parameters__", + "__unpacked__", + ): + with self.subTest(generic_alias_property=generic_alias_property): + self.assertIn(generic_alias_property, dir_of_gen_alias) + for blocked in ( + "__bases__", + "__copy__", + "__deepcopy__", + ): + with self.subTest(blocked=blocked): + self.assertNotIn(blocked, dir_of_gen_alias) + + for entry in dir_of_gen_alias: + with self.subTest(entry=entry): + getattr(ga, entry) # must not raise `AttributeError` def test_weakref(self): for t in self.generic_types: @@ -337,6 +469,44 @@ class BaseTest(unittest.TestCase): with self.assertRaises(TypeError): Bad(list, int, bad=int) + def test_iter_creates_starred_tuple(self): + t = tuple[int, str] + iter_t = iter(t) + x = next(iter_t) + self.assertEqual(repr(x), '*tuple[int, str]') + + def test_calling_next_twice_raises_stopiteration(self): + t = tuple[int, str] + iter_t = iter(t) + next(iter_t) + with self.assertRaises(StopIteration): + next(iter_t) + + def test_del_iter(self): + t = tuple[int, str] + iter_x = iter(t) + del iter_x + + +class TypeIterationTests(unittest.TestCase): + _UNITERABLE_TYPES = (list, tuple) + + def test_cannot_iterate(self): + for test_type in self._UNITERABLE_TYPES: + with self.subTest(type=test_type): + expected_error_regex = "object is not iterable" + with self.assertRaisesRegex(TypeError, expected_error_regex): + iter(test_type) + with self.assertRaisesRegex(TypeError, expected_error_regex): + list(test_type) + with self.assertRaisesRegex(TypeError, expected_error_regex): + for _ in test_type: + pass + + def test_is_not_instance_of_iterable(self): + for type_to_test in self._UNITERABLE_TYPES: + self.assertNotIsInstance(type_to_test, Iterable) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_htmlparser.py b/Lib/test/test_htmlparser.py index 380bbe401..6a1d69335 100644 --- a/Lib/test/test_htmlparser.py +++ b/Lib/test/test_htmlparser.py @@ -10,10 +10,13 @@ from test import support class EventCollector(html.parser.HTMLParser): - def __init__(self, *args, **kw): + def __init__(self, *args, autocdata=False, **kw): + self.autocdata = autocdata self.events = [] self.append = self.events.append html.parser.HTMLParser.__init__(self, *args, **kw) + if autocdata: + self._set_support_cdata(False) def get_events(self): # Normalize the list of events so that buffer artefacts don't @@ -34,12 +37,16 @@ class EventCollector(html.parser.HTMLParser): def handle_starttag(self, tag, attrs): self.append(("starttag", tag, attrs)) + if self.autocdata and tag == 'svg': + self._set_support_cdata(True) def handle_startendtag(self, tag, attrs): self.append(("startendtag", tag, attrs)) def handle_endtag(self, tag): self.append(("endtag", tag)) + if self.autocdata and tag == 'svg': + self._set_support_cdata(False) # all other markup @@ -767,10 +774,6 @@ text ('' '' @@ -845,28 +860,53 @@ text ] self._run_check(html, expected) - def test_cdata_declarations(self): - # More tests should be added. See also "8.2.4.42. Markup - # declaration open state", "8.2.4.69. CDATA section state", - # and issue 32876 - html = ('') - expected = [('unknown decl', 'CDATA[just some plain text')] - self._run_check(html, expected) - - def test_cdata_declarations_multiline(self): - html = (' b) {' - ' printf("[How?]");' - ' }' - ']]>') + @support.subTests('content', [ + 'just some plain text', + '', + '¬-an-entity-ref;', + "", + '', + '[[I have many brackets]]', + 'I have a > in the middle', + 'I have a ]] in the middle', + '] ]>', + ']] >', + ('\n' + ' if (a < b && a > b) {\n' + ' printf("[How?]");\n' + ' }\n'), + ]) + def test_cdata_section_content(self, content): + # See "13.2.5.42 Markup declaration open state", + # "13.2.5.69 CDATA section state", and issue bpo-32876. + html = f'{content}' expected = [ - ('starttag', 'code', []), - ('unknown decl', - 'CDATA[ if (a < b && a > b) { ' - 'printf("[How?]"); }'), - ('endtag', 'code') + ('starttag', 'svg', []), + ('starttag', 'text', [('y', '100')]), + ('unknown decl', 'CDATA[' + content), + ('endtag', 'text'), + ('endtag', 'svg'), ] self._run_check(html, expected) + self._run_check(html, expected, collector=EventCollector(autocdata=True)) + + def test_cdata_section(self): + # See "13.2.5.42 Markup declaration open state". + html = ('bar]]>' + 'foo<br>bar' + 'bar]]>') + expected = [ + ('comment', '[CDATA[foo'), + ('starttag', 'svg', []), + ('starttag', 'text', [('y', '100')]), + ('unknown decl', 'CDATA[foo
bar'), + ('endtag', 'text'), + ('endtag', 'svg'), + ('comment', '[CDATA[foo'), + ] + self._run_check(html, expected, collector=EventCollector(autocdata=True)) def test_convert_charrefs_dropped_text(self): # #23144: make sure that all the events are triggered when diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index 7e368f115..9e1f46f64 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -345,8 +345,7 @@ class TestEnUSCollation(BaseLocalizedTest, TestCollation): enc = codecs.lookup(locale.getencoding() or 'ascii').name if enc not in ('utf-8', 'iso8859-1', 'cp1252'): raise unittest.SkipTest('encoding not suitable') - if enc != 'iso8859-1' and (sys.platform == 'darwin' or is_android or - sys.platform.startswith('freebsd')): + if enc != 'iso8859-1' and is_android: raise unittest.SkipTest('wcscoll/wcsxfrm have known bugs') BaseLocalizedTest.setUp(self) @@ -512,7 +511,7 @@ class TestRealLocales(unittest.TestCase): self.skipTest(f"setlocale(LC_CTYPE, {loc!r}) failed: {exc!r}") self.assertEqual(loc, locale.getlocale(locale.LC_CTYPE)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; Error not raised") + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; Error not raised') @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_setlocale_long_encoding(self): with self.assertRaises(locale.Error): diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 3b8690367..dbcc85bd6 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1324,17 +1324,22 @@ class LongTest(unittest.TestCase): check(tests4, 'little', signed=False) self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=False) - self.assertRaises(OverflowError, (256).to_bytes, 1, 'big', signed=True) self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=False) - self.assertRaises(OverflowError, (256).to_bytes, 1, 'little', signed=True) + self.assertRaises(OverflowError, (128).to_bytes, 1, 'big', signed=True) + self.assertRaises(OverflowError, (128).to_bytes, 1, 'little', signed=True) + self.assertRaises(OverflowError, (-129).to_bytes, 1, 'big', signed=True) + self.assertRaises(OverflowError, (-129).to_bytes, 1, 'little', signed=True) self.assertRaises(OverflowError, (-1).to_bytes, 2, 'big', signed=False) self.assertRaises(OverflowError, (-1).to_bytes, 2, 'little', signed=False) self.assertEqual((0).to_bytes(0, 'big'), b'') + self.assertEqual((0).to_bytes(0, 'big', signed=True), b'') self.assertEqual((1).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x01') self.assertEqual((0).to_bytes(5, 'big'), b'\x00\x00\x00\x00\x00') self.assertEqual((-1).to_bytes(5, 'big', signed=True), b'\xff\xff\xff\xff\xff') self.assertRaises(OverflowError, (1).to_bytes, 0, 'big') + self.assertRaises(OverflowError, (-1).to_bytes, 0, 'big', signed=True) + self.assertRaises(OverflowError, (-1).to_bytes, 0, 'little', signed=True) # gh-98783 class SubStr(str): @@ -1646,5 +1651,21 @@ class LongTest(unittest.TestCase): # GH-117195 -- This shouldn't crash object.__sizeof__(1) + def test_hash(self): + # gh-136599 + self.assertEqual(hash(-1), -2) + self.assertEqual(hash(0), 0) + self.assertEqual(hash(10), 10) + + self.assertEqual(hash(sys.hash_info.modulus - 2), sys.hash_info.modulus - 2) + self.assertEqual(hash(sys.hash_info.modulus - 1), sys.hash_info.modulus - 1) + self.assertEqual(hash(sys.hash_info.modulus), 0) + self.assertEqual(hash(sys.hash_info.modulus + 1), 1) + + self.assertEqual(hash(-sys.hash_info.modulus - 2), -2) + self.assertEqual(hash(-sys.hash_info.modulus - 1), -2) + self.assertEqual(hash(-sys.hash_info.modulus), 0) + self.assertEqual(hash(-sys.hash_info.modulus + 1), -sys.hash_info.modulus + 1) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index dc0c6fe51..c327d2add 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1,17 +1,19 @@ "Test posix functions" from test import support -from test.support import import_helper +from test.support import is_apple from test.support import os_helper from test.support import warnings_helper from test.support.script_helper import assert_python_ok +import copy import errno import sys import signal import time import os import platform +import pickle import stat import tempfile import unittest @@ -411,8 +413,10 @@ class PosixTester(unittest.TestCase): # issue33655: Also ignore EINVAL on *BSD since ZFS is also # often used there. if inst.errno == errno.EINVAL and sys.platform.startswith( - ('sunos', 'freebsd', 'netbsd', 'openbsd', 'gnukfreebsd')): + ('sunos', 'freebsd', 'openbsd', 'gnukfreebsd')): raise unittest.SkipTest("test may fail on ZFS filesystems") + elif inst.errno == errno.EOPNOTSUPP and sys.platform.startswith("netbsd"): + raise unittest.SkipTest("test may fail on FFS filesystems") else: raise finally: @@ -565,8 +569,37 @@ class PosixTester(unittest.TestCase): @unittest.skipUnless(hasattr(posix, 'confstr'), 'test needs posix.confstr()') def test_confstr(self): - self.assertRaises(ValueError, posix.confstr, "CS_garbage") - self.assertEqual(len(posix.confstr("CS_PATH")) > 0, True) + with self.assertRaisesRegex( + ValueError, "unrecognized configuration name" + ): + posix.confstr("CS_garbage") + + with self.assertRaisesRegex( + TypeError, "configuration names must be strings or integers" + ): + posix.confstr(1.23) + + path = posix.confstr("CS_PATH") + self.assertGreater(len(path), 0) + self.assertEqual(posix.confstr(posix.confstr_names["CS_PATH"]), path) + + @unittest.skipUnless(hasattr(posix, 'sysconf'), + 'test needs posix.sysconf()') + def test_sysconf(self): + with self.assertRaisesRegex( + ValueError, "unrecognized configuration name" + ): + posix.sysconf("SC_garbage") + + with self.assertRaisesRegex( + TypeError, "configuration names must be strings or integers" + ): + posix.sysconf(1.23) + + arg_max = posix.sysconf("SC_ARG_MAX") + self.assertGreater(arg_max, 0) + self.assertEqual( + posix.sysconf(posix.sysconf_names["SC_ARG_MAX"]), arg_max) @unittest.skipUnless(hasattr(posix, 'dup2'), 'test needs posix.dup2()') @@ -703,7 +736,8 @@ class PosixTester(unittest.TestCase): self.assertEqual(posix.major(dev), major) self.assertRaises(TypeError, posix.major, float(dev)) self.assertRaises(TypeError, posix.major) - self.assertRaises((ValueError, OverflowError), posix.major, -1) + for x in -2, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.major, x) minor = posix.minor(dev) self.assertIsInstance(minor, int) @@ -711,13 +745,23 @@ class PosixTester(unittest.TestCase): self.assertEqual(posix.minor(dev), minor) self.assertRaises(TypeError, posix.minor, float(dev)) self.assertRaises(TypeError, posix.minor) - self.assertRaises((ValueError, OverflowError), posix.minor, -1) + for x in -2, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.minor, x) self.assertEqual(posix.makedev(major, minor), dev) self.assertRaises(TypeError, posix.makedev, float(major), minor) self.assertRaises(TypeError, posix.makedev, major, float(minor)) self.assertRaises(TypeError, posix.makedev, major) self.assertRaises(TypeError, posix.makedev) + for x in -2, 2**32, 2**64, -2**63-1: + self.assertRaises((ValueError, OverflowError), posix.makedev, x, minor) + self.assertRaises((ValueError, OverflowError), posix.makedev, major, x) + + if sys.platform == 'linux' and not support.linked_to_musl(): + NODEV = -1 + self.assertEqual(posix.major(NODEV), NODEV) + self.assertEqual(posix.minor(NODEV), NODEV) + self.assertEqual(posix.makedev(NODEV, NODEV), NODEV) def _test_all_chown_common(self, chown_func, first_param, stat_func): """Common code for chown, fchown and lchown tests.""" @@ -781,9 +825,10 @@ class PosixTester(unittest.TestCase): check_stat(uid, gid) self.assertRaises(OSError, chown_func, first_param, 0, -1) check_stat(uid, gid) - if 0 not in os.getgroups(): - self.assertRaises(OSError, chown_func, first_param, -1, 0) - check_stat(uid, gid) + if hasattr(os, 'getgroups'): + if 0 not in os.getgroups(): + self.assertRaises(OSError, chown_func, first_param, -1, 0) + check_stat(uid, gid) # test illegal types for t in str, float: self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) @@ -936,6 +981,7 @@ class PosixTester(unittest.TestCase): posix.utime(os_helper.TESTFN, (now, now)) def check_chmod(self, chmod_func, target, **kwargs): + closefd = not isinstance(target, int) mode = os.stat(target).st_mode try: new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) @@ -943,7 +989,7 @@ class PosixTester(unittest.TestCase): self.assertEqual(os.stat(target).st_mode, new_mode) if stat.S_ISREG(mode): try: - with open(target, 'wb+'): + with open(target, 'wb+', closefd=closefd): pass except PermissionError: pass @@ -951,10 +997,10 @@ class PosixTester(unittest.TestCase): chmod_func(target, new_mode, **kwargs) self.assertEqual(os.stat(target).st_mode, new_mode) if stat.S_ISREG(mode): - with open(target, 'wb+'): + with open(target, 'wb+', closefd=closefd): pass finally: - posix.chmod(target, mode) + chmod_func(target, mode) @os_helper.skip_unless_working_chmod def test_chmod_file(self): @@ -971,6 +1017,12 @@ class PosixTester(unittest.TestCase): target = self.tempdir() self.check_chmod(posix.chmod, target) + @os_helper.skip_unless_working_chmod + def test_fchmod_file(self): + with open(os_helper.TESTFN, 'wb+') as f: + self.check_chmod(posix.fchmod, f.fileno()) + self.check_chmod(posix.chmod, f.fileno()) + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') def test_lchmod_file(self): self.check_chmod(posix.lchmod, os_helper.TESTFN) @@ -1019,7 +1071,7 @@ class PosixTester(unittest.TestCase): self.check_lchmod_link(posix.chmod, target, link) else: self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) @os_helper.skip_unless_symlink def test_chmod_dir_symlink(self): @@ -1031,7 +1083,7 @@ class PosixTester(unittest.TestCase): self.check_lchmod_link(posix.chmod, target, link) else: self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') @os_helper.skip_unless_symlink @@ -1249,8 +1301,8 @@ class PosixTester(unittest.TestCase): self.assertIsInstance(lo, int) self.assertIsInstance(hi, int) self.assertGreaterEqual(hi, lo) - # OSX evidently just returns 15 without checking the argument. - if sys.platform != "darwin": + # Apple platforms return 15 without checking the argument. + if not is_apple: self.assertRaises(OSError, posix.sched_get_priority_min, -23) self.assertRaises(OSError, posix.sched_get_priority_max, -23) @@ -1262,9 +1314,10 @@ class PosixTester(unittest.TestCase): self.assertIn(mine, possible_schedulers) try: parent = posix.sched_getscheduler(os.getppid()) - except OSError as e: - if e.errno != errno.EPERM: - raise + except PermissionError: + # POSIX specifies EPERM, but Android returns EACCES. Both errno + # values are mapped to PermissionError. + pass else: self.assertIn(parent, possible_schedulers) self.assertRaises(OSError, posix.sched_getscheduler, -1) @@ -1279,9 +1332,8 @@ class PosixTester(unittest.TestCase): try: posix.sched_setscheduler(0, mine, param) posix.sched_setparam(0, param) - except OSError as e: - if e.errno != errno.EPERM: - raise + except PermissionError: + pass self.assertRaises(OSError, posix.sched_setparam, -1, param) self.assertRaises(OSError, posix.sched_setscheduler, -1, mine, param) @@ -1295,6 +1347,25 @@ class PosixTester(unittest.TestCase): param = posix.sched_param(sched_priority=-large) self.assertRaises(OverflowError, posix.sched_setparam, 0, param) + @requires_sched + def test_sched_param(self): + param = posix.sched_param(1) + for proto in range(pickle.HIGHEST_PROTOCOL+1): + newparam = pickle.loads(pickle.dumps(param, proto)) + self.assertEqual(newparam, param) + newparam = copy.copy(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.deepcopy(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.replace(param) + self.assertIsNot(newparam, param) + self.assertEqual(newparam, param) + newparam = copy.replace(param, sched_priority=0) + self.assertNotEqual(newparam, param) + self.assertEqual(newparam.sched_priority, 0) + @unittest.skipUnless(hasattr(posix, "sched_rr_get_interval"), "no function") def test_sched_rr_get_interval(self): try: @@ -1515,6 +1586,13 @@ class TestPosixDirFd(unittest.TestCase): self.assertRaises(OverflowError, posix.stat, name, dir_fd=10**20) + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor') as cm: + with self.assertRaises(OSError): + posix.stat('nonexisting', dir_fd=fd) + self.assertEqual(cm.filename, __file__) + @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()") def test_utime_dir_fd(self): with self.prepare_file() as (dir_fd, name, fullname): @@ -1890,7 +1968,6 @@ class _PosixSpawnMixin: [sys.executable, "-c", "pass"], os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) - @unittest.expectedFailure @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") @@ -1911,10 +1988,14 @@ class _PosixSpawnMixin: ) support.wait_process(pid, exitcode=0) - @unittest.expectedFailure @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") + @unittest.skipIf(platform.libc_ver()[0] == 'glibc' and + os.sched_getscheduler(0) in [ + os.SCHED_BATCH, + os.SCHED_IDLE], + "Skip test due to glibc posix_spawn policy") def test_setscheduler_with_policy(self): policy = os.sched_getscheduler(0) priority = os.sched_get_priority_min(policy) @@ -1993,10 +2074,6 @@ class _PosixSpawnMixin: with open(outfile, encoding="utf-8") as f: self.assertEqual(f.read(), 'hello') - # TODO: RUSTPYTHON: the rust runtime reopens closed stdio fds at startup, - # so this test fails, even though POSIX_SPAWN_CLOSE does - # actually have an effect - @unittest.expectedFailure def test_close_file(self): closefile = os_helper.TESTFN self.addCleanup(os_helper.unlink, closefile) @@ -2036,11 +2113,13 @@ class _PosixSpawnMixin: @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") +@support.requires_subprocess() class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): spawn_func = getattr(posix, 'posix_spawn', None) @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") +@support.requires_subprocess() class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): spawn_func = getattr(posix, 'posix_spawnp', None) @@ -2117,6 +2196,13 @@ class TestPosixWeaklinking(unittest.TestCase): with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): os.stat("file", dir_fd=0) + def test_ptsname_r(self): + self._verify_available("HAVE_PTSNAME_R") + if self.mac_ver >= (10, 13, 4): + self.assertIn("HAVE_PTSNAME_R", posix._have_functions) + else: + self.assertNotIn("HAVE_PTSNAME_R", posix._have_functions) + def test_access(self): self._verify_available("HAVE_FACCESSAT") if self.mac_ver >= (10, 10): diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 33e1ffb83..9747cf662 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -1,13 +1,14 @@ # XXX TypeErrors on calling handlers, or on bad return values from a # handler, are obscure and unhelpful. -from io import BytesIO import os -import platform import sys import sysconfig import unittest import traceback +from io import BytesIO +from test import support +from test.support import os_helper from xml.parsers import expat from xml.parsers.expat import errors @@ -19,32 +20,28 @@ class SetAttributeTest(unittest.TestCase): def setUp(self): self.parser = expat.ParserCreate(namespace_separator='!') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_buffer_text(self): self.assertIs(self.parser.buffer_text, False) for x in 0, 1, 2, 0: self.parser.buffer_text = x self.assertIs(self.parser.buffer_text, bool(x)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_namespace_prefixes(self): self.assertIs(self.parser.namespace_prefixes, False) for x in 0, 1, 2, 0: self.parser.namespace_prefixes = x self.assertIs(self.parser.namespace_prefixes, bool(x)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ordered_attributes(self): self.assertIs(self.parser.ordered_attributes, False) for x in 0, 1, 2, 0: self.parser.ordered_attributes = x self.assertIs(self.parser.ordered_attributes, bool(x)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_specified_attributes(self): self.assertIs(self.parser.specified_attributes, False) for x in 0, 1, 2, 0: @@ -234,8 +231,7 @@ class ParseTest(unittest.TestCase): for operation, expected_operation in zip(operations, expected_operations): self.assertEqual(operation, expected_operation) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_bytes(self): out = self.Outputter() parser = expat.ParserCreate(namespace_separator='!') @@ -248,8 +244,7 @@ class ParseTest(unittest.TestCase): # Issue #6697. self.assertRaises(AttributeError, getattr, parser, '\uD800') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_str(self): out = self.Outputter() parser = expat.ParserCreate(namespace_separator='!') @@ -260,8 +255,7 @@ class ParseTest(unittest.TestCase): operations = out.out self._verify_parse_output(operations) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_file(self): # Try parsing a file out = self.Outputter() @@ -274,8 +268,7 @@ class ParseTest(unittest.TestCase): operations = out.out self._verify_parse_output(operations) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parse_again(self): parser = expat.ParserCreate() file = BytesIO(data) @@ -289,8 +282,7 @@ class ParseTest(unittest.TestCase): expat.errors.XML_ERROR_FINISHED) class NamespaceSeparatorTest(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_legal(self): # Tests that make sure we get errors when the namespace_separator value # is illegal, and that we don't for good values: @@ -298,15 +290,12 @@ class NamespaceSeparatorTest(unittest.TestCase): expat.ParserCreate(namespace_separator=None) expat.ParserCreate(namespace_separator=' ') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_illegal(self): - try: + with self.assertRaisesRegex(TypeError, + r"ParserCreate\(\) argument (2|'namespace_separator') " + r"must be str or None, not int"): expat.ParserCreate(namespace_separator=42) - self.fail() - except TypeError as e: - self.assertEqual(str(e), - "ParserCreate() argument 'namespace_separator' must be str or None, not int") try: expat.ParserCreate(namespace_separator='too long') @@ -328,9 +317,7 @@ class NamespaceSeparatorTest(unittest.TestCase): class InterningTest(unittest.TestCase): - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test(self): # Test the interning machinery. p = expat.ParserCreate() @@ -346,8 +333,7 @@ class InterningTest(unittest.TestCase): # L should have the same string repeated over and over. self.assertTrue(tag is entry) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_issue9402(self): # create an ExternalEntityParserCreate with buffer text class ExternalOutputter: @@ -405,8 +391,7 @@ class BufferTextTest(unittest.TestCase): parser = expat.ParserCreate() self.assertFalse(parser.buffer_text) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_buffering_enabled(self): # Make sure buffering is turned on self.assertTrue(self.parser.buffer_text) @@ -414,8 +399,7 @@ class BufferTextTest(unittest.TestCase): self.assertEqual(self.stuff, ['123'], "buffered text not properly collapsed") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test1(self): # XXX This test exposes more detail of Expat's text chunking than we # XXX like, but it tests what we need to concisely. @@ -425,23 +409,20 @@ class BufferTextTest(unittest.TestCase): ["", "1", "", "2", "\n", "3", "", "4\n5"], "buffering control not reacting as expected") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test2(self): self.parser.Parse(b"1<2> \n 3", True) self.assertEqual(self.stuff, ["1<2> \n 3"], "buffered text not properly collapsed") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test3(self): self.setHandlers(["StartElementHandler"]) self.parser.Parse(b"123", True) self.assertEqual(self.stuff, ["", "1", "", "2", "", "3"], "buffered text not properly split") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test4(self): self.setHandlers(["StartElementHandler", "EndElementHandler"]) self.parser.CharacterDataHandler = None @@ -449,16 +430,14 @@ class BufferTextTest(unittest.TestCase): self.assertEqual(self.stuff, ["", "", "", "", "", ""]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test5(self): self.setHandlers(["StartElementHandler", "EndElementHandler"]) self.parser.Parse(b"123", True) self.assertEqual(self.stuff, ["", "1", "", "", "2", "", "", "3", ""]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test6(self): self.setHandlers(["CommentHandler", "EndElementHandler", "StartElementHandler"]) @@ -467,8 +446,7 @@ class BufferTextTest(unittest.TestCase): ["", "1", "", "", "2", "", "", "345", ""], "buffered text not properly split") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test7(self): self.setHandlers(["CommentHandler", "EndElementHandler", "StartElementHandler"]) @@ -482,35 +460,60 @@ class BufferTextTest(unittest.TestCase): # Test handling of exception from callback: class HandlerExceptionTest(unittest.TestCase): def StartElementHandler(self, name, attrs): - raise RuntimeError(name) + raise RuntimeError(f'StartElementHandler: <{name}>') def check_traceback_entry(self, entry, filename, funcname): - self.assertEqual(os.path.basename(entry[0]), filename) - self.assertEqual(entry[2], funcname) + self.assertEqual(os.path.basename(entry.filename), filename) + self.assertEqual(entry.name, funcname) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.cpython_only def test_exception(self): + # gh-66652: test _PyTraceback_Add() used by pyexpat.c to inject frames + + # Change the current directory to the Python source code directory + # if it is available. + src_dir = sysconfig.get_config_var('abs_builddir') + if src_dir: + have_source = os.path.isdir(src_dir) + else: + have_source = False + if have_source: + with os_helper.change_cwd(src_dir): + self._test_exception(have_source) + else: + self._test_exception(have_source) + + def _test_exception(self, have_source): + # Use path relative to the current directory which should be the Python + # source code directory (if it is available). + PYEXPAT_C = os.path.join('Modules', 'pyexpat.c') + parser = expat.ParserCreate() parser.StartElementHandler = self.StartElementHandler try: parser.Parse(b"", True) - self.fail() - except RuntimeError as e: - self.assertEqual(e.args[0], 'a', - "Expected RuntimeError for element 'a', but" + \ - " found %r" % e.args[0]) - # Check that the traceback contains the relevant line in pyexpat.c - entries = traceback.extract_tb(e.__traceback__) - self.assertEqual(len(entries), 3) - self.check_traceback_entry(entries[0], - "test_pyexpat.py", "test_exception") - self.check_traceback_entry(entries[1], - "pyexpat.c", "StartElement") - self.check_traceback_entry(entries[2], - "test_pyexpat.py", "StartElementHandler") - if sysconfig.is_python_build() and not (sys.platform == 'win32' and platform.machine() == 'ARM'): - self.assertIn('call_with_frame("StartElement"', entries[1][3]) + + self.fail("the parser did not raise RuntimeError") + except RuntimeError as exc: + self.assertEqual(exc.args[0], 'StartElementHandler: ', exc) + entries = traceback.extract_tb(exc.__traceback__) + + self.assertEqual(len(entries), 3, entries) + self.check_traceback_entry(entries[0], + "test_pyexpat.py", "_test_exception") + self.check_traceback_entry(entries[1], + os.path.basename(PYEXPAT_C), + "StartElement") + self.check_traceback_entry(entries[2], + "test_pyexpat.py", "StartElementHandler") + + # Check that the traceback contains the relevant line in + # Modules/pyexpat.c. Skip the test if Modules/pyexpat.c is not + # available. + if have_source and os.path.exists(PYEXPAT_C): + self.assertIn('call_with_frame("StartElement"', + entries[1].line) # Test Current* members: @@ -533,8 +536,7 @@ class PositionTest(unittest.TestCase): 'Expected position %s, got position %s' %(pos, expected)) self.upto += 1 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test(self): self.parser = expat.ParserCreate() self.parser.StartElementHandler = self.StartElementHandler @@ -548,9 +550,8 @@ class PositionTest(unittest.TestCase): class sf1296433Test(unittest.TestCase): - def test_parse_only_xml_data(self): - # http://python.org/sf/1296433 + # https://bugs.python.org/issue1296433 # xml = "%s" % ('a' * 1025) # this one doesn't crash @@ -565,25 +566,22 @@ class sf1296433Test(unittest.TestCase): parser = expat.ParserCreate() parser.CharacterDataHandler = handler - self.assertRaises(Exception, parser.Parse, xml.encode('iso8859')) + self.assertRaises(SpecificException, parser.Parse, xml.encode('iso8859')) class ChardataBufferTest(unittest.TestCase): """ test setting of chardata buffer size """ - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_1025_bytes(self): self.assertEqual(self.small_buffer_test(1025), 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_1000_bytes(self): self.assertEqual(self.small_buffer_test(1000), 1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_wrong_size(self): parser = expat.ParserCreate() parser.buffer_text = 1 @@ -596,8 +594,7 @@ class ChardataBufferTest(unittest.TestCase): with self.assertRaises(TypeError): parser.buffer_size = 512.0 - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_unchanged_size(self): xml1 = b"" + b'a' * 512 xml2 = b'a'*512 + b'' @@ -620,8 +617,8 @@ class ChardataBufferTest(unittest.TestCase): parser.Parse(xml2) self.assertEqual(self.n, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + + @unittest.expectedFailure # TODO: RUSTPYTHON def test_disabling_buffer(self): xml1 = b"" + b'a' * 512 xml2 = b'b' * 1024 @@ -666,8 +663,7 @@ class ChardataBufferTest(unittest.TestCase): parser.Parse(xml) return self.n - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_change_size_1(self): xml1 = b"" + b'a' * 1024 xml2 = b'aaa' + b'a' * 1025 + b'' @@ -684,8 +680,7 @@ class ChardataBufferTest(unittest.TestCase): parser.Parse(xml2, True) self.assertEqual(self.n, 2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_change_size_2(self): xml1 = b"a" + b'a' * 1023 xml2 = b'aaa' + b'a' * 1025 + b'' @@ -703,9 +698,7 @@ class ChardataBufferTest(unittest.TestCase): self.assertEqual(self.n, 4) class MalformedInputTest(unittest.TestCase): - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test1(self): xml = b"\0\r\n" parser = expat.ParserCreate() @@ -715,8 +708,7 @@ class MalformedInputTest(unittest.TestCase): except expat.ExpatError as e: self.assertEqual(str(e), 'unclosed token: line 2, column 0') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test2(self): # \xc2\x85 is UTF-8 encoded U+0085 (NEXT LINE) xml = b"\r\n" @@ -726,16 +718,13 @@ class MalformedInputTest(unittest.TestCase): parser.Parse(xml, True) class ErrorMessageTest(unittest.TestCase): - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_codes(self): # verify mapping of errors.codes and errors.messages self.assertEqual(errors.XML_ERROR_SYNTAX, errors.messages[errors.codes[errors.XML_ERROR_SYNTAX]]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_expaterror(self): xml = b'<' parser = expat.ParserCreate() @@ -751,9 +740,7 @@ class ForeignDTDTests(unittest.TestCase): """ Tests for the UseForeignDTD method of expat parser objects. """ - - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_use_foreign_dtd(self): """ If UseForeignDTD is passed True and a document without an external @@ -782,8 +769,7 @@ class ForeignDTDTests(unittest.TestCase): parser.Parse(b"") self.assertEqual(handler_call_args, [(None, None)]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_ignore_use_foreign_dtd(self): """ If UseForeignDTD is passed True and a document with an external @@ -804,5 +790,95 @@ class ForeignDTDTests(unittest.TestCase): self.assertEqual(handler_call_args, [("bar", "baz")]) +class ParentParserLifetimeTest(unittest.TestCase): + """ + Subparsers make use of their parent XML_Parser inside of Expat. + As a result, parent parsers need to outlive subparsers. + + See https://github.com/python/cpython/issues/139400. + """ + + def test_parent_parser_outlives_its_subparsers__single(self): + parser = expat.ParserCreate() + subparser = parser.ExternalEntityParserCreate(None) + + # Now try to cause garbage collection of the parent parser + # while it's still being referenced by a related subparser. + del parser + + def test_parent_parser_outlives_its_subparsers__multiple(self): + parser = expat.ParserCreate() + subparser_one = parser.ExternalEntityParserCreate(None) + subparser_two = parser.ExternalEntityParserCreate(None) + + # Now try to cause garbage collection of the parent parser + # while it's still being referenced by a related subparser. + del parser + + def test_parent_parser_outlives_its_subparsers__chain(self): + parser = expat.ParserCreate() + subparser = parser.ExternalEntityParserCreate(None) + subsubparser = subparser.ExternalEntityParserCreate(None) + + # Now try to cause garbage collection of the parent parsers + # while they are still being referenced by a related subparser. + del parser + del subparser + + +class ReparseDeferralTest(unittest.TestCase): + def test_getter_setter_round_trip(self): + parser = expat.ParserCreate() + enabled = (expat.version_info >= (2, 6, 0)) + + self.assertIs(parser.GetReparseDeferralEnabled(), enabled) + parser.SetReparseDeferralEnabled(False) + self.assertIs(parser.GetReparseDeferralEnabled(), False) + parser.SetReparseDeferralEnabled(True) + self.assertIs(parser.GetReparseDeferralEnabled(), enabled) + + def test_reparse_deferral_enabled(self): + if expat.version_info < (2, 6, 0): + self.skipTest(f'Expat {expat.version_info} does not ' + 'support reparse deferral') + + started = [] + + def start_element(name, _): + started.append(name) + + parser = expat.ParserCreate() + parser.StartElementHandler = start_element + self.assertTrue(parser.GetReparseDeferralEnabled()) + + for chunk in (b''): + parser.Parse(chunk, False) + + # The key test: Have handlers already fired? Expecting: no. + self.assertEqual(started, []) + + parser.Parse(b'', True) + + self.assertEqual(started, ['doc']) + + def test_reparse_deferral_disabled(self): + started = [] + + def start_element(name, _): + started.append(name) + + parser = expat.ParserCreate() + parser.StartElementHandler = start_element + if expat.version_info >= (2, 6, 0): + parser.SetReparseDeferralEnabled(False) + self.assertFalse(parser.GetReparseDeferralEnabled()) + + for chunk in (b''): + parser.Parse(chunk, False) + + # The key test: Have handlers already fired? Expecting: yes. + self.assertEqual(started, ['doc']) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index f5b8d42db..9001b8170 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -12,6 +12,7 @@ from test.support import os_helper from test.support import socket_helper from test.support import captured_stderr from test.support.os_helper import TESTFN, EnvironmentVarGuard +from test.support.script_helper import spawn_python, kill_python import ast import builtins import glob @@ -24,6 +25,7 @@ import subprocess import sys import sysconfig import tempfile +from textwrap import dedent import urllib.error import urllib.request from unittest import mock @@ -221,8 +223,7 @@ class HelperFunctionsTests(unittest.TestCase): finally: pth_file.cleanup() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(sys.platform == 'win32', 'test needs Windows') @support.requires_subprocess() def test_addsitedir_hidden_file_attribute(self): @@ -330,15 +331,13 @@ class HelperFunctionsTests(unittest.TestCase): if sys.platlibdir != "lib": self.assertEqual(len(dirs), 2) wanted = os.path.join('xoxo', sys.platlibdir, - # XXX: RUSTPYTHON - f'rustpython{sysconfig._get_python_version_abi()}', + f'python{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[0], wanted) else: self.assertEqual(len(dirs), 1) wanted = os.path.join('xoxo', 'lib', - # XXX: RUSTPYTHON - f'rustpython{sysconfig._get_python_version_abi()}', + f'python{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[-1], wanted) else: @@ -581,8 +580,7 @@ class ImportSideEffectTests(unittest.TestCase): class StartupImportTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_startup_imports(self): # Get sys.path in isolated mode (python3 -I) @@ -715,8 +713,7 @@ class _pthFileTests(unittest.TestCase): pth_lines.append('import site') return pth_lines - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_underpth_basic(self): pth_lines = ['#.', '# ..', *sys.path, '.', '..'] @@ -736,8 +733,7 @@ class _pthFileTests(unittest.TestCase): "sys.path is incorrect" ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_underpth_nosite_file(self): libpath = test.support.STDLIB_DIR @@ -762,8 +758,7 @@ class _pthFileTests(unittest.TestCase): "sys.path is incorrect" ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_underpth_file(self): libpath = test.support.STDLIB_DIR @@ -784,8 +779,7 @@ class _pthFileTests(unittest.TestCase): )], env=env) self.assertTrue(rc, "sys.path is incorrect") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_underpth_dll_file(self): libpath = test.support.STDLIB_DIR @@ -807,5 +801,107 @@ class _pthFileTests(unittest.TestCase): self.assertTrue(rc, "sys.path is incorrect") +class CommandLineTests(unittest.TestCase): + def exists(self, path): + if path is not None and os.path.isdir(path): + return "exists" + else: + return "doesn't exist" + + def get_excepted_output(self, *args): + if len(args) == 0: + user_base = site.getuserbase() + user_site = site.getusersitepackages() + output = io.StringIO() + output.write("sys.path = [\n") + for dir in sys.path: + output.write(" %r,\n" % (dir,)) + output.write("]\n") + output.write(f"USER_BASE: {user_base} ({self.exists(user_base)})\n") + output.write(f"USER_SITE: {user_site} ({self.exists(user_site)})\n") + output.write(f"ENABLE_USER_SITE: {site.ENABLE_USER_SITE}\n") + return 0, dedent(output.getvalue()).strip() + + buffer = [] + if '--user-base' in args: + buffer.append(site.getuserbase()) + if '--user-site' in args: + buffer.append(site.getusersitepackages()) + + if buffer: + return_code = 3 + if site.ENABLE_USER_SITE: + return_code = 0 + elif site.ENABLE_USER_SITE is False: + return_code = 1 + elif site.ENABLE_USER_SITE is None: + return_code = 2 + output = os.pathsep.join(buffer) + return return_code, os.path.normpath(dedent(output).strip()) + else: + return 10, None + + def invoke_command_line(self, *args): + args = ["-m", "site", *args] + + with EnvironmentVarGuard() as env: + env["PYTHONUTF8"] = "1" + env["PYTHONIOENCODING"] = "utf-8" + proc = spawn_python(*args, text=True, env=env, + encoding='utf-8', errors='replace') + + output = kill_python(proc) + return_code = proc.returncode + return return_code, os.path.normpath(dedent(output).strip()) + + @support.requires_subprocess() + def test_no_args(self): + return_code, output = self.invoke_command_line() + excepted_return_code, _ = self.get_excepted_output() + self.assertEqual(return_code, excepted_return_code) + lines = output.splitlines() + self.assertEqual(lines[0], "sys.path = [") + self.assertEqual(lines[-4], "]") + excepted_base = f"USER_BASE: '{site.getuserbase()}'" +\ + f" ({self.exists(site.getuserbase())})" + self.assertEqual(lines[-3], excepted_base) + excepted_site = f"USER_SITE: '{site.getusersitepackages()}'" +\ + f" ({self.exists(site.getusersitepackages())})" + self.assertEqual(lines[-2], excepted_site) + self.assertEqual(lines[-1], f"ENABLE_USER_SITE: {site.ENABLE_USER_SITE}") + + @support.requires_subprocess() + def test_unknown_args(self): + return_code, output = self.invoke_command_line("--unknown-arg") + excepted_return_code, _ = self.get_excepted_output("--unknown-arg") + self.assertEqual(return_code, excepted_return_code) + self.assertIn('[--user-base] [--user-site]', output) + + @support.requires_subprocess() + def test_base_arg(self): + return_code, output = self.invoke_command_line("--user-base") + excepted = self.get_excepted_output("--user-base") + excepted_return_code, excepted_output = excepted + self.assertEqual(return_code, excepted_return_code) + self.assertEqual(output, excepted_output) + + @support.requires_subprocess() + def test_site_arg(self): + return_code, output = self.invoke_command_line("--user-site") + excepted = self.get_excepted_output("--user-site") + excepted_return_code, excepted_output = excepted + self.assertEqual(return_code, excepted_return_code) + self.assertEqual(output, excepted_output) + + @support.requires_subprocess() + def test_both_args(self): + return_code, output = self.invoke_command_line("--user-base", + "--user-site") + excepted = self.get_excepted_output("--user-base", "--user-site") + excepted_return_code, excepted_output = excepted + self.assertEqual(return_code, excepted_return_code) + self.assertEqual(output, excepted_output) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index da242c7df..dc9701661 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -166,8 +166,7 @@ class TestSysConfig(unittest.TestCase): binpath = 'bin' incpath = 'include' libpath = os.path.join('lib', - # XXX: RUSTPYTHON - f'rustpython{sysconfig._get_python_version_abi()}', + f'python{sysconfig._get_python_version_abi()}', 'site-packages') # Resolve the paths in an imaginary venv/ directory @@ -352,6 +351,13 @@ class TestSysConfig(unittest.TestCase): self.assertEqual(get_platform(), 'macosx-10.4-%s' % arch) + for macver in range(11, 16): + _osx_support._remove_original_values(get_config_vars()) + get_config_vars()['CFLAGS'] = ('-fno-strict-overflow -Wsign-compare -Wunreachable-code' + '-arch arm64 -fno-common -dynamic -DNDEBUG -g -O3 -Wall') + get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = f"{macver}.0" + self.assertEqual(get_platform(), 'macosx-%d.0-arm64' % macver) + # linux debian sarge os.name = 'posix' sys.version = ('2.3.5 (#1, Jul 4 2007, 17:28:59) ' @@ -379,8 +385,7 @@ class TestSysConfig(unittest.TestCase): # XXX more platforms to tests here - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") @unittest.skipIf(is_apple_mobile, f"{sys.platform} doesn't distribute header files in the runtime environment") @@ -441,8 +446,7 @@ class TestSysConfig(unittest.TestCase): _main() self.assertTrue(len(output.getvalue().split('\n')) > 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.platform == "win32", "Does not apply to Windows") def test_ldshared_value(self): ldflags = sysconfig.get_config_var('LDFLAGS') @@ -455,8 +459,7 @@ class TestSysConfig(unittest.TestCase): soabi = sysconfig.get_config_var('SOABI') self.assertIn(soabi, _imp.extension_suffixes()[0]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_library(self): library = sysconfig.get_config_var('LIBRARY') ldlibrary = sysconfig.get_config_var('LDLIBRARY') @@ -520,7 +523,7 @@ class TestSysConfig(unittest.TestCase): self.assertEqual(status, 0) self.assertEqual(my_platform, test_platform) - @unittest.expectedFailureIf(sys.platform != "win32", "TODO: RUSTPYTHON") + @unittest.expectedFailureIf(sys.platform != 'win32', 'TODO: RUSTPYTHON') @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") @unittest.skipIf(is_apple_mobile, f"{sys.platform} doesn't include config folder at runtime") @@ -581,8 +584,7 @@ class TestSysConfig(unittest.TestCase): self.assertTrue(suffix.endswith(expected_suffixes), f'unexpected suffix {suffix!r}') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(sys.platform == 'android', 'Android-specific test') def test_android_ext_suffix(self): machine = platform.machine() @@ -602,8 +604,7 @@ class TestSysConfig(unittest.TestCase): suffix = sysconfig.get_config_var('EXT_SUFFIX') self.assertTrue(suffix.endswith('-darwin.so'), suffix) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_subprocess() def test_config_vars_depend_on_site_initialization(self): script = textwrap.dedent(""" @@ -628,8 +629,7 @@ class TestSysConfig(unittest.TestCase): self.assertEqual(no_site_config_vars['base'], site_config_vars['installed_base']) self.assertEqual(no_site_config_vars['platbase'], site_config_vars['installed_platbase']) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_subprocess() def test_config_vars_recalculation_after_site_initialization(self): script = textwrap.dedent(""" @@ -654,8 +654,7 @@ class TestSysConfig(unittest.TestCase): #self.assertEqual(config_vars['after']['prefix'], venv.prefix) # FIXME: prefix gets overwriten by _init_posix #self.assertEqual(config_vars['after']['exec_prefix'], venv.prefix) # FIXME: exec_prefix gets overwriten by _init_posix - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @requires_subprocess() def test_paths_depend_on_site_initialization(self): script = textwrap.dedent(""" @@ -676,8 +675,7 @@ class TestSysConfig(unittest.TestCase): class MakefileTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(sys.platform.startswith('win'), 'Test is not Windows compatible') @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 318c088fb..e42d1f9e4 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -49,8 +49,6 @@ from test.support import captured_stderr, cpython_only, infinite_recursion, requ from test.support.testcase import ExtraAssertions from test.typinganndata import ann_module695, mod_generics_cache, _typed_dict_helper -# TODO: RUSTPYTHON -import unittest CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes' NOT_A_BASE_TYPE = "type 'typing.%s' is not an acceptable base type" @@ -966,8 +964,7 @@ class GenericAliasSubstitutionTests(BaseTestCase): ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_two_parameters(self): T1 = TypeVar('T1') T2 = TypeVar('T2') @@ -1065,8 +1062,7 @@ class GenericAliasSubstitutionTests(BaseTestCase): eval(expected_str) ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_variadic_parameters(self): T1 = TypeVar('T1') T2 = TypeVar('T2') @@ -3231,8 +3227,7 @@ class ProtocolTests(BaseTestCase): self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto) self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto) - # TODO: RUSTPYTHON - @unittest.skip("TODO: RUSTPYTHON (no gc)") + @unittest.skip('TODO: RUSTPYTHON; (no gc)') def test_isinstance_checks_not_at_whim_of_gc(self): self.addCleanup(gc.enable) gc.disable() @@ -4175,8 +4170,7 @@ class ProtocolTests(BaseTestCase): Alias2 = typing.Union[P, typing.Iterable] self.assertEqual(Alias, Alias2) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_protocols_pickleable(self): global P, CP # pickle wants to reference the class by name T = TypeVar('T') @@ -5151,8 +5145,7 @@ class GenericTests(BaseTestCase): self.assertNotEqual(repr(base), '') self.assertEqual(base, base) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickle(self): global C # pickle wants to reference the class by name T = TypeVar('T') @@ -5274,8 +5267,7 @@ class GenericTests(BaseTestCase): for t in things: self.assertEqual(weakref.ref(t)(), t) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parameterized_slots(self): T = TypeVar('T') class C(Generic[T]): @@ -5295,8 +5287,7 @@ class GenericTests(BaseTestCase): self.assertEqual(get_type_hints(foo, globals(), locals())['x'], C[C]) self.assertEqual(copy(C[int]), deepcopy(C[int])) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parameterized_slots_dict(self): T = TypeVar('T') class D(Generic[T]): @@ -5722,6 +5713,23 @@ class GenericTests(BaseTestCase): with self.assertRaises(TypeError): a[int] + def test_return_non_tuple_while_unpacking(self): + # GH-138497: GenericAlias objects didn't ensure that __typing_subst__ actually + # returned a tuple + class EvilTypeVar: + __typing_is_unpacked_typevartuple__ = True + def __typing_prepare_subst__(*_): + return None # any value + def __typing_subst__(*_): + return 42 # not tuple + + evil = EvilTypeVar() + # Create a dummy TypeAlias that will be given the evil generic from + # above. + type type_alias[*_] = 0 + with self.assertRaisesRegex(TypeError, ".+__typing_subst__.+tuple.+int.*"): + type_alias[evil][0] + class ClassVarTests(BaseTestCase): @@ -5824,8 +5832,7 @@ class FinalDecoratorTests(BaseTestCase): def func(x): ... self.assertIs(func, final(func)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dunder_final(self): @final def func(): ... @@ -6844,8 +6851,7 @@ class GetTypeHintTests(BaseTestCase): with self.assertRaises(TypeError): gth(None) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_type_hints_modules(self): ann_module_type_hints = {1: 2, 'f': Tuple[int, int], 'x': int, 'y': str, 'u': int | float} self.assertEqual(gth(ann_module), ann_module_type_hints) @@ -7096,8 +7102,7 @@ class GetTypeHintTests(BaseTestCase): ): get_type_hints(ann_module6) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_type_hints_typeddict(self): self.assertEqual(get_type_hints(TotalMovie), {'title': str, 'year': int}) self.assertEqual(get_type_hints(TotalMovie, include_extras=True), { @@ -8138,8 +8143,7 @@ class NamedTupleTests(BaseTestCase): self.assertEqual(struct.__annotations__, {}) self.assertIsInstance(struct(), struct) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_namedtuple_errors(self): with self.assertRaises(TypeError): NamedTuple.__new__() @@ -8227,8 +8231,7 @@ class NamedTupleTests(BaseTestCase): self.assertIsInstance(bar.attr, Vanilla) self.assertEqual(bar.attr.name, "attr") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_setname_raises_the_same_as_on_other_classes(self): class CustomException(BaseException): pass @@ -8644,16 +8647,14 @@ class TypedDictTests(BaseTestCase): # The TypedDict constructor is not itself a TypedDict self.assertIs(is_typeddict(TypedDict), False) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_type_hints(self): self.assertEqual( get_type_hints(Bar), {'a': typing.Optional[int], 'b': int} ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_get_type_hints_generic(self): self.assertEqual( get_type_hints(BarGeneric), @@ -8786,8 +8787,7 @@ class TypedDictTests(BaseTestCase): with self.assertRaises(TypeError): WithImplicitAny[str] - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_non_generic_subscript(self): # For backward compatibility, subscription works # on arbitrary TypedDict types. @@ -8915,8 +8915,7 @@ class TypedDictTests(BaseTestCase): self.assertEqual(Child.__readonly_keys__, frozenset()) self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_combine_qualifiers(self): class AllTheThings(TypedDict): a: Annotated[Required[ReadOnly[int]], "why not"] @@ -9110,8 +9109,7 @@ class RETests(BaseTestCase): self.assertEqual(repr(Match[str]), 'typing.Match[str]') self.assertEqual(repr(Match[bytes]), 'typing.Match[bytes]') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cannot_subclass(self): with self.assertRaisesRegex( TypeError, @@ -10330,8 +10328,7 @@ class SpecialAttrsTests(BaseTestCase): TypeName = typing.NewType('SpecialAttrsTests.TypeName', Any) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_special_attrs2(self): # Forward refs provide a different introspection API. __name__ and # __qualname__ make little sense for forward refs as they can store @@ -10498,8 +10495,7 @@ class DataclassTransformTests(BaseTestCase): class NoDefaultTests(BaseTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): s = pickle.dumps(NoDefault, proto) @@ -10525,8 +10521,7 @@ class NoDefaultTests(BaseTestCase): with self.assertRaises(TypeError): NoDefault() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_no_attributes(self): with self.assertRaises(AttributeError): NoDefault.foo = 3 @@ -10602,8 +10597,7 @@ class TypeIterationTests(BaseTestCase): Annotated[T, ''], ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_cannot_iterate(self): expected_error_regex = "object is not iterable" for test_type in self._UNITERABLE_TYPES: From 9b400a9b6f32823136845d74d2a0db12bfed84ac Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:12:08 +0300 Subject: [PATCH 02/15] Reapply some patches --- Lib/_collections_abc.py | 4 + Lib/sysconfig/__init__.py | 2 +- Lib/test/support/__init__.py | 223 +++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 1 deletion(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 6e224d360..e02fc2273 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -512,6 +512,10 @@ class _CallableGenericAlias(GenericAlias): new_args = (t_args, t_result) return _CallableGenericAlias(Callable, tuple(new_args)) + # TODO: RUSTPYTHON; patch for common call + def __or__(self, other): + super().__or__(other) + def _is_param_expr(obj): """Checks if obj matches either a list of types, ``...``, ``ParamSpec`` or ``_ConcatenateGenericAlias`` from typing.py diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index f7bd675bb..8365236f6 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -107,7 +107,7 @@ else: _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv'] def _get_implementation(): - return 'Python' + return 'RustPython' # XXX: For site-packages # NOTE: site.py has copy of this function. # Sync it when modify this function. diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 4605938b8..444ca2219 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -511,6 +511,7 @@ def requires_lzma(reason='requires lzma'): import lzma except ImportError: lzma = None + lzma = None # XXX: RUSTPYTHON; xz is not supported yet return unittest.skipUnless(lzma, reason) def has_no_debug_ranges(): @@ -841,6 +842,8 @@ def gc_collect(): longer than expected. This function tries its best to force all garbage objects to disappear. """ + return # TODO: RUSTPYTHON + import gc gc.collect() gc.collect() @@ -848,6 +851,13 @@ def gc_collect(): @contextlib.contextmanager def disable_gc(): + # TODO: RUSTPYTHON; GC is not supported yet + try: + yield + finally: + pass + return + import gc have_gc = gc.isenabled() gc.disable() @@ -859,6 +869,13 @@ def disable_gc(): @contextlib.contextmanager def gc_threshold(*args): + # TODO: RUSTPYTHON; GC is not supported yet + try: + yield + finally: + pass + return + import gc old_threshold = gc.get_threshold() gc.set_threshold(*args) @@ -1921,6 +1938,10 @@ def _check_tracemalloc(): def check_free_after_iterating(test, iter, cls, args=()): + # TODO: RUSTPYTHON; GC is not supported yet + test.assertTrue(False) + return + done = False def wrapper(): class A(cls): @@ -2845,3 +2866,205 @@ def linked_to_musl(): except (OSError, subprocess.CalledProcessError): return False return ('musl' in stdout) + + +# TODO: RUSTPYTHON +# Every line of code below allowed us to update `Lib/test/support/__init__.py` without +# needing to update `libregtest` and its dependencies. +# Ideally we want to remove all code below and update `libregtest`. +# +# Code below was copied from: https://github.com/RustPython/RustPython/blob/9499d39f55b73535e2405bf208d5380241f79ada/Lib/test/support/__init__.py + +from .testresult import get_test_runner + +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 + +# By default, don't filter tests +_match_test_func = None + +_accept_test_patterns = None +_ignore_test_patterns = None + +def match_test(test): + # Function used by support.run_unittest() and regrtest --list-cases + if _match_test_func is None: + return True + else: + return _match_test_func(test.id()) + +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(accept_patterns=None, ignore_patterns=None): + global _match_test_func, _accept_test_patterns, _ignore_test_patterns + + if accept_patterns is None: + accept_patterns = () + if ignore_patterns is None: + ignore_patterns = () + + accept_func = ignore_func = None + + if accept_patterns != _accept_test_patterns: + accept_patterns, accept_func = _compile_match_function(accept_patterns) + if ignore_patterns != _ignore_test_patterns: + ignore_patterns, ignore_func = _compile_match_function(ignore_patterns) + + # Create a copy since patterns can be mutable and so modified later + _accept_test_patterns = tuple(accept_patterns) + _ignore_test_patterns = tuple(ignore_patterns) + + if accept_func is not None or ignore_func is not None: + def match_function(test_id): + accept = True + ignore = False + if accept_func: + accept = accept_func(test_id) + if ignore_func: + ignore = ignore_func(test_id) + return accept and not ignore + + _match_test_func = match_function + +def _compile_match_function(patterns): + if not patterns: + func = None + # set_match_tests(None) behaves as set_match_tests(()) + patterns = () + elif 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. + func = 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): + 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("."))) + + func = match_test_regex + + return patterns, func + +def run_unittest(*classes): + """Run tests from unittest.TestCase-derived classes.""" + valid_types = (unittest.TestSuite, unittest.TestCase) + loader = unittest.TestLoader() + suite = unittest.TestSuite() + for cls in classes: + if isinstance(cls, str): + if cls in sys.modules: + suite.addTest(loader.loadTestsFromModule(sys.modules[cls])) + else: + raise ValueError("str arguments must be keys in sys.modules") + elif isinstance(cls, valid_types): + suite.addTest(cls) + else: + suite.addTest(loader.loadTestsFromTestCase(cls)) + _filter_suite(suite, match_test) + return _run_suite(suite) + +def _run_suite(suite): + """Run tests from a unittest.TestSuite-derived class.""" + runner = get_test_runner(sys.stdout, + verbosity=verbose, + capture_output=(junit_xml_list is not None)) + + result = runner.run(suite) + + if junit_xml_list is not None: + junit_xml_list.append(result.get_xml_element()) + + if not result.testsRun and not result.skipped and not result.errors: + raise 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 TestFailedWithDetails(err, errors, failures, stats=stats) + return result + +@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) + + def accumulate(self, stats): + self.tests_run += stats.tests_run + self.failures += stats.failures + self.skipped += stats.skipped + + +def run_doctest(module, verbosity=None, optionflags=0): + """Run doctest on the given module. Return (#failures, #tests). + + If optional argument verbosity is not specified (or is None), pass + support's belief about verbosity on to doctest. Else doctest's + usual behavior is used (it searches sys.argv for -v). + """ + + import doctest + + if verbosity is None: + verbosity = verbose + else: + verbosity = None + + results = doctest.testmod(module, + verbose=verbosity, + optionflags=optionflags) + if results.failed: + stats = TestStats.from_doctest(results) + raise TestFailed(f"{results.failed} of {results.attempted} " + f"doctests failed", + stats=stats) + if verbose: + print('doctest (%s) ... %d tests with zero failures' % + (module.__name__, results.attempted)) + return results From 360f8caead1281770adfc9361fb01645ad244d6f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:18:15 +0300 Subject: [PATCH 03/15] Reaaply patches to `test_bytes.py` --- Lib/test/test_bytes.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 0847afe01..8f01f8903 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1579,6 +1579,11 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): self.assertEqual(b, b1) self.assertIs(b, b1) + # NOTE: RUSTPYTHON: + # + # The second instance of self.assertGreater was replaced with + # self.assertGreaterEqual since, in RustPython, the underlying storage + # is a Vec which doesn't require trailing null byte. def test_alloc(self): b = bytearray() alloc = b.__alloc__() @@ -1587,10 +1592,15 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): for i in range(100): b += b"x" alloc = b.__alloc__() - self.assertGreater(alloc, len(b)) # including trailing null byte + self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched if alloc not in seq: seq.append(alloc) + # NOTE: RUSTPYTHON: + # + # The usages of self.assertGreater were replaced with + # self.assertGreaterEqual since, in RustPython, the underlying storage + # is a Vec which doesn't require trailing null byte. def test_init_alloc(self): b = bytearray() def g(): @@ -1601,12 +1611,12 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase): self.assertEqual(len(b), len(a)) self.assertLessEqual(len(b), i) alloc = b.__alloc__() - self.assertGreater(alloc, len(b)) # including trailing null byte + self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched b.__init__(g()) self.assertEqual(list(b), list(range(1, 100))) self.assertEqual(len(b), 99) alloc = b.__alloc__() - self.assertGreater(alloc, len(b)) + self.assertGreaterEqual(alloc, len(b)) # NOTE: RUSTPYTHON patched def test_extend(self): orig = b'hello' From e18354b990b6535a7f3517bfedd194b5c1b3c0d8 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:23:24 +0300 Subject: [PATCH 04/15] fix test markers in `test_exceptions.py` --- Lib/test/test_exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index eee8c1338..efee74d02 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1152,6 +1152,7 @@ class ExceptionTests(unittest.TestCase): self.assertIs(c.__context__, b) self.assertIsNone(b.__context__) + @unittest.skip("TODO: RUSTPYTHON; Infinite loop") def test_no_hang_on_context_chain_cycle1(self): # See issue 25782. Cycle in context chain. @@ -1207,6 +1208,7 @@ class ExceptionTests(unittest.TestCase): self.assertIs(b.__context__, a) self.assertIs(a.__context__, c) + @unittest.skip("TODO: RUSTPYTHON; Infinite loop") def test_no_hang_on_context_chain_cycle3(self): # See issue 25782. Longer context chain with cycle. From 84b254209fe27ca8d7d2985e02a47019bb9bb232 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:25:51 +0300 Subject: [PATCH 05/15] Patch `test_posix.py` --- Lib/test/test_posix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index c327d2add..00da4c2c4 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2074,6 +2074,7 @@ class _PosixSpawnMixin: with open(outfile, encoding="utf-8") as f: self.assertEqual(f.read(), 'hello') + @unittest.expectedFailure # TODO: RUSTPYTHON; the rust runtime reopens closed stdio fds at startup, so this test fails, even though POSIX_SPAWN_CLOSE does actually have an effect def test_close_file(self): closefile = os_helper.TESTFN self.addCleanup(os_helper.unlink, closefile) From aa56ebb0574c72bd155498b5cc1adb70c2c66b9e Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:32:26 +0300 Subject: [PATCH 06/15] Patched `test_{pyexpat,site,sysconfig}.py` --- Lib/test/test_pyexpat.py | 1 - Lib/test/test_site.py | 6 ++++-- Lib/test/test_sysconfig.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 9747cf662..914a00269 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -466,7 +466,6 @@ class HandlerExceptionTest(unittest.TestCase): self.assertEqual(os.path.basename(entry.filename), filename) self.assertEqual(entry.name, funcname) - @unittest.expectedFailure # TODO: RUSTPYTHON @support.cpython_only def test_exception(self): # gh-66652: test _PyTraceback_Add() used by pyexpat.c to inject frames diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 9001b8170..88ca66ac7 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -331,13 +331,15 @@ class HelperFunctionsTests(unittest.TestCase): if sys.platlibdir != "lib": self.assertEqual(len(dirs), 2) wanted = os.path.join('xoxo', sys.platlibdir, - f'python{sysconfig._get_python_version_abi()}', + # XXX: RUSTPYTHON + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[0], wanted) else: self.assertEqual(len(dirs), 1) wanted = os.path.join('xoxo', 'lib', - f'python{sysconfig._get_python_version_abi()}', + # XXX: RUSTPYTHON + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') self.assertEqual(dirs[-1], wanted) else: diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index dc9701661..35e62d546 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -166,7 +166,8 @@ class TestSysConfig(unittest.TestCase): binpath = 'bin' incpath = 'include' libpath = os.path.join('lib', - f'python{sysconfig._get_python_version_abi()}', + # XXX: RUSTPYTHON + f'rustpython{sysconfig._get_python_version_abi()}', 'site-packages') # Resolve the paths in an imaginary venv/ directory From 9557acb1c338f505a585a5d9c4f6ddf03ad90359 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:34:15 +0300 Subject: [PATCH 07/15] Patched `test_typing.py` --- Lib/test/test_typing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index e42d1f9e4..ea5c1482f 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -49,6 +49,8 @@ from test.support import captured_stderr, cpython_only, infinite_recursion, requ from test.support.testcase import ExtraAssertions from test.typinganndata import ann_module695, mod_generics_cache, _typed_dict_helper +import unittest # XXX: RUSTPYTHON + CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes' NOT_A_BASE_TYPE = "type 'typing.%s' is not an acceptable base type" From 3f7deb49c827f560cf28549730701c810ed19395 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:40:38 +0300 Subject: [PATCH 08/15] Patch failing tests in `test_typing.py` --- Lib/test/test_typing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ea5c1482f..3f268c623 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5715,6 +5715,7 @@ class GenericTests(BaseTestCase): with self.assertRaises(TypeError): a[int] + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ".+__typing_subst__.+tuple.+int.*" does not match "'TypeAliasType' object is not subscriptable" def test_return_non_tuple_while_unpacking(self): # GH-138497: GenericAlias objects didn't ensure that __typing_subst__ actually # returned a tuple From 624a561145c0c2042e7560bd13f20fdb585fd498 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:42:16 +0300 Subject: [PATCH 09/15] Update `seq_tests` from 3.13.8 --- Lib/test/seq_tests.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/seq_tests.py b/Lib/test/seq_tests.py index 59db2664a..54eb5e65a 100644 --- a/Lib/test/seq_tests.py +++ b/Lib/test/seq_tests.py @@ -145,6 +145,9 @@ class CommonTest(unittest.TestCase): self.assertEqual(self.type2test(LyingTuple((2,))), self.type2test((1,))) self.assertEqual(self.type2test(LyingList([2])), self.type2test([1])) + with self.assertRaises(TypeError): + self.type2test(unsupported_arg=[]) + def test_truth(self): self.assertFalse(self.type2test()) self.assertTrue(self.type2test([42])) @@ -423,8 +426,8 @@ class CommonTest(unittest.TestCase): self.assertEqual(lst2, lst) self.assertNotEqual(id(lst2), id(lst)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON + @support.suppress_immortalization() def test_free_after_iterating(self): support.check_free_after_iterating(self, iter, self.type2test) support.check_free_after_iterating(self, reversed, self.type2test) From 296da56190781f229e98e0e6f802e3d96e3656f0 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:01:33 +0300 Subject: [PATCH 10/15] Mark failing tests in `test_genericalias.py` --- Lib/test/test_genericalias.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 4a87e2bf7..c1c49dc29 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -151,6 +151,7 @@ class BaseTest(unittest.TestCase): self.assertEqual(alias.__args__, (int,)) self.assertEqual(alias.__parameters__, ()) + @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_unsubscriptable(self): for t in int, str, float, Sized, Hashable: tname = t.__name__ @@ -209,6 +210,7 @@ class BaseTest(unittest.TestCase): self.assertEqual(t.__args__, (int,)) self.assertEqual(t.__parameters__, ()) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_repr(self): class MyList(list): pass @@ -332,6 +334,7 @@ class BaseTest(unittest.TestCase): with self.assertRaises(TypeError): dict[T, T][str, int] + @unittest.expectedFailure # TODO: RUSTPYTHON def test_equality(self): self.assertEqual(list[int], list[int]) self.assertEqual(dict[str, int], dict[str, int]) @@ -362,6 +365,7 @@ class BaseTest(unittest.TestCase): self.assertEqual(t(test), Test) self.assertEqual(t(0), int) + @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_type_subclass_generic(self): class MyType(type): pass @@ -422,6 +426,7 @@ class BaseTest(unittest.TestCase): self.assertEqual(a.__args__, (list[T], tuple[T, ...])) self.assertEqual(a.__parameters__, (T,)) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dir(self): ga = list[int] dir_of_gen_alias = set(dir(ga)) From e069244f890f8054d8a5256ddaf8004b15706e2f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:07:03 +0300 Subject: [PATCH 11/15] mark failing tests in `test_pyexpat.py` --- Lib/test/test_pyexpat.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 914a00269..80485cc74 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -549,6 +549,7 @@ class PositionTest(unittest.TestCase): class sf1296433Test(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'str' but 'bytes' found. def test_parse_only_xml_data(self): # https://bugs.python.org/issue1296433 # @@ -797,6 +798,7 @@ class ParentParserLifetimeTest(unittest.TestCase): See https://github.com/python/cpython/issues/139400. """ + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate' def test_parent_parser_outlives_its_subparsers__single(self): parser = expat.ParserCreate() subparser = parser.ExternalEntityParserCreate(None) @@ -805,6 +807,7 @@ class ParentParserLifetimeTest(unittest.TestCase): # while it's still being referenced by a related subparser. del parser + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate' def test_parent_parser_outlives_its_subparsers__multiple(self): parser = expat.ParserCreate() subparser_one = parser.ExternalEntityParserCreate(None) @@ -814,6 +817,7 @@ class ParentParserLifetimeTest(unittest.TestCase): # while it's still being referenced by a related subparser. del parser + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate' def test_parent_parser_outlives_its_subparsers__chain(self): parser = expat.ParserCreate() subparser = parser.ExternalEntityParserCreate(None) @@ -826,6 +830,7 @@ class ParentParserLifetimeTest(unittest.TestCase): class ReparseDeferralTest(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled' def test_getter_setter_round_trip(self): parser = expat.ParserCreate() enabled = (expat.version_info >= (2, 6, 0)) @@ -836,6 +841,7 @@ class ReparseDeferralTest(unittest.TestCase): parser.SetReparseDeferralEnabled(True) self.assertIs(parser.GetReparseDeferralEnabled(), enabled) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled' def test_reparse_deferral_enabled(self): if expat.version_info < (2, 6, 0): self.skipTest(f'Expat {expat.version_info} does not ' @@ -860,6 +866,7 @@ class ReparseDeferralTest(unittest.TestCase): self.assertEqual(started, ['doc']) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetReparseDeferralEnabled' def test_reparse_deferral_disabled(self): started = [] From 604b708741578ae75d5645dab9923fad3e129e94 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:04:49 +0300 Subject: [PATCH 12/15] Mark failing tests in `test_posix.py` --- Lib/test/test_posix.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 00da4c2c4..d4020ed97 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -583,6 +583,7 @@ class PosixTester(unittest.TestCase): self.assertGreater(len(path), 0) self.assertEqual(posix.confstr(posix.confstr_names["CS_PATH"]), path) + @unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), '''TODO: RUSTPYTHON; AssertionError: "configuration names must be strings or integers" does not match "Expected type 'str' but 'float' found."''') @unittest.skipUnless(hasattr(posix, 'sysconf'), 'test needs posix.sysconf()') def test_sysconf(self): @@ -1017,6 +1018,7 @@ class PosixTester(unittest.TestCase): target = self.tempdir() self.check_chmod(posix.chmod, target) + @unittest.skipIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; crash') @os_helper.skip_unless_working_chmod def test_fchmod_file(self): with open(os_helper.TESTFN, 'wb+') as f: @@ -1073,6 +1075,7 @@ class PosixTester(unittest.TestCase): self.check_chmod_link(posix.chmod, target, link) self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @os_helper.skip_unless_symlink def test_chmod_dir_symlink(self): target = self.tempdir() @@ -1566,6 +1569,7 @@ class TestPosixDirFd(unittest.TestCase): with self.prepare_file() as (dir_fd, name, fullname): posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd) + @unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; AssertionError: RuntimeWarning not triggered') @unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()") def test_stat_dir_fd(self): with self.prepare() as (dir_fd, name, fullname): @@ -1968,6 +1972,7 @@ class _PosixSpawnMixin: [sys.executable, "-c", "pass"], os.environ, setsigdef=[signal.NSIG, signal.NSIG+1]) + @unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented') @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") @@ -1988,6 +1993,7 @@ class _PosixSpawnMixin: ) support.wait_process(pid, exitcode=0) + @unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented') @requires_sched @unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')), "bpo-34685: test can fail on BSD") From 68e7310d221708718daab2520228a8cd37925ab0 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:02:36 +0300 Subject: [PATCH 13/15] reapply patch --- Lib/test/test_exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index efee74d02..6148bd3a6 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1678,6 +1678,7 @@ class ExceptionTests(unittest.TestCase): gc_collect() # For PyPy or other GCs. self.assertEqual(wr(), None) + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; error specific to cpython') def test_errno_ENOTDIR(self): # Issue #12802: "not a directory" errors are ENOTDIR even on Windows with self.assertRaises(OSError) as cm: From 715529bef1d0e1df314689173d270c5bb856c7d1 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:36:40 +0300 Subject: [PATCH 14/15] mark failing tests --- Lib/test/test_posix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index d4020ed97..bb6e4b126 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1063,6 +1063,7 @@ class PosixTester(unittest.TestCase): self.assertEqual(os.stat(target).st_mode, target_mode) self.assertEqual(os.lstat(link).st_mode, new_mode) + @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @os_helper.skip_unless_symlink def test_chmod_file_symlink(self): target = os_helper.TESTFN @@ -1350,6 +1351,7 @@ class PosixTester(unittest.TestCase): param = posix.sched_param(sched_priority=-large) self.assertRaises(OverflowError, posix.sched_setparam, 0, param) + @unittest.expectedFailureIf(sys.platform == 'linux', "TODO: RUSTPYTHON; TypeError: cannot pickle 'sched_param' object") @requires_sched def test_sched_param(self): param = posix.sched_param(1) From a50cc9b915df0ef4d79179979ab73841872d044f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:04:01 +0300 Subject: [PATCH 15/15] skip flaky test --- Lib/test/test_posix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index bb6e4b126..de3184a37 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1076,7 +1076,7 @@ class PosixTester(unittest.TestCase): self.check_chmod_link(posix.chmod, target, link) self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') + @unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; flaky') @os_helper.skip_unless_symlink def test_chmod_dir_symlink(self): target = self.tempdir()