Skip to content

Commit a642405

Browse files
committed
test: call-shape falsifier extension for emitCallExceptionHandler D1-D3
Per pythia python#77 python#3 [chat L1937] gap + supervisor [chat L1939] authorization + theologian [chat L1942] spec: extend test_phoenix_jit_inline_except_closure.py with TestJitCallExceptionHandler class covering 3 tests that exercise the emitCallExceptionHandler unique invariants (D1-D3) NOT covered by the existing inline-except (BINARY_SUBSCR_DICT) tests. GAP CONTEXT: push 60 (9630005) landed emitCallExceptionHandler with shared-helper design — D1-D3 unique invariants ride only the shared P2+P3+P4 helper through the closure-LOAD_DEREF inline-except falsifier. Call-shape (function-call-in-try) was empirically untested at push 60. This falsifier closes that coverage gap retroactively. Lib/test/test_phoenix_jit_inline_except_closure.py: +135 lines, new class TestJitCallExceptionHandler with 3 tests: - C1 test_call_in_try_simple_except: function-call-in-try, ValueError matched + return from except. Exercises D1 (setSuppressExceptionDeopt + pop result pre-call) + D3 (RefineType + result push on OK path). - C2 test_call_in_try_with_closure_load_deref: function-call-in-try, LOAD_DEREF on closure variable in except body. Exercises D1-D3 + PA (D-1774910012 prev_exc Py_None placeholder under emitCallExceptionHandler entry). - C3 test_call_in_try_deopt_path: function-call-in-try, unmatched exception (TypeError vs except ValueError). Exercises D2 (DeoptTC zero re-push, no left/right context — call-handler doesn't have left/right unlike inline-match). EXISTING TESTS PRESERVED unchanged: TestJitInlineExceptClosure (3 tests) — D-1774910012 PA invariant via inline-except path, retained as the original falsifier. EMPIRICAL VALIDATION RESULT (testkeeper [chat L1946]): Run on push 60 binary 9630005 (ARM64 pydebug): 6/6 PASS, OK, 0.177s, EXIT=0, NO core dump. All D1-D3 invariants empirically validated; PA preserved across both call-handler and inline-match entry paths. PYTHIA python#77 python#3 SUBSTANCE CLOSED: D1-D3 unique invariants now empirically exercised, not only ride-along through shared helper. Falsifier-pre-port discipline restored for emitCallExceptionHandler (regression test exists post-port; ideal would have been pre-port but supervisor [chat L1939] accepted post-port retroactive validation as remediation). Bundled with push 61 (b28b512 emitAnyCall PartialConversion, Tier 5 close) per testkeeper [chat L1946] (a) sequencing — single gate cycle, single push, no separate gate ceremony for .py-only change. Authorization chain: - pythia python#77 python#3: chat L1937 (gap surfaced) - supervisor authorization: chat L1939 - theologian spec: chat L1942 - testkeeper draft + push-60-binary validation: chat L1946
1 parent b28b512 commit a642405

1 file changed

Lines changed: 115 additions & 0 deletions

File tree

Lib/test/test_phoenix_jit_inline_except_closure.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,120 @@ def inner(should_raise):
138138
self.assertEqual(r2, ("post-except", before + 2))
139139

140140

141+
@unittest.skipUnless(HAS_JIT, "requires JIT")
142+
class TestJitCallExceptionHandler(unittest.TestCase):
143+
"""Falsifier for emitCallExceptionHandler D1-D3 unique invariants.
144+
145+
Per theologian [chat 17:23:36Z] spec — push 60 emitCallExceptionHandler C
146+
port (9630005502) introduced D1 (setSuppressExceptionDeopt + pop result
147+
pre-call), D2 (DeoptTC zero re-push), D3 (RefineType + result push) on top
148+
of the shared P2+P3+P4 helper. The shared helper is exercised by
149+
TestJitInlineExceptClosure above; D1-D3 unique invariants are exercised
150+
here via function-call-in-try shape (NOT BINARY_SUBSCR_DICT).
151+
152+
Pythia #77 #3 + supervisor [chat 17:23:12Z] authorized this as pre-push-61
153+
gate (regression-test-pre-exists discipline matching D-1774910012).
154+
"""
155+
156+
def test_call_in_try_simple_except(self):
157+
"""C1 — function call in try, ValueError matched, return from except.
158+
159+
Triggers D1 (setSuppressExceptionDeopt + pop) and D3 (RefineType +
160+
result push). D3 omission would NameError-on-result on OK path; D1
161+
omission would deopt instead of inline-handle.
162+
"""
163+
def _helper(should_raise):
164+
if should_raise:
165+
raise ValueError("test")
166+
return 42
167+
168+
def caller(should_raise):
169+
try:
170+
return _helper(should_raise)
171+
except ValueError:
172+
return -1
173+
174+
for _ in range(WARMUP):
175+
self.assertEqual(caller(False), 42)
176+
self.assertTrue(cinderjit.is_jit_compiled(caller),
177+
"caller was NOT JIT-compiled after warmup")
178+
179+
# Exercise both OK (D3) and except (D1) paths under JIT
180+
self.assertEqual(caller(False), 42)
181+
self.assertEqual(caller(True), -1)
182+
for _ in range(100):
183+
self.assertEqual(caller(False), 42)
184+
self.assertEqual(caller(True), -1)
185+
186+
def test_call_in_try_with_closure_load_deref(self):
187+
"""C2 — function call in try, except body LOAD_DEREFs closure variable.
188+
189+
Exercises BOTH D1-D3 (call-handler entry path) AND PA (D-1774910012
190+
prev_exc Py_None placeholder push + POP_EXCEPT pop semantics). PA
191+
failure would corrupt sys.exc_info post-loop.
192+
"""
193+
def _helper(should_raise):
194+
if should_raise:
195+
raise ValueError("test")
196+
return 42
197+
198+
def make_handler(default):
199+
def caller(should_raise):
200+
try:
201+
return _helper(should_raise)
202+
except ValueError:
203+
return default # LOAD_DEREF on closure
204+
return caller
205+
206+
handler = make_handler(-99)
207+
for _ in range(WARMUP):
208+
self.assertEqual(handler(False), 42)
209+
self.assertTrue(cinderjit.is_jit_compiled(handler),
210+
"handler was NOT JIT-compiled after warmup")
211+
212+
self.assertEqual(handler(False), 42)
213+
self.assertEqual(handler(True), -99)
214+
# exc_info chain must remain clean post-loop (PA invariant)
215+
for _ in range(100):
216+
self.assertEqual(handler(True), -99)
217+
self.assertEqual(handler(False), 42)
218+
self.assertIsNone(sys.exc_info()[1],
219+
"sys.exc_info corrupted by call-handler path")
220+
221+
def test_call_in_try_deopt_path(self):
222+
"""C3 — function call in try, exception NOT matched (TypeError vs ValueError).
223+
224+
Triggers D2 (DeoptTC zero re-push, no left/right context). D2 omission
225+
would corrupt deopt frame state when unmatched exception propagates
226+
through the call-handler path.
227+
"""
228+
def _maybe_raise(should_raise):
229+
if should_raise:
230+
raise TypeError("wrong type")
231+
return 42
232+
233+
def caller(should_raise):
234+
try:
235+
return _maybe_raise(should_raise)
236+
except ValueError: # WRONG type — TypeError will not match
237+
return -1
238+
239+
for _ in range(WARMUP):
240+
self.assertEqual(caller(False), 42)
241+
self.assertTrue(cinderjit.is_jit_compiled(caller),
242+
"caller was NOT JIT-compiled after warmup")
243+
244+
# Exercise deopt path: TypeError propagates out of caller (unmatched)
245+
with self.assertRaises(TypeError):
246+
caller(True)
247+
# OK path still correct post-deopt
248+
self.assertEqual(caller(False), 42)
249+
# Run several deopts in a row
250+
for _ in range(50):
251+
with self.assertRaises(TypeError):
252+
caller(True)
253+
self.assertEqual(caller(False), 42)
254+
255+
141256
if __name__ == "__main__":
142257
unittest.main()

0 commit comments

Comments
 (0)