1# Testing the line trace facility. 2 3from test import support 4import unittest 5import sys 6import difflib 7import gc 8 9# A very basic example. If this fails, we're in deep trouble. 10def basic(): 11 return 1 12 13basic.events = [(0, 'call'), 14 (1, 'line'), 15 (1, 'return')] 16 17# Many of the tests below are tricky because they involve pass statements. 18# If there is implicit control flow around a pass statement (in an except 19# clause or else caluse) under what conditions do you set a line number 20# following that clause? 21 22 23# The entire "while 0:" statement is optimized away. No code 24# exists for it, so the line numbers skip directly from "del x" 25# to "x = 1". 26def arigo_example(): 27 x = 1 28 del x 29 while 0: 30 pass 31 x = 1 32 33arigo_example.events = [(0, 'call'), 34 (1, 'line'), 35 (2, 'line'), 36 (5, 'line'), 37 (5, 'return')] 38 39# check that lines consisting of just one instruction get traced: 40def one_instr_line(): 41 x = 1 42 del x 43 x = 1 44 45one_instr_line.events = [(0, 'call'), 46 (1, 'line'), 47 (2, 'line'), 48 (3, 'line'), 49 (3, 'return')] 50 51def no_pop_tops(): # 0 52 x = 1 # 1 53 for a in range(2): # 2 54 if a: # 3 55 x = 1 # 4 56 else: # 5 57 x = 1 # 6 58 59no_pop_tops.events = [(0, 'call'), 60 (1, 'line'), 61 (2, 'line'), 62 (3, 'line'), 63 (6, 'line'), 64 (2, 'line'), 65 (3, 'line'), 66 (4, 'line'), 67 (2, 'line'), 68 (2, 'return')] 69 70def no_pop_blocks(): 71 y = 1 72 while not y: 73 bla 74 x = 1 75 76no_pop_blocks.events = [(0, 'call'), 77 (1, 'line'), 78 (2, 'line'), 79 (4, 'line'), 80 (4, 'return')] 81 82def called(): # line -3 83 x = 1 84 85def call(): # line 0 86 called() 87 88call.events = [(0, 'call'), 89 (1, 'line'), 90 (-3, 'call'), 91 (-2, 'line'), 92 (-2, 'return'), 93 (1, 'return')] 94 95def raises(): 96 raise Exception 97 98def test_raise(): 99 try: 100 raises() 101 except Exception as exc: 102 x = 1 103 104test_raise.events = [(0, 'call'), 105 (1, 'line'), 106 (2, 'line'), 107 (-3, 'call'), 108 (-2, 'line'), 109 (-2, 'exception'), 110 (-2, 'return'), 111 (2, 'exception'), 112 (3, 'line'), 113 (4, 'line'), 114 (4, 'return')] 115 116def _settrace_and_return(tracefunc): 117 sys.settrace(tracefunc) 118 sys._getframe().f_back.f_trace = tracefunc 119def settrace_and_return(tracefunc): 120 _settrace_and_return(tracefunc) 121 122settrace_and_return.events = [(1, 'return')] 123 124def _settrace_and_raise(tracefunc): 125 sys.settrace(tracefunc) 126 sys._getframe().f_back.f_trace = tracefunc 127 raise RuntimeError 128def settrace_and_raise(tracefunc): 129 try: 130 _settrace_and_raise(tracefunc) 131 except RuntimeError as exc: 132 pass 133 134settrace_and_raise.events = [(2, 'exception'), 135 (3, 'line'), 136 (4, 'line'), 137 (4, 'return')] 138 139# implicit return example 140# This test is interesting because of the else: pass 141# part of the code. The code generate for the true 142# part of the if contains a jump past the else branch. 143# The compiler then generates an implicit "return None" 144# Internally, the compiler visits the pass statement 145# and stores its line number for use on the next instruction. 146# The next instruction is the implicit return None. 147def ireturn_example(): 148 a = 5 149 b = 5 150 if a == b: 151 b = a+1 152 else: 153 pass 154 155ireturn_example.events = [(0, 'call'), 156 (1, 'line'), 157 (2, 'line'), 158 (3, 'line'), 159 (4, 'line'), 160 (6, 'line'), 161 (6, 'return')] 162 163# Tight loop with while(1) example (SF #765624) 164def tightloop_example(): 165 items = range(0, 3) 166 try: 167 i = 0 168 while 1: 169 b = items[i]; i+=1 170 except IndexError: 171 pass 172 173tightloop_example.events = [(0, 'call'), 174 (1, 'line'), 175 (2, 'line'), 176 (3, 'line'), 177 (4, 'line'), 178 (5, 'line'), 179 (5, 'line'), 180 (5, 'line'), 181 (5, 'line'), 182 (5, 'exception'), 183 (6, 'line'), 184 (7, 'line'), 185 (7, 'return')] 186 187def tighterloop_example(): 188 items = range(1, 4) 189 try: 190 i = 0 191 while 1: i = items[i] 192 except IndexError: 193 pass 194 195tighterloop_example.events = [(0, 'call'), 196 (1, 'line'), 197 (2, 'line'), 198 (3, 'line'), 199 (4, 'line'), 200 (4, 'line'), 201 (4, 'line'), 202 (4, 'line'), 203 (4, 'exception'), 204 (5, 'line'), 205 (6, 'line'), 206 (6, 'return')] 207 208def generator_function(): 209 try: 210 yield True 211 "continued" 212 finally: 213 "finally" 214def generator_example(): 215 # any() will leave the generator before its end 216 x = any(generator_function()) 217 218 # the following lines were not traced 219 for x in range(10): 220 y = x 221 222generator_example.events = ([(0, 'call'), 223 (2, 'line'), 224 (-6, 'call'), 225 (-5, 'line'), 226 (-4, 'line'), 227 (-4, 'return'), 228 (-4, 'call'), 229 (-4, 'exception'), 230 (-1, 'line'), 231 (-1, 'return')] + 232 [(5, 'line'), (6, 'line')] * 10 + 233 [(5, 'line'), (5, 'return')]) 234 235 236class Tracer: 237 def __init__(self): 238 self.events = [] 239 def trace(self, frame, event, arg): 240 self.events.append((frame.f_lineno, event)) 241 return self.trace 242 def traceWithGenexp(self, frame, event, arg): 243 (o for o in [1]) 244 self.events.append((frame.f_lineno, event)) 245 return self.trace 246 247class TraceTestCase(unittest.TestCase): 248 249 # Disable gc collection when tracing, otherwise the 250 # deallocators may be traced as well. 251 def setUp(self): 252 self.using_gc = gc.isenabled() 253 gc.disable() 254 self.addCleanup(sys.settrace, sys.gettrace()) 255 256 def tearDown(self): 257 if self.using_gc: 258 gc.enable() 259 260 def compare_events(self, line_offset, events, expected_events): 261 events = [(l - line_offset, e) for (l, e) in events] 262 if events != expected_events: 263 self.fail( 264 "events did not match expectation:\n" + 265 "\n".join(difflib.ndiff([str(x) for x in expected_events], 266 [str(x) for x in events]))) 267 268 def run_and_compare(self, func, events): 269 tracer = Tracer() 270 sys.settrace(tracer.trace) 271 func() 272 sys.settrace(None) 273 self.compare_events(func.__code__.co_firstlineno, 274 tracer.events, events) 275 276 def run_test(self, func): 277 self.run_and_compare(func, func.events) 278 279 def run_test2(self, func): 280 tracer = Tracer() 281 func(tracer.trace) 282 sys.settrace(None) 283 self.compare_events(func.__code__.co_firstlineno, 284 tracer.events, func.events) 285 286 def test_set_and_retrieve_none(self): 287 sys.settrace(None) 288 assert sys.gettrace() is None 289 290 def test_set_and_retrieve_func(self): 291 def fn(*args): 292 pass 293 294 sys.settrace(fn) 295 try: 296 assert sys.gettrace() is fn 297 finally: 298 sys.settrace(None) 299 300 def test_01_basic(self): 301 self.run_test(basic) 302 def test_02_arigo(self): 303 self.run_test(arigo_example) 304 def test_03_one_instr(self): 305 self.run_test(one_instr_line) 306 def test_04_no_pop_blocks(self): 307 self.run_test(no_pop_blocks) 308 def test_05_no_pop_tops(self): 309 self.run_test(no_pop_tops) 310 def test_06_call(self): 311 self.run_test(call) 312 def test_07_raise(self): 313 self.run_test(test_raise) 314 315 def test_08_settrace_and_return(self): 316 self.run_test2(settrace_and_return) 317 def test_09_settrace_and_raise(self): 318 self.run_test2(settrace_and_raise) 319 def test_10_ireturn(self): 320 self.run_test(ireturn_example) 321 def test_11_tightloop(self): 322 self.run_test(tightloop_example) 323 def test_12_tighterloop(self): 324 self.run_test(tighterloop_example) 325 326 def test_13_genexp(self): 327 self.run_test(generator_example) 328 # issue1265: if the trace function contains a generator, 329 # and if the traced function contains another generator 330 # that is not completely exhausted, the trace stopped. 331 # Worse: the 'finally' clause was not invoked. 332 tracer = Tracer() 333 sys.settrace(tracer.traceWithGenexp) 334 generator_example() 335 sys.settrace(None) 336 self.compare_events(generator_example.__code__.co_firstlineno, 337 tracer.events, generator_example.events) 338 339 def test_14_onliner_if(self): 340 def onliners(): 341 if True: x=False 342 else: x=True 343 return 0 344 self.run_and_compare( 345 onliners, 346 [(0, 'call'), 347 (1, 'line'), 348 (3, 'line'), 349 (3, 'return')]) 350 351 def test_15_loops(self): 352 # issue1750076: "while" expression is skipped by debugger 353 def for_example(): 354 for x in range(2): 355 pass 356 self.run_and_compare( 357 for_example, 358 [(0, 'call'), 359 (1, 'line'), 360 (2, 'line'), 361 (1, 'line'), 362 (2, 'line'), 363 (1, 'line'), 364 (1, 'return')]) 365 366 def while_example(): 367 # While expression should be traced on every loop 368 x = 2 369 while x > 0: 370 x -= 1 371 self.run_and_compare( 372 while_example, 373 [(0, 'call'), 374 (2, 'line'), 375 (3, 'line'), 376 (4, 'line'), 377 (3, 'line'), 378 (4, 'line'), 379 (3, 'line'), 380 (3, 'return')]) 381 382 def test_16_blank_lines(self): 383 namespace = {} 384 exec("def f():\n" + "\n" * 256 + " pass", namespace) 385 self.run_and_compare( 386 namespace["f"], 387 [(0, 'call'), 388 (257, 'line'), 389 (257, 'return')]) 390 391 def test_17_none_f_trace(self): 392 # Issue 20041: fix TypeError when f_trace is set to None. 393 def func(): 394 sys._getframe().f_trace = None 395 lineno = 2 396 self.run_and_compare(func, 397 [(0, 'call'), 398 (1, 'line')]) 399 400 401class RaisingTraceFuncTestCase(unittest.TestCase): 402 def setUp(self): 403 self.addCleanup(sys.settrace, sys.gettrace()) 404 405 def trace(self, frame, event, arg): 406 """A trace function that raises an exception in response to a 407 specific trace event.""" 408 if event == self.raiseOnEvent: 409 raise ValueError # just something that isn't RuntimeError 410 else: 411 return self.trace 412 413 def f(self): 414 """The function to trace; raises an exception if that's the case 415 we're testing, so that the 'exception' trace event fires.""" 416 if self.raiseOnEvent == 'exception': 417 x = 0 418 y = 1/x 419 else: 420 return 1 421 422 def run_test_for_event(self, event): 423 """Tests that an exception raised in response to the given event is 424 handled OK.""" 425 self.raiseOnEvent = event 426 try: 427 for i in range(sys.getrecursionlimit() + 1): 428 sys.settrace(self.trace) 429 try: 430 self.f() 431 except ValueError: 432 pass 433 else: 434 self.fail("exception not raised!") 435 except RuntimeError: 436 self.fail("recursion counter not reset") 437 438 # Test the handling of exceptions raised by each kind of trace event. 439 def test_call(self): 440 self.run_test_for_event('call') 441 def test_line(self): 442 self.run_test_for_event('line') 443 def test_return(self): 444 self.run_test_for_event('return') 445 def test_exception(self): 446 self.run_test_for_event('exception') 447 448 def test_trash_stack(self): 449 def f(): 450 for i in range(5): 451 print(i) # line tracing will raise an exception at this line 452 453 def g(frame, why, extra): 454 if (why == 'line' and 455 frame.f_lineno == f.__code__.co_firstlineno + 2): 456 raise RuntimeError("i am crashing") 457 return g 458 459 sys.settrace(g) 460 try: 461 f() 462 except RuntimeError: 463 # the test is really that this doesn't segfault: 464 import gc 465 gc.collect() 466 else: 467 self.fail("exception not propagated") 468 469 470 def test_exception_arguments(self): 471 def f(): 472 x = 0 473 # this should raise an error 474 x.no_such_attr 475 def g(frame, event, arg): 476 if (event == 'exception'): 477 type, exception, trace = arg 478 self.assertIsInstance(exception, Exception) 479 return g 480 481 existing = sys.gettrace() 482 try: 483 sys.settrace(g) 484 try: 485 f() 486 except AttributeError: 487 # this is expected 488 pass 489 finally: 490 sys.settrace(existing) 491 492 493# 'Jump' tests: assigning to frame.f_lineno within a trace function 494# moves the execution position - it's how debuggers implement a Jump 495# command (aka. "Set next statement"). 496 497class JumpTracer: 498 """Defines a trace function that jumps from one place to another, 499 with the source and destination lines of the jump being defined by 500 the 'jump' property of the function under test.""" 501 502 def __init__(self, function): 503 self.function = function 504 self.jumpFrom = function.jump[0] 505 self.jumpTo = function.jump[1] 506 self.done = False 507 508 def trace(self, frame, event, arg): 509 if not self.done and frame.f_code == self.function.__code__: 510 firstLine = frame.f_code.co_firstlineno 511 if event == 'line' and frame.f_lineno == firstLine + self.jumpFrom: 512 # Cope with non-integer self.jumpTo (because of 513 # no_jump_to_non_integers below). 514 try: 515 frame.f_lineno = firstLine + self.jumpTo 516 except TypeError: 517 frame.f_lineno = self.jumpTo 518 self.done = True 519 return self.trace 520 521# The first set of 'jump' tests are for things that are allowed: 522 523def jump_simple_forwards(output): 524 output.append(1) 525 output.append(2) 526 output.append(3) 527 528jump_simple_forwards.jump = (1, 3) 529jump_simple_forwards.output = [3] 530 531def jump_simple_backwards(output): 532 output.append(1) 533 output.append(2) 534 535jump_simple_backwards.jump = (2, 1) 536jump_simple_backwards.output = [1, 1, 2] 537 538def jump_out_of_block_forwards(output): 539 for i in 1, 2: 540 output.append(2) 541 for j in [3]: # Also tests jumping over a block 542 output.append(4) 543 output.append(5) 544 545jump_out_of_block_forwards.jump = (3, 5) 546jump_out_of_block_forwards.output = [2, 5] 547 548def jump_out_of_block_backwards(output): 549 output.append(1) 550 for i in [1]: 551 output.append(3) 552 for j in [2]: # Also tests jumping over a block 553 output.append(5) 554 output.append(6) 555 output.append(7) 556 557jump_out_of_block_backwards.jump = (6, 1) 558jump_out_of_block_backwards.output = [1, 3, 5, 1, 3, 5, 6, 7] 559 560def jump_to_codeless_line(output): 561 output.append(1) 562 # Jumping to this line should skip to the next one. 563 output.append(3) 564 565jump_to_codeless_line.jump = (1, 2) 566jump_to_codeless_line.output = [3] 567 568def jump_to_same_line(output): 569 output.append(1) 570 output.append(2) 571 output.append(3) 572 573jump_to_same_line.jump = (2, 2) 574jump_to_same_line.output = [1, 2, 3] 575 576# Tests jumping within a finally block, and over one. 577def jump_in_nested_finally(output): 578 try: 579 output.append(2) 580 finally: 581 output.append(4) 582 try: 583 output.append(6) 584 finally: 585 output.append(8) 586 output.append(9) 587 588jump_in_nested_finally.jump = (4, 9) 589jump_in_nested_finally.output = [2, 9] 590 591def jump_infinite_while_loop(output): 592 output.append(1) 593 while 1: 594 output.append(2) 595 output.append(3) 596 597jump_infinite_while_loop.jump = (3, 4) 598jump_infinite_while_loop.output = [1, 3] 599 600# The second set of 'jump' tests are for things that are not allowed: 601 602def no_jump_too_far_forwards(output): 603 try: 604 output.append(2) 605 output.append(3) 606 except ValueError as e: 607 output.append('after' in str(e)) 608 609no_jump_too_far_forwards.jump = (3, 6) 610no_jump_too_far_forwards.output = [2, True] 611 612def no_jump_too_far_backwards(output): 613 try: 614 output.append(2) 615 output.append(3) 616 except ValueError as e: 617 output.append('before' in str(e)) 618 619no_jump_too_far_backwards.jump = (3, -1) 620no_jump_too_far_backwards.output = [2, True] 621 622# Test each kind of 'except' line. 623def no_jump_to_except_1(output): 624 try: 625 output.append(2) 626 except: 627 e = sys.exc_info()[1] 628 output.append('except' in str(e)) 629 630no_jump_to_except_1.jump = (2, 3) 631no_jump_to_except_1.output = [True] 632 633def no_jump_to_except_2(output): 634 try: 635 output.append(2) 636 except ValueError: 637 e = sys.exc_info()[1] 638 output.append('except' in str(e)) 639 640no_jump_to_except_2.jump = (2, 3) 641no_jump_to_except_2.output = [True] 642 643def no_jump_to_except_3(output): 644 try: 645 output.append(2) 646 except ValueError as e: 647 output.append('except' in str(e)) 648 649no_jump_to_except_3.jump = (2, 3) 650no_jump_to_except_3.output = [True] 651 652def no_jump_to_except_4(output): 653 try: 654 output.append(2) 655 except (ValueError, RuntimeError) as e: 656 output.append('except' in str(e)) 657 658no_jump_to_except_4.jump = (2, 3) 659no_jump_to_except_4.output = [True] 660 661def no_jump_forwards_into_block(output): 662 try: 663 output.append(2) 664 for i in 1, 2: 665 output.append(4) 666 except ValueError as e: 667 output.append('into' in str(e)) 668 669no_jump_forwards_into_block.jump = (2, 4) 670no_jump_forwards_into_block.output = [True] 671 672def no_jump_backwards_into_block(output): 673 try: 674 for i in 1, 2: 675 output.append(3) 676 output.append(4) 677 except ValueError as e: 678 output.append('into' in str(e)) 679 680no_jump_backwards_into_block.jump = (4, 3) 681no_jump_backwards_into_block.output = [3, 3, True] 682 683def no_jump_into_finally_block(output): 684 try: 685 try: 686 output.append(3) 687 x = 1 688 finally: 689 output.append(6) 690 except ValueError as e: 691 output.append('finally' in str(e)) 692 693no_jump_into_finally_block.jump = (4, 6) 694no_jump_into_finally_block.output = [3, 6, True] # The 'finally' still runs 695 696def no_jump_out_of_finally_block(output): 697 try: 698 try: 699 output.append(3) 700 finally: 701 output.append(5) 702 output.append(6) 703 except ValueError as e: 704 output.append('finally' in str(e)) 705 706no_jump_out_of_finally_block.jump = (5, 1) 707no_jump_out_of_finally_block.output = [3, True] 708 709# This verifies the line-numbers-must-be-integers rule. 710def no_jump_to_non_integers(output): 711 try: 712 output.append(2) 713 except ValueError as e: 714 output.append('integer' in str(e)) 715 716no_jump_to_non_integers.jump = (2, "Spam") 717no_jump_to_non_integers.output = [True] 718 719def jump_across_with(output): 720 with open(support.TESTFN, "wb") as fp: 721 pass 722 with open(support.TESTFN, "wb") as fp: 723 pass 724jump_across_with.jump = (1, 3) 725jump_across_with.output = [] 726 727# This verifies that you can't set f_lineno via _getframe or similar 728# trickery. 729def no_jump_without_trace_function(): 730 try: 731 previous_frame = sys._getframe().f_back 732 previous_frame.f_lineno = previous_frame.f_lineno 733 except ValueError as e: 734 # This is the exception we wanted; make sure the error message 735 # talks about trace functions. 736 if 'trace' not in str(e): 737 raise 738 else: 739 # Something's wrong - the expected exception wasn't raised. 740 raise RuntimeError("Trace-function-less jump failed to fail") 741 742 743class JumpTestCase(unittest.TestCase): 744 def setUp(self): 745 self.addCleanup(sys.settrace, sys.gettrace()) 746 sys.settrace(None) 747 748 def compare_jump_output(self, expected, received): 749 if received != expected: 750 self.fail( "Outputs don't match:\n" + 751 "Expected: " + repr(expected) + "\n" + 752 "Received: " + repr(received)) 753 754 def run_test(self, func): 755 tracer = JumpTracer(func) 756 sys.settrace(tracer.trace) 757 output = [] 758 func(output) 759 sys.settrace(None) 760 self.compare_jump_output(func.output, output) 761 762 def test_01_jump_simple_forwards(self): 763 self.run_test(jump_simple_forwards) 764 def test_02_jump_simple_backwards(self): 765 self.run_test(jump_simple_backwards) 766 def test_03_jump_out_of_block_forwards(self): 767 self.run_test(jump_out_of_block_forwards) 768 def test_04_jump_out_of_block_backwards(self): 769 self.run_test(jump_out_of_block_backwards) 770 def test_05_jump_to_codeless_line(self): 771 self.run_test(jump_to_codeless_line) 772 def test_06_jump_to_same_line(self): 773 self.run_test(jump_to_same_line) 774 def test_07_jump_in_nested_finally(self): 775 self.run_test(jump_in_nested_finally) 776 def test_jump_infinite_while_loop(self): 777 self.run_test(jump_infinite_while_loop) 778 def test_08_no_jump_too_far_forwards(self): 779 self.run_test(no_jump_too_far_forwards) 780 def test_09_no_jump_too_far_backwards(self): 781 self.run_test(no_jump_too_far_backwards) 782 def test_10_no_jump_to_except_1(self): 783 self.run_test(no_jump_to_except_1) 784 def test_11_no_jump_to_except_2(self): 785 self.run_test(no_jump_to_except_2) 786 def test_12_no_jump_to_except_3(self): 787 self.run_test(no_jump_to_except_3) 788 def test_13_no_jump_to_except_4(self): 789 self.run_test(no_jump_to_except_4) 790 def test_14_no_jump_forwards_into_block(self): 791 self.run_test(no_jump_forwards_into_block) 792 def test_15_no_jump_backwards_into_block(self): 793 self.run_test(no_jump_backwards_into_block) 794 def test_16_no_jump_into_finally_block(self): 795 self.run_test(no_jump_into_finally_block) 796 def test_17_no_jump_out_of_finally_block(self): 797 self.run_test(no_jump_out_of_finally_block) 798 def test_18_no_jump_to_non_integers(self): 799 self.run_test(no_jump_to_non_integers) 800 def test_19_no_jump_without_trace_function(self): 801 # Must set sys.settrace(None) in setUp(), else condition is not 802 # triggered. 803 no_jump_without_trace_function() 804 def test_jump_across_with(self): 805 self.addCleanup(support.unlink, support.TESTFN) 806 self.run_test(jump_across_with) 807 808 def test_20_large_function(self): 809 d = {} 810 exec("""def f(output): # line 0 811 x = 0 # line 1 812 y = 1 # line 2 813 ''' # line 3 814 %s # lines 4-1004 815 ''' # line 1005 816 x += 1 # line 1006 817 output.append(x) # line 1007 818 return""" % ('\n' * 1000,), d) 819 f = d['f'] 820 821 f.jump = (2, 1007) 822 f.output = [0] 823 self.run_test(f) 824 825 def test_jump_to_firstlineno(self): 826 # This tests that PDB can jump back to the first line in a 827 # file. See issue #1689458. It can only be triggered in a 828 # function call if the function is defined on a single line. 829 code = compile(""" 830# Comments don't count. 831output.append(2) # firstlineno is here. 832output.append(3) 833output.append(4) 834""", "<fake module>", "exec") 835 class fake_function: 836 __code__ = code 837 jump = (2, 0) 838 tracer = JumpTracer(fake_function) 839 sys.settrace(tracer.trace) 840 namespace = {"output": []} 841 exec(code, namespace) 842 sys.settrace(None) 843 self.compare_jump_output([2, 3, 2, 3, 4], namespace["output"]) 844 845 846def test_main(): 847 support.run_unittest( 848 TraceTestCase, 849 RaisingTraceFuncTestCase, 850 JumpTestCase 851 ) 852 853if __name__ == "__main__": 854 test_main() 855