From 501d163c5c2b1f479dc0cf33ad32bbf213cbdb82 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 23 Jul 2018 18:25:10 +0100 Subject: [PATCH] System Tests for unhandled exceptions (#669) * tests for unhandled exceptions * Test source and fix linter issues --- .../unhandled_exceptions_launch.py | 3 + tests/system_tests/test_exceptions.py | 192 ++++++++++++++---- 2 files changed, 159 insertions(+), 36 deletions(-) create mode 100644 tests/resources/system_tests/test_exceptions/unhandled_exceptions_launch.py diff --git a/tests/resources/system_tests/test_exceptions/unhandled_exceptions_launch.py b/tests/resources/system_tests/test_exceptions/unhandled_exceptions_launch.py new file mode 100644 index 000000000..4234ac513 --- /dev/null +++ b/tests/resources/system_tests/test_exceptions/unhandled_exceptions_launch.py @@ -0,0 +1,3 @@ +print('one') +raise ArithmeticError('Hello') +print('two') diff --git a/tests/system_tests/test_exceptions.py b/tests/system_tests/test_exceptions.py index 6c8abb382..ebd3e6f60 100644 --- a/tests/system_tests/test_exceptions.py +++ b/tests/system_tests/test_exceptions.py @@ -5,25 +5,27 @@ from tests.helpers.debugsession import Awaitable from tests.helpers.resource import TestResources from . import ( - _strip_newline_output_events, lifecycle_handshake, - LifecycleTestsBase, DebugInfo, PORT, + _strip_newline_output_events, + lifecycle_handshake, + LifecycleTestsBase, + DebugInfo, + PORT, ) - TEST_FILES = TestResources.from_module(__name__) class ExceptionTests(LifecycleTestsBase): - def run_test_not_breaking_into_handled_exceptions(self, debug_info): excbreakpoints = [{'filters': ['uncaught']}] options = {'debugOptions': ['RedirectOutput']} with self.start_debugging(debug_info) as dbg: - (_, req_attach, _, _, _, _ - ) = lifecycle_handshake(dbg.session, debug_info.starttype, - excbreakpoints=excbreakpoints, - options=options) + lifecycle_handshake( + dbg.session, + debug_info.starttype, + excbreakpoints=excbreakpoints, + options=options) received = list(_strip_newline_output_events(dbg.session.received)) self.assert_contains(received, [ @@ -32,17 +34,44 @@ def run_test_not_breaking_into_handled_exceptions(self, debug_info): self.new_event('terminated'), ]) - def run_test_breaking_into_handled_exceptions(self, debug_info): - excbreakpoints = [{'filters': ['raised', 'uncaught']}] + def run_test_not_breaking_into_unhandled_exceptions(self, debug_info): + excbreakpoints = [{'filters': []}] + options = {'debugOptions': ['RedirectOutput']} + + with self.start_debugging(debug_info) as dbg: + lifecycle_handshake( + dbg.session, + debug_info.starttype, + excbreakpoints=excbreakpoints, + options=options) + + received = list(_strip_newline_output_events(dbg.session.received)) + self.assertEqual( + len(self.find_events(received, 'output', {'category': 'stdout'})), + 1) + std_errs = self.find_events(received, 'output', {'category': 'stderr'}) + self.assertGreaterEqual(len(std_errs), 1) + std_err_msg = ''.join([msg.body['output'] for msg in std_errs]) + self.assertIn('ArithmeticError: Hello', std_err_msg) + self.assert_contains(received, [ + self.new_event('output', category='stdout', output='one'), + self.new_event('exited', exitCode=0), + self.new_event('terminated'), + ]) + + def run_test_breaking_into_handled_exceptions(self, debug_info, + expected_source_name): + excbreakpoints = [{'filters': ['raised']}] options = {'debugOptions': ['RedirectOutput']} with self.start_debugging(debug_info) as dbg: stopped = dbg.session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _ - ) = lifecycle_handshake(dbg.session, debug_info.starttype, - excbreakpoints=excbreakpoints, - options=options, - threads=True) + (_, req_launch_attach, _, _, _, _) = lifecycle_handshake( + dbg.session, + debug_info.starttype, + excbreakpoints=excbreakpoints, + options=options, + threads=True) Awaitable.wait_all(req_launch_attach, stopped) self.assertEqual(stopped.event.body['text'], 'ArithmeticError') @@ -57,14 +86,15 @@ def run_test_breaking_into_handled_exceptions(self, debug_info): req_exc_info.wait() exc_info = req_exc_info.resp.body - self.assert_is_subset(exc_info, { - 'exceptionId': 'ArithmeticError', - 'breakMode': 'always', - 'details': { - 'typeName': 'ArithmeticError', - # 'source': debug_info.filename - } - }) + self.assert_is_subset( + exc_info, { + 'exceptionId': 'ArithmeticError', + 'breakMode': 'always', + 'details': { + 'typeName': 'ArithmeticError', + 'source': expected_source_name + } + }) continued = dbg.session.get_awaiter_for_event('continued') dbg.session.send_request( @@ -81,30 +111,101 @@ def run_test_breaking_into_handled_exceptions(self, debug_info): self.new_event('terminated'), ]) + def run_test_breaking_into_unhandled_exceptions(self, debug_info, + expected_source_name): + excbreakpoints = [{'filters': ['uncaught']}] + options = {'debugOptions': ['RedirectOutput']} + + with self.start_debugging(debug_info) as dbg: + stopped = dbg.session.get_awaiter_for_event('stopped') + (_, req_launch_attach, _, _, _, _) = lifecycle_handshake( + dbg.session, + debug_info.starttype, + excbreakpoints=excbreakpoints, + options=options, + threads=True) + + Awaitable.wait_all(req_launch_attach, stopped) + self.assertEqual(stopped.event.body['text'], 'ArithmeticError') + self.assertIn("ArithmeticError('Hello'", + stopped.event.body['description']) + + thread_id = stopped.event.body['threadId'] + req_exc_info = dbg.session.send_request( + 'exceptionInfo', + threadId=thread_id, + ) + req_exc_info.wait() + exc_info = req_exc_info.resp.body + + self.assert_is_subset( + exc_info, { + 'exceptionId': 'ArithmeticError', + 'breakMode': 'unhandled', + 'details': { + 'typeName': 'ArithmeticError', + 'source': expected_source_name + } + }) + + continued = dbg.session.get_awaiter_for_event('continued') + dbg.session.send_request( + 'continue', + threadId=thread_id, + ).wait() + Awaitable.wait_all(continued) + + received = list(_strip_newline_output_events(dbg.session.received)) + self.assertEqual( + len(self.find_events(received, 'output', {'category': 'stdout'})), + 1) + std_errs = self.find_events(received, 'output', {'category': 'stderr'}) + self.assertGreaterEqual(len(std_errs), 1) + std_err_msg = ''.join([msg.body['output'] for msg in std_errs]) + self.assertIn('ArithmeticError: Hello', std_err_msg) + self.assert_contains(received, [ + self.new_event('continued', threadId=thread_id), + self.new_event('output', category='stdout', output='one'), + self.new_event('exited', exitCode=0), + self.new_event('terminated'), + ]) -class LaunchFileTests(ExceptionTests): +class LaunchFileTests(ExceptionTests): def test_not_breaking_into_handled_exceptions(self): filename = TEST_FILES.resolve('handled_exceptions_launch.py') cwd = os.path.dirname(filename) self.run_test_not_breaking_into_handled_exceptions( DebugInfo(filename=filename, cwd=cwd)) + def test_not_breaking_into_unhandled_exceptions(self): + filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') + cwd = os.path.dirname(filename) + self.run_test_not_breaking_into_unhandled_exceptions( + DebugInfo(filename=filename, cwd=cwd)) + def test_breaking_into_handled_exceptions(self): filename = TEST_FILES.resolve('handled_exceptions_launch.py') cwd = os.path.dirname(filename) self.run_test_breaking_into_handled_exceptions( - DebugInfo(filename=filename, cwd=cwd)) + DebugInfo(filename=filename, cwd=cwd), filename) + @unittest.skip('Issue #653') + def test_breaking_into_unhandled_exceptions(self): + filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') + cwd = os.path.dirname(filename) + self.run_test_breaking_into_unhandled_exceptions( + DebugInfo(filename=filename, cwd=cwd), filename) -class LaunchModuleExceptionLifecycleTests(ExceptionTests): +class LaunchModuleExceptionLifecycleTests(ExceptionTests): def test_breaking_into_handled_exceptions(self): module_name = 'mypkg_launch1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.parent.root self.run_test_breaking_into_handled_exceptions( - DebugInfo(modulename=module_name, env=env, cwd=cwd)) + DebugInfo(modulename=module_name, env=env, cwd=cwd), + os.path.join(TEST_FILES.root, module_name, '__init__.py')) def test_not_breaking_into_handled_exceptions(self): module_name = 'mypkg_launch1' @@ -115,12 +216,23 @@ def test_not_breaking_into_handled_exceptions(self): class ServerAttachExceptionLifecycleTests(ExceptionTests): - def test_breaking_into_handled_exceptions(self): filename = TEST_FILES.resolve('handled_exceptions_launch.py') cwd = os.path.dirname(filename) argv = ['localhost', str(PORT)] self.run_test_breaking_into_handled_exceptions( + DebugInfo( + filename=filename, + cwd=cwd, + starttype='attach', + argv=argv, + ), filename) + + def test_not_breaking_into_unhandled_exceptions(self): + filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') + cwd = os.path.dirname(filename) + argv = ['localhost', str(PORT)] + self.run_test_not_breaking_into_unhandled_exceptions( DebugInfo( filename=filename, cwd=cwd, @@ -140,9 +252,20 @@ def test_not_breaking_into_handled_exceptions(self): argv=argv, )) + def test_breaking_into_unhandled_exceptions(self): + filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') + cwd = os.path.dirname(filename) + argv = ['localhost', str(PORT)] + self.run_test_breaking_into_unhandled_exceptions( + DebugInfo( + filename=filename, + cwd=cwd, + starttype='attach', + argv=argv, + ), filename) -class PTVSDAttachExceptionLifecycleTests(ExceptionTests): +class PTVSDAttachExceptionLifecycleTests(ExceptionTests): def test_breaking_into_handled_exceptions(self): filename = TEST_FILES.resolve('handled_exceptions_attach.py') cwd = os.path.dirname(filename) @@ -154,9 +277,9 @@ def test_breaking_into_handled_exceptions(self): cwd=cwd, starttype='attach', argv=argv, - )) + ), filename) - @unittest.skip('Needs fixing in #609') + @unittest.skip('Needs fixing in #609, #580') def test_not_breaking_into_handled_exceptions(self): filename = TEST_FILES.resolve('handled_exceptions_attach.py') cwd = os.path.dirname(filename) @@ -172,7 +295,6 @@ def test_not_breaking_into_handled_exceptions(self): class ServerAttachModuleExceptionLifecycleTests(ExceptionTests): - def test_breaking_into_handled_exceptions(self): module_name = 'mypkg_launch1' env = TEST_FILES.env_with_py_path() @@ -185,7 +307,7 @@ def test_breaking_into_handled_exceptions(self): cwd=cwd, argv=argv, starttype='attach', - )) + ), os.path.join(TEST_FILES.root, module_name, '__init__.py')) def test_not_breaking_into_handled_exceptions(self): module_name = 'mypkg_launch1' @@ -202,9 +324,7 @@ def test_not_breaking_into_handled_exceptions(self): )) -@unittest.skip('Needs fixing') class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests): - def test_breaking_into_handled_exceptions(self): module_name = 'mypkg_attach1' env = TEST_FILES.env_with_py_path() @@ -218,7 +338,7 @@ def test_breaking_into_handled_exceptions(self): argv=argv, attachtype='import', starttype='attach', - )) + ), os.path.join(TEST_FILES.root, module_name, '__init__.py')) def test_not_breaking_into_handled_exceptions(self): module_name = 'mypkg_attach1'