bpo-9263: Dump Python object on GC assertion failure (GH-10062) · python/cpython@626bff8 (original) (raw)
1
1
`import unittest
`
2
2
`from test.support import (verbose, refcount_test, run_unittest,
`
3
3
`strip_python_stderr, cpython_only, start_threads,
`
4
``
`-
temp_dir, requires_type_collecting, TESTFN, unlink)
`
``
4
`+
temp_dir, requires_type_collecting, TESTFN, unlink,
`
``
5
`+
import_module)
`
5
6
`from test.support.script_helper import assert_python_ok, make_script
`
6
7
``
``
8
`+
import gc
`
7
9
`import sys
`
``
10
`+
import sysconfig
`
``
11
`+
import textwrap
`
``
12
`+
import threading
`
8
13
`import time
`
9
``
`-
import gc
`
10
14
`import weakref
`
11
``
`-
import threading
`
12
15
``
13
16
`try:
`
14
17
`from _testcapi import with_tp_del
`
`@@ -62,6 +65,14 @@ def init(self, partner=None):
`
62
65
`def tp_del(self):
`
63
66
`pass
`
64
67
``
``
68
`+
if sysconfig.get_config_vars().get('PY_CFLAGS', ''):
`
``
69
`+
BUILD_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS'])
`
``
70
`+
else:
`
``
71
`+
Usually, sys.gettotalrefcount() is only present if Python has been
`
``
72
`+
compiled in debug mode. If it's missing, expect that Python has
`
``
73
`+
been released in release mode: with NDEBUG defined.
`
``
74
`+
BUILD_WITH_NDEBUG = (not hasattr(sys, 'gettotalrefcount'))
`
``
75
+
65
76
`### Tests
`
66
77
`###############################################################################
`
67
78
``
`@@ -878,6 +889,58 @@ def test_collect_garbage(self):
`
878
889
`self.assertEqual(len(gc.garbage), 0)
`
879
890
``
880
891
``
``
892
`+
@unittest.skipIf(BUILD_WITH_NDEBUG,
`
``
893
`+
'built with -NDEBUG')
`
``
894
`+
def test_refcount_errors(self):
`
``
895
`+
self.preclean()
`
``
896
`+
Verify the "handling" of objects with broken refcounts
`
``
897
+
``
898
`+
Skip the test if ctypes is not available
`
``
899
`+
import_module("ctypes")
`
``
900
+
``
901
`+
import subprocess
`
``
902
`+
code = textwrap.dedent('''
`
``
903
`+
from test.support import gc_collect, SuppressCrashReport
`
``
904
+
``
905
`+
a = [1, 2, 3]
`
``
906
`+
b = [a]
`
``
907
+
``
908
`+
Avoid coredump when Py_FatalError() calls abort()
`
``
909
`+
SuppressCrashReport().enter()
`
``
910
+
``
911
`+
Simulate the refcount of "a" being too low (compared to the
`
``
912
`+
references held on it by live data), but keeping it above zero
`
``
913
`+
(to avoid deallocating it):
`
``
914
`+
import ctypes
`
``
915
`+
ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))
`
``
916
+
``
917
`+
The garbage collector should now have a fatal error
`
``
918
`+
when it reaches the broken object
`
``
919
`+
gc_collect()
`
``
920
`+
''')
`
``
921
`+
p = subprocess.Popen([sys.executable, "-c", code],
`
``
922
`+
stdout=subprocess.PIPE,
`
``
923
`+
stderr=subprocess.PIPE)
`
``
924
`+
stdout, stderr = p.communicate()
`
``
925
`+
p.stdout.close()
`
``
926
`+
p.stderr.close()
`
``
927
`+
Verify that stderr has a useful error message:
`
``
928
`+
self.assertRegex(stderr,
`
``
929
`+
br'gcmodule.c:[0-9]+: gc_decref: Assertion "gc_get_refs(g) > 0" failed.')
`
``
930
`+
self.assertRegex(stderr,
`
``
931
`+
br'refcount is too small')
`
``
932
`+
self.assertRegex(stderr,
`
``
933
`+
br'object : [1, 2, 3]')
`
``
934
`+
self.assertRegex(stderr,
`
``
935
`+
br'type : list')
`
``
936
`+
self.assertRegex(stderr,
`
``
937
`+
br'refcount: 1')
`
``
938
`+
"address : 0x7fb5062efc18"
`
``
939
`+
"address : 7FB5062EFC18"
`
``
940
`+
self.assertRegex(stderr,
`
``
941
`+
br'address : [0-9a-fA-Fx]+')
`
``
942
+
``
943
+
881
944
`class GCTogglingTests(unittest.TestCase):
`
882
945
`def setUp(self):
`
883
946
`gc.enable()
`