|
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() |
|
0 commit comments