diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 8fc4d08dd..d35805220 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3432,7 +3432,6 @@ class ClassPropertiesAndMethods(unittest.TestCase): l = [A() for x in range(100)] del l - @unittest.expectedFailure # TODO: RUSTPYTHON def test_set_dict(self): # Testing __dict__ assignment... class C(object): pass @@ -5288,7 +5287,6 @@ class AAAPTypesLongInitTest(unittest.TestCase): class MiscTests(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON; a class namespace drops keys that are not strings, so MyKey.__eq__ is never reached and __bases__ stays put def test_type_lookup_mro_reference(self): # Issue #14199: _PyType_Lookup() has to keep a strong reference to # the type MRO because it may be modified during the lookup, if @@ -5924,7 +5922,6 @@ class MroTest(unittest.TestCase): with self.assertRaises(TypeError): B1.__bases__ += () - @unittest.expectedFailure # TODO: RUSTPYTHON def test_tp_subclasses_cycle_in_update_slots(self): """ type_set_bases must check for reentrancy upon finishing its job @@ -5961,7 +5958,6 @@ class MroTest(unittest.TestCase): self.assertEqual(B1.__bases__, (C,)) self.assertEqual(C.__subclasses__(), [B1]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_tp_subclasses_cycle_error_return_path(self): """ The same as test_tp_subclasses_cycle_in_update_slots, but tests diff --git a/Lib/test/test_doctest/test_doctest.py b/Lib/test/test_doctest/test_doctest.py index 4a42abf44..8b6276942 100644 --- a/Lib/test/test_doctest/test_doctest.py +++ b/Lib/test/test_doctest/test_doctest.py @@ -2129,7 +2129,7 @@ if not hasattr(sys, 'gettrace') or not sys.gettrace(): ... 'list', # list source from example 3 ... 'continue', # stop debugging ... '']) - >>> try: runner.run(test) # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + >>> try: runner.run(test) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE > (3)g() diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index cb9ae24cc..d56524d58 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -1226,7 +1226,6 @@ class ArgsTestCase(BaseTestCase): regex = ('10 slowest tests:\n') self.check_line(output, regex) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Regex didn't match: '^lines +cov% +module +\\(path\\)\\n(?: *[0-9]+ *[0-9]{1,2}\\.[0-9]% *[^ ]+ +\\([^)]+\\)+)+' not found in 'Warning: collecting coverage without -j is imprecise. Configure --with-pydebug and run -m test -T -j for best results.\nUsing random seed: 2780369491\n0:00:00 Run 1 test sequentially in a single process\n0:00:00 [1/1] test_regrtest_coverage\n0:00:00 [1/1] test_regrtest_coverage passed\n\n== Tests result: SUCCESS ==\n\n1 test OK.\n\nTotal duration: 102 ms\nTotal tests: run=1\nTotal test files: run=1/1\nResult: SUCCESS\n' def test_coverage(self): # test --coverage test = self.create_test('coverage') diff --git a/Lib/test/test_sys_setprofile.py b/Lib/test/test_sys_setprofile.py index d0d2b0c3e..18607a733 100644 --- a/Lib/test/test_sys_setprofile.py +++ b/Lib/test/test_sys_setprofile.py @@ -260,7 +260,6 @@ class ProfileHookTestCase(TestCaseBase): (1, 'return', g_ident), ]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_unfinished_generator(self): def f(): for i in range(2): diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index 54d31a1b6..640240935 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -413,51 +413,36 @@ class TraceTestCase(unittest.TestCase): finally: sys.settrace(None) - @unittest.skip("TODO: RUSTPYTHON") def test_01_basic(self): self.run_test(basic) - @unittest.skip("TODO: RUSTPYTHON") def test_02_arigo0(self): self.run_test(arigo_example0) - @unittest.skip("TODO: RUSTPYTHON") def test_02_arigo1(self): self.run_test(arigo_example1) - @unittest.skip("TODO: RUSTPYTHON") def test_02_arigo2(self): self.run_test(arigo_example2) - @unittest.skip("TODO: RUSTPYTHON") def test_03_one_instr(self): self.run_test(one_instr_line) - @unittest.skip("TODO: RUSTPYTHON") def test_04_no_pop_blocks(self): self.run_test(no_pop_blocks) - @unittest.skip("TODO: RUSTPYTHON") def test_05_no_pop_tops(self): self.run_test(no_pop_tops) - @unittest.skip("TODO: RUSTPYTHON") def test_06_call(self): self.run_test(call) - @unittest.skip("TODO: RUSTPYTHON") def test_07_raise(self): self.run_test(test_raise) - @unittest.skip("TODO: RUSTPYTHON") def test_08_settrace_and_return(self): self.run_test2(settrace_and_return) - @unittest.skip("TODO: RUSTPYTHON") def test_09_settrace_and_raise(self): self.run_test2(settrace_and_raise) - @unittest.skip("TODO: RUSTPYTHON") def test_10_ireturn(self): self.run_test(ireturn_example) - @unittest.skip("TODO: RUSTPYTHON") def test_11_tightloop(self): self.run_test(tightloop_example) - @unittest.skip("TODO: RUSTPYTHON") def test_12_tighterloop(self): self.run_test(tighterloop_example) - @unittest.skip("TODO: RUSTPYTHON") def test_13_genexp(self): self.run_test(generator_example) # issue1265: if the trace function contains a generator, @@ -471,7 +456,6 @@ class TraceTestCase(unittest.TestCase): self.compare_events(generator_example.__code__.co_firstlineno, tracer.events, generator_example.events) - @unittest.skip("TODO: RUSTPYTHON") def test_14_onliner_if(self): def onliners(): if True: x=False @@ -484,7 +468,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_15_loops(self): # issue1750076: "while" expression is skipped by debugger def for_example(): @@ -516,7 +499,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_16_blank_lines(self): namespace = {} exec("def f():\n" + "\n" * 256 + " pass", namespace) @@ -526,7 +508,6 @@ class TraceTestCase(unittest.TestCase): (257, 'line'), (257, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_17_none_f_trace(self): # Issue 20041: fix TypeError when f_trace is set to None. def func(): @@ -536,7 +517,6 @@ class TraceTestCase(unittest.TestCase): [(0, 'call'), (1, 'line')]) - @unittest.skip("TODO: RUSTPYTHON") def test_18_except_with_name(self): def func(): try: @@ -561,7 +541,6 @@ class TraceTestCase(unittest.TestCase): (9, 'line'), (9, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_19_except_with_finally(self): def func(): try: @@ -583,7 +562,6 @@ class TraceTestCase(unittest.TestCase): (7, 'line'), (7, 'return')]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_20_async_for_loop(self): class AsyncIteratorWrapper: def __init__(self, obj): @@ -660,7 +638,6 @@ class TraceTestCase(unittest.TestCase): self.compare_events(doit_async.__code__.co_firstlineno, tracer.events, events) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_async_for_backwards_jump_has_no_line(self): async def arange(n): for i in range(n): @@ -713,7 +690,6 @@ class TraceTestCase(unittest.TestCase): self.compare_events(f.__code__.co_firstlineno, tracer.events, events) - @unittest.skip("TODO: RUSTPYTHON") def test_21_repeated_pass(self): def func(): pass @@ -725,7 +701,6 @@ class TraceTestCase(unittest.TestCase): (2, 'line'), (2, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_loop_in_try_except(self): # https://bugs.python.org/issue41670 @@ -743,7 +718,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_no_exception(self): def func(): @@ -773,7 +747,6 @@ class TraceTestCase(unittest.TestCase): (14, 'line'), (14, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_exception_in_else(self): def func(): @@ -806,7 +779,6 @@ class TraceTestCase(unittest.TestCase): (14, 'line'), (14, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_nested_loops(self): def func(): @@ -833,7 +805,6 @@ class TraceTestCase(unittest.TestCase): (4, 'line'), (4, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_if_break(self): def func(): @@ -859,7 +830,6 @@ class TraceTestCase(unittest.TestCase): (8, 'line'), (8, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_break_through_finally(self): def func(): @@ -895,7 +865,6 @@ class TraceTestCase(unittest.TestCase): (10, 'line')] + ([(13, 'line'), (13, 'return')] if __debug__ else [(10, 'return')])) - @unittest.skip("TODO: RUSTPYTHON") def test_continue_through_finally(self): def func(): @@ -932,7 +901,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line')] + ([(13, 'line'), (13, 'return')] if __debug__ else [(3, 'return')])) - @unittest.skip("TODO: RUSTPYTHON") def test_return_through_finally(self): def func(): @@ -948,7 +916,6 @@ class TraceTestCase(unittest.TestCase): (4, 'line'), (4, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_with_wrong_type(self): def func(): @@ -976,7 +943,6 @@ class TraceTestCase(unittest.TestCase): (10, 'line'), (10, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_finally_with_conditional(self): # See gh-105658 @@ -1006,7 +972,6 @@ class TraceTestCase(unittest.TestCase): (10, 'line'), (10, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_break_to_continue1(self): def func(): @@ -1030,7 +995,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_break_to_continue2(self): def func(): @@ -1054,7 +1018,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_break_to_break(self): def func(): @@ -1073,7 +1036,6 @@ class TraceTestCase(unittest.TestCase): (5, 'line'), (5, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_nested_ifs(self): def func(): @@ -1094,7 +1056,6 @@ class TraceTestCase(unittest.TestCase): (4, 'line'), (4, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_nested_ifs_with_and(self): def func(): @@ -1118,7 +1079,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_nested_try_if(self): def func(): @@ -1141,7 +1101,6 @@ class TraceTestCase(unittest.TestCase): (7, 'line'), (7, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_if_false_in_with(self): class C: @@ -1168,7 +1127,6 @@ class TraceTestCase(unittest.TestCase): (-2, 'return'), (1, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_if_false_in_try_except(self): def func(): @@ -1184,7 +1142,6 @@ class TraceTestCase(unittest.TestCase): (2, 'line'), (2, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_implicit_return_in_class(self): def func(): @@ -1204,7 +1161,6 @@ class TraceTestCase(unittest.TestCase): (3, 'return'), (1, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_in_try(self): def func(): try: @@ -1222,7 +1178,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_in_try_with_exception(self): def func(): @@ -1264,7 +1219,6 @@ class TraceTestCase(unittest.TestCase): (5, 'line'), (5, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_if_in_if_in_if(self): def func(a=0, p=1, z=1): if p: @@ -1282,7 +1236,6 @@ class TraceTestCase(unittest.TestCase): (2, 'line'), (2, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_early_exit_with(self): class C: @@ -1329,7 +1282,6 @@ class TraceTestCase(unittest.TestCase): (-8, 'return'), (1, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_flow_converges_on_same_line(self): def foo(x): @@ -1366,7 +1318,6 @@ class TraceTestCase(unittest.TestCase): (1, 'line'), (1, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_no_tracing_of_named_except_cleanup(self): def func(): @@ -1389,7 +1340,6 @@ class TraceTestCase(unittest.TestCase): (7, 'line'), (7, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_tracing_exception_raised_in_with(self): class NullCtx: @@ -1422,7 +1372,6 @@ class TraceTestCase(unittest.TestCase): (5, 'line'), (5, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_star_no_exception(self): def func(): @@ -1452,7 +1401,6 @@ class TraceTestCase(unittest.TestCase): (14, 'line'), (14, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_star_named_no_exception(self): def func(): @@ -1473,7 +1421,6 @@ class TraceTestCase(unittest.TestCase): (8, 'line'), (8, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_star_exception_caught(self): def func(): @@ -1496,7 +1443,6 @@ class TraceTestCase(unittest.TestCase): (8, 'line'), (8, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_star_named_exception_caught(self): def func(): @@ -1519,7 +1465,6 @@ class TraceTestCase(unittest.TestCase): (8, 'line'), (8, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_star_exception_not_caught(self): def func(): @@ -1542,7 +1487,6 @@ class TraceTestCase(unittest.TestCase): (7, 'line'), (7, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_star_named_exception_not_caught(self): def func(): @@ -1565,7 +1509,6 @@ class TraceTestCase(unittest.TestCase): (7, 'line'), (7, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_try_except_star_nested(self): def func(): @@ -1613,7 +1556,6 @@ class TraceTestCase(unittest.TestCase): (19, 'line'), (19, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_notrace_lambda(self): #Regression test for issue 46314 @@ -1629,7 +1571,6 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_class_creation_with_docstrings(self): def func(): @@ -1649,7 +1590,6 @@ class TraceTestCase(unittest.TestCase): (3, 'return'), (1, 'return')]) - @unittest.skip("TODO: RUSTPYTHON") def test_class_creation_with_decorator(self): def func(): def decorator(arg): @@ -1740,7 +1680,6 @@ class TraceTestCase(unittest.TestCase): self.run_and_compare(func, EXPECTED_EVENTS) - @unittest.skip("TODO: RUSTPYTHON") def test_correct_tracing_quickened_call_class_init(self): class C: @@ -1764,7 +1703,6 @@ class TraceTestCase(unittest.TestCase): func() self.run_and_compare(func, EXPECTED_EVENTS) - @unittest.skip("TODO: RUSTPYTHON") def test_settrace_error(self): raised = False def error_once(frame, event, arg): @@ -1819,7 +1757,6 @@ class TraceTestCase(unittest.TestCase): finally: sys.settrace(None) - @unittest.skip("TODO: RUSTPYTHON") def test_very_large_function(self): # There is a separate code path when the number of lines > (1 << 15). d = {} @@ -1862,6 +1799,9 @@ class SkipLineEventsTraceTestCase(TraceTestCase): def make_tracer(): return Tracer(trace_line_events=False) + def test_12_tighterloop(self): + TraceTestCase.test_12_tighterloop(self) + @support.cpython_only class TraceOpcodesTestCase(TraceTestCase): @@ -1951,16 +1891,12 @@ class RaisingTraceFuncTestCase(unittest.TestCase): self.fail("recursion counter not reset") # Test the handling of exceptions raised by each kind of trace event. - @unittest.skip("TODO: RUSTPYTHON; spurious error?") def test_call(self): self.run_test_for_event('call') - @unittest.skip("TODO: RUSTPYTHON; spurious error?") def test_line(self): self.run_test_for_event('line') - @unittest.skip("TODO: RUSTPYTHON; spurious error?") def test_return(self): self.run_test_for_event('return') - @unittest.skip("TODO: RUSTPYTHON; spurious error?") def test_exception(self): self.run_test_for_event('exception') @@ -2008,7 +1944,6 @@ class RaisingTraceFuncTestCase(unittest.TestCase): finally: sys.settrace(existing) - @unittest.skip("TODO: RUSTPYTHON") def test_line_event_raises_before_opcode_event(self): exception = ValueError("BOOM!") def trace(frame, event, arg): @@ -2171,20 +2106,17 @@ class JumpTestCase(unittest.TestCase): ## The first set of 'jump' tests are for things that are allowed: - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 3, [3]) def test_jump_simple_forwards(output): output.append(1) output.append(2) output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 1, [1, 1, 2]) def test_jump_simple_backwards(output): output.append(1) output.append(2) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 4, [5], warning=(RuntimeWarning, unbound_locals)) def test_jump_is_none_forwards(output): x = None @@ -2193,7 +2125,6 @@ class JumpTestCase(unittest.TestCase): else: output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(6, 5, [3, 5, 6]) def test_jump_is_none_backwards(output): x = None @@ -2203,7 +2134,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) output.append(6) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 4, [5]) def test_jump_is_not_none_forwards(output): x = None @@ -2212,7 +2142,6 @@ class JumpTestCase(unittest.TestCase): else: output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(6, 5, [5, 5, 6]) def test_jump_is_not_none_backwards(output): x = None @@ -2222,7 +2151,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) output.append(6) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 5, [2, 5], warning=(RuntimeWarning, unbound_locals)) def test_jump_out_of_block_forwards(output): for i in 1, 2: @@ -2231,7 +2159,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(6, 1, [1, 3, 5, 1, 3, 5, 6, 7]) def test_jump_out_of_block_backwards(output): output.append(1) @@ -2242,7 +2169,6 @@ class JumpTestCase(unittest.TestCase): output.append(6) output.append(7) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(4, 5, [3, 5]) @clean_asynciter async def test_jump_out_of_async_for_block_forwards(output, asynciter): @@ -2252,7 +2178,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(5, 2, [2, 4, 2, 4, 5, 6]) @clean_asynciter async def test_jump_out_of_async_for_block_backwards(output, asynciter): @@ -2263,14 +2188,12 @@ class JumpTestCase(unittest.TestCase): output.append(5) output.append(6) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 2, [3]) def test_jump_to_codeless_line(output): output.append(1) # Jumping to this line should skip to the next one. output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 2, [1, 2, 3]) def test_jump_to_same_line(output): output.append(1) @@ -2278,7 +2201,6 @@ class JumpTestCase(unittest.TestCase): output.append(3) # Tests jumping within a finally block, and over one. - @unittest.skip("TODO: RUSTPYTHON") @jump_test(4, 9, [2, 9]) def test_jump_in_nested_finally(output): try: @@ -2291,7 +2213,6 @@ class JumpTestCase(unittest.TestCase): output.append(8) output.append(9) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(6, 7, [2, 7], (ZeroDivisionError, '')) def test_jump_in_nested_finally_2(output): try: @@ -2303,7 +2224,6 @@ class JumpTestCase(unittest.TestCase): output.append(7) output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(6, 11, [2, 11], (ZeroDivisionError, '')) def test_jump_in_nested_finally_3(output): try: @@ -2319,7 +2239,6 @@ class JumpTestCase(unittest.TestCase): output.append(11) output.append(12) - @unittest.skip("TODO: RUSTPYTHON; infinite loop never exits") @jump_test(3, 4, [1], (ValueError, 'after')) def test_no_jump_infinite_while_loop(output): output.append(1) @@ -2327,7 +2246,6 @@ class JumpTestCase(unittest.TestCase): output.append(3) output.append(4) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 4, [4, 4]) def test_jump_forwards_into_while_block(output): i = 1 @@ -2336,7 +2254,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) i += 1 - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 3, [3, 3, 3, 5]) def test_jump_backwards_into_while_block(output): i = 1 @@ -2345,35 +2262,30 @@ class JumpTestCase(unittest.TestCase): i += 1 output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [1, 3]) def test_jump_forwards_out_of_with_block(output): with tracecontext(output, 1): output.append(2) output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(2, 3, [1, 3]) async def test_jump_forwards_out_of_async_with_block(output): async with asynctracecontext(output, 1): output.append(2) output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 1, [1, 2, 1, 2, 3, -2]) def test_jump_backwards_out_of_with_block(output): output.append(1) with tracecontext(output, 2): output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(3, 1, [1, 2, 1, 2, 3, -2]) async def test_jump_backwards_out_of_async_with_block(output): output.append(1) async with asynctracecontext(output, 2): output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 5, [5]) def test_jump_forwards_out_of_try_finally_block(output): try: @@ -2382,7 +2294,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 1, [1, 1, 3, 5]) def test_jump_backwards_out_of_try_finally_block(output): output.append(1) @@ -2391,7 +2302,6 @@ class JumpTestCase(unittest.TestCase): finally: output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 6, [6]) def test_jump_forwards_out_of_try_except_block(output): try: @@ -2401,7 +2311,6 @@ class JumpTestCase(unittest.TestCase): raise output.append(6) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 1, [1, 1, 3]) def test_jump_backwards_out_of_try_except_block(output): output.append(1) @@ -2411,7 +2320,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) raise - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 7, [4, 7, 8]) def test_jump_between_except_blocks(output): try: @@ -2423,7 +2331,6 @@ class JumpTestCase(unittest.TestCase): output.append(7) output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 7, [4, 7, 8]) def test_jump_from_except_to_finally(output): try: @@ -2435,7 +2342,6 @@ class JumpTestCase(unittest.TestCase): output.append(7) output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 6, [4, 6, 7]) def test_jump_within_except_block(output): try: @@ -2446,7 +2352,6 @@ class JumpTestCase(unittest.TestCase): output.append(6) output.append(7) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(6, 1, [1, 5, 1, 5], warning=(RuntimeWarning, unbound_locals)) def test_jump_over_try_except(output): output.append(1) @@ -2456,7 +2361,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) x = 42 # has to be a two-instruction block - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 4, [1, 4, 5, -4]) def test_jump_across_with(output): output.append(1) @@ -2465,7 +2369,6 @@ class JumpTestCase(unittest.TestCase): with tracecontext(output, 4): output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(2, 4, [1, 4, 5, -4]) async def test_jump_across_async_with(output): output.append(1) @@ -2474,7 +2377,6 @@ class JumpTestCase(unittest.TestCase): async with asynctracecontext(output, 4): output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(4, 5, [1, 3, 5, 6]) def test_jump_out_of_with_block_within_for_block(output): output.append(1) @@ -2484,7 +2386,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) output.append(6) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(4, 5, [1, 3, 5, 6]) async def test_jump_out_of_async_with_block_within_for_block(output): output.append(1) @@ -2494,7 +2395,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) output.append(6) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(4, 5, [1, 2, 3, 5, -2, 6]) def test_jump_out_of_with_block_within_with_block(output): output.append(1) @@ -2504,7 +2404,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) output.append(6) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(4, 5, [1, 2, 3, 5, -2, 6]) async def test_jump_out_of_async_with_block_within_with_block(output): output.append(1) @@ -2514,7 +2413,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) output.append(6) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 6, [2, 4, 6, 7]) def test_jump_out_of_with_block_within_finally_block(output): try: @@ -2525,7 +2423,6 @@ class JumpTestCase(unittest.TestCase): output.append(6) output.append(7) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(5, 6, [2, 4, 6, 7]) async def test_jump_out_of_async_with_block_within_finally_block(output): try: @@ -2536,7 +2433,6 @@ class JumpTestCase(unittest.TestCase): output.append(6) output.append(7) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(8, 11, [1, 3, 5, 11, 12]) def test_jump_out_of_complex_nested_blocks(output): output.append(1) @@ -2552,7 +2448,6 @@ class JumpTestCase(unittest.TestCase): output.append(11) output.append(12) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 5, [1, 2, 5], warning=(RuntimeWarning, unbound_locals)) def test_jump_out_of_with_assignment(output): output.append(1) @@ -2561,7 +2456,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(3, 5, [1, 2, 5], warning=(RuntimeWarning, unbound_locals)) async def test_jump_out_of_async_with_assignment(output): output.append(1) @@ -2570,7 +2464,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 6, [1, 6, 8, 9]) def test_jump_over_return_in_try_finally_block(output): output.append(1) @@ -2583,7 +2476,6 @@ class JumpTestCase(unittest.TestCase): output.append(8) output.append(9) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 8, [1, 3, 8, 10, 11, 13]) def test_jump_over_break_in_try_finally_block(output): output.append(1) @@ -2600,7 +2492,6 @@ class JumpTestCase(unittest.TestCase): break output.append(13) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 7, [7, 8], warning=(RuntimeWarning, unbound_locals)) def test_jump_over_for_block_before_else(output): output.append(1) @@ -2612,7 +2503,6 @@ class JumpTestCase(unittest.TestCase): output.append(7) output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(1, 7, [7, 8], warning=(RuntimeWarning, unbound_locals)) async def test_jump_over_async_for_block_before_else(output): output.append(1) @@ -2626,20 +2516,17 @@ class JumpTestCase(unittest.TestCase): # The second set of 'jump' tests are for things that are not allowed: - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [1], (ValueError, 'after')) def test_no_jump_too_far_forwards(output): output.append(1) output.append(2) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, -2, [1], (ValueError, 'before')) def test_no_jump_too_far_backwards(output): output.append(1) output.append(2) # Test each kind of 'except' line. - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [4], (ValueError, 'except')) def test_no_jump_to_except_1(output): try: @@ -2648,7 +2535,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) raise - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [4], (ValueError, 'except')) def test_no_jump_to_except_2(output): try: @@ -2657,7 +2543,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) raise - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [4], (ValueError, 'except')) def test_no_jump_to_except_3(output): try: @@ -2666,7 +2551,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) raise e - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [4], (ValueError, 'except')) def test_no_jump_to_except_4(output): try: @@ -2675,14 +2559,12 @@ class JumpTestCase(unittest.TestCase): output.append(4) raise e - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 3, [], (ValueError, 'into')) def test_no_jump_forwards_into_for_block(output): output.append(1) for i in 1, 2: output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(1, 3, [], (ValueError, 'into')) async def test_no_jump_forwards_into_async_for_block(output): output.append(1) @@ -2690,7 +2572,6 @@ class JumpTestCase(unittest.TestCase): output.append(3) pass - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 2, [2, 2], (ValueError, 'into')) def test_no_jump_backwards_into_for_block(output): for i in 1, 2: @@ -2698,42 +2579,36 @@ class JumpTestCase(unittest.TestCase): output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(3, 2, [2, 2], (ValueError, "can't jump into the body of a for loop")) async def test_no_jump_backwards_into_async_for_block(output): async for i in asynciter([1, 2]): output.append(2) output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 3, [], (ValueError, 'stack')) def test_no_jump_forwards_into_with_block(output): output.append(1) with tracecontext(output, 2): output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(1, 3, [], (ValueError, 'stack')) async def test_no_jump_forwards_into_async_with_block(output): output.append(1) async with asynctracecontext(output, 2): output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 2, [1, 2, -1], (ValueError, 'stack')) def test_no_jump_backwards_into_with_block(output): with tracecontext(output, 1): output.append(2) output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(3, 2, [1, 2, -1], (ValueError, 'stack')) async def test_no_jump_backwards_into_async_with_block(output): async with asynctracecontext(output, 1): output.append(2) output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 3, [3, 5]) def test_jump_forwards_into_try_finally_block(output): output.append(1) @@ -2742,7 +2617,6 @@ class JumpTestCase(unittest.TestCase): finally: output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 2, [2, 4, 2, 4, 5]) def test_jump_backwards_into_try_finally_block(output): try: @@ -2751,7 +2625,6 @@ class JumpTestCase(unittest.TestCase): output.append(4) output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 3, [3]) def test_jump_forwards_into_try_except_block(output): output.append(1) @@ -2761,7 +2634,6 @@ class JumpTestCase(unittest.TestCase): output.append(5) raise - @unittest.skip("TODO: RUSTPYTHON") @jump_test(6, 2, [2, 2, 6]) def test_jump_backwards_into_try_except_block(output): try: @@ -2772,7 +2644,6 @@ class JumpTestCase(unittest.TestCase): output.append(6) # 'except' with a variable creates an implicit finally block - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 7, [4, 7, 8], warning=(RuntimeWarning, unbound_locals)) def test_jump_between_except_blocks_2(output): try: @@ -2784,7 +2655,6 @@ class JumpTestCase(unittest.TestCase): output.append(7) output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 5, [5]) def test_jump_into_finally_block(output): output.append(1) @@ -2793,7 +2663,6 @@ class JumpTestCase(unittest.TestCase): finally: output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 6, [2, 6, 7]) def test_jump_into_finally_block_from_try_block(output): try: @@ -2804,7 +2673,6 @@ class JumpTestCase(unittest.TestCase): output.append(6) output.append(7) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 1, [1, 3, 1, 3, 5]) def test_jump_out_of_finally_block(output): output.append(1) @@ -2813,7 +2681,6 @@ class JumpTestCase(unittest.TestCase): finally: output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 5, [], (ValueError, "can't jump into an 'except' block as there's no exception")) def test_no_jump_into_bare_except_block(output): output.append(1) @@ -2822,7 +2689,6 @@ class JumpTestCase(unittest.TestCase): except: output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(1, 5, [], (ValueError, "can't jump into an 'except' block as there's no exception")) def test_no_jump_into_qualified_except_block(output): output.append(1) @@ -2831,7 +2697,6 @@ class JumpTestCase(unittest.TestCase): except Exception: output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 6, [2, 5, 6], (ValueError, "can't jump into an 'except' block as there's no exception")) def test_no_jump_into_bare_except_block_from_try_block(output): try: @@ -2843,7 +2708,6 @@ class JumpTestCase(unittest.TestCase): raise output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 6, [2], (ValueError, "can't jump into an 'except' block as there's no exception")) def test_no_jump_into_qualified_except_block_from_try_block(output): try: @@ -2855,7 +2719,6 @@ class JumpTestCase(unittest.TestCase): raise output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(7, 1, [1, 3, 6, 1, 3, 6, 7]) def test_jump_out_of_bare_except_block(output): output.append(1) @@ -2866,7 +2729,6 @@ class JumpTestCase(unittest.TestCase): output.append(6) output.append(7) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(7, 1, [1, 3, 6, 1, 3, 6, 7]) def test_jump_out_of_qualified_except_block(output): output.append(1) @@ -2877,7 +2739,6 @@ class JumpTestCase(unittest.TestCase): output.append(6) output.append(7) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 5, [1, 2, 5, -2]) def test_jump_between_with_blocks(output): output.append(1) @@ -2886,7 +2747,6 @@ class JumpTestCase(unittest.TestCase): with tracecontext(output, 4): output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(3, 5, [1, 2, 5, -2]) async def test_jump_between_async_with_blocks(output): output.append(1) @@ -2895,7 +2755,6 @@ class JumpTestCase(unittest.TestCase): async with asynctracecontext(output, 4): output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(5, 7, [2, 4], (ValueError, "after")) def test_no_jump_over_return_out_of_finally_block(output): try: @@ -2906,7 +2765,6 @@ class JumpTestCase(unittest.TestCase): return output.append(7) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(7, 4, [1, 6], (ValueError, 'into')) def test_no_jump_into_for_block_before_else(output): output.append(1) @@ -2918,7 +2776,6 @@ class JumpTestCase(unittest.TestCase): output.append(7) output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(7, 4, [1, 6], (ValueError, 'into')) async def test_no_jump_into_async_for_block_before_else(output): output.append(1) @@ -2930,17 +2787,14 @@ class JumpTestCase(unittest.TestCase): output.append(7) output.append(8) - @unittest.skip("TODO: RUSTPYTHON") def test_no_jump_to_non_integers(self): self.run_test(no_jump_to_non_integers, 2, "Spam", [True]) - @unittest.skip("TODO: RUSTPYTHON") def test_no_jump_without_trace_function(self): # Must set sys.settrace(None) in setUp(), else condition is not # triggered. no_jump_without_trace_function() - @unittest.skip("TODO: RUSTPYTHON") def test_large_function(self): d = {} exec("""def f(output): # line 0 @@ -2955,7 +2809,6 @@ class JumpTestCase(unittest.TestCase): f = d['f'] self.run_test(f, 2, 1007, [0], warning=(RuntimeWarning, self.unbound_locals)) - @unittest.skip("TODO: RUSTPYTHON") def test_jump_to_firstlineno(self): # This tests that PDB can jump back to the first line in a # file. See issue #1689458. It can only be triggered in a @@ -2975,7 +2828,6 @@ output.append(4) sys.settrace(None) self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"]) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [1], event='call', error=(ValueError, "can't jump from" " the 'call' trace event of a new frame")) def test_no_jump_from_call(output): @@ -2985,21 +2837,18 @@ output.append(4) nested() output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 1, [1], event='return', error=(ValueError, "can only jump from a 'line' trace event")) def test_no_jump_from_return_event(output): output.append(1) return - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 1, [1], event='exception', error=(ValueError, "can only jump from a 'line' trace event")) def test_no_jump_from_exception_event(output): output.append(1) 1 / 0 - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 2, [2, 2, 5], event='return') def test_jump_from_yield(output): def gen(): @@ -3008,7 +2857,6 @@ output.append(4) next(gen()) output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [1, 3], warning=(RuntimeWarning, unbound_locals)) def test_jump_forward_over_listcomp(output): output.append(1) @@ -3017,14 +2865,12 @@ output.append(4) # checking for segfaults. # See https://github.com/python/cpython/issues/92311 - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 1, [], warning=(RuntimeWarning, unbound_locals)) def test_jump_backward_over_listcomp(output): a = 1 x = [i for i in range(10)] c = 3 - @unittest.skip("TODO: RUSTPYTHON") @jump_test(8, 2, [2, 7, 2], warning=(RuntimeWarning, unbound_locals)) def test_jump_backward_over_listcomp_v2(output): flag = False @@ -3036,21 +2882,18 @@ output.append(4) output.append(7) output.append(8) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(2, 3, [1, 3], warning=(RuntimeWarning, unbound_locals)) async def test_jump_forward_over_async_listcomp(output): output.append(1) x = [i async for i in asynciter(range(10))] output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(3, 1, [], warning=(RuntimeWarning, unbound_locals)) async def test_jump_backward_over_async_listcomp(output): a = 1 x = [i async for i in asynciter(range(10))] c = 3 - @unittest.skip("TODO: RUSTPYTHON") @async_jump_test(8, 2, [2, 7, 2], warning=(RuntimeWarning, unbound_locals)) async def test_jump_backward_over_async_listcomp_v2(output): flag = False @@ -3063,7 +2906,6 @@ output.append(4) output.append(8) # checking for segfaults. - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 7, [], error=(ValueError, "stack")) def test_jump_with_null_on_stack_load_global(output): a = 1 @@ -3083,7 +2925,6 @@ output.append(4) output.append(15) # checking for segfaults. - @unittest.skip("TODO: RUSTPYTHON") @jump_test(4, 8, [], error=(ValueError, "stack")) def test_jump_with_null_on_stack_push_null(output): a = 1 @@ -3104,7 +2945,6 @@ output.append(4) output.append(16) # checking for segfaults. - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 7, [], error=(ValueError, "stack")) def test_jump_with_null_on_stack_load_attr(output): a = 1 @@ -3123,14 +2963,12 @@ output.append(4) ) output.append(15) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [1, 3], warning=(RuntimeWarning, unbound_locals)) def test_jump_extended_args_unpack_ex_simple(output): output.append(1) _, *_, _ = output.append(2) or "Spam" output.append(3) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(3, 4, [1, 4, 4, 5], warning=(RuntimeWarning, unbound_locals)) def test_jump_extended_args_unpack_ex_tricky(output): output.append(1) @@ -3139,7 +2977,6 @@ output.append(4) ) = output.append(4) or "Spam" output.append(5) - @unittest.skip("TODO: RUSTPYTHON") @support.requires_resource('cpu') def test_jump_extended_args_for_iter(self): # In addition to failing when extended arg handling is broken, this can @@ -3156,7 +2993,6 @@ output.append(4) f = namespace["f"] self.run_test(f, 2, 100_000, [1, 100_000], warning=(RuntimeWarning, self.unbound_locals)) - @unittest.skip("TODO: RUSTPYTHON") @jump_test(2, 3, [1, 3], warning=(RuntimeWarning, unbound_locals)) def test_jump_or_pop(output): output.append(1) @@ -3245,7 +3081,6 @@ class TestEdgeCases(unittest.TestCase): class TestLinesAfterTraceStarted(TraceTestCase): - @unittest.skip("TODO: RUSTPYTHON") def test_events(self): tracer = Tracer() sys._getframe().f_trace = tracer.trace diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py index 098de8b22..f1b3a3827 100644 --- a/Lib/test/test_trace.py +++ b/Lib/test/test_trace.py @@ -130,7 +130,6 @@ class TestLineCounts(unittest.TestCase): self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) self.my_py_filename = fix_ext_py(__file__) - @unittest.expectedFailure # TODO: RUSTPYTHON; + ('crates/pylib/Lib/test/test_trace.py', 48): 1} def test_traced_func_linear(self): result = self.tracer.runfunc(traced_func_linear, 2, 5) self.assertEqual(result, 7) @@ -143,7 +142,6 @@ class TestLineCounts(unittest.TestCase): self.assertEqual(self.tracer.results().counts, expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; + ('crates/pylib/Lib/test/test_trace.py', 54): 1} def test_traced_func_loop(self): self.tracer.runfunc(traced_func_loop, 2, 3) @@ -156,7 +154,6 @@ class TestLineCounts(unittest.TestCase): } self.assertEqual(self.tracer.results().counts, expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; + ('crates/pylib/Lib/test/tracedmodules/testmod.py', 3): 1} def test_traced_func_importing(self): self.tracer.runfunc(traced_func_importing, 2, 5) @@ -169,7 +166,6 @@ class TestLineCounts(unittest.TestCase): self.assertEqual(self.tracer.results().counts, expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; + ('crates/pylib/Lib/test/test_trace.py', 76): 10} def test_trace_func_generator(self): self.tracer.runfunc(traced_func_calling_generator) @@ -185,7 +181,6 @@ class TestLineCounts(unittest.TestCase): } self.assertEqual(self.tracer.results().counts, expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; + ('crates/pylib/Lib/test/test_trace.py', 87): 1} def test_trace_list_comprehension(self): self.tracer.runfunc(traced_caller_list_comprehension) @@ -199,7 +194,6 @@ class TestLineCounts(unittest.TestCase): } self.assertEqual(self.tracer.results().counts, expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; Diff is 996 characters long. Set self.maxDiff to None to see it. def test_traced_decorated_function(self): self.tracer.runfunc(traced_decorated_function) @@ -219,7 +213,6 @@ class TestLineCounts(unittest.TestCase): } self.assertEqual(self.tracer.results().counts, expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; + {('crates/pylib/Lib/test/test_trace.py', 108): 1} def test_linear_methods(self): # XXX todo: later add 'static_method_linear' and 'class_method_linear' # here, once issue1764286 is resolved @@ -243,7 +236,6 @@ class TestRunExecCounts(unittest.TestCase): self.my_py_filename = fix_ext_py(__file__) self.addCleanup(sys.settrace, sys.gettrace()) - @unittest.expectedFailure # TODO: RUSTPYTHON; KeyError: ('crates/pylib/Lib/test/test_trace.py', 51) def test_exec_counts(self): self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) code = r'''traced_func_loop(2, 5)''' @@ -545,7 +537,6 @@ class TestCommandLine(unittest.TestCase): PYTHONIOENCODING='utf-8') self.assertIn(direct_stdout.strip(), trace_stdout) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'lines cov% module (path)' not found in '' def test_count_and_summary(self): filename = f'{TESTFN}.py' coverfilename = f'{TESTFN}.cover' @@ -581,7 +572,6 @@ class TestTrace(unittest.TestCase): self.tracer = Trace(count=0, trace=1) self.filemod = my_file_and_modname() - @unittest.expectedFailure # TODO: RUSTPYTHON; IndexError: list index out of range def test_no_source_file(self): filename = "" co = traced_func_linear.__code__ diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index e638cdf3a..698c99410 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -349,18 +349,18 @@ pub(crate) mod stack_analysis { } } _ => { - // Default: use stack_effect - let effect: StackEffect = opcode.stack_effect_info(oparg); - let popped = effect.popped() as i64; - let pushed = effect.pushed() as i64; - let mut ns = next_stack; - for _ in 0..popped { - ns = pop_value(ns); + // PyCompile_OpcodeStackEffect: apply the net delta so + // overlapping in/out (GET_ANEXT: aiter -- aiter, awaitable) + // keep the original kind of the surviving entries. + let mut delta = opcode.stack_effect(oparg); + while delta < 0 { + next_stack = pop_value(next_stack); + delta += 1; } - for _ in 0..pushed { - ns = push_value(ns, Kind::Object as i64); + while delta > 0 { + next_stack = push_value(next_stack, Kind::Object as i64); + delta -= 1; } - next_stack = ns; if next_i < stacks.len() { stacks[next_i] = next_stack; } @@ -484,34 +484,21 @@ impl FrameObject { #[pygetset] fn f_lasti(&self) -> u32 { - // Return byte offset (each instruction is 2 bytes) for compatibility. - // For materialized frames, read live lasti from the source iframe on - // the TLS chain so f_lasti reflects the current execution position. + // Byte offset of the current opcode. lasti is stored as the next + // instruction index (see FrameObject::run), so the executing unit + // is lasti-1. let live = self.find_live_source_iframe(); let val = if !live.is_null() { unsafe { (*live).lasti.load(Relaxed) } } else { self.lasti() }; - val * 2 + if val == 0 { 0 } else { (val - 1) * 2 } } /// Current line, or -1 when the linetable has no line for lasti. pub fn lineno(&self) -> i32 { - let lasti_bytes = self.f_lasti() as i32; - // If lasti is 0, execution hasn't started yet - use first line number. - // Read the live iframe so a materialized copy that still has lasti==0 - // does not hide the current line while the frame is running. - if lasti_bytes == 0 { - return self - .iframe() - .code() - .first_line_number - .map_or(1, |n| n.get() as i32); - } - // lasti is stored as the next instruction index (see FrameObject::run), - // so the executing opcode is at lasti-1 / lasti_bytes-2. - self.f_code().addr2line(lasti_bytes - 2) + self.f_code().addr2line(self.f_lasti() as i32) } #[pygetset] @@ -536,6 +523,47 @@ impl FrameObject { } }; + let what_event = vm.what_event.get(); + if what_event < 0 { + return Err( + vm.new_value_error("f_lineno can only be set in a trace function".to_owned()) + ); + } + { + use crate::stdlib::sys::monitoring as mon; + match what_event { + mon::WHAT_PY_RESUME + | mon::WHAT_JUMP + | mon::WHAT_BRANCH + | mon::WHAT_BRANCH_LEFT + | mon::WHAT_BRANCH_RIGHT + | mon::WHAT_LINE + | mon::WHAT_PY_YIELD => {} + mon::WHAT_PY_START => { + return Err(vm.new_value_error( + "can't jump from the 'call' trace event of a new frame".to_owned(), + )); + } + mon::WHAT_CALL | mon::WHAT_C_RETURN => { + return Err(vm.new_value_error("can't jump during a call".to_owned())); + } + mon::WHAT_PY_RETURN + | mon::WHAT_PY_UNWIND + | mon::WHAT_PY_THROW + | mon::WHAT_RAISE + | mon::WHAT_C_RAISE + | mon::WHAT_INSTRUCTION + | mon::WHAT_EXCEPTION_HANDLED => { + return Err( + vm.new_value_error("can only jump from a 'line' trace event".to_owned()) + ); + } + _ => { + return Err(vm.new_system_error("unexpected event type".to_owned())); + } + } + } + let first_line = self .iframe() .code() @@ -575,7 +603,7 @@ impl FrameObject { self.lasti() as usize }; let start_idx = current_lasti.saturating_sub(1); - let start_stack = if start_idx < stacks.len() { + let mut start_stack = if start_idx < stacks.len() { stacks[start_idx] } else { OVERFLOWED @@ -611,6 +639,18 @@ impl FrameObject { return Err(vm.new_value_error(msg.to_owned())); } + // Yield leaves the yielded value on the modeled stack; the eval + // loop has already popped it for a suspended generator. + let is_suspended = live.is_null() + && current_lasti > 0 + && matches!( + FrameOwner::from_i8(self.iframe().owner.load(Relaxed)), + FrameOwner::Generator + ); + if is_suspended { + start_stack = pop_value(start_stack); + } + // Count how many entries to pop let mut pop_count = 0usize; { diff --git a/crates/vm/src/builtins/module.rs b/crates/vm/src/builtins/module.rs index 9abbf459a..4178e7073 100644 --- a/crates/vm/src/builtins/module.rs +++ b/crates/vm/src/builtins/module.rs @@ -291,6 +291,13 @@ impl PyModule { Self::new().into_ref_with_type(vm, cls).map(Into::into) } + #[pymember] + fn __dict__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult { + zelf.dict() + .map(Into::into) + .ok_or_else(|| vm.new_attribute_error("module has no __dict__")) + } + #[pymethod] fn __dir__(zelf: &Py, vm: &VirtualMachine) -> PyResult> { // First check if __dict__ attribute exists and is actually a dictionary diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 3079aaebe..10ab347ca 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1459,7 +1459,10 @@ impl PyType { /// Raw MRO walk without cache. fn find_name_in_mro_uncached(&self, name: &'static PyStrInterned) -> Option { - for cls in self.mro.read().iter() { + // Keep a strong snapshot of the MRO because tp_mro can be replaced + // during dict lookup, e.g. when comparing to non-string keys. + let mro: Vec = self.mro.read().iter().cloned().collect(); + for cls in &mro { if let Some(value) = cls.attributes.get(name) { return Some(value); } @@ -1767,8 +1770,7 @@ impl PyType { } }; - // Register this type as a subclass of the given bases - let register_subclasses = |bases: &[PyTypeRef]| { + let add_as_subclass = |bases: &[PyTypeRef]| { let weakref_type = super::PyWeak::static_type(); for base in bases { base.subclasses.write().push( @@ -1779,19 +1781,8 @@ impl PyType { } }; - let result = Self::with_type_lock(vm, || { - for base in bases.iter() { - if is_subtype_with_mro(&base.mro.read(), base, zelf) - || (!base.mro.read().is_empty() && type_is_subtype_base_chain(base, zelf, vm)) - { - return Err(vm.new_type_error("a __bases__ item causes an inheritance cycle")); - } - } - - // Remove this class from the old bases' subclass lists, pruning - // dead entries along the way. Upgraded refs are retired so the - // last strong reference is never dropped under the lock. - for base in zelf.bases.read().iter() { + let remove_as_subclass = |bases: &[PyTypeRef], retired: &mut Vec| { + for base in bases { let mut subclasses = base.subclasses.write(); let mut kept = Vec::with_capacity(subclasses.len()); for weak in subclasses.drain(..) { @@ -1809,9 +1800,19 @@ impl PyType { } *subclasses = kept; } + }; - let old_bases = core::mem::replace(&mut *zelf.bases.write(), bases); - let old_base = unsafe { zelf.base.swap(Some(new_base)) }; + let result = Self::with_type_lock(vm, || { + for base in bases.iter() { + if is_subtype_with_mro(&base.mro.read(), base, zelf) + || (!base.mro.read().is_empty() && type_is_subtype_base_chain(base, zelf, vm)) + { + return Err(vm.new_type_error("a __bases__ item causes an inheritance cycle")); + } + } + + let old_bases = core::mem::replace(&mut *zelf.bases.write(), bases.clone()); + let old_base = unsafe { zelf.base.swap(Some(new_base.clone())) }; // Recursively update the mros of this class and all subclasses, // recording the previous mros so a failure can be rolled back. @@ -1851,13 +1852,20 @@ impl PyType { retired.extend(failed_mro.into_iter().map(Into::into)); retired.push(cls.into()); } - let failed_bases = core::mem::replace(&mut *zelf.bases.write(), old_bases); - if let Some(failed_base) = unsafe { zelf.base.swap(old_base) } { - keep_alive(failed_base, &mut retired); + // Take no action if tp_bases was replaced through reentrance. + if core::ptr::eq(&**zelf.bases.read(), &*bases) { + let failed_bases = core::mem::replace(&mut *zelf.bases.write(), old_bases); + if let Some(failed_base) = unsafe { zelf.base.swap(old_base) } { + keep_alive(failed_base, &mut retired); + } + retired.push(failed_bases.into_untyped().into()); + zelf.modified_inner(); + } else { + retired.push(old_bases.into_untyped().into()); + if let Some(old_base) = old_base { + keep_alive(old_base, &mut retired); + } } - register_subclasses(&zelf.bases.read()); - retired.push(failed_bases.into_untyped().into()); - zelf.modified_inner(); return Err(err); } // Retire the replaced mros as well; dropping them here would @@ -1866,16 +1874,18 @@ impl PyType { retired.extend(old_mro.into_iter().map(Into::into)); retired.push(cls.into()); } + + // Take no action if tp_bases was replaced through reentrance. + if core::ptr::eq(&**zelf.bases.read(), &*bases) { + remove_as_subclass(&old_bases, &mut retired); + add_as_subclass(&bases); + zelf.update_all_slots(&vm.ctx); + } + retired.push(old_bases.into_untyped().into()); if let Some(old_base) = old_base { keep_alive(old_base, &mut retired); } - - // Invalidate inline caches and rebuild every slot for this type and - // all descendants so slots whose methods left the MRO are reset. - zelf.update_all_slots(&vm.ctx); - - register_subclasses(&zelf.bases.read()); crate::stdlib::_testinternalcapi::note_set_bases(); Ok(()) }); @@ -2650,6 +2660,15 @@ impl Constructor for PyType { ) .map_err(|e| vm.new_type_error(e))?; + // tp_dict keeps non-string keys so lookup can hash-compare them. + if let Some(tp_dict) = typ.attributes.as_dict() { + for (key, value) in &*dict { + if key.downcast_ref::().is_none() { + tp_dict.set_item(&*key, value, vm)?; + } + } + } + // Fill __classcell__ before a custom mro() runs so methods that // close over __class__ can execute during type creation. if let Some(cell) = typ.attributes.get(identifier!(vm, __classcell__)) { diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 93c0ec8cd..932676cd0 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -648,6 +648,16 @@ impl PyBaseException { flags(BASETYPE, HAS_DICT) )] impl PyBaseException { + #[pygetset] + fn __dict__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + crate::builtins::object::object_get_dict(zelf, vm) + } + + #[pygetset(setter)] + fn set___dict__(zelf: PyObjectRef, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { + crate::builtins::object::object_generic_set_dict(zelf, value, vm) + } + #[pygetset] pub fn args(&self) -> PyTupleRef { self.args.read().clone() diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index bf2c41f25..9c27ef112 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -2498,6 +2498,7 @@ impl Py { prev_line: &iframe.prev_line, monitoring_mask: 0, flatten: Flatten::Nothing, + call_traced: false, }; f(exec) } @@ -2583,6 +2584,7 @@ impl Py { prev_line: &iframe.prev_line, monitoring_mask: 0, flatten: Flatten::Nothing, + call_traced: false, }; exec.yield_from_target().map(PyObject::to_owned) } @@ -2659,8 +2661,8 @@ pub(crate) fn trampoline_handle_exception( ) -> FrameResult { let mut exec = exec_iframe(iframe, Flatten::Nothing, vm); - // lasti points past the CallPyExactArgs instruction (+ cache entries). - // The exception occurred at the previous instruction (the call site). + // lasti points past the call opcode and its inline caches. Do not + // decode those cache units: their bytes are not valid opcodes. let idx = (exec.lasti() as usize).saturating_sub(1); // Add traceback entry at the call site. @@ -2670,10 +2672,25 @@ pub(crate) fn trampoline_handle_exception( exception.set_traceback(Some(new_traceback.into_ref(&vm.ctx))); } + exec.fire_exception_trace(exception, vm)?; + let exception = { + let mon_events = vm.state.monitoring_events.load(); + if mon_events & monitoring::EVENT_RAISE != 0 { + let offset = idx as u32 * 2; + let exc_obj: PyObjectRef = exception.clone().into(); + match monitoring::fire_raise(vm, exec.code, offset, &exc_obj) { + Ok(()) => exception.clone(), + Err(monitor_exc) => monitor_exc, + } + } else { + exception.clone() + } + }; + exec.unwind_blocks( vm, UnwindReason::Raising { - exception: exception.clone(), + exception, offset: idx as u32, }, ) @@ -2722,6 +2739,7 @@ fn exec_iframe<'a>( prev_line: &iframe.prev_line, monitoring_mask: 0, flatten, + call_traced: false, } } @@ -2914,6 +2932,8 @@ pub(crate) struct ExecutingFrame<'a> { /// What this frame may hand back to the trampoline, if it is running /// under one at all. flatten: Flatten, + /// PY_START/PY_RESUME Call already fired for this activation. + call_traced: bool, } /// How much of what a frame does the trampoline can take over. @@ -3293,6 +3313,35 @@ impl ExecutingFrame<'_> { .is_some_and(|c| c.trace.lock().is_some()) } + /// PY_START / PY_RESUME → PyTrace_CALL. Fired from the first RESUME of + /// this activation so lasti is already the resume unit + /// (COPY_FREE_VARS / RETURN_GENERATOR that precede it are not a 'call', + /// and later RESUMEs in a SEND loop are not a new call either). + fn trace_call_from_resume(&mut self, vm: &VirtualMachine, resume_type: u32) -> PyResult<()> { + if self.call_traced { + return Ok(()); + } + self.call_traced = true; + if !vm.use_tracing.get() { + return Ok(()); + } + // RESUME oparg 0 is PY_START; nonzero is PY_RESUME. + let what = if resume_type == 0 { + monitoring::WHAT_PY_START + } else { + monitoring::WHAT_PY_RESUME + }; + let trace_result = vm.trace_event_what(crate::protocol::TraceEvent::Call, what, None)?; + if let Some(local_trace) = trace_result { + let was_unset = self.iframe().cold().trace.lock().is_none(); + *self.iframe().cold().trace.lock() = Some(local_trace); + if was_unset { + self.iframe().sync_prev_line_from_lasti(); + } + } + Ok(()) + } + /// Access the frame's trace_opcodes lock. #[inline] fn trace_opcodes_is_set(&self) -> bool { @@ -3301,6 +3350,14 @@ impl ExecutingFrame<'_> { .is_some_and(|c| *c.trace_opcodes.lock()) } + /// f_trace_lines, defaulting to true when cold data is not allocated. + #[inline] + fn trace_lines_is_set(&self) -> bool { + self.iframe() + .cold_opt() + .is_none_or(|c| *c.trace_lines.lock()) + } + /// Get pending_stack_pops from the frame. #[inline] fn pending_stack_pops(&self) -> u32 { @@ -3373,6 +3430,45 @@ impl ExecutingFrame<'_> { .expect("cell slot is not a PyCell") } + /// Apply a pending `f_lineno` jump's stack pops, if any. + fn apply_pending_lineno_jump(&mut self, vm: &VirtualMachine) { + let pops = self.pending_stack_pops(); + if pops > 0 { + let from_stack = self.pending_unwind_from_stack(); + self.unwind_stack_for_lineno(pops as usize, from_stack, vm); + self.set_pending_stack_pops(0); + } + } + + /// `_YIELD_VALUE_EVENT`: fire PY_YIELD while the value is still on the + /// stack. If the tracer assigned `f_lineno`, continue at the new lasti + /// instead of suspending. + fn yield_value_event(&mut self, vm: &VirtualMachine) -> PyResult { + let lasti_before = self.lasti(); + if vm.use_tracing.get() { + let value = self.top_value().to_owned(); + vm.trace_event_what( + crate::protocol::TraceEvent::Return, + monitoring::WHAT_PY_YIELD, + Some(value), + )?; + if self.lasti() != lasti_before { + self.apply_pending_lineno_jump(vm); + return Ok(true); + } + } + if self.monitoring_mask & monitoring::EVENT_PY_YIELD != 0 { + let value = self.top_value().to_owned(); + let offset = (self.lasti() - 1) * 2; + monitoring::fire_py_yield(vm, self.code, offset, &value)?; + if self.lasti() != lasti_before { + self.apply_pending_lineno_jump(vm); + return Ok(true); + } + } + Ok(false) + } + /// Perform deferred stack unwinding after set_f_lineno. /// /// set_f_lineno cannot pop the value stack directly because the execution @@ -3471,35 +3567,69 @@ impl ExecutingFrame<'_> { // Only fire if this frame has a per-frame trace function set // (frames entered before sys.settrace() have trace=None). // Skip RESUME – it should not generate user-visible line events. + // Skip NO_LOCATION units (addr2line == -1); the locations table + // fills those with a dummy line, which would emit a 'line' + // event whose f_lineno is None. if tracing && self.trace_is_set(vm) + && self.trace_lines_is_set() && !matches!( self.code.instructions.read_op(idx), Instruction::Resume { .. } | Instruction::InstrumentedResume ) - && let Some((loc, _)) = self.code.locations.get(idx) - && loc.line.get() as u32 != self.prev_line.get() { - self.prev_line.set(loc.line.get() as u32); - vm.trace_event(crate::protocol::TraceEvent::Line, None)?; - // The trace callback may have toggled tracing (e.g. via - // sys.settrace(None)); refresh before the opcode-trace check - // below reuses this flag. - tracing = vm.use_tracing.get(); - // Trace callback may have changed lasti via set_f_lineno. - // Re-read and restart the loop from the new position. - if lasti_cell.load(Relaxed) != (idx as u32 + 1) { - // set_f_lineno defers stack unwinding because we hold - // the state mutex. Perform it now. - let pops = self.pending_stack_pops(); - if pops > 0 { - let from_stack = self.pending_unwind_from_stack(); - self.unwind_stack_for_lineno(pops as usize, from_stack, vm); - self.set_pending_stack_pops(0); + let line = self.code.addr2line(idx as i32 * 2); + if line >= 0 && line as u32 != self.prev_line.get() { + self.prev_line.set(line as u32); + match vm.trace_event(crate::protocol::TraceEvent::Line, None) { + Ok(_) => {} + Err(exception) => { + if let Some((loc, _end_loc)) = self.code.locations.get(idx) { + let next = exception.__traceback__(); + let new_traceback = PyTraceback::new( + next, + self.frame_object(vm), + idx as u32 * 2, + loc.line, + ); + exception.set_traceback(Some(new_traceback.into_ref(&vm.ctx))); + } + match self.unwind_blocks( + vm, + UnwindReason::Raising { + exception, + offset: idx as u32, + }, + ) { + Ok(None) => { + arg_state.reset(); + idx = lasti_cell.load(Relaxed) as usize; + continue; + } + Ok(Some(value)) => break Ok(value), + Err(e) => break Err(e), + } + } + } + // The trace callback may have toggled tracing (e.g. via + // sys.settrace(None)); refresh before the opcode-trace check + // below reuses this flag. + tracing = vm.use_tracing.get(); + // Trace callback may have changed lasti via set_f_lineno. + // Re-read and restart the loop from the new position. + if lasti_cell.load(Relaxed) != (idx as u32 + 1) { + // set_f_lineno defers stack unwinding because we hold + // the state mutex. Perform it now. + let pops = self.pending_stack_pops(); + if pops > 0 { + let from_stack = self.pending_unwind_from_stack(); + self.unwind_stack_for_lineno(pops as usize, from_stack, vm); + self.set_pending_stack_pops(0); + } + arg_state.reset(); + idx = lasti_cell.load(Relaxed) as usize; + continue; } - arg_state.reset(); - idx = lasti_cell.load(Relaxed) as usize; - continue; } } // One aligned acquire load fetches opcode and arg together; two @@ -3970,6 +4100,15 @@ impl ExecutingFrame<'_> { // Fire PY_THROW and RAISE events before raising the exception. // If a monitoring callback fails, its exception replaces the original. + if vm.use_tracing.get() { + let what = monitoring::WHAT_PY_THROW; + if let Some(local_trace) = + vm.trace_event_what(crate::protocol::TraceEvent::Call, what, None)? + { + *self.iframe().cold().trace.lock() = Some(local_trace); + } + self.fire_exception_trace(&exception, vm)?; + } let exception = { let mon_events = vm.state.monitoring_events.load(); let exception = if mon_events & monitoring::EVENT_PY_THROW != 0 { @@ -4722,15 +4861,15 @@ impl ExecutingFrame<'_> { .instructions .replace_op(instr_idx, Instruction::JumpBackwardNoJit); } - self.jump_relative_backward(u32::from(arg), 1); + self.jump_relative_backward_and_trace_line(u32::from(arg), 1, vm)?; Ok(None) } Instruction::JumpBackwardJit | Instruction::JumpBackwardNoJit => { - self.jump_relative_backward(u32::from(arg), 1); + self.jump_relative_backward_and_trace_line(u32::from(arg), 1, vm)?; Ok(None) } Instruction::JumpBackwardNoInterrupt { .. } => { - self.jump_relative_backward(u32::from(arg), 0); + self.jump_relative_backward_and_trace_line(u32::from(arg), 0, vm)?; Ok(None) } Instruction::ListAppend { i } => { @@ -5395,11 +5534,20 @@ impl ExecutingFrame<'_> { .store(global_ver, atomic::Ordering::Release); // Re-execute this instruction (it may now be INSTRUMENTED_RESUME) self.update_lasti(|i| *i -= 1); + } else { + self.trace_call_from_resume(vm, u32::from(arg))?; } Ok(None) } Instruction::ReturnValue => { let value = self.pop_value(); + if vm.use_tracing.get() { + vm.trace_event_what( + crate::protocol::TraceEvent::Return, + monitoring::WHAT_PY_RETURN, + Some(value.clone()), + )?; + } self.unwind_blocks(vm, UnwindReason::Returning { value }) } Instruction::SetAdd { i } => { @@ -5643,6 +5791,11 @@ impl ExecutingFrame<'_> { .all(|sr| !sr.is_borrowed()), "borrowed refs on stack at yield point" ); + // _YIELD_VALUE_EVENT: if f_lineno jumped, DISPATCH to the + // new instruction instead of suspending. + if self.yield_value_event(vm)? { + return Ok(None); + } Ok(Some(ExecutionResult::Yield(self.pop_value()))) } Instruction::Send { .. } => { @@ -7856,10 +8009,18 @@ impl ExecutingFrame<'_> { } else if self.monitoring_mask & monitoring::EVENT_PY_RESUME != 0 { monitoring::fire_py_resume(vm, self.code, offset)?; } + self.trace_call_from_resume(vm, resume_type)?; Ok(None) } Instruction::InstrumentedReturnValue => { let value = self.pop_value(); + if vm.use_tracing.get() { + vm.trace_event_what( + crate::protocol::TraceEvent::Return, + monitoring::WHAT_PY_RETURN, + Some(value.clone()), + )?; + } if self.monitoring_mask & monitoring::EVENT_PY_RETURN != 0 { let offset = (self.lasti() - 1) * 2; monitoring::fire_py_return(vm, self.code, offset, &value)?; @@ -7876,12 +8037,10 @@ impl ExecutingFrame<'_> { .all(|sr| !sr.is_borrowed()), "borrowed refs on stack at yield point" ); - let value = self.pop_value(); - if self.monitoring_mask & monitoring::EVENT_PY_YIELD != 0 { - let offset = (self.lasti() - 1) * 2; - monitoring::fire_py_yield(vm, self.code, offset, &value)?; + if self.yield_value_event(vm)? { + return Ok(None); } - Ok(Some(ExecutionResult::Yield(value))) + Ok(Some(ExecutionResult::Yield(self.pop_value()))) } Instruction::InstrumentedCall => { let args = self.collect_positional_args(u32::from(arg)); @@ -7951,12 +8110,14 @@ impl ExecutingFrame<'_> { } Instruction::InstrumentedJumpBackward => { let src_offset = (self.lasti() - 1) * 2; + let from_idx = self.lasti().saturating_sub(1) as usize; let target_idx = self.lasti() + 1 - u32::from(arg); let target = bytecode::Label::from_u32(target_idx); self.jump(target); if self.monitoring_mask & monitoring::EVENT_JUMP != 0 { monitoring::fire_jump(vm, self.code, src_offset, target.as_u32() * 2)?; } + self.trace_backward_same_line(from_idx, vm)?; Ok(None) } Instruction::InstrumentedForIter => { @@ -9054,6 +9215,42 @@ impl ExecutingFrame<'_> { self.update_lasti(|i| *i = target); } + /// JUMP_BACKWARD plus the settrace LINE event for a backward edge that + /// stays on the same source line (`sys_trace_jump_func`). + fn jump_relative_backward_and_trace_line( + &mut self, + delta: u32, + caches: u32, + vm: &VirtualMachine, + ) -> PyResult<()> { + let from_idx = self.lasti().saturating_sub(1) as usize; + self.jump_relative_backward(delta, caches); + self.trace_backward_same_line(from_idx, vm) + } + + /// sys.settrace generates line events for all backward edges, even if on + /// the same line. + fn trace_backward_same_line(&mut self, from_idx: usize, vm: &VirtualMachine) -> PyResult<()> { + if !(vm.use_tracing.get() && self.trace_is_set(vm) && self.trace_lines_is_set()) { + return Ok(()); + } + let to_idx = self.lasti() as usize; + let from_line = self.code.addr2line(from_idx as i32 * 2); + let to_line = self.code.addr2line(to_idx as i32 * 2); + if to_line >= 0 && to_line == from_line { + self.prev_line.set(to_line as u32); + // lasti currently names the destination instruction; f_lineno + // reads lasti-1 (the in-flight opcode), so point past dest for + // the duration of the callback. + let dest = self.lasti(); + self.update_lasti(|i| *i = dest + 1); + let result = vm.trace_event(crate::protocol::TraceEvent::Line, None); + self.update_lasti(|i| *i = dest); + result?; + } + Ok(()) + } + /// Step over the `NOT_TAKEN` marker on a conditional jump's fall-through edge. /// /// The compiler plants `NOT_TAKEN` after every conditional jump purely as the diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index e8647ebe1..82c57633a 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -176,6 +176,22 @@ impl TraceEvent { pub(crate) const fn is_opcode_event(self) -> bool { matches!(self, Self::Opcode) } + + /// Default `what_event` for this legacy event. + #[must_use] + const fn default_what(self) -> i32 { + use crate::stdlib::sys::monitoring as mon; + match self { + Self::Call => mon::WHAT_PY_START, + Self::Return => mon::WHAT_PY_RETURN, + Self::Exception => mon::WHAT_RAISE, + Self::Line => mon::WHAT_LINE, + Self::Opcode => mon::WHAT_INSTRUCTION, + Self::CCall => mon::WHAT_CALL, + Self::CReturn => mon::WHAT_C_RETURN, + Self::CException => mon::WHAT_C_RAISE, + } + } } impl core::fmt::Display for TraceEvent { @@ -208,9 +224,25 @@ impl VirtualMachine { &self, event: TraceEvent, arg: Option, + ) -> PyResult> { + self.trace_event_what(event, event.default_what(), arg) + } + + /// Like [`Self::trace_event`], but records `what` as `tstate->what_event` + /// for the duration of the callback (so `f_lineno` assignment can tell + /// a 'line' event from a 'call'/'return'/'exception'). + #[inline] + pub(crate) fn trace_event_what( + &self, + event: TraceEvent, + what: i32, + arg: Option, ) -> PyResult> { if self.use_tracing.get() && !self.tracing_is_suppressed() { - self._trace_event_inner(event, arg) + let old = self.what_event.replace(what); + let result = self._trace_event_inner(event, arg); + self.what_event.set(old); + result } else { Ok(None) } @@ -244,32 +276,49 @@ impl VirtualMachine { return Ok(None); } - let frame: PyObjectRef = frame_ref.into(); - let event = self.ctx.new_str(event.to_string()).into(); - let args = vec![frame, event, arg.unwrap_or_else(|| self.ctx.none())]; + // trace_trampoline: CALL uses the global callback; every other + // event uses the per-frame f_trace (and is a no-op if that is unset). + let callback = if event == TraceEvent::Call { + if self.is_none(&trace_func) { + None + } else { + Some(trace_func) + } + } else { + frame_ref + .iframe() + .cold_opt() + .and_then(|c| c.trace.lock().clone()) + }; + + let frame: PyObjectRef = frame_ref.to_owned().into(); + let event_str: PyObjectRef = self.ctx.new_str(event.to_string()).into(); + let args = vec![frame, event_str, arg.unwrap_or_else(|| self.ctx.none())]; let mut trace_result = None; // temporarily disable tracing, during the call to the // tracing function itself. - if is_trace_event && !self.is_none(&trace_func) { + if is_trace_event && let Some(callback) = callback { self.use_tracing.set(false); self.enter_tracing(); - let res = trace_func.call(args.clone(), self); + let res = callback.call(args.clone(), self); self.leave_tracing(); self.use_tracing.set(true); match res { Ok(result) => { if !self.is_none(&result) { + *frame_ref.iframe().cold().trace.lock() = Some(result.clone()); trace_result = Some(result); } } Err(e) => { - // trace_trampoline behavior: clear per-frame f_trace - // and propagate the error. - if let Some(frame_ref) = self.current_frame() { - *frame_ref.iframe().cold().trace.lock() = None; - } + // trace_trampoline: disable the global tracer and clear + // this frame's f_trace, then propagate. + *self.trace_func.borrow_mut() = self.ctx.none(); + *frame_ref.iframe().cold().trace.lock() = None; + let profile_is_none = self.is_none(&self.profile_func.borrow()); + self.use_tracing.set(!profile_is_none); return Err(e); } } diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index c12d11a68..bfdc22588 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -1276,6 +1276,12 @@ pub mod sys { fn settrace(function: PyObjectRef, vm: &VirtualMachine) { vm.trace_func.replace(function); update_use_tracing(vm); + // The rest of the current line already started before tracing was + // enabled; sync prev_line so leftover opcodes on this line do not + // emit a spurious 'line' event. + if let Some(frame) = vm.current_frame() { + frame.iframe().sync_prev_line_from_lasti(); + } } #[pyfunction] diff --git a/crates/vm/src/stdlib/sys/monitoring.rs b/crates/vm/src/stdlib/sys/monitoring.rs index fa52be226..43c797e22 100644 --- a/crates/vm/src/stdlib/sys/monitoring.rs +++ b/crates/vm/src/stdlib/sys/monitoring.rs @@ -58,6 +58,29 @@ pub(crate) const EVENT_STOP_ITERATION: u32 = MonitoringEvents::STOP_ITERATION.bi pub(crate) const EVENT_PY_THROW: u32 = MonitoringEvents::PY_THROW.bits(); const EVENT_BRANCH: u32 = MonitoringEvents::BRANCH.bits(); pub(crate) const EVENT_RERAISE: u32 = MonitoringEvents::RERAISE.bits(); + +/// `tstate->what_event` values, the event currently being monitored. +pub(crate) const WHAT_PY_START: i32 = 0; +pub(crate) const WHAT_PY_RESUME: i32 = 1; +pub(crate) const WHAT_PY_RETURN: i32 = 2; +pub(crate) const WHAT_PY_YIELD: i32 = 3; +pub(crate) const WHAT_CALL: i32 = 4; +pub(crate) const WHAT_LINE: i32 = 5; +pub(crate) const WHAT_INSTRUCTION: i32 = 6; +pub(crate) const WHAT_JUMP: i32 = 7; +pub(crate) const WHAT_BRANCH_LEFT: i32 = 8; +pub(crate) const WHAT_BRANCH_RIGHT: i32 = 9; +#[allow(dead_code)] +pub(crate) const WHAT_STOP_ITERATION: i32 = 10; +pub(crate) const WHAT_RAISE: i32 = 11; +pub(crate) const WHAT_EXCEPTION_HANDLED: i32 = 12; +pub(crate) const WHAT_PY_UNWIND: i32 = 13; +pub(crate) const WHAT_PY_THROW: i32 = 14; +#[allow(dead_code)] +pub(crate) const WHAT_RERAISE: i32 = 15; +pub(crate) const WHAT_C_RETURN: i32 = 16; +pub(crate) const WHAT_C_RAISE: i32 = 17; +pub(crate) const WHAT_BRANCH: i32 = 18; const EVENT_C_RETURN_MASK: u32 = EVENT_C_RETURN | EVENT_C_RAISE; const EVENT_NAMES: [&str; EVENTS_COUNT] = [ @@ -883,6 +906,7 @@ fn fire( FIRING.with(|f| f.set(true)); vm.enter_tracing(); + let old_what = vm.what_event.replace(event_id as i32); let result = (|| { for (tool, cb) in callbacks { let result = cb.call(args.clone(), vm)?; @@ -905,6 +929,7 @@ fn fire( } Ok(()) })(); + vm.what_event.set(old_what); vm.leave_tracing(); FIRING.with(|f| f.set(false)); result diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 8a228c547..a017415b9 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -95,6 +95,8 @@ pub struct VirtualMachine { pub profile_func: RefCell, pub trace_func: RefCell, pub use_tracing: Cell, + /// Event currently being monitored, or -1 when not in a callback. + pub(crate) what_event: Cell, tracing_depth: Cell, pub recursion_limit: Cell, pub(crate) signal_handlers: OnceCell, @@ -1200,6 +1202,7 @@ impl VirtualMachine { profile_func, trace_func, use_tracing: Cell::new(false), + what_event: Cell::new(-1), tracing_depth: Cell::new(0), recursion_limit: Cell::new(if cfg!(debug_assertions) { 256 } else { 1000 }), signal_handlers, @@ -2919,35 +2922,28 @@ impl VirtualMachine { ) -> PyResult { use crate::protocol::TraceEvent; - // Fire 'call' trace event. current_frame() now returns the callee. - let trace_result = self.trace_event(TraceEvent::Call, None)?; - if let Some(local_trace) = trace_result { - let was_unset = frame.iframe().cold().trace.lock().is_none(); - *frame.iframe().cold().trace.lock() = Some(local_trace); - if was_unset { - // For a fresh frame this is a no-op (lasti is still 0 here, - // before the frame body below has run). For a generator - // resumed mid-body, `lasti` already reflects the suspended - // position, so `prev_line` -- stale from before tracing was - // installed -- must be synced again or the very next - // instruction fires a spurious 'line' event. - frame.iframe().sync_prev_line_from_lasti(); - } - } + // 'call' is PY_START / PY_RESUME, fired from RESUME once lasti is + // the resume unit. Wrapping the body would report lasti=0 (and + // trace RETURN_GENERATOR on async-def construction). let result = f(frame); - // Fire 'return' event if frame is being traced or profiled. - // PY_UNWIND fires PyTrace_RETURN with arg=None — so we fire for - // both Ok and Err, matching `call_trace_protected` behavior. - if self.use_tracing.get() + // PY_RETURN / PY_YIELD are fired from RETURN_VALUE / YIELD_VALUE. + // PY_UNWIND fires PyTrace_RETURN with arg=None when the exception + // leaves this frame. + if result.is_err() + && self.use_tracing.get() && (!self.is_none(&self.profile_func.borrow()) || frame .iframe() .cold_opt() .is_some_and(|c| c.trace.lock().is_some())) { - let ret_result = self.trace_event(TraceEvent::Return, None); + let ret_result = self.trace_event_what( + TraceEvent::Return, + crate::stdlib::sys::monitoring::WHAT_PY_UNWIND, + None, + ); // call_trace_protected: if trace function raises, its error // replaces the original exception. ret_result?; diff --git a/crates/vm/src/vm/thread.rs b/crates/vm/src/vm/thread.rs index 4b9426ef0..3d1046184 100644 --- a/crates/vm/src/vm/thread.rs +++ b/crates/vm/src/vm/thread.rs @@ -1399,6 +1399,7 @@ impl VirtualMachine { profile_func: RefCell::new(global_profile.unwrap_or_else(|| self.ctx.none())), trace_func: RefCell::new(global_trace.unwrap_or_else(|| self.ctx.none())), use_tracing: Cell::new(use_tracing), + what_event: Cell::new(-1), tracing_depth: Cell::new(0), recursion_limit: self.recursion_limit.clone(), signal_handlers: core::cell::OnceCell::new(),