Skip to content

Commit

Permalink
gh-127146: Emscripten: Skip segfaults in test suite (#127151)
Browse files Browse the repository at this point in the history
Added skips for tests known to cause problems when running on Emscripten. 
These mostly relate to the limited stack depth on Emscripten.
  • Loading branch information
hoodmane authored Dec 5, 2024
1 parent 2f1cee8 commit 43634fc
Show file tree
Hide file tree
Showing 21 changed files with 46 additions and 8 deletions.
3 changes: 2 additions & 1 deletion Lib/test/list_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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, skip_emscripten_stack_overflow


class CommonTest(seq_tests.CommonTest):
Expand Down Expand Up @@ -59,6 +59,7 @@ def test_repr(self):
self.assertEqual(str(a2), "[0, 1, 2, [...], 3]")
self.assertEqual(repr(a2), "[0, 1, 2, [...], 3]")

@skip_emscripten_stack_overflow()
def test_repr_deep(self):
a = self.type2test([])
for i in range(get_c_recursion_limit() + 1):
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/mapping_tests.py
Original file line number Diff line number Diff line change
@@ -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, skip_emscripten_stack_overflow


class BasicTestMappingProtocol(unittest.TestCase):
Expand Down Expand Up @@ -622,6 +622,7 @@ def __repr__(self):
d = self._full_mapping({1: BadRepr()})
self.assertRaises(Exc, repr, d)

@skip_emscripten_stack_overflow()
def test_repr_deep(self):
d = self._empty_mapping()
for i in range(get_c_recursion_limit() + 1):
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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 (
Expand Down Expand Up @@ -745,6 +745,7 @@ def next(self):
enum._test_simple_enum(_Precedence, ast._Precedence)

@support.cpython_only
@skip_emscripten_stack_overflow()
def test_ast_recursion_limit(self):
fail_depth = support.exceeds_recursion_limit()
crash_depth = 100_000
Expand Down Expand Up @@ -1661,13 +1662,15 @@ def test_level_as_none(self):
exec(code, ns)
self.assertIn('sleep', ns)

@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
with self.assertRaises(RecursionError):
with support.infinite_recursion():
compile(ast.Expression(e), "<test>", "eval")

@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))
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_call.py
Original file line number Diff line number Diff line change
@@ -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)
set_recursion_limit, skip_on_s390x, skip_emscripten_stack_overflow)
try:
import _testcapi
except ImportError:
Expand Down Expand Up @@ -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")
@skip_emscripten_stack_overflow()
def test_super_deep(self):

def recurse(n):
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_class.py
Original file line number Diff line number Diff line change
@@ -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, skip_emscripten_stack_overflow

testmeths = [

Expand Down Expand Up @@ -554,6 +554,7 @@ class Custom:
self.assertFalse(hasattr(o, "__call__"))
self.assertFalse(hasattr(c, "__call__"))

@skip_emscripten_stack_overflow()
def testSFBug532646(self):
# Test for SF bug 532646

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def __getitem__(self, key):
self.assertEqual(d['z'], 12)

@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
@support.skip_emscripten_stack_overflow()
def test_extended_arg(self):
repeat = int(get_c_recursion_limit() * 0.9)
longexpr = 'x = x or ' + '-x' * repeat
Expand Down Expand Up @@ -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")
@support.skip_emscripten_stack_overflow()
def test_compiler_recursion_limit(self):
# Expected limit is Py_C_RECURSION_LIMIT
limit = get_c_recursion_limit()
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ def test_deepcopy_list(self):
self.assertIsNot(x, y)
self.assertIsNot(x[0], y[0])

@support.skip_emscripten_stack_overflow()
def test_deepcopy_reflexive_list(self):
x = []
x.append(x)
Expand Down Expand Up @@ -398,6 +399,7 @@ def test_deepcopy_tuple_of_immutables(self):
y = copy.deepcopy(x)
self.assertIs(x, y)

@support.skip_emscripten_stack_overflow()
def test_deepcopy_reflexive_tuple(self):
x = ([],)
x[0].append(x)
Expand All @@ -415,6 +417,7 @@ def test_deepcopy_dict(self):
self.assertIsNot(x, y)
self.assertIsNot(x["foo"], y["foo"])

@support.skip_emscripten_stack_overflow()
def test_deepcopy_reflexive_dict(self):
x = {}
x['foo'] = x
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3663,6 +3663,7 @@ def f(a): return a
encoding='latin1', errors='replace')
self.assertEqual(ba, b'abc\xbd?')

@support.skip_emscripten_stack_overflow()
def test_recursive_call(self):
# Testing recursive __call__() by setting to instance of class...
class A(object):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -4864,6 +4866,7 @@ class Thing:
# CALL_METHOD_DESCRIPTOR_O
deque.append(thing, thing)

@support.skip_emscripten_stack_overflow()
def test_repr_as_str(self):
# Issue #11603: crash or infinite loop when rebinding __str__ as
# __repr__.
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ def __repr__(self):
d = {1: BadRepr()}
self.assertRaises(Exc, repr, d)

@support.skip_emscripten_stack_overflow()
def test_repr_deep(self):
d = {}
for i in range(get_c_recursion_limit() + 1):
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_dictviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, skip_emscripten_stack_overflow

class DictSetTest(unittest.TestCase):

Expand Down Expand Up @@ -277,6 +277,7 @@ def test_recursive_repr(self):
# Again.
self.assertIsInstance(r, str)

@skip_emscripten_stack_overflow()
def test_deeply_nested_repr(self):
d = {}
for i in range(get_c_recursion_limit()//2 + 100):
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_exception_group.py
Original file line number Diff line number Diff line change
@@ -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, skip_emscripten_stack_overflow

class TestExceptionGroupTypeHierarchy(unittest.TestCase):
def test_exception_group_types(self):
Expand Down Expand Up @@ -464,11 +464,13 @@ def make_deep_eg(self):
e = ExceptionGroup('eg', [e])
return e

@skip_emscripten_stack_overflow()
def test_deep_split(self):
e = self.make_deep_eg()
with self.assertRaises(RecursionError):
e.split(TypeError)

@skip_emscripten_stack_overflow()
def test_deep_subgroup(self):
e = self.make_deep_eg()
with self.assertRaises(RecursionError):
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ def test_setstate_subclasses(self):
self.assertEqual(r, ((1, 2), {}))
self.assertIs(type(r[0]), tuple)

@support.skip_emscripten_stack_overflow()
def test_recursive_pickle(self):
with replaced_module('functools', self.module):
f = self.partial(capture)
Expand Down Expand Up @@ -2054,6 +2055,7 @@ def orig(a, /, b, c=True): ...

@support.skip_on_s390x
@unittest.skipIf(support.is_wasi, "WASI has limited C stack")
@support.skip_emscripten_stack_overflow()
def test_lru_recursion(self):

@self.module.lru_cache
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_isinstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))))

@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)

@support.skip_emscripten_stack_overflow()
def test_isinstance_recursion_limit(self):
# make sure that issubclass raises RecursionError before the C stack is
# blown
Expand Down Expand Up @@ -315,6 +317,7 @@ def __bases__(self):
self.assertRaises(RecursionError, issubclass, int, X())
self.assertRaises(RecursionError, isinstance, 1, X())

@support.skip_emscripten_stack_overflow()
def test_infinite_recursion_via_bases_tuple(self):
"""Regression test for bpo-30570."""
class Failure(object):
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_json/test_recursion.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def default(self, o):
self.fail("didn't raise ValueError on default recursion")


@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
Expand All @@ -81,6 +82,7 @@ def test_highly_nested_objects_decoding(self):
with support.infinite_recursion():
self.loads('[' * 100000 + '1' + ']' * 100000)

@support.skip_emscripten_stack_overflow()
def test_highly_nested_objects_encoding(self):
# See #12051
l, d = [], {}
Expand All @@ -93,6 +95,7 @@ def test_highly_nested_objects_encoding(self):
with support.infinite_recursion(5000):
self.dumps(d)

@support.skip_emscripten_stack_overflow()
def test_endless_recursion(self):
# See #12051
class EndlessJSONEncoder(self.json.JSONEncoder):
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -2298,6 +2298,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)
Expand Down Expand Up @@ -2393,6 +2394,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)
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -2097,6 +2097,7 @@ def deep_eg(self):
return e

@cpython_only
@support.skip_emscripten_stack_overflow()
def test_exception_group_deep_recursion_capi(self):
from _testcapi import exception_print
LIMIT = 75
Expand All @@ -2108,6 +2109,7 @@ def test_exception_group_deep_recursion_capi(self):
self.assertIn('ExceptionGroup', output)
self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)

@support.skip_emscripten_stack_overflow()
def test_exception_group_deep_recursion_traceback(self):
LIMIT = 75
eg = self.deep_eg()
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_xml_etree_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2334,6 +2334,7 @@ AS_CASE([$ac_sys_system],
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js"])
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sEXPORTED_RUNTIME_METHODS=FS,callMain"])
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version"])
AS_VAR_APPEND([LDFLAGS_NODIST], [" -sSTACK_SIZE=5MB"])
AS_VAR_IF([enable_wasm_dynamic_linking], [yes], [
AS_VAR_APPEND([LINKFORSHARED], [" -sMAIN_MODULE"])
Expand Down

0 comments on commit 43634fc

Please sign in to comment.