summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/support/__init__.py37
-rw-r--r--Lib/test/test_gc.py14
2 files changed, 41 insertions, 10 deletions
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 780d04b..4baaeb7 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -2213,3 +2213,40 @@ def requires_venv_with_pip():
# True if Python is built with the Py_DEBUG macro defined: if
# Python is built in debug mode (./configure --with-pydebug).
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
+
+
+def late_deletion(obj):
+ """
+ Keep a Python alive as long as possible.
+
+ Create a reference cycle and store the cycle in an object deleted late in
+ Python finalization. Try to keep the object alive until the very last
+ garbage collection.
+
+ The function keeps a strong reference by design. It should be called in a
+ subprocess to not mark a test as "leaking a reference".
+ """
+
+ # Late CPython finalization:
+ # - finalize_interp_clear()
+ # - _PyInterpreterState_Clear(): Clear PyInterpreterState members
+ # (ex: codec_search_path, before_forkers)
+ # - clear os.register_at_fork() callbacks
+ # - clear codecs.register() callbacks
+
+ ref_cycle = [obj]
+ ref_cycle.append(ref_cycle)
+
+ # Store a reference in PyInterpreterState.codec_search_path
+ import codecs
+ def search_func(encoding):
+ return None
+ search_func.reference = ref_cycle
+ codecs.register(search_func)
+
+ if hasattr(os, 'register_at_fork'):
+ # Store a reference in PyInterpreterState.before_forkers
+ def atfork_func():
+ pass
+ atfork_func.reference = ref_cycle
+ os.register_at_fork(before=atfork_func)
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index dbbd67b..087f727 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -1440,19 +1440,13 @@ class PythonFinalizationTests(unittest.TestCase):
code = textwrap.dedent("""
import ast
import codecs
+ from test import support
# Small AST tree to keep their AST types alive
tree = ast.parse("def f(x, y): return 2*x-y")
- x = [tree]
- x.append(x)
-
- # Put the cycle somewhere to survive until the last GC collection.
- # Codec search functions are only cleared at the end of
- # interpreter_clear().
- def search_func(encoding):
- return None
- search_func.a = x
- codecs.register(search_func)
+
+ # Store the tree somewhere to survive until the last GC collection
+ support.late_deletion(tree)
""")
assert_python_ok("-c", code)