summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_gc.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_gc.py')
-rw-r--r--Lib/test/test_gc.py69
1 files changed, 66 insertions, 3 deletions
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index 8d806db..16b2242 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -1,14 +1,17 @@
import unittest
from test.support import (verbose, refcount_test, run_unittest,
strip_python_stderr, cpython_only, start_threads,
- temp_dir, requires_type_collecting, TESTFN, unlink)
+ temp_dir, requires_type_collecting, TESTFN, unlink,
+ import_module)
from test.support.script_helper import assert_python_ok, make_script
+import gc
import sys
+import sysconfig
+import textwrap
+import threading
import time
-import gc
import weakref
-import threading
try:
from _testcapi import with_tp_del
@@ -62,6 +65,14 @@ class Uncollectable(object):
def __tp_del__(self):
pass
+if sysconfig.get_config_vars().get('PY_CFLAGS', ''):
+ BUILD_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS'])
+else:
+ # Usually, sys.gettotalrefcount() is only present if Python has been
+ # compiled in debug mode. If it's missing, expect that Python has
+ # been released in release mode: with NDEBUG defined.
+ BUILD_WITH_NDEBUG = (not hasattr(sys, 'gettotalrefcount'))
+
### Tests
###############################################################################
@@ -878,6 +889,58 @@ class GCCallbackTests(unittest.TestCase):
self.assertEqual(len(gc.garbage), 0)
+ @unittest.skipIf(BUILD_WITH_NDEBUG,
+ 'built with -NDEBUG')
+ def test_refcount_errors(self):
+ self.preclean()
+ # Verify the "handling" of objects with broken refcounts
+
+ # Skip the test if ctypes is not available
+ import_module("ctypes")
+
+ import subprocess
+ code = textwrap.dedent('''
+ from test.support import gc_collect, SuppressCrashReport
+
+ a = [1, 2, 3]
+ b = [a]
+
+ # Avoid coredump when Py_FatalError() calls abort()
+ SuppressCrashReport().__enter__()
+
+ # Simulate the refcount of "a" being too low (compared to the
+ # references held on it by live data), but keeping it above zero
+ # (to avoid deallocating it):
+ import ctypes
+ ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))
+
+ # The garbage collector should now have a fatal error
+ # when it reaches the broken object
+ gc_collect()
+ ''')
+ p = subprocess.Popen([sys.executable, "-c", code],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ p.stdout.close()
+ p.stderr.close()
+ # Verify that stderr has a useful error message:
+ self.assertRegex(stderr,
+ br'gcmodule\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.')
+ self.assertRegex(stderr,
+ br'refcount is too small')
+ self.assertRegex(stderr,
+ br'object : \[1, 2, 3\]')
+ self.assertRegex(stderr,
+ br'type : list')
+ self.assertRegex(stderr,
+ br'refcount: 1')
+ # "address : 0x7fb5062efc18"
+ # "address : 7FB5062EFC18"
+ self.assertRegex(stderr,
+ br'address : [0-9a-fA-Fx]+')
+
+
class GCTogglingTests(unittest.TestCase):
def setUp(self):
gc.enable()