mirror of
				https://github.com/python/cpython.git
				synced 2025-11-01 06:01:29 +00:00 
			
		
		
		
	gh-87320: In the code module, handle exceptions raised in sys.excepthook (GH-122456)
Before, the exception caused by calling non-default sys.excepthook in code.InteractiveInterpreter bubbled up to the caller, ending the REPL.
This commit is contained in:
		
							parent
							
								
									e60ee11cb5
								
							
						
					
					
						commit
						bd3d31f380
					
				
					 4 changed files with 76 additions and 3 deletions
				
			
		
							
								
								
									
										19
									
								
								Lib/code.py
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								Lib/code.py
									
										
									
									
									
								
							|  | @ -129,7 +129,7 @@ def showsyntaxerror(self, filename=None, **kwargs): | |||
|         else: | ||||
|             # If someone has set sys.excepthook, we let that take precedence | ||||
|             # over self.write | ||||
|             sys.excepthook(type, value, tb) | ||||
|             self._call_excepthook(type, value, tb) | ||||
| 
 | ||||
|     def showtraceback(self, **kwargs): | ||||
|         """Display the exception that just occurred. | ||||
|  | @ -144,16 +144,29 @@ def showtraceback(self, **kwargs): | |||
|         sys.last_traceback = last_tb | ||||
|         sys.last_exc = ei[1] | ||||
|         try: | ||||
|             lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next, colorize=colorize) | ||||
|             if sys.excepthook is sys.__excepthook__: | ||||
|                 lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next, colorize=colorize) | ||||
|                 self.write(''.join(lines)) | ||||
|             else: | ||||
|                 # If someone has set sys.excepthook, we let that take precedence | ||||
|                 # over self.write | ||||
|                 sys.excepthook(ei[0], ei[1], last_tb) | ||||
|                 self._call_excepthook(ei[0], ei[1], last_tb) | ||||
|         finally: | ||||
|             last_tb = ei = None | ||||
| 
 | ||||
|     def _call_excepthook(self, typ, value, tb): | ||||
|         try: | ||||
|             sys.excepthook(typ, value, tb) | ||||
|         except SystemExit: | ||||
|             raise | ||||
|         except BaseException as e: | ||||
|             e.__context__ = None | ||||
|             print('Error in sys.excepthook:', file=sys.stderr) | ||||
|             sys.__excepthook__(type(e), e, e.__traceback__.tb_next) | ||||
|             print(file=sys.stderr) | ||||
|             print('Original exception was:', file=sys.stderr) | ||||
|             sys.__excepthook__(typ, value, tb) | ||||
| 
 | ||||
|     def write(self, data): | ||||
|         """Write a string. | ||||
| 
 | ||||
|  |  | |||
|  | @ -77,6 +77,39 @@ def test_sysexcepthook(self): | |||
|         self.console.interact() | ||||
|         self.assertTrue(hook.called) | ||||
| 
 | ||||
|     def test_sysexcepthook_crashing_doesnt_close_repl(self): | ||||
|         self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')] | ||||
|         self.sysmod.excepthook = 1 | ||||
|         self.console.interact() | ||||
|         self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0]) | ||||
|         error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write') | ||||
|         self.assertIn("Error in sys.excepthook:", error) | ||||
|         self.assertEqual(error.count("'int' object is not callable"), 1) | ||||
|         self.assertIn("Original exception was:", error) | ||||
|         self.assertIn("division by zero", error) | ||||
| 
 | ||||
|     def test_sysexcepthook_raising_BaseException(self): | ||||
|         self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')] | ||||
|         s = "not so fast" | ||||
|         def raise_base(*args, **kwargs): | ||||
|             raise BaseException(s) | ||||
|         self.sysmod.excepthook = raise_base | ||||
|         self.console.interact() | ||||
|         self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0]) | ||||
|         error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write') | ||||
|         self.assertIn("Error in sys.excepthook:", error) | ||||
|         self.assertEqual(error.count("not so fast"), 1) | ||||
|         self.assertIn("Original exception was:", error) | ||||
|         self.assertIn("division by zero", error) | ||||
| 
 | ||||
|     def test_sysexcepthook_raising_SystemExit_gets_through(self): | ||||
|         self.infunc.side_effect = ["1/0"] | ||||
|         def raise_base(*args, **kwargs): | ||||
|             raise SystemExit | ||||
|         self.sysmod.excepthook = raise_base | ||||
|         with self.assertRaises(SystemExit): | ||||
|             self.console.interact() | ||||
| 
 | ||||
|     def test_banner(self): | ||||
|         # with banner | ||||
|         self.infunc.side_effect = EOFError('Finished') | ||||
|  |  | |||
|  | @ -1049,6 +1049,30 @@ def test_python_basic_repl(self): | |||
|         self.assertNotIn("Exception", output) | ||||
|         self.assertNotIn("Traceback", output) | ||||
| 
 | ||||
|     @force_not_colorized | ||||
|     def test_bad_sys_excepthook_doesnt_crash_pyrepl(self): | ||||
|         env = os.environ.copy() | ||||
|         commands = ("import sys\n" | ||||
|                     "sys.excepthook = 1\n" | ||||
|                     "1/0\n" | ||||
|                     "exit()\n") | ||||
| 
 | ||||
|         def check(output, exitcode): | ||||
|             self.assertIn("Error in sys.excepthook:", output) | ||||
|             self.assertEqual(output.count("'int' object is not callable"), 1) | ||||
|             self.assertIn("Original exception was:", output) | ||||
|             self.assertIn("division by zero", output) | ||||
|             self.assertEqual(exitcode, 0) | ||||
|         env.pop("PYTHON_BASIC_REPL", None) | ||||
|         output, exit_code = self.run_repl(commands, env=env) | ||||
|         if "can\'t use pyrepl" in output: | ||||
|             self.skipTest("pyrepl not available") | ||||
|         check(output, exit_code) | ||||
| 
 | ||||
|         env["PYTHON_BASIC_REPL"] = "1" | ||||
|         output, exit_code = self.run_repl(commands, env=env) | ||||
|         check(output, exit_code) | ||||
| 
 | ||||
|     def test_not_wiping_history_file(self): | ||||
|         # skip, if readline module is not available | ||||
|         import_module('readline') | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| In :class:`code.InteractiveInterpreter`, handle exceptions caused by calling a | ||||
| non-default :func:`sys.excepthook`. Before, the exception bubbled up to the | ||||
| caller, ending the :term:`REPL`. | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 CF Bolz-Tereick
						CF Bolz-Tereick