From c6425ea595ff8cd3d54e1de02daa3ca07744ae1c Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 22 Nov 2024 15:32:59 +0100 Subject: [PATCH 1/5] gh-127146 Emscripten: Skip segfaults in test suite After this, Emscripten makes it all the way through the test suite when I run it locally. --- Lib/test/list_tests.py | 4 +++- Lib/test/mapping_tests.py | 3 ++- Lib/test/test_ast/test_ast.py | 5 ++++- Lib/test/test_call.py | 5 +++-- Lib/test/test_capi/test_misc.py | 21 +++++++++++---------- Lib/test/test_class.py | 3 ++- Lib/test/test_compile.py | 2 ++ Lib/test/test_copy.py | 3 +++ Lib/test/test_descr.py | 3 +++ Lib/test/test_dict.py | 1 + Lib/test/test_dictviews.py | 3 ++- Lib/test/test_exception_group.py | 4 +++- Lib/test/test_functools.py | 2 ++ Lib/test/test_isinstance.py | 3 +++ Lib/test/test_json/test_recursion.py | 4 ++++ Lib/test/test_pathlib/test_pathlib_abc.py | 4 +++- Lib/test/test_traceback.py | 4 +++- Lib/test/test_xml_etree_c.py | 1 + configure | 1 + configure.ac | 1 + 20 files changed, 57 insertions(+), 20 deletions(-) diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py index dbc5ef4f9f2cd5..dc670732a0c721 100644 --- a/Lib/test/list_tests.py +++ b/Lib/test/list_tests.py @@ -3,10 +3,11 @@ """ import sys +import unittest from functools import cmp_to_key from test import seq_tests -from test.support import ALWAYS_EQ, NEVER_EQ, get_c_recursion_limit +from test.support import ALWAYS_EQ, NEVER_EQ, get_c_recursion_limit, is_emscripten class CommonTest(seq_tests.CommonTest): @@ -59,6 +60,7 @@ def test_repr(self): self.assertEqual(str(a2), "[0, 1, 2, [...], 3]") self.assertEqual(repr(a2), "[0, 1, 2, [...], 3]") + @unittest.skipIf(is_emscripten, "Stack overflow") def test_repr_deep(self): a = self.type2test([]) for i in range(get_c_recursion_limit() + 1): diff --git a/Lib/test/mapping_tests.py b/Lib/test/mapping_tests.py index ed89a81a6ea685..0e0aece306c81b 100644 --- a/Lib/test/mapping_tests.py +++ b/Lib/test/mapping_tests.py @@ -1,7 +1,7 @@ # tests common to dict and UserDict import unittest import collections -from test.support import get_c_recursion_limit +from test.support import get_c_recursion_limit, is_emscripten class BasicTestMappingProtocol(unittest.TestCase): @@ -622,6 +622,7 @@ def __repr__(self): d = self._full_mapping({1: BadRepr()}) self.assertRaises(Exc, repr, d) + @unittest.skipIf(is_emscripten, "Stack overflow") def test_repr_deep(self): d = self._empty_mapping() for i in range(get_c_recursion_limit() + 1): diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 67ab8cf6baf657..ee977421093036 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -18,7 +18,7 @@ _testinternalcapi = None from test import support -from test.support import os_helper, script_helper +from test.support import os_helper, script_helper, is_emscripten from test.support.ast_helper import ASTTestMixin from test.test_ast.utils import to_tuple from test.test_ast.snippets import ( @@ -745,6 +745,7 @@ def next(self): enum._test_simple_enum(_Precedence, ast._Precedence) @support.cpython_only + @unittest.skipIf(is_emscripten, "Stack overflow") def test_ast_recursion_limit(self): fail_depth = support.exceeds_recursion_limit() crash_depth = 100_000 @@ -1661,6 +1662,7 @@ def test_level_as_none(self): exec(code, ns) self.assertIn('sleep', ns) + @unittest.skipIf(is_emscripten, "Stack overflow") def test_recursion_direct(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) e.operand = e @@ -1668,6 +1670,7 @@ def test_recursion_direct(self): with support.infinite_recursion(): compile(ast.Expression(e), "", "eval") + @unittest.skipIf(is_emscripten, "Stack overflow") def test_recursion_indirect(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 9d5256b566b8af..cc9403e18cc841 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -1,6 +1,6 @@ import unittest -from test.support import (cpython_only, is_wasi, requires_limited_api, Py_DEBUG, - set_recursion_limit, skip_on_s390x) +from test.support import (cpython_only, is_wasi, is_emscripten, requires_limited_api, + Py_DEBUG, set_recursion_limit, skip_on_s390x) try: import _testcapi except ImportError: @@ -1038,6 +1038,7 @@ class TestRecursion(unittest.TestCase): @skip_on_s390x @unittest.skipIf(is_wasi and Py_DEBUG, "requires deep stack") @unittest.skipIf(_testcapi is None, "requires _testcapi") + @unittest.skipIf(is_emscripten, "requires deep stack") def test_super_deep(self): def recurse(n): diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 80e705a37c4c5e..2efdcbce5a27b6 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2215,16 +2215,17 @@ def test_configured_settings(self): self.assertEqual(settings, expected) # expected to fail - for config in expected_to_fail: - kwargs = dict(zip(kwlist, config)) - with self.subTest(config): - script = textwrap.dedent(f''' - import _testinternalcapi - _testinternalcapi.get_interp_settings() - raise NotImplementedError('unreachable') - ''') - with self.assertRaises(_interpreters.InterpreterError): - support.run_in_subinterp_with_config(script, **kwargs) + if _interpreters is not None: + for config in expected_to_fail: + kwargs = dict(zip(kwlist, config)) + with self.subTest(config): + script = textwrap.dedent(f''' + import _testinternalcapi + _testinternalcapi.get_interp_settings() + raise NotImplementedError('unreachable') + ''') + with self.assertRaises(_interpreters.InterpreterError): + support.run_in_subinterp_with_config(script, **kwargs) @unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module") @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 7720cf157fa9ae..023f21518e25fd 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -1,7 +1,7 @@ "Test the functionality of Python classes implementing operators." import unittest -from test.support import cpython_only, import_helper, script_helper +from test.support import cpython_only, import_helper, script_helper, is_emscripten testmeths = [ @@ -554,6 +554,7 @@ class Custom: self.assertFalse(hasattr(o, "__call__")) self.assertFalse(hasattr(c, "__call__")) + @unittest.skipIf(is_emscripten, "exhausts limited stack") def testSFBug532646(self): # Test for SF bug 532646 diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index f7ea923ef17672..a824fa8fd3b7d2 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -121,6 +121,7 @@ def __getitem__(self, key): self.assertEqual(d['z'], 12) @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") + @unittest.skipIf(support.is_emscripten, "exhausts limited stack") def test_extended_arg(self): repeat = int(get_c_recursion_limit() * 0.9) longexpr = 'x = x or ' + '-x' * repeat @@ -709,6 +710,7 @@ def test_yet_more_evil_still_undecodable(self): @support.cpython_only @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") + @unittest.skipIf(support.is_emscripten, "exhausts limited stack") def test_compiler_recursion_limit(self): # Expected limit is Py_C_RECURSION_LIMIT limit = get_c_recursion_limit() diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 3dec64cc9a2414..5e3d70a8426c74 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -371,6 +371,7 @@ def test_deepcopy_list(self): self.assertIsNot(x, y) self.assertIsNot(x[0], y[0]) + @unittest.skipIf(support.is_emscripten, "exhausts limited stack") def test_deepcopy_reflexive_list(self): x = [] x.append(x) @@ -398,6 +399,7 @@ def test_deepcopy_tuple_of_immutables(self): y = copy.deepcopy(x) self.assertIs(x, y) + @unittest.skipIf(support.is_emscripten, "exhausts limited stack") def test_deepcopy_reflexive_tuple(self): x = ([],) x[0].append(x) @@ -415,6 +417,7 @@ def test_deepcopy_dict(self): self.assertIsNot(x, y) self.assertIsNot(x["foo"], y["foo"]) + @unittest.skipIf(support.is_emscripten, "exhausts limited stack") def test_deepcopy_reflexive_dict(self): x = {} x['foo'] = x diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index aa801b9c4f7ad9..2669075d0cfb69 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3663,6 +3663,7 @@ def f(a): return a encoding='latin1', errors='replace') self.assertEqual(ba, b'abc\xbd?') + @unittest.skipIf(support.is_emscripten, "exhasts limited stack") def test_recursive_call(self): # Testing recursive __call__() by setting to instance of class... class A(object): @@ -3942,6 +3943,7 @@ def __del__(self): # it as a leak. del C.__del__ + @unittest.skipIf(support.is_emscripten, "Seems to works in Pyodide?") def test_slots_trash(self): # Testing slot trash... # Deallocating deeply nested slotted trash caused stack overflows @@ -4864,6 +4866,7 @@ class Thing: # CALL_METHOD_DESCRIPTOR_O deque.append(thing, thing) + @unittest.skipIf(support.is_emscripten, "Stack overflow") def test_repr_as_str(self): # Issue #11603: crash or infinite loop when rebinding __str__ as # __repr__. diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index c94dc2df4f0a7f..9f25ace7b5fc91 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -594,6 +594,7 @@ def __repr__(self): d = {1: BadRepr()} self.assertRaises(Exc, repr, d) + @unittest.skipIf(support.is_emscripten, "Exhausts limited stack") def test_repr_deep(self): d = {} for i in range(get_c_recursion_limit() + 1): diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index d9881611c19c43..d928bd70ca90fa 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -2,7 +2,7 @@ import copy import pickle import unittest -from test.support import get_c_recursion_limit +from test.support import get_c_recursion_limit, is_emscripten class DictSetTest(unittest.TestCase): @@ -277,6 +277,7 @@ def test_recursive_repr(self): # Again. self.assertIsInstance(r, str) + @unittest.skipIf(is_emscripten, "exhausts limited stack") def test_deeply_nested_repr(self): d = {} for i in range(get_c_recursion_limit()//2 + 100): diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index b4fc290b1f32b6..5ad4327d116ae4 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -1,7 +1,7 @@ import collections.abc import types import unittest -from test.support import get_c_recursion_limit +from test.support import get_c_recursion_limit, is_emscripten class TestExceptionGroupTypeHierarchy(unittest.TestCase): def test_exception_group_types(self): @@ -464,11 +464,13 @@ def make_deep_eg(self): e = ExceptionGroup('eg', [e]) return e + @unittest.skipIf(is_emscripten, "exhausts limited stack") def test_deep_split(self): e = self.make_deep_eg() with self.assertRaises(RecursionError): e.split(TypeError) + @unittest.skipIf(is_emscripten, "exhausts limited stack") def test_deep_subgroup(self): e = self.make_deep_eg() with self.assertRaises(RecursionError): diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 6d60f6941c4c5d..801d62ba29e147 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -404,6 +404,7 @@ def test_setstate_subclasses(self): self.assertEqual(r, ((1, 2), {})) self.assertIs(type(r[0]), tuple) + @unittest.skipIf(support.is_emscripten, "limited C stack") def test_recursive_pickle(self): with replaced_module('functools', self.module): f = self.partial(capture) @@ -2054,6 +2055,7 @@ def orig(a, /, b, c=True): ... @support.skip_on_s390x @unittest.skipIf(support.is_wasi, "WASI has limited C stack") + @unittest.skipIf(support.is_emscripten, "limited C stack") def test_lru_recursion(self): @self.module.lru_cache diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index 95a119ba683e09..0c1546129ce221 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -263,12 +263,14 @@ def test_subclass_tuple(self): self.assertEqual(True, issubclass(int, (int, (float, int)))) self.assertEqual(True, issubclass(str, (str, (Child, str)))) + @unittest.skipIf(support.is_emscripten, "limited C stack") def test_subclass_recursion_limit(self): # make sure that issubclass raises RecursionError before the C stack is # blown with support.infinite_recursion(): self.assertRaises(RecursionError, blowstack, issubclass, str, str) + @unittest.skipIf(support.is_emscripten, "limited C stack") def test_isinstance_recursion_limit(self): # make sure that issubclass raises RecursionError before the C stack is # blown @@ -315,6 +317,7 @@ def __bases__(self): self.assertRaises(RecursionError, issubclass, int, X()) self.assertRaises(RecursionError, isinstance, 1, X()) + @unittest.skipIf(support.is_emscripten, "limited C stack") def test_infinite_recursion_via_bases_tuple(self): """Regression test for bpo-30570.""" class Failure(object): diff --git a/Lib/test/test_json/test_recursion.py b/Lib/test/test_json/test_recursion.py index 290207e9c15b88..c3e4445ee53881 100644 --- a/Lib/test/test_json/test_recursion.py +++ b/Lib/test/test_json/test_recursion.py @@ -1,5 +1,6 @@ from test import support from test.test_json import PyTest, CTest +import unittest class JSONTestObject: @@ -68,6 +69,7 @@ def default(self, o): self.fail("didn't raise ValueError on default recursion") + @unittest.skipIf(support.is_emscripten, "limited C stack") def test_highly_nested_objects_decoding(self): # test that loading highly-nested objects doesn't segfault when C # accelerations are used. See #12017 @@ -81,6 +83,7 @@ def test_highly_nested_objects_decoding(self): with support.infinite_recursion(): self.loads('[' * 100000 + '1' + ']' * 100000) + @unittest.skipIf(support.is_emscripten, "limited C stack") def test_highly_nested_objects_encoding(self): # See #12051 l, d = [], {} @@ -93,6 +96,7 @@ def test_highly_nested_objects_encoding(self): with support.infinite_recursion(5000): self.dumps(d) + @unittest.skipIf(support.is_emscripten, "limited C stack") def test_endless_recursion(self): # See #12051 class EndlessJSONEncoder(self.json.JSONEncoder): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index b69d674e1cf1ed..dee9b8a037f726 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -9,7 +9,7 @@ from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase import posixpath -from test.support import is_wasi +from test.support import is_wasi, is_emscripten from test.support.os_helper import TESTFN @@ -2301,6 +2301,7 @@ def _check(path, pattern, case_sensitive, expected): _check(path, "dirb/file*", False, ["dirB/fileB"]) @needs_symlinks + @unittest.skipIf(is_emscripten, "Hangs") def test_glob_recurse_symlinks_common(self): def _check(path, glob, expected): actual = {path for path in path.glob(glob, recurse_symlinks=True) @@ -2396,6 +2397,7 @@ def test_rglob_windows(self): self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") }) @needs_symlinks + @unittest.skipIf(is_emscripten, "Hangs") def test_rglob_recurse_symlinks_common(self): def _check(path, glob, expected): actual = {path for path in path.rglob(glob, recurse_symlinks=True) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index ec69412f5511eb..bf4e468d8342e6 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -21,7 +21,7 @@ from test.support.os_helper import TESTFN, unlink from test.support.script_helper import assert_python_ok, assert_python_failure from test.support.import_helper import forget -from test.support import force_not_colorized +from test.support import force_not_colorized, is_emscripten import json import textwrap @@ -2097,6 +2097,7 @@ def deep_eg(self): return e @cpython_only + @unittest.skipIf(is_emscripten, "exhausts limited stack") def test_exception_group_deep_recursion_capi(self): from _testcapi import exception_print LIMIT = 75 @@ -2108,6 +2109,7 @@ def test_exception_group_deep_recursion_capi(self): self.assertIn('ExceptionGroup', output) self.assertLessEqual(output.count('ExceptionGroup'), LIMIT) + @unittest.skipIf(is_emscripten, "exhausts limited stack") def test_exception_group_deep_recursion_traceback(self): LIMIT = 75 eg = self.deep_eg() diff --git a/Lib/test/test_xml_etree_c.py b/Lib/test/test_xml_etree_c.py index 3a0fc572f457ff..db19af419bdeab 100644 --- a/Lib/test/test_xml_etree_c.py +++ b/Lib/test/test_xml_etree_c.py @@ -57,6 +57,7 @@ def test_del_attribute(self): del element.attrib self.assertEqual(element.attrib, {'A': 'B', 'C': 'D'}) + @unittest.skipIf(support.is_emscripten, "segfaults") def test_trashcan(self): # If this test fails, it will most likely die via segfault. e = root = cET.Element('root') diff --git a/configure b/configure index 84b74ac3584bcd..34dd109cd94385 100755 --- a/configure +++ b/configure @@ -9433,6 +9433,7 @@ fi as_fn_append LDFLAGS_NODIST " -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js" as_fn_append LDFLAGS_NODIST " -sEXPORTED_RUNTIME_METHODS=FS" + as_fn_append LDFLAGS_NODIST " -sSTACK_SIZE=5MB" if test "x$enable_wasm_dynamic_linking" = xyes then : diff --git a/configure.ac b/configure.ac index 8fa6cb60900ad1..4e7df49590fa62 100644 --- a/configure.ac +++ b/configure.ac @@ -2329,6 +2329,7 @@ AS_CASE([$ac_sys_system], dnl Include file system support AS_VAR_APPEND([LDFLAGS_NODIST], [" -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js"]) AS_VAR_APPEND([LDFLAGS_NODIST], [" -sEXPORTED_RUNTIME_METHODS=FS"]) + AS_VAR_APPEND([LDFLAGS_NODIST], [" -sSTACK_SIZE=5MB"]) AS_VAR_IF([enable_wasm_dynamic_linking], [yes], [ AS_VAR_APPEND([LINKFORSHARED], [" -sMAIN_MODULE"]) From e3c2da58c60569242ee89307e6fd73dc4099a5a7 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sat, 30 Nov 2024 15:58:26 +0100 Subject: [PATCH 2/5] Make common support decorator for stack overflow skips --- Lib/test/list_tests.py | 4 ++-- Lib/test/mapping_tests.py | 4 ++-- Lib/test/support/__init__.py | 3 +++ Lib/test/test_ast/test_ast.py | 8 ++++---- Lib/test/test_call.py | 6 +++--- Lib/test/test_class.py | 4 ++-- Lib/test/test_compile.py | 4 ++-- Lib/test/test_copy.py | 6 +++--- Lib/test/test_descr.py | 4 ++-- Lib/test/test_dict.py | 2 +- Lib/test/test_dictviews.py | 4 ++-- Lib/test/test_exception_group.py | 6 +++--- Lib/test/test_functools.py | 4 ++-- Lib/test/test_isinstance.py | 6 +++--- Lib/test/test_json/test_recursion.py | 6 +++--- Lib/test/test_traceback.py | 6 +++--- 16 files changed, 40 insertions(+), 37 deletions(-) diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py index dc670732a0c721..fd4f39f51ca232 100644 --- a/Lib/test/list_tests.py +++ b/Lib/test/list_tests.py @@ -7,7 +7,7 @@ from functools import cmp_to_key from test import seq_tests -from test.support import ALWAYS_EQ, NEVER_EQ, get_c_recursion_limit, is_emscripten +from test.support import ALWAYS_EQ, NEVER_EQ, get_c_recursion_limit, skip_emscripten_stack_overflow class CommonTest(seq_tests.CommonTest): @@ -60,7 +60,7 @@ def test_repr(self): self.assertEqual(str(a2), "[0, 1, 2, [...], 3]") self.assertEqual(repr(a2), "[0, 1, 2, [...], 3]") - @unittest.skipIf(is_emscripten, "Stack overflow") + @skip_emscripten_stack_overflow() def test_repr_deep(self): a = self.type2test([]) for i in range(get_c_recursion_limit() + 1): diff --git a/Lib/test/mapping_tests.py b/Lib/test/mapping_tests.py index 0e0aece306c81b..d575ee9900c0b0 100644 --- a/Lib/test/mapping_tests.py +++ b/Lib/test/mapping_tests.py @@ -1,7 +1,7 @@ # tests common to dict and UserDict import unittest import collections -from test.support import get_c_recursion_limit, is_emscripten +from test.support import get_c_recursion_limit, skip_emscripten_stack_overflow class BasicTestMappingProtocol(unittest.TestCase): @@ -622,7 +622,7 @@ def __repr__(self): d = self._full_mapping({1: BadRepr()}) self.assertRaises(Exc, repr, d) - @unittest.skipIf(is_emscripten, "Stack overflow") + @skip_emscripten_stack_overflow def test_repr_deep(self): d = self._empty_mapping() for i in range(get_c_recursion_limit() + 1): diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 2ad267e3e08f0f..5c738ffaa27713 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -535,6 +535,9 @@ def skip_android_selinux(name): is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" +def skip_emscripten_stack_overflow(): + return unittest.skipIf(is_emscripten, "Exhausts limited stack on Emscripten") + is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} is_apple = is_apple_mobile or sys.platform == "darwin" diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index ee977421093036..6970c7df6333c2 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -18,7 +18,7 @@ _testinternalcapi = None from test import support -from test.support import os_helper, script_helper, is_emscripten +from test.support import os_helper, script_helper, skip_emscripten_stack_overflow from test.support.ast_helper import ASTTestMixin from test.test_ast.utils import to_tuple from test.test_ast.snippets import ( @@ -745,7 +745,7 @@ def next(self): enum._test_simple_enum(_Precedence, ast._Precedence) @support.cpython_only - @unittest.skipIf(is_emscripten, "Stack overflow") + @skip_emscripten_stack_overflow def test_ast_recursion_limit(self): fail_depth = support.exceeds_recursion_limit() crash_depth = 100_000 @@ -1662,7 +1662,7 @@ def test_level_as_none(self): exec(code, ns) self.assertIn('sleep', ns) - @unittest.skipIf(is_emscripten, "Stack overflow") + @skip_emscripten_stack_overflow def test_recursion_direct(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) e.operand = e @@ -1670,7 +1670,7 @@ def test_recursion_direct(self): with support.infinite_recursion(): compile(ast.Expression(e), "", "eval") - @unittest.skipIf(is_emscripten, "Stack overflow") + @skip_emscripten_stack_overflow def test_recursion_indirect(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index cc9403e18cc841..78a706436aea0e 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -1,6 +1,6 @@ import unittest -from test.support import (cpython_only, is_wasi, is_emscripten, requires_limited_api, - Py_DEBUG, set_recursion_limit, skip_on_s390x) +from test.support import (cpython_only, is_wasi, requires_limited_api, Py_DEBUG, + set_recursion_limit, skip_on_s390x, skip_emscripten_stack_overflow) try: import _testcapi except ImportError: @@ -1038,7 +1038,7 @@ class TestRecursion(unittest.TestCase): @skip_on_s390x @unittest.skipIf(is_wasi and Py_DEBUG, "requires deep stack") @unittest.skipIf(_testcapi is None, "requires _testcapi") - @unittest.skipIf(is_emscripten, "requires deep stack") + @skip_emscripten_stack_overflow() def test_super_deep(self): def recurse(n): diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 023f21518e25fd..e20e59944e9ce9 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -1,7 +1,7 @@ "Test the functionality of Python classes implementing operators." import unittest -from test.support import cpython_only, import_helper, script_helper, is_emscripten +from test.support import cpython_only, import_helper, script_helper, skip_emscripten_stack_overflow testmeths = [ @@ -554,7 +554,7 @@ class Custom: self.assertFalse(hasattr(o, "__call__")) self.assertFalse(hasattr(c, "__call__")) - @unittest.skipIf(is_emscripten, "exhausts limited stack") + @skip_emscripten_stack_overflow() def testSFBug532646(self): # Test for SF bug 532646 diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index a824fa8fd3b7d2..b5cf2ad18fe60b 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -121,7 +121,7 @@ def __getitem__(self, key): self.assertEqual(d['z'], 12) @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") - @unittest.skipIf(support.is_emscripten, "exhausts limited stack") + @support.skip_emscripten_stack_overflow() def test_extended_arg(self): repeat = int(get_c_recursion_limit() * 0.9) longexpr = 'x = x or ' + '-x' * repeat @@ -710,7 +710,7 @@ def test_yet_more_evil_still_undecodable(self): @support.cpython_only @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") - @unittest.skipIf(support.is_emscripten, "exhausts limited stack") + @support.skip_emscripten_stack_overflow() def test_compiler_recursion_limit(self): # Expected limit is Py_C_RECURSION_LIMIT limit = get_c_recursion_limit() diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 5e3d70a8426c74..d76341417e9bef 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -371,7 +371,7 @@ def test_deepcopy_list(self): self.assertIsNot(x, y) self.assertIsNot(x[0], y[0]) - @unittest.skipIf(support.is_emscripten, "exhausts limited stack") + @support.skip_emscripten_stack_overflow() def test_deepcopy_reflexive_list(self): x = [] x.append(x) @@ -399,7 +399,7 @@ def test_deepcopy_tuple_of_immutables(self): y = copy.deepcopy(x) self.assertIs(x, y) - @unittest.skipIf(support.is_emscripten, "exhausts limited stack") + @support.skip_emscripten_stack_overflow() def test_deepcopy_reflexive_tuple(self): x = ([],) x[0].append(x) @@ -417,7 +417,7 @@ def test_deepcopy_dict(self): self.assertIsNot(x, y) self.assertIsNot(x["foo"], y["foo"]) - @unittest.skipIf(support.is_emscripten, "exhausts limited stack") + @support.skip_emscripten_stack_overflow() def test_deepcopy_reflexive_dict(self): x = {} x['foo'] = x diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 2669075d0cfb69..168b78a477ee9c 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3663,7 +3663,7 @@ def f(a): return a encoding='latin1', errors='replace') self.assertEqual(ba, b'abc\xbd?') - @unittest.skipIf(support.is_emscripten, "exhasts limited stack") + @support.skip_emscripten_stack_overflow() def test_recursive_call(self): # Testing recursive __call__() by setting to instance of class... class A(object): @@ -4866,7 +4866,7 @@ class Thing: # CALL_METHOD_DESCRIPTOR_O deque.append(thing, thing) - @unittest.skipIf(support.is_emscripten, "Stack overflow") + @support.skip_emscripten_stack_overflow() def test_repr_as_str(self): # Issue #11603: crash or infinite loop when rebinding __str__ as # __repr__. diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 9f25ace7b5fc91..86b2f22dee5347 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -594,7 +594,7 @@ def __repr__(self): d = {1: BadRepr()} self.assertRaises(Exc, repr, d) - @unittest.skipIf(support.is_emscripten, "Exhausts limited stack") + @support.skip_emscripten_stack_overflow() def test_repr_deep(self): d = {} for i in range(get_c_recursion_limit() + 1): diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index d928bd70ca90fa..d6bf00eeeb0013 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -2,7 +2,7 @@ import copy import pickle import unittest -from test.support import get_c_recursion_limit, is_emscripten +from test.support import get_c_recursion_limit, skip_emscripten_stack_overflow class DictSetTest(unittest.TestCase): @@ -277,7 +277,7 @@ def test_recursive_repr(self): # Again. self.assertIsInstance(r, str) - @unittest.skipIf(is_emscripten, "exhausts limited stack") + @skip_emscripten_stack_overflow() def test_deeply_nested_repr(self): d = {} for i in range(get_c_recursion_limit()//2 + 100): diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index 5ad4327d116ae4..53212529c27e28 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -1,7 +1,7 @@ import collections.abc import types import unittest -from test.support import get_c_recursion_limit, is_emscripten +from test.support import get_c_recursion_limit, skip_emscripten_stack_overflow class TestExceptionGroupTypeHierarchy(unittest.TestCase): def test_exception_group_types(self): @@ -464,13 +464,13 @@ def make_deep_eg(self): e = ExceptionGroup('eg', [e]) return e - @unittest.skipIf(is_emscripten, "exhausts limited stack") + @skip_emscripten_stack_overflow() def test_deep_split(self): e = self.make_deep_eg() with self.assertRaises(RecursionError): e.split(TypeError) - @unittest.skipIf(is_emscripten, "exhausts limited stack") + @skip_emscripten_stack_overflow() def test_deep_subgroup(self): e = self.make_deep_eg() with self.assertRaises(RecursionError): diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 801d62ba29e147..ffd2adb8665b45 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -404,7 +404,7 @@ def test_setstate_subclasses(self): self.assertEqual(r, ((1, 2), {})) self.assertIs(type(r[0]), tuple) - @unittest.skipIf(support.is_emscripten, "limited C stack") + @support.skip_emscripten_stack_overflow() def test_recursive_pickle(self): with replaced_module('functools', self.module): f = self.partial(capture) @@ -2055,7 +2055,7 @@ def orig(a, /, b, c=True): ... @support.skip_on_s390x @unittest.skipIf(support.is_wasi, "WASI has limited C stack") - @unittest.skipIf(support.is_emscripten, "limited C stack") + @support.skip_emscripten_stack_overflow() def test_lru_recursion(self): @self.module.lru_cache diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index 0c1546129ce221..abc75c82375d98 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -263,14 +263,14 @@ def test_subclass_tuple(self): self.assertEqual(True, issubclass(int, (int, (float, int)))) self.assertEqual(True, issubclass(str, (str, (Child, str)))) - @unittest.skipIf(support.is_emscripten, "limited C stack") + @support.skip_emscripten_stack_overflow() def test_subclass_recursion_limit(self): # make sure that issubclass raises RecursionError before the C stack is # blown with support.infinite_recursion(): self.assertRaises(RecursionError, blowstack, issubclass, str, str) - @unittest.skipIf(support.is_emscripten, "limited C stack") + @support.skip_emscripten_stack_overflow() def test_isinstance_recursion_limit(self): # make sure that issubclass raises RecursionError before the C stack is # blown @@ -317,7 +317,7 @@ def __bases__(self): self.assertRaises(RecursionError, issubclass, int, X()) self.assertRaises(RecursionError, isinstance, 1, X()) - @unittest.skipIf(support.is_emscripten, "limited C stack") + @support.skip_emscripten_stack_overflow() def test_infinite_recursion_via_bases_tuple(self): """Regression test for bpo-30570.""" class Failure(object): diff --git a/Lib/test/test_json/test_recursion.py b/Lib/test/test_json/test_recursion.py index c3e4445ee53881..3315de26e541b8 100644 --- a/Lib/test/test_json/test_recursion.py +++ b/Lib/test/test_json/test_recursion.py @@ -69,7 +69,7 @@ def default(self, o): self.fail("didn't raise ValueError on default recursion") - @unittest.skipIf(support.is_emscripten, "limited C stack") + @support.skip_emscripten_stack_overflow() def test_highly_nested_objects_decoding(self): # test that loading highly-nested objects doesn't segfault when C # accelerations are used. See #12017 @@ -83,7 +83,7 @@ def test_highly_nested_objects_decoding(self): with support.infinite_recursion(): self.loads('[' * 100000 + '1' + ']' * 100000) - @unittest.skipIf(support.is_emscripten, "limited C stack") + @support.skip_emscripten_stack_overflow() def test_highly_nested_objects_encoding(self): # See #12051 l, d = [], {} @@ -96,7 +96,7 @@ def test_highly_nested_objects_encoding(self): with support.infinite_recursion(5000): self.dumps(d) - @unittest.skipIf(support.is_emscripten, "limited C stack") + @support.skip_emscripten_stack_overflow() def test_endless_recursion(self): # See #12051 class EndlessJSONEncoder(self.json.JSONEncoder): diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index bf4e468d8342e6..155faff22fe683 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -21,7 +21,7 @@ from test.support.os_helper import TESTFN, unlink from test.support.script_helper import assert_python_ok, assert_python_failure from test.support.import_helper import forget -from test.support import force_not_colorized, is_emscripten +from test.support import force_not_colorized import json import textwrap @@ -2097,7 +2097,7 @@ def deep_eg(self): return e @cpython_only - @unittest.skipIf(is_emscripten, "exhausts limited stack") + @support.skip_emscripten_stack_overflow() def test_exception_group_deep_recursion_capi(self): from _testcapi import exception_print LIMIT = 75 @@ -2109,7 +2109,7 @@ def test_exception_group_deep_recursion_capi(self): self.assertIn('ExceptionGroup', output) self.assertLessEqual(output.count('ExceptionGroup'), LIMIT) - @unittest.skipIf(is_emscripten, "exhausts limited stack") + @support.skip_emscripten_stack_overflow() def test_exception_group_deep_recursion_traceback(self): LIMIT = 75 eg = self.deep_eg() From 16ca04486e77fd76243bec008a1ac4a0e45026ec Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sat, 30 Nov 2024 16:01:24 +0100 Subject: [PATCH 3/5] Cleanup --- Lib/test/list_tests.py | 1 - Lib/test/test_ast/test_ast.py | 6 +++--- Lib/test/test_json/test_recursion.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py index fd4f39f51ca232..dbd9f27872962d 100644 --- a/Lib/test/list_tests.py +++ b/Lib/test/list_tests.py @@ -3,7 +3,6 @@ """ import sys -import unittest from functools import cmp_to_key from test import seq_tests diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 6970c7df6333c2..c268a1f00f938e 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -745,7 +745,7 @@ def next(self): enum._test_simple_enum(_Precedence, ast._Precedence) @support.cpython_only - @skip_emscripten_stack_overflow + @skip_emscripten_stack_overflow() def test_ast_recursion_limit(self): fail_depth = support.exceeds_recursion_limit() crash_depth = 100_000 @@ -1662,7 +1662,7 @@ def test_level_as_none(self): exec(code, ns) self.assertIn('sleep', ns) - @skip_emscripten_stack_overflow + @skip_emscripten_stack_overflow() def test_recursion_direct(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) e.operand = e @@ -1670,7 +1670,7 @@ def test_recursion_direct(self): with support.infinite_recursion(): compile(ast.Expression(e), "", "eval") - @skip_emscripten_stack_overflow + @skip_emscripten_stack_overflow() def test_recursion_indirect(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) diff --git a/Lib/test/test_json/test_recursion.py b/Lib/test/test_json/test_recursion.py index 3315de26e541b8..663c0643579ac8 100644 --- a/Lib/test/test_json/test_recursion.py +++ b/Lib/test/test_json/test_recursion.py @@ -1,6 +1,5 @@ from test import support from test.test_json import PyTest, CTest -import unittest class JSONTestObject: From ce736ec8a3286808170853bc7f8334b2b91b0c3b Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Mon, 2 Dec 2024 12:02:33 +0100 Subject: [PATCH 4/5] Add requires_subinterpreters decorator --- Lib/test/test_capi/test_misc.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 2efdcbce5a27b6..8e0271919cc8a5 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2137,6 +2137,7 @@ def test_py_config_isoloated_per_interpreter(self): # test fails, assume that the environment in this process may # be altered and suspect. + @requires_subinterpreters @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") def test_configured_settings(self): """ @@ -2215,17 +2216,16 @@ def test_configured_settings(self): self.assertEqual(settings, expected) # expected to fail - if _interpreters is not None: - for config in expected_to_fail: - kwargs = dict(zip(kwlist, config)) - with self.subTest(config): - script = textwrap.dedent(f''' - import _testinternalcapi - _testinternalcapi.get_interp_settings() - raise NotImplementedError('unreachable') - ''') - with self.assertRaises(_interpreters.InterpreterError): - support.run_in_subinterp_with_config(script, **kwargs) + for config in expected_to_fail: + kwargs = dict(zip(kwlist, config)) + with self.subTest(config): + script = textwrap.dedent(f''' + import _testinternalcapi + _testinternalcapi.get_interp_settings() + raise NotImplementedError('unreachable') + ''') + with self.assertRaises(_interpreters.InterpreterError): + support.run_in_subinterp_with_config(script, **kwargs) @unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module") @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") From ea21724769c8ababdb8508a678224c384bdf29b2 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Mon, 2 Dec 2024 12:03:07 +0100 Subject: [PATCH 5/5] Typo fix --- Lib/test/mapping_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/mapping_tests.py b/Lib/test/mapping_tests.py index d575ee9900c0b0..f249f0021e9c1c 100644 --- a/Lib/test/mapping_tests.py +++ b/Lib/test/mapping_tests.py @@ -622,7 +622,7 @@ def __repr__(self): d = self._full_mapping({1: BadRepr()}) self.assertRaises(Exc, repr, d) - @skip_emscripten_stack_overflow + @skip_emscripten_stack_overflow() def test_repr_deep(self): d = self._empty_mapping() for i in range(get_c_recursion_limit() + 1):