Skip to content

Commit a09b474

Browse files
Tests for get_gc_stats for main interpreter
1 parent 0886a07 commit a09b474

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

Lib/test/test_get_gc_stats.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import gc
2+
import json
3+
import os
4+
import subprocess
5+
import sys
6+
import textwrap
7+
import unittest
8+
9+
from test.support import (
10+
SHORT_TIMEOUT,
11+
requires_remote_subprocess_debugging,
12+
)
13+
14+
15+
def get_interpreter_identifiers(gc_stats: tuple[dict[str, str|int|float]]) -> list[str]:
16+
return [s["iid"] for s in gc_stats]
17+
18+
19+
def get_generations(gc_stats: tuple[dict[str, str|int|float]]) -> tuple[int,int,int]:
20+
generations = set()
21+
for s in gc_stats:
22+
generations.add(s["gen"])
23+
24+
return tuple(sorted(generations))
25+
26+
27+
def get_last_item_for_generation(gc_stats: tuple[dict[str, str|int|float]],
28+
generation:int) -> dict[str, str|int|float] | None:
29+
item = None
30+
for s in gc_stats:
31+
if s["gen"] == generation:
32+
if item is None or item["ts_start"] < s["ts_start"]:
33+
item = s
34+
35+
return item
36+
37+
38+
39+
@requires_remote_subprocess_debugging()
40+
class TestGetStackTrace(unittest.TestCase):
41+
42+
def run_child_process(self):
43+
# Run the test in a subprocess to avoid side effects
44+
script = textwrap.dedent("""\
45+
import json
46+
import os
47+
import sys
48+
import _remote_debugging
49+
50+
pid = int(sys.argv[1])
51+
gc_stats = _remote_debugging.get_gc_stats(pid, all_interpreters=False)
52+
print(json.dumps(gc_stats, indent=1))
53+
""")
54+
55+
gc.collect(0)
56+
gc.collect(1)
57+
gc.collect(2)
58+
59+
result = subprocess.run(
60+
[sys.executable, "-c", script, str(os.getpid())],
61+
capture_output=True,
62+
text=True,
63+
timeout=SHORT_TIMEOUT,
64+
)
65+
self.assertEqual(
66+
result.returncode, 0,
67+
f"stdout: {result.stdout}\nstderr: {result.stderr}"
68+
)
69+
return result
70+
71+
def test_get_gc_stats_for_main_interpreter(self):
72+
"""Test that RemoteUnwinder works on the same process after _ctypes import.
73+
74+
When _ctypes is imported, it may call dlopen on the libpython shared
75+
library, creating a duplicate mapping in the process address space.
76+
The remote debugging code must skip these uninitialized duplicate
77+
mappings and find the real PyRuntime. See gh-144563.
78+
"""
79+
80+
# Skip the test if the _ctypes module is missing.
81+
82+
before_stats = json.loads(self.run_child_process().stdout)
83+
after_stats = json.loads(self.run_child_process().stdout)
84+
85+
before_iids = get_interpreter_identifiers(before_stats)
86+
after_iids = get_interpreter_identifiers(after_stats)
87+
88+
self.assertTrue(all([0 == iid for iid in before_iids]))
89+
self.assertTrue(all([0 == iid for iid in after_iids]))
90+
91+
before_gens = get_generations(before_stats)
92+
after_gens = get_generations(after_stats)
93+
94+
self.assertEqual(before_gens, (0, 1, 2))
95+
self.assertEqual(after_gens, (0, 1, 2))
96+
97+
before_last_items = (get_last_item_for_generation(before_stats, 0),
98+
get_last_item_for_generation(before_stats, 1),
99+
get_last_item_for_generation(before_stats, 2))
100+
101+
after_last_items = (get_last_item_for_generation(after_stats, 0),
102+
get_last_item_for_generation(after_stats, 1),
103+
get_last_item_for_generation(after_stats, 2))
104+
105+
for before, after in zip(before_last_items, after_last_items):
106+
self.assertIsNotNone(before)
107+
self.assertIsNotNone(after)
108+
109+
self.assertGreater(after["collections"], before["collections"], (before, after))
110+
self.assertGreater(after["ts_start"], before["ts_start"], (before, after))
111+
self.assertGreater(after["ts_stop"], before["ts_stop"], (before, after))
112+
self.assertGreater(after["duration"], before["duration"], (before, after))
113+
114+
self.assertGreater(after["object_visits"], before["object_visits"], (before, after))
115+
self.assertGreater(after["candidates"], before["candidates"], (before, after))
116+
117+
# may not grow
118+
self.assertGreaterEqual(after["collected"], before["collected"], (before, after))
119+
self.assertGreaterEqual(after["uncollectable"], before["uncollectable"], (before, after))
120+
121+
if before["gen"] == 1:
122+
self.assertGreaterEqual(after["objects_transitively_reachable"], before["objects_transitively_reachable"], (before, after))
123+
self.assertGreaterEqual(after["objects_not_transitively_reachable"], before["objects_not_transitively_reachable"], (before, after))

0 commit comments

Comments
 (0)