Skip to content

Commit 77777a6

Browse files
committed
Assert backtrace shape in test_gdb.test_jit
Add a shared helper that asserts exactly one py::jit_entry frame above at least one eval frame, so regressions producing duplicate JIT frames or JIT-below-eval can't pass the old tolerant regex.
1 parent 3eeddb9 commit 77777a6

File tree

1 file changed

+42
-24
lines changed

1 file changed

+42
-24
lines changed

Lib/test/test_gdb/test_jit.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,41 @@ def setUpModule():
8484
@unittest.skipUnless(hasattr(sys, "_jit") and sys._jit.is_enabled(),
8585
"requires a JIT-enabled build with JIT execution active")
8686
class JitBacktraceTests(DebuggerTests):
87+
def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top):
88+
# Shape assertions applied to every JIT backtrace we produce:
89+
# 1. The synthetic JIT symbol appears exactly once. A second
90+
# py::jit_entry:<jit> frame would mean the unwinder is
91+
# materializing two native frames for a single logical JIT
92+
# region, or failing to unwind out of the region entirely.
93+
# 2. At least one _PyEval_EvalFrameDefault / _PyEval_Vector
94+
# frame appears (the JIT is always reached from the eval
95+
# loop in these tests).
96+
# 3. The JIT frame is above (i.e. textually before, since GDB
97+
# prints innermost first) an eval frame.
98+
# 4. For tests that assert a specific entry PC, the JIT frame
99+
# is also at #0.
100+
jit_count = len(re.findall(r"py::jit_entry:<jit>", gdb_output))
101+
self.assertEqual(
102+
jit_count, 1,
103+
f"expected exactly 1 py::jit_entry:<jit> frame, got {jit_count}\n"
104+
f"backtrace:\n{gdb_output}",
105+
)
106+
eval_count = len(re.findall(EVAL_FRAME_RE, gdb_output))
107+
self.assertGreaterEqual(
108+
eval_count, 1,
109+
f"expected at least one _PyEval_* frame, got {eval_count}\n"
110+
f"backtrace:\n{gdb_output}",
111+
)
112+
jit_before_eval = re.compile(
113+
rf"py::jit_entry:<jit>.*{EVAL_FRAME_RE}", re.DOTALL
114+
)
115+
self.assertRegex(gdb_output, jit_before_eval)
116+
if anchor_at_top:
117+
self.assertRegex(
118+
gdb_output,
119+
re.compile(r"#0\s+py::jit_entry:<jit>", re.DOTALL),
120+
)
121+
87122
def test_bt_shows_compiled_jit_entry(self):
88123
gdb_output = self.get_stack_trace(
89124
script=JIT_SAMPLE_SCRIPT,
@@ -96,14 +131,9 @@ def test_bt_shows_compiled_jit_entry(self):
96131
PYTHON_JIT="1",
97132
)
98133
# GDB registers the compiled JIT entry and per-trace JIT regions under
99-
# the same synthetic symbol name.
100-
self.assertRegex(
101-
gdb_output,
102-
re.compile(
103-
rf"#0\s+py::jit_entry:<jit>.*{EVAL_FRAME_RE}",
104-
re.DOTALL,
105-
),
106-
)
134+
# the same synthetic symbol name; breaking at the entry PC pins the
135+
# JIT frame at #0.
136+
self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=True)
107137

108138
def test_bt_unwinds_through_jit_frames(self):
109139
gdb_output = self.get_stack_trace(
@@ -114,13 +144,7 @@ def test_bt_unwinds_through_jit_frames(self):
114144
# The executor should appear as a named JIT frame and unwind back into
115145
# the eval loop. Whether GDB also materializes a separate shim frame is
116146
# an implementation detail of the synthetic executor CFI.
117-
self.assertRegex(
118-
gdb_output,
119-
re.compile(
120-
rf"py::jit_entry:<jit>.*{EVAL_FRAME_RE}",
121-
re.DOTALL,
122-
),
123-
)
147+
self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=False)
124148

125149
def test_bt_unwinds_from_inside_jit_entry(self):
126150
gdb_output = self.get_stack_trace(
@@ -132,12 +156,6 @@ def test_bt_unwinds_from_inside_jit_entry(self):
132156
],
133157
PYTHON_JIT="1",
134158
)
135-
# Once the selected PC is inside the JIT entry, we only require that
136-
# GDB can identify the JIT frame and keep unwinding into _PyEval_*.
137-
self.assertRegex(
138-
gdb_output,
139-
re.compile(
140-
rf"#0\s+py::jit_entry:<jit>.*{EVAL_FRAME_RE}",
141-
re.DOTALL,
142-
),
143-
)
159+
# Once the selected PC is inside the JIT entry, we require that GDB
160+
# identifies the JIT frame at #0 and keeps unwinding into _PyEval_*.
161+
self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=True)

0 commit comments

Comments
 (0)