Add unhandled exception tests (#682)

Adds unhandled exception tests #654
Fixes a bug in _io.py that caused JSONDecodeError in tests sometimes #683
Fixes a issue for some cases where the tests were timing out on windows
This commit is contained in:
Karthik Nadig 2018-07-23 13:23:04 -07:00 committed by GitHub
parent 501d163c5c
commit 5db5b30ea8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 422 additions and 34 deletions

View file

@ -653,6 +653,7 @@ class InternalsFilter(object):
"""Identifies debugger internal artifacts.
"""
# TODO: Move the internal thread identifier here
def __init__(self):
if platform.system() == 'Windows':
self._init_windows()
@ -1407,6 +1408,7 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
'\t'.join(project_dirs))
def _is_stdlib(self, filepath):
filepath = os.path.normcase(os.path.normpath(filepath))
for prefix in STDLIB_PATH_PREFIXES:
if prefix != '' and filepath.startswith(prefix):
return True
@ -2090,7 +2092,7 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
condition = None
expressions = re.findall('\{.*?\}', logMessage)
if len(expressions) == 0:
expression = '{}'.format(repr(logMessage)) # noqa
expression = '{}'.format(repr(logMessage)) # noqa
else:
raw_text = reduce(lambda a, b: a.replace(b, '{}'), expressions, logMessage) # noqa
raw_text = raw_text.replace('"', '\\"')

View file

@ -103,6 +103,9 @@ def iter_lines_buffered(read, sep=b'\n', initial=b'', stop=noop):
data = read(1024)
if not data:
raise EOFError()
if buf and buf[-1:] == b'\r':
data = buf + data
buf = b''
except EOFError as exc:
exc.remainder = buf
raise

View file

@ -0,0 +1,7 @@
import ptvsd
import sys
ptvsd.enable_attach((sys.argv[1], sys.argv[2]))
ptvsd.wait_for_attach()
raise ArithmeticError('Hello')
sys.stdout.write('end')

View file

@ -0,0 +1,4 @@
import sys
raise ArithmeticError('Hello')
sys.stdout.write('end')

View file

@ -0,0 +1,7 @@
import sys
import ptvsd
ptvsd.enable_attach((('localhost', 9879)))
ptvsd.wait_for_attach()
raise ArithmeticError('Hello')
sys.stdout.write('end')

View file

@ -46,15 +46,17 @@ class ExceptionTests(LifecycleTestsBase):
options=options)
received = list(_strip_newline_output_events(dbg.session.received))
self.assertEqual(
len(self.find_events(received, 'output', {'category': 'stdout'})),
1)
# TODO: Re-enable after fixing #685
#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'),
# TODO: Re-enable after fixing #685
# self.new_event('output', category='stdout', output='one'),
self.new_event('exited', exitCode=0),
self.new_event('terminated'),
])
@ -156,16 +158,135 @@ class ExceptionTests(LifecycleTestsBase):
Awaitable.wait_all(continued)
received = list(_strip_newline_output_events(dbg.session.received))
self.assertEqual(
len(self.find_events(received, 'output', {'category': 'stdout'})),
1)
# TODO: Re-enable after fixing #685
#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'),
# TODO: Re-enable after fixing #685
# self.new_event('output', category='stdout', output='one'),
self.new_event('exited', exitCode=0),
self.new_event('terminated'),
])
def run_test_breaking_into_raised_exceptions_only(self, debug_info,
expected_source_name):
# NOTE: for this case we will be using a unhandled exception. The
# behavior expected here is that it breaks once when the exception
# was raised but not during postmortem
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)
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': 'always',
'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.assert_contains(received, [
self.new_event('continued', threadId=thread_id),
self.new_event('exited', exitCode=0),
self.new_event('terminated'),
])
def run_test_breaking_into_raised_and_unhandled_exceptions(
self, debug_info, expected_source_name):
excbreakpoints = [{'filters': ['raised', 'uncaught']}]
options = {'debugOptions': ['RedirectOutput']}
expected = {
'exceptionId': 'ArithmeticError',
'breakMode': 'always',
'details': {
'typeName': 'ArithmeticError',
'source': expected_source_name
}
}
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)
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()
self.assert_is_subset(req_exc_info.resp.body, expected)
stopped2 = dbg.session.get_awaiter_for_event('stopped')
continued = dbg.session.get_awaiter_for_event('continued')
dbg.session.send_request(
'continue',
threadId=thread_id,
).wait()
Awaitable.wait_all(stopped2, continued)
# Second hit on uncaught exception
self.assertEqual(stopped2.event.body['text'], 'ArithmeticError')
self.assertIn("ArithmeticError('Hello'",
stopped2.event.body['description'])
req_exc_info2 = dbg.session.send_request(
'exceptionInfo',
threadId=thread_id,
)
req_exc_info2.wait()
self.assert_is_subset(req_exc_info2.resp.body, expected)
continued2 = dbg.session.get_awaiter_for_event('continued')
dbg.session.send_request(
'continue',
threadId=thread_id,
).wait()
Awaitable.wait_all(continued2)
received = list(_strip_newline_output_events(dbg.session.received))
self.assert_contains(received, [
self.new_event('continued', threadId=thread_id),
self.new_event('continued', threadId=thread_id), # expect 2 events
self.new_event('exited', exitCode=0),
self.new_event('terminated'),
])
@ -190,15 +311,40 @@ class LaunchFileTests(ExceptionTests):
self.run_test_breaking_into_handled_exceptions(
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)
def test_breaking_into_raised_exceptions_only(self):
filename = TEST_FILES.resolve('unhandled_exceptions_launch.py')
cwd = os.path.dirname(filename)
self.run_test_breaking_into_raised_exceptions_only(
DebugInfo(filename=filename, cwd=cwd), filename)
def test_breaking_into_raised_and_unhandled_exceptions(self):
filename = TEST_FILES.resolve('unhandled_exceptions_launch.py')
cwd = os.path.dirname(filename)
self.run_test_breaking_into_raised_and_unhandled_exceptions(
DebugInfo(filename=filename, cwd=cwd), filename)
class LaunchModuleExceptionLifecycleTests(ExceptionTests):
def test_not_breaking_into_handled_exceptions(self):
module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.parent.root
self.run_test_not_breaking_into_handled_exceptions(
DebugInfo(modulename=module_name, env=env, cwd=cwd))
def test_not_breaking_into_unhandled_exceptions(self):
module_name = 'mypkg_launch_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.parent.root
self.run_test_not_breaking_into_unhandled_exceptions(
DebugInfo(modulename=module_name, env=env, cwd=cwd))
def test_breaking_into_handled_exceptions(self):
module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path()
@ -207,26 +353,43 @@ class LaunchModuleExceptionLifecycleTests(ExceptionTests):
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'
def test_breaking_into_unhandled_exceptions(self):
module_name = 'mypkg_launch_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.parent.root
self.run_test_not_breaking_into_handled_exceptions(
DebugInfo(modulename=module_name, env=env, cwd=cwd))
self.run_test_breaking_into_unhandled_exceptions(
DebugInfo(modulename=module_name, env=env, cwd=cwd),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))
def test_breaking_into_raised_exceptions_only(self):
module_name = 'mypkg_launch_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.parent.root
self.run_test_breaking_into_raised_exceptions_only(
DebugInfo(modulename=module_name, env=env, cwd=cwd),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))
def test_breaking_into_raised_and_unhandled_exceptions(self):
module_name = 'mypkg_launch_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.parent.root
self.run_test_breaking_into_raised_and_unhandled_exceptions(
DebugInfo(modulename=module_name, env=env, cwd=cwd),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))
class ServerAttachExceptionLifecycleTests(ExceptionTests):
def test_breaking_into_handled_exceptions(self):
def test_not_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(
self.run_test_not_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')
@ -240,17 +403,17 @@ class ServerAttachExceptionLifecycleTests(ExceptionTests):
argv=argv,
))
def test_not_breaking_into_handled_exceptions(self):
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_not_breaking_into_handled_exceptions(
self.run_test_breaking_into_handled_exceptions(
DebugInfo(
filename=filename,
cwd=cwd,
starttype='attach',
argv=argv,
))
), filename)
def test_breaking_into_unhandled_exceptions(self):
filename = TEST_FILES.resolve('unhandled_exceptions_launch.py')
@ -264,21 +427,32 @@ class ServerAttachExceptionLifecycleTests(ExceptionTests):
argv=argv,
), filename)
class PTVSDAttachExceptionLifecycleTests(ExceptionTests):
def test_breaking_into_handled_exceptions(self):
filename = TEST_FILES.resolve('handled_exceptions_attach.py')
def test_breaking_into_raised_exceptions_only(self):
filename = TEST_FILES.resolve('unhandled_exceptions_launch.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_handled_exceptions(
self.run_test_breaking_into_raised_exceptions_only(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv,
), filename)
def test_breaking_into_raised_and_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_raised_and_unhandled_exceptions(
DebugInfo(
filename=filename,
cwd=cwd,
starttype='attach',
argv=argv,
), filename)
class PTVSDAttachExceptionLifecycleTests(ExceptionTests):
@unittest.skip('Needs fixing in #609, #580')
def test_not_breaking_into_handled_exceptions(self):
filename = TEST_FILES.resolve('handled_exceptions_attach.py')
@ -293,8 +467,105 @@ class PTVSDAttachExceptionLifecycleTests(ExceptionTests):
argv=argv,
))
@unittest.skip('Needs fixing in #609, #580')
def test_not_breaking_into_unhandled_exceptions(self):
filename = TEST_FILES.resolve('unhandled_exceptions_attach.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
self.run_test_not_breaking_into_unhandled_exceptions(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv,
))
@unittest.skip('#686')
def test_breaking_into_handled_exceptions(self):
filename = TEST_FILES.resolve('handled_exceptions_attach.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_handled_exceptions(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv,
), filename)
def test_breaking_into_unhandled_exceptions(self):
filename = TEST_FILES.resolve('unhandled_exceptions_attach.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_unhandled_exceptions(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv,
), filename)
@unittest.skip('Needs fixing in #609')
def test_breaking_into_raised_exceptions_only(self):
filename = TEST_FILES.resolve('unhandled_exceptions_attach.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_raised_exceptions_only(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv,
), filename)
@unittest.skip('Needs fixing in #609')
def test_breaking_into_raised_and_unhandled_exceptions(self):
filename = TEST_FILES.resolve('unhandled_exceptions_attach.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_raised_and_unhandled_exceptions(
DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv,
), filename)
class ServerAttachModuleExceptionLifecycleTests(ExceptionTests):
def test_not_breaking_into_handled_exceptions(self):
module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_not_breaking_into_handled_exceptions(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
starttype='attach',
))
def test_not_breaking_into_unhandled_exceptions(self):
module_name = 'mypkg_launch_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_not_breaking_into_unhandled_exceptions(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
starttype='attach',
))
def test_breaking_into_handled_exceptions(self):
module_name = 'mypkg_launch1'
env = TEST_FILES.env_with_py_path()
@ -309,22 +580,83 @@ class ServerAttachModuleExceptionLifecycleTests(ExceptionTests):
starttype='attach',
), os.path.join(TEST_FILES.root, module_name, '__init__.py'))
def test_not_breaking_into_handled_exceptions(self):
module_name = 'mypkg_launch1'
def test_breaking_into_unhandled_exceptions(self):
module_name = 'mypkg_launch_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_not_breaking_into_handled_exceptions(
self.run_test_breaking_into_unhandled_exceptions(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
starttype='attach',
))
),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))
def test_breaking_into_raised_exceptions_only(self):
module_name = 'mypkg_launch_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_raised_exceptions_only(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
starttype='attach',
),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))
def test_breaking_into_raised_and_unhandled_exceptions(self):
module_name = 'mypkg_launch_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_raised_and_unhandled_exceptions(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
starttype='attach',
),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))
class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests):
def test_not_breaking_into_handled_exceptions(self):
module_name = 'mypkg_attach1'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_not_breaking_into_handled_exceptions(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
attachtype='import',
starttype='attach',
))
def test_not_breaking_into_unhandled_exceptions(self):
module_name = 'mypkg_attach_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_not_breaking_into_unhandled_exceptions(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
attachtype='import',
starttype='attach',
))
def test_breaking_into_handled_exceptions(self):
module_name = 'mypkg_attach1'
env = TEST_FILES.env_with_py_path()
@ -340,12 +672,12 @@ class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests):
starttype='attach',
), os.path.join(TEST_FILES.root, module_name, '__init__.py'))
def test_not_breaking_into_handled_exceptions(self):
module_name = 'mypkg_attach1'
def test_breaking_into_unhandled_exceptions(self):
module_name = 'mypkg_attach_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_not_breaking_into_handled_exceptions(
self.run_test_breaking_into_unhandled_exceptions(
DebugInfo(
modulename=module_name,
env=env,
@ -353,4 +685,37 @@ class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests):
argv=argv,
attachtype='import',
starttype='attach',
))
),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))
def test_breaking_into_raised_exceptions_only(self):
module_name = 'mypkg_attach_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_raised_exceptions_only(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
attachtype='import',
starttype='attach',
),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))
def test_breaking_into_raised_and_unhandled_exceptions(self):
module_name = 'mypkg_attach_unhandled'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
self.run_test_breaking_into_raised_and_unhandled_exceptions(
DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
attachtype='import',
starttype='attach',
),
os.path.join(TEST_FILES.root, module_name, '__main__.py'))