Coverage for run.py: 58%

406 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-11 13:22 -0700

1""" idlelib.run 

2 

3Simplified, pyshell.ModifiedInterpreter spawns a subprocess with 

4f'''{sys.executable} -c "__import__('idlelib.run').run.main()"''' 

5'.run' is needed because __import__ returns idlelib, not idlelib.run. 

6""" 

7import contextlib 

8import functools 

9import io 

10import linecache 

11import queue 

12import sys 

13import textwrap 

14import time 

15import traceback 

16import _thread as thread 

17import threading 

18import warnings 

19 

20import idlelib # testing 

21from idlelib import autocomplete # AutoComplete, fetch_encodings 

22from idlelib import calltip # Calltip 

23from idlelib import debugger_r # start_debugger 

24from idlelib import debugobj_r # remote_object_tree_item 

25from idlelib import iomenu # encoding 

26from idlelib import rpc # multiple objects 

27from idlelib import stackviewer # StackTreeItem 

28import __main__ 

29 

30import tkinter # Use tcl and, if startup fails, messagebox. 

31if not hasattr(sys.modules['idlelib.run'], 'firstrun'): 31 ↛ 41line 31 didn't jump to line 41, because the condition on line 31 was never false

32 # Undo modifications of tkinter by idlelib imports; see bpo-25507. 

33 for mod in ('simpledialog', 'messagebox', 'font', 

34 'dialog', 'filedialog', 'commondialog', 

35 'ttk'): 

36 delattr(tkinter, mod) 

37 del sys.modules['tkinter.' + mod] 

38 # Avoid AttributeError if run again; see bpo-37038. 

39 sys.modules['idlelib.run'].firstrun = False 

40 

41LOCALHOST = '127.0.0.1' 

42 

43try: 

44 eof = 'Ctrl-D (end-of-file)' 

45 exit.eof = eof 

46 quit.eof = eof 

47except NameError: # In case subprocess started with -S (maybe in future). 

48 pass 

49 

50 

51def idle_formatwarning(message, category, filename, lineno, line=None): 

52 """Format warnings the IDLE way.""" 

53 

54 s = "\nWarning (from warnings module):\n" 1mtu

55 s += ' File \"%s\", line %s\n' % (filename, lineno) 1mtu

56 if line is None: 56 ↛ 57line 56 didn't jump to line 57, because the condition on line 56 was never true1mtu

57 line = linecache.getline(filename, lineno) 

58 line = line.strip() 1mtu

59 if line: 59 ↛ 61line 59 didn't jump to line 61, because the condition on line 59 was never false1mtu

60 s += " %s\n" % line 1mtu

61 s += "%s: %s\n" % (category.__name__, message) 1mtu

62 return s 1mtu

63 

64def idle_showwarning_subproc( 

65 message, category, filename, lineno, file=None, line=None): 

66 """Show Idle-format warning after replacing warnings.showwarning. 

67 

68 The only difference is the formatter called. 

69 """ 

70 if file is None: 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true1m

71 file = sys.stderr 

72 try: 1m

73 file.write(idle_formatwarning( 1m

74 message, category, filename, lineno, line)) 

75 except OSError: 

76 pass # the file (probably stderr) is invalid - this warning gets lost. 

77 

78_warnings_showwarning = None 

79 

80def capture_warnings(capture): 

81 "Replace warning.showwarning with idle_showwarning_subproc, or reverse." 

82 

83 global _warnings_showwarning 

84 if capture: 1av

85 if _warnings_showwarning is None: 85 ↛ exitline 85 didn't return from function 'capture_warnings', because the condition on line 85 was never false1av

86 _warnings_showwarning = warnings.showwarning 1av

87 warnings.showwarning = idle_showwarning_subproc 1av

88 else: 

89 if _warnings_showwarning is not None: 89 ↛ exitline 89 didn't return from function 'capture_warnings', because the condition on line 89 was never false1av

90 warnings.showwarning = _warnings_showwarning 1av

91 _warnings_showwarning = None 1av

92 

93capture_warnings(True) 

94tcl = tkinter.Tcl() 

95 

96def handle_tk_events(tcl=tcl): 

97 """Process any tk events that are ready to be dispatched if tkinter 

98 has been imported, a tcl interpreter has been created and tk has been 

99 loaded.""" 

100 tcl.eval("update") 

101 

102# Thread shared globals: Establish a queue between a subthread (which handles 

103# the socket) and the main thread (which runs user code), plus global 

104# completion, exit and interruptable (the main thread) flags: 

105 

106exit_now = False 

107quitting = False 

108interruptable = False 

109 

110def main(del_exitfunc=False): 

111 """Start the Python execution server in a subprocess 

112 

113 In the Python subprocess, RPCServer is instantiated with handlerclass 

114 MyHandler, which inherits register/unregister methods from RPCHandler via 

115 the mix-in class SocketIO. 

116 

117 When the RPCServer 'server' is instantiated, the TCPServer initialization 

118 creates an instance of run.MyHandler and calls its handle() method. 

119 handle() instantiates a run.Executive object, passing it a reference to the 

120 MyHandler object. That reference is saved as attribute rpchandler of the 

121 Executive instance. The Executive methods have access to the reference and 

122 can pass it on to entities that they command 

123 (e.g. debugger_r.Debugger.start_debugger()). The latter, in turn, can 

124 call MyHandler(SocketIO) register/unregister methods via the reference to 

125 register and unregister themselves. 

126 

127 """ 

128 global exit_now 

129 global quitting 

130 global no_exitfunc 

131 no_exitfunc = del_exitfunc 

132 #time.sleep(15) # test subprocess not responding 

133 try: 

134 assert(len(sys.argv) > 1) 

135 port = int(sys.argv[-1]) 

136 except: 

137 print("IDLE Subprocess: no IP port passed in sys.argv.", 

138 file=sys.__stderr__) 

139 return 

140 

141 capture_warnings(True) 

142 sys.argv[:] = [""] 

143 sockthread = threading.Thread(target=manage_socket, 

144 name='SockThread', 

145 args=((LOCALHOST, port),)) 

146 sockthread.daemon = True 

147 sockthread.start() 

148 while True: 

149 try: 

150 if exit_now: 

151 try: 

152 exit() 

153 except KeyboardInterrupt: 

154 # exiting but got an extra KBI? Try again! 

155 continue 

156 try: 

157 request = rpc.request_queue.get(block=True, timeout=0.05) 

158 except queue.Empty: 

159 request = None 

160 # Issue 32207: calling handle_tk_events here adds spurious 

161 # queue.Empty traceback to event handling exceptions. 

162 if request: 

163 seq, (method, args, kwargs) = request 

164 ret = method(*args, **kwargs) 

165 rpc.response_queue.put((seq, ret)) 

166 else: 

167 handle_tk_events() 

168 except KeyboardInterrupt: 

169 if quitting: 

170 exit_now = True 

171 continue 

172 except SystemExit: 

173 capture_warnings(False) 

174 raise 

175 except: 

176 type, value, tb = sys.exc_info() 

177 try: 

178 print_exception() 

179 rpc.response_queue.put((seq, None)) 

180 except: 

181 # Link didn't work, print same exception to __stderr__ 

182 traceback.print_exception(type, value, tb, file=sys.__stderr__) 

183 exit() 

184 else: 

185 continue 

186 

187def manage_socket(address): 

188 for i in range(3): 

189 time.sleep(i) 

190 try: 

191 server = MyRPCServer(address, MyHandler) 

192 break 

193 except OSError as err: 

194 print("IDLE Subprocess: OSError: " + err.args[1] + 

195 ", retrying....", file=sys.__stderr__) 

196 socket_error = err 

197 else: 

198 print("IDLE Subprocess: Connection to " 

199 "IDLE GUI failed, exiting.", file=sys.__stderr__) 

200 show_socket_error(socket_error, address) 

201 global exit_now 

202 exit_now = True 

203 return 

204 server.handle_request() # A single request only 

205 

206def show_socket_error(err, address): 

207 "Display socket error from manage_socket." 

208 import tkinter 

209 from tkinter.messagebox import showerror 

210 root = tkinter.Tk() 

211 fix_scaling(root) 

212 root.withdraw() 

213 showerror( 

214 "Subprocess Connection Error", 

215 f"IDLE's subprocess can't connect to {address[0]}:{address[1]}.\n" 

216 f"Fatal OSError #{err.errno}: {err.strerror}.\n" 

217 "See the 'Startup failure' section of the IDLE doc, online at\n" 

218 "https://docs.python.org/3/library/idle.html#startup-failure", 

219 parent=root) 

220 root.destroy() 

221 

222 

223def get_message_lines(typ, exc, tb): 

224 "Return line composing the exception message." 

225 if typ in (AttributeError, NameError): 1wbc

226 # 3.10+ hints are not directly accessible from python (#44026). 

227 err = io.StringIO() 1wb

228 with contextlib.redirect_stderr(err): 1wb

229 sys.__excepthook__(typ, exc, tb) 1wb

230 return [err.getvalue().split("\n")[-2] + "\n"] 1wb

231 else: 

232 return traceback.format_exception_only(typ, exc) 1wbc

233 

234 

235def print_exception(): 

236 import linecache 1bc

237 linecache.checkcache() 1bc

238 flush_stdout() 1bc

239 efile = sys.stderr 1bc

240 typ, val, tb = excinfo = sys.exc_info() 1bc

241 sys.last_type, sys.last_value, sys.last_traceback = excinfo 1bc

242 seen = set() 1bc

243 

244 def print_exc(typ, exc, tb): 1bc

245 seen.add(id(exc)) 1bc

246 context = exc.__context__ 1bc

247 cause = exc.__cause__ 1bc

248 if cause is not None and id(cause) not in seen: 248 ↛ 249line 248 didn't jump to line 249, because the condition on line 248 was never true1bc

249 print_exc(type(cause), cause, cause.__traceback__) 

250 print("\nThe above exception was the direct cause " 

251 "of the following exception:\n", file=efile) 

252 elif (context is not None and 1bc

253 not exc.__suppress_context__ and 

254 id(context) not in seen): 

255 print_exc(type(context), context, context.__traceback__) 1bc

256 print("\nDuring handling of the above exception, " 1bc

257 "another exception occurred:\n", file=efile) 

258 if tb: 258 ↛ 265line 258 didn't jump to line 265, because the condition on line 258 was never false1bc

259 tbe = traceback.extract_tb(tb) 1bc

260 print('Traceback (most recent call last):', file=efile) 1bc

261 exclude = ("run.py", "rpc.py", "threading.py", "queue.py", 1bc

262 "debugger_r.py", "bdb.py") 

263 cleanup_traceback(tbe, exclude) 1bc

264 traceback.print_list(tbe, file=efile) 1bc

265 lines = get_message_lines(typ, exc, tb) 1bc

266 for line in lines: 1bc

267 print(line, end='', file=efile) 1bc

268 

269 print_exc(typ, val, tb) 1bc

270 

271def cleanup_traceback(tb, exclude): 

272 "Remove excluded traces from beginning/end of tb; get cached lines" 

273 orig_tb = tb[:] 

274 while tb: 

275 for rpcfile in exclude: 

276 if tb[0][0].count(rpcfile): 

277 break # found an exclude, break for: and delete tb[0] 

278 else: 

279 break # no excludes, have left RPC code, break while: 

280 del tb[0] 

281 while tb: 

282 for rpcfile in exclude: 

283 if tb[-1][0].count(rpcfile): 

284 break 

285 else: 

286 break 

287 del tb[-1] 

288 if len(tb) == 0: 

289 # exception was in IDLE internals, don't prune! 

290 tb[:] = orig_tb[:] 

291 print("** IDLE Internal Exception: ", file=sys.stderr) 

292 rpchandler = rpc.objecttable['exec'].rpchandler 

293 for i in range(len(tb)): 

294 fn, ln, nm, line = tb[i] 

295 if nm == '?': 

296 nm = "-toplevel-" 

297 if not line and fn.startswith("<pyshell#"): 

298 line = rpchandler.remotecall('linecache', 'getline', 

299 (fn, ln), {}) 

300 tb[i] = fn, ln, nm, line 

301 

302def flush_stdout(): 1abc

303 """XXX How to do this now?""" 

304 

305def exit(): 

306 """Exit subprocess, possibly after first clearing exit functions. 

307 

308 If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any 

309 functions registered with atexit will be removed before exiting. 

310 (VPython support) 

311 

312 """ 

313 if no_exitfunc: 

314 import atexit 

315 atexit._clear() 

316 capture_warnings(False) 

317 sys.exit(0) 

318 

319 

320def fix_scaling(root): 

321 """Scale fonts on HiDPI displays.""" 

322 import tkinter.font 

323 scaling = float(root.tk.call('tk', 'scaling')) 

324 if scaling > 1.4: 

325 for name in tkinter.font.names(root): 

326 font = tkinter.font.Font(root=root, name=name, exists=True) 

327 size = int(font['size']) 

328 if size < 0: 

329 font['size'] = round(-0.75*size) 

330 

331 

332def fixdoc(fun, text): 

333 tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else '' 1elzg

334 fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text)) 1elzg

335 

336RECURSIONLIMIT_DELTA = 30 

337 

338def install_recursionlimit_wrappers(): 

339 """Install wrappers to always add 30 to the recursion limit.""" 

340 # see: bpo-26806 

341 

342 @functools.wraps(sys.setrecursionlimit) 1elg

343 def setrecursionlimit(*args, **kwargs): 1elg

344 # mimic the original sys.setrecursionlimit()'s input handling 

345 if kwargs: 1aeg

346 raise TypeError( 1e

347 "setrecursionlimit() takes no keyword arguments") 

348 try: 1aeg

349 limit, = args 1aeg

350 except ValueError: 1e

351 raise TypeError(f"setrecursionlimit() takes exactly one " 1e

352 f"argument ({len(args)} given)") 

353 if not limit > 0: 1aeg

354 raise ValueError( 1e

355 "recursion limit must be greater or equal than 1") 

356 

357 return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA) 1ag

358 

359 fixdoc(setrecursionlimit, f"""\ 1elg

360 This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible 

361 uninterruptible loops.""") 

362 

363 @functools.wraps(sys.getrecursionlimit) 1elg

364 def getrecursionlimit(): 1elg

365 return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA 1lg

366 

367 fixdoc(getrecursionlimit, f"""\ 1elg

368 This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate 

369 for the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.""") 

370 

371 # add the delta to the default recursion limit, to compensate 

372 sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA) 1elg

373 

374 sys.setrecursionlimit = setrecursionlimit 1elg

375 sys.getrecursionlimit = getrecursionlimit 1elg

376 

377 

378def uninstall_recursionlimit_wrappers(): 

379 """Uninstall the recursion limit wrappers from the sys module. 

380 

381 IDLE only uses this for tests. Users can import run and call 

382 this to remove the wrapping. 

383 """ 

384 if ( 384 ↛ exitline 384 didn't jump to the function exit

385 getattr(sys.setrecursionlimit, '__wrapped__', None) and 

386 getattr(sys.getrecursionlimit, '__wrapped__', None) 

387 ): 

388 sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__ 

389 sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__ 

390 sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA) 

391 

392 

393class MyRPCServer(rpc.RPCServer): 

394 

395 def handle_error(self, request, client_address): 

396 """Override RPCServer method for IDLE 

397 

398 Interrupt the MainThread and exit server if link is dropped. 

399 

400 """ 

401 global quitting 

402 try: 1k

403 raise 1k

404 except SystemExit: 404 ↛ 405line 404 didn't jump to line 405, because the exception caught by line 404 didn't happen1k

405 raise 

406 except EOFError: 1k

407 global exit_now 

408 exit_now = True 1k

409 thread.interrupt_main() 1k

410 except: 1k

411 erf = sys.__stderr__ 1k

412 print(textwrap.dedent(f""" 1k

413 {'-'*40} 

414 Unhandled exception in user code execution server!' 

415 Thread: {threading.current_thread().name} 

416 IDLE Client Address: {client_address} 

417 Request: {request!r} 

418 """), file=erf) 

419 traceback.print_exc(limit=-20, file=erf) 1k

420 print(textwrap.dedent(f""" 1k

421 *** Unrecoverable, server exiting! 

422 

423 Users should never see this message; it is likely transient. 

424 If this recurs, report this with a copy of the message 

425 and an explanation of how to make it repeat. 

426 {'-'*40}"""), file=erf) 

427 quitting = True 1k

428 thread.interrupt_main() 1k

429 

430 

431# Pseudofiles for shell-remote communication (also used in pyshell) 

432 

433class StdioFile(io.TextIOBase): 

434 

435 def __init__(self, shell, tags, encoding='utf-8', errors='strict'): 

436 self.shell = shell 1hodijxnpyqrs

437 self.tags = tags 1hodijxnpyqrs

438 self._encoding = encoding 1hodijxnpyqrs

439 self._errors = errors 1hodijxnpyqrs

440 

441 @property 

442 def encoding(self): 

443 return self._encoding 1onpqrs

444 

445 @property 

446 def errors(self): 

447 return self._errors 1onpqrs

448 

449 @property 

450 def name(self): 

451 return '<%s>' % self.tags 1op

452 

453 def isatty(self): 

454 return True 1op

455 

456 

457class StdOutputFile(StdioFile): 

458 

459 def writable(self): 

460 return True 1p

461 

462 def write(self, s): 

463 if self.closed: 1nqrs

464 raise ValueError("write to closed file") 1n

465 s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors) 1nqrs

466 return self.shell.write(s, self.tags) 1nqrs

467 

468 

469class StdInputFile(StdioFile): 

470 _line_buffer = '' 

471 

472 def readable(self): 

473 return True 1o

474 

475 def read(self, size=-1): 

476 if self.closed: 476 ↛ 477line 476 didn't jump to line 477, because the condition on line 476 was never true1d

477 raise ValueError("read from closed file") 

478 if size is None: 1d

479 size = -1 1d

480 elif not isinstance(size, int): 1d

481 raise TypeError('must be int, not ' + type(size).__name__) 1d

482 result = self._line_buffer 1d

483 self._line_buffer = '' 1d

484 if size < 0: 1d

485 while line := self.shell.readline(): 1d

486 result += line 1d

487 else: 

488 while len(result) < size: 1d

489 line = self.shell.readline() 1d

490 if not line: break 1d

491 result += line 1d

492 self._line_buffer = result[size:] 1d

493 result = result[:size] 1d

494 return result 1d

495 

496 def readline(self, size=-1): 

497 if self.closed: 497 ↛ 498line 497 didn't jump to line 498, because the condition on line 497 was never true1hij

498 raise ValueError("read from closed file") 

499 if size is None: 1hij

500 size = -1 1i

501 elif not isinstance(size, int): 501 ↛ 502line 501 didn't jump to line 502, because the condition on line 501 was never true1hij

502 raise TypeError('must be int, not ' + type(size).__name__) 

503 line = self._line_buffer or self.shell.readline() 1hij

504 if size < 0: 1hij

505 size = len(line) 1hij

506 eol = line.find('\n', 0, size) 1hij

507 if eol >= 0: 1hij

508 size = eol + 1 1hij

509 self._line_buffer = line[size:] 1hij

510 return line[:size] 1hij

511 

512 def close(self): 

513 self.shell.close() 1ah

514 

515 

516class MyHandler(rpc.RPCHandler): 

517 

518 def handle(self): 

519 """Override base method""" 

520 executive = Executive(self) 

521 self.register("exec", executive) 

522 self.console = self.get_remote_proxy("console") 

523 sys.stdin = StdInputFile(self.console, "stdin", 

524 iomenu.encoding, iomenu.errors) 

525 sys.stdout = StdOutputFile(self.console, "stdout", 

526 iomenu.encoding, iomenu.errors) 

527 sys.stderr = StdOutputFile(self.console, "stderr", 

528 iomenu.encoding, "backslashreplace") 

529 

530 sys.displayhook = rpc.displayhook 

531 # page help() text to shell. 

532 import pydoc # import must be done here to capture i/o binding 

533 pydoc.pager = pydoc.plainpager 

534 

535 # Keep a reference to stdin so that it won't try to exit IDLE if 

536 # sys.stdin gets changed from within IDLE's shell. See issue17838. 

537 self._keep_stdin = sys.stdin 

538 

539 install_recursionlimit_wrappers() 

540 

541 self.interp = self.get_remote_proxy("interp") 

542 rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) 

543 

544 def exithook(self): 

545 "override SocketIO method - wait for MainThread to shut us down" 

546 time.sleep(10) 

547 

548 def EOFhook(self): 

549 "Override SocketIO method - terminate wait on callback and exit thread" 

550 global quitting 

551 quitting = True 

552 thread.interrupt_main() 

553 

554 def decode_interrupthook(self): 

555 "interrupt awakened thread" 

556 global quitting 

557 quitting = True 

558 thread.interrupt_main() 

559 

560 

561class Executive: 

562 

563 def __init__(self, rpchandler): 

564 self.rpchandler = rpchandler 

565 if idlelib.testing is False: 565 ↛ 570line 565 didn't jump to line 570, because the condition on line 565 was never false

566 self.locals = __main__.__dict__ 

567 self.calltip = calltip.Calltip() 

568 self.autocomplete = autocomplete.AutoComplete() 

569 else: 

570 self.locals = {} 

571 

572 def runcode(self, code): 

573 global interruptable 

574 try: 1f

575 self.user_exc_info = None 1f

576 interruptable = True 1f

577 try: 1f

578 exec(code, self.locals) 1f

579 finally: 

580 interruptable = False 1f

581 except SystemExit as e: 581 ↛ 582line 581 didn't jump to line 582, because the exception caught by line 581 didn't happen1f

582 if e.args: # SystemExit called with an argument. 

583 ob = e.args[0] 

584 if not isinstance(ob, (type(None), int)): 

585 print('SystemExit: ' + str(ob), file=sys.stderr) 

586 # Return to the interactive prompt. 

587 except: 1f

588 self.user_exc_info = sys.exc_info() # For testing, hook, viewer. 1f

589 if quitting: 589 ↛ 590line 589 didn't jump to line 590, because the condition on line 589 was never true1f

590 exit() 

591 if sys.excepthook is sys.__excepthook__: 1f

592 print_exception() 1f

593 else: 

594 try: 1f

595 sys.excepthook(*self.user_exc_info) 1f

596 except: 1f

597 self.user_exc_info = sys.exc_info() # For testing. 1f

598 print_exception() 1f

599 jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>") 1f

600 if jit: 600 ↛ 601line 600 didn't jump to line 601, because the condition on line 600 was never true1f

601 self.rpchandler.interp.open_remote_stack_viewer() 

602 else: 

603 flush_stdout() 

604 

605 def interrupt_the_server(self): 

606 if interruptable: 

607 thread.interrupt_main() 

608 

609 def start_the_debugger(self, gui_adap_oid): 

610 return debugger_r.start_debugger(self.rpchandler, gui_adap_oid) 

611 

612 def stop_the_debugger(self, idb_adap_oid): 

613 "Unregister the Idb Adapter. Link objects and Idb then subject to GC" 

614 self.rpchandler.unregister(idb_adap_oid) 

615 

616 def get_the_calltip(self, name): 

617 return self.calltip.fetch_tip(name) 

618 

619 def get_the_completion_list(self, what, mode): 

620 return self.autocomplete.fetch_completions(what, mode) 

621 

622 def stackviewer(self, flist_oid=None): 

623 if self.user_exc_info: 

624 typ, val, tb = self.user_exc_info 

625 else: 

626 return None 

627 flist = None 

628 if flist_oid is not None: 

629 flist = self.rpchandler.get_remote_proxy(flist_oid) 

630 while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]: 

631 tb = tb.tb_next 

632 sys.last_type = typ 

633 sys.last_value = val 

634 item = stackviewer.StackTreeItem(flist, tb) 

635 return debugobj_r.remote_object_tree_item(item) 

636 

637 

638if __name__ == '__main__': 638 ↛ 639line 638 didn't jump to line 639, because the condition on line 638 was never true

639 from unittest import main 

640 main('idlelib.idle_test.test_run', verbosity=2) 

641 

642capture_warnings(False) # Make sure turned off; see bpo-18081.