From 895f2fa28f61dcdce9b2ccdc4c390543fb2b04f3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 22 Aug 2023 16:12:40 +0200 Subject: [PATCH 1/3] gh-108297: Update test_crashers * Rename Lib/test/crashers/ to Lib/test/test_crashers/. * Move Lib/test/test_crashers.py to Lib/test/test_crashers/__init__.py. * test_crashers is no longer skipped and makes sure that scripts do crash, and no simply fail with a non-zero exit code. * Update bogus_code_obj.py to use CodeType.replace(). * Scripts crashing Python now uses SuppressCrashReport of test.support to not create coredump files. * Remove Lib/test/crashers/ scripts which no longer crash: * recursive_call.py: fixed by gh-89419 * mutation_inside_cyclegc.py: fixed by gh-97922 * trace_at_recursion_limit.py: fixed by Python 3.7 --- Lib/test/crashers/mutation_inside_cyclegc.py | 31 ------------------- Lib/test/crashers/recursive_call.py | 15 --------- Lib/test/crashers/trace_at_recursion_limit.py | 27 ---------------- Lib/test/{crashers => test_crashers}/README | 8 ++--- .../__init__.py} | 30 +++++++++++------- .../bogus_code_obj.py | 13 +++++--- .../gc_inspection.py | 8 ++++- .../infinite_loop_re.py | 0 .../underlying_dict.py | 4 ++- Makefile.pre.in | 2 +- 10 files changed, 42 insertions(+), 96 deletions(-) delete mode 100644 Lib/test/crashers/mutation_inside_cyclegc.py delete mode 100755 Lib/test/crashers/recursive_call.py delete mode 100644 Lib/test/crashers/trace_at_recursion_limit.py rename Lib/test/{crashers => test_crashers}/README (75%) rename Lib/test/{test_crashers.py => test_crashers/__init__.py} (56%) rename Lib/test/{crashers => test_crashers}/bogus_code_obj.py (71%) rename Lib/test/{crashers => test_crashers}/gc_inspection.py (82%) rename Lib/test/{crashers => test_crashers}/infinite_loop_re.py (100%) rename Lib/test/{crashers => test_crashers}/underlying_dict.py (70%) diff --git a/Lib/test/crashers/mutation_inside_cyclegc.py b/Lib/test/crashers/mutation_inside_cyclegc.py deleted file mode 100644 index 2b67398bccce23..00000000000000 --- a/Lib/test/crashers/mutation_inside_cyclegc.py +++ /dev/null @@ -1,31 +0,0 @@ - -# The cycle GC collector can be executed when any GC-tracked object is -# allocated, e.g. during a call to PyList_New(), PyDict_New(), ... -# Moreover, it can invoke arbitrary Python code via a weakref callback. -# This means that there are many places in the source where an arbitrary -# mutation could unexpectedly occur. - -# The example below shows list_slice() not expecting the call to -# PyList_New to mutate the input list. (Of course there are many -# more examples like this one.) - - -import weakref - -class A(object): - pass - -def callback(x): - del lst[:] - - -keepalive = [] - -for i in range(100): - lst = [str(i)] - a = A() - a.cycle = a - keepalive.append(weakref.ref(a, callback)) - del a - while lst: - keepalive.append(lst[:]) diff --git a/Lib/test/crashers/recursive_call.py b/Lib/test/crashers/recursive_call.py deleted file mode 100755 index 2d160a7de5dac7..00000000000000 --- a/Lib/test/crashers/recursive_call.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 - -# No bug report AFAIK, mail on python-dev on 2006-01-10 - -# This is a "won't fix" case. It is known that setting a high enough -# recursion limit crashes by overflowing the stack. Unless this is -# redesigned somehow, it won't go away. - -import sys - -sys.setrecursionlimit(1 << 30) -f = lambda f:f(f) - -if __name__ == '__main__': - f(f) diff --git a/Lib/test/crashers/trace_at_recursion_limit.py b/Lib/test/crashers/trace_at_recursion_limit.py deleted file mode 100644 index acd863f5509c49..00000000000000 --- a/Lib/test/crashers/trace_at_recursion_limit.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -From http://bugs.python.org/issue6717 - -A misbehaving trace hook can trigger a segfault by exceeding the recursion -limit. -""" -import sys - - -def x(): - pass - -def g(*args): - if True: # change to True to crash interpreter - try: - x() - except: - pass - return g - -def f(): - print(sys.getrecursionlimit()) - f() - -sys.settrace(g) - -f() diff --git a/Lib/test/crashers/README b/Lib/test/test_crashers/README similarity index 75% rename from Lib/test/crashers/README rename to Lib/test/test_crashers/README index 0259a0688cbc67..19f67b7ea2c94e 100644 --- a/Lib/test/crashers/README +++ b/Lib/test/test_crashers/README @@ -5,7 +5,7 @@ too obscure to invest the effort. Each test should fail when run from the command line: - ./python Lib/test/crashers/weakref_in_del.py + ./python Lib/test/test_crashers/bogus_code_obj.py Put as much info into a docstring or comments to help determine the cause of the failure, as well as a bugs.python.org issue number if it exists. Particularly @@ -15,6 +15,6 @@ Once the crash is fixed, the test case should be moved into an appropriate test (even if it was originally from the test suite). This ensures the regression doesn't happen again. And if it does, it should be easier to track down. -Also see Lib/test_crashers.py which exercises the crashers in this directory. -In particular, make sure to add any new infinite loop crashers to the black -list so it doesn't try to run them. +Also see Lib/test/test_crashers/__init__.py which exercises the crashers in +this directory. In particular, make sure to add any new infinite loop crashers +to the black list so it doesn't try to run them. diff --git a/Lib/test/test_crashers.py b/Lib/test/test_crashers/__init__.py similarity index 56% rename from Lib/test/test_crashers.py rename to Lib/test/test_crashers/__init__.py index 31b712028f8a12..44c4e94673fbb0 100644 --- a/Lib/test/test_crashers.py +++ b/Lib/test/test_crashers/__init__.py @@ -4,34 +4,40 @@ # If a crasher is fixed, it should be moved elsewhere in the test suite to # ensure it continues to work correctly. -import unittest import glob import os.path -import test.support +import unittest +from test import support from test.support.script_helper import assert_python_failure -CRASHER_DIR = os.path.join(os.path.dirname(__file__), "crashers") + +CRASHER_DIR = os.path.abspath(os.path.dirname(__file__)) CRASHER_FILES = os.path.join(glob.escape(CRASHER_DIR), "*.py") +infinite_loops = frozenset(["infinite_loop_re.py"]) -infinite_loops = ["infinite_loop_re.py", "nasty_eq_vs_dict.py"] class CrasherTest(unittest.TestCase): - - @unittest.skip("these tests are too fragile") - @test.support.cpython_only + @support.cpython_only def test_crashers_crash(self): + if support.verbose: + print() for fname in glob.glob(CRASHER_FILES): - if os.path.basename(fname) in infinite_loops: + script = os.path.basename(fname) + if script == "__init__.py": + continue + if script in infinite_loops: continue # Some "crashers" only trigger an exception rather than a # segfault. Consider that an acceptable outcome. - if test.support.verbose: - print("Checking crasher:", fname) - assert_python_failure(fname) + if support.verbose: + print(f"Checking crasher: {script}", flush=True) + proc = assert_python_failure(fname) + self.assertLess(proc.rc, 0, proc) def tearDownModule(): - test.support.reap_children() + support.reap_children() + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/crashers/bogus_code_obj.py b/Lib/test/test_crashers/bogus_code_obj.py similarity index 71% rename from Lib/test/crashers/bogus_code_obj.py rename to Lib/test/test_crashers/bogus_code_obj.py index e71b3582cf2d76..125ebf762e7021 100644 --- a/Lib/test/crashers/bogus_code_obj.py +++ b/Lib/test/test_crashers/bogus_code_obj.py @@ -12,8 +12,13 @@ """ -import types +from test.support import SuppressCrashReport -co = types.CodeType(0, 0, 0, 0, 0, 0, b'\x04\x00\x71\x00', - (), (), (), '', '', 1, b'') -exec(co) +def func(): + pass + +invalid_code = b'\x04\x00\x71\x00' +func.__code__ = func.__code__.replace(co_code=invalid_code) + +with SuppressCrashReport(): + func() diff --git a/Lib/test/crashers/gc_inspection.py b/Lib/test/test_crashers/gc_inspection.py similarity index 82% rename from Lib/test/crashers/gc_inspection.py rename to Lib/test/test_crashers/gc_inspection.py index ae85f97a74adc9..2df6013a34f2a0 100644 --- a/Lib/test/crashers/gc_inspection.py +++ b/Lib/test/test_crashers/gc_inspection.py @@ -15,9 +15,14 @@ fixes to the documentation for extension module writers. It's unlikely to happen, though. So this is currently classified as "gc.get_referrers() is dangerous, use only for debugging". + +* https://github.com/python/cpython/issues/39117 +* https://github.com/python/cpython/issues/59313 +* https://github.com/python/cpython/pull/107183 """ import gc +from test.support import SuppressCrashReport def g(): @@ -29,4 +34,5 @@ def g(): print(tup[1]) -tuple(g()) +with SuppressCrashReport(): + tuple(g()) diff --git a/Lib/test/crashers/infinite_loop_re.py b/Lib/test/test_crashers/infinite_loop_re.py similarity index 100% rename from Lib/test/crashers/infinite_loop_re.py rename to Lib/test/test_crashers/infinite_loop_re.py diff --git a/Lib/test/crashers/underlying_dict.py b/Lib/test/test_crashers/underlying_dict.py similarity index 70% rename from Lib/test/crashers/underlying_dict.py rename to Lib/test/test_crashers/underlying_dict.py index a4b799374381d5..219a5d31bb2a20 100644 --- a/Lib/test/crashers/underlying_dict.py +++ b/Lib/test/test_crashers/underlying_dict.py @@ -1,3 +1,4 @@ +from test.support import SuppressCrashReport import gc thingy = object() @@ -17,4 +18,5 @@ def f(self): a.f() dct["f"] = lambda self: 2 -print(a.f()) # should print 1 +with SuppressCrashReport(): + print(a.f()) # should print 1 diff --git a/Makefile.pre.in b/Makefile.pre.in index d66764e6165409..2a3328d920a2f6 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2144,7 +2144,6 @@ TESTSUBDIRS= idlelib/idle_test \ test/audiodata \ test/capath \ test/cjkencodings \ - test/crashers \ test/data \ test/decimaltestdata \ test/dtracedata \ @@ -2157,6 +2156,7 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_asyncio \ test/test_capi \ test/test_cppext \ + test/test_crashers \ test/test_ctypes \ test/test_email \ test/test_email/data \ From fb5d432269583d30b9be3a1b1637e492c5a14912 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 22 Aug 2023 20:23:38 +0200 Subject: [PATCH 2/3] Fix test_crashers on Windows --- Lib/test/test_crashers/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_crashers/__init__.py b/Lib/test/test_crashers/__init__.py index 44c4e94673fbb0..f935a069dc9370 100644 --- a/Lib/test/test_crashers/__init__.py +++ b/Lib/test/test_crashers/__init__.py @@ -32,7 +32,13 @@ def test_crashers_crash(self): if support.verbose: print(f"Checking crasher: {script}", flush=True) proc = assert_python_failure(fname) - self.assertLess(proc.rc, 0, proc) + if os.name != "nt": + # On Unix, if proc.rc is negative, the process was killed + # by a signal + self.assertLess(proc.rc, 0, proc) + else: + # Windows. For example, C0000005 is an Access Violation + self.assertGreaterEqual(proc.rc, 0xC0000000, proc) def tearDownModule(): From ecb3fd65c4317e4222465b2c877cdf0184727eb7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 22 Aug 2023 21:35:59 +0200 Subject: [PATCH 3/3] Skip tests on ASAN and MSAN sanitizers --- Lib/test/test_crashers/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_crashers/__init__.py b/Lib/test/test_crashers/__init__.py index f935a069dc9370..7ee3791045b93b 100644 --- a/Lib/test/test_crashers/__init__.py +++ b/Lib/test/test_crashers/__init__.py @@ -16,8 +16,9 @@ infinite_loops = frozenset(["infinite_loop_re.py"]) +@support.skip_if_sanitizer(address=True, memory=True) +@support.cpython_only class CrasherTest(unittest.TestCase): - @support.cpython_only def test_crashers_crash(self): if support.verbose: print()