diff --git a/tests/helpers/debugclient.py b/tests/helpers/debugclient.py index fe994575..5fe28a15 100644 --- a/tests/helpers/debugclient.py +++ b/tests/helpers/debugclient.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import os +import traceback import warnings from ptvsd.socket import Address @@ -15,12 +17,13 @@ class _LifecycleClient(Closeable): SESSION = DebugSession - def __init__(self, - addr=None, - port=8888, - breakpoints=None, - connecttimeout=1.0, - ): + def __init__( + self, + addr=None, + port=8888, + breakpoints=None, + connecttimeout=1.0, + ): super(_LifecycleClient, self).__init__() self._addr = Address.from_raw(addr, defaultport=port) self._connecttimeout = connecttimeout @@ -165,7 +168,6 @@ class DebugClient(_LifecycleClient): class EasyDebugClient(DebugClient): - def start_detached(self, argv): """Start an adapter in a background process.""" if self.closed: @@ -191,8 +193,14 @@ class EasyDebugClient(DebugClient): assert self._session is None addr = ('localhost', self._addr.port) + self._run_server_ex = None + def run(): - self._session = self.SESSION.create_server(addr, **kwargs) + try: + self._session = self.SESSION.create_server(addr, **kwargs) + except Exception as ex: + self._run_server_ex = traceback.format_exc() + t = new_hidden_thread( target=run, name='test.client', @@ -204,8 +212,14 @@ class EasyDebugClient(DebugClient): if t.is_alive(): warnings.warn('timed out waiting for connection') if self._session is None: - raise RuntimeError('unable to connect after {} secs'.format( - self._connecttimeout)) + message = 'unable to connect after {} secs'.format( # noqa + self._connecttimeout) + if self._run_server_ex is None: + raise Exception(message) + else: + message = message + os.linesep + self._run_server_ex # noqa + raise Exception(message) + # The adapter will close when the connection does. self._launch( diff --git a/tests/helpers/pydevd/_binder.py b/tests/helpers/pydevd/_binder.py index 5116e2a6..ae178839 100644 --- a/tests/helpers/pydevd/_binder.py +++ b/tests/helpers/pydevd/_binder.py @@ -132,13 +132,17 @@ class BinderBase(object): else: raise RuntimeError('timed out') return self._wrap_sock() + return connect, remote - def wait_until_done(self): + def wait_until_done(self, timeout=10.0): """Wait for the started debugger operation to finish.""" if self._thread is None: return - self._thread.join() + self._thread.join(timeout) + if self._thread.isAlive(): + raise Exception( + 'wait_until_done timed out after {} secs'.format(timeout)) #################### # for subclassing @@ -187,8 +191,10 @@ class Binder(BinderBase): def __init__(self, do_debugging=None, **kwargs): if do_debugging is None: + def do_debugging(external, internal): time.sleep(5) + super(Binder, self).__init__(**kwargs) self._do_debugging = do_debugging diff --git a/tests/highlevel/__init__.py b/tests/highlevel/__init__.py index b0b4b33d..76c269ff 100644 --- a/tests/highlevel/__init__.py +++ b/tests/highlevel/__init__.py @@ -234,7 +234,7 @@ class VSCLifecycle(object): daemon.wait_until_connected() return daemon - def _stop_daemon(self, daemon, disconnect=True): + def _stop_daemon(self, daemon, disconnect=True, timeout=10.0): # We must close ptvsd directly (rather than closing the external # socket (i.e. "daemon"). This is because cloing ptvsd blocks, # keeping us from sending the disconnect request we need to send @@ -251,7 +251,10 @@ class VSCLifecycle(object): t.start() if disconnect: self.disconnect() - t.join() + t.join(timeout) + if t.isAlive(): + raise Exception( + '_stop_daemon timed out after {} secs'.format(timeout)) daemon.close() def _handshake(self, command, threadnames=None, config=None, requests=None, diff --git a/tests/highlevel/test_live_pydevd.py b/tests/highlevel/test_live_pydevd.py index 62251149..c179fa7a 100644 --- a/tests/highlevel/test_live_pydevd.py +++ b/tests/highlevel/test_live_pydevd.py @@ -4,10 +4,11 @@ import contextlib import os import sys from textwrap import dedent +import traceback import unittest import ptvsd -from ptvsd.wrapper import INITIALIZE_RESPONSE # noqa +from ptvsd.wrapper import INITIALIZE_RESPONSE # noqa from tests.helpers._io import captured_stdio from tests.helpers.pydevd._live import LivePyDevd from tests.helpers.script import set_lock, find_line @@ -20,7 +21,6 @@ from . import ( class Fixture(VSCFixture): - def __init__(self, source, new_fake=None): self._pydevd = LivePyDevd(source) super(Fixture, self).__init__( @@ -100,6 +100,7 @@ class TestBase(VSCTest): self.pathentry.install() self._filename = 'module:' + name + ################################## # lifecycle tests @@ -116,7 +117,7 @@ class LifecycleTests(TestBase, unittest.TestCase): addr = (None, 8888) with self.fake.start(addr): #with self.fix.install_sig_handler(): - yield + yield def test_launch(self): addr = (None, 8888) @@ -143,33 +144,37 @@ class LifecycleTests(TestBase, unittest.TestCase): self.fix.binder.wait_until_done() received = self.vsc.received - self.assert_vsc_received(received, [ - self.new_event( - 'output', - category='telemetry', - output='ptvsd', - data={'version': ptvsd.__version__}), - self.new_response(req_initialize, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_attach), - self.new_response(req_config), - self.new_event('process', **dict( - name=sys.argv[0], - systemProcessId=os.getpid(), - isLocalProcess=True, - startMethod='attach', - )), - self.new_event('thread', reason='started', threadId=1), - #self.new_event('exited', exitCode=0), - #self.new_event('terminated'), - ]) + self.assert_vsc_received( + received, + [ + self.new_event( + 'output', + category='telemetry', + output='ptvsd', + data={'version': ptvsd.__version__}), + self.new_response(req_initialize, **INITIALIZE_RESPONSE), + self.new_event('initialized'), + self.new_response(req_attach), + self.new_response(req_config), + self.new_event( + 'process', + **dict( + name=sys.argv[0], + systemProcessId=os.getpid(), + isLocalProcess=True, + startMethod='attach', + )), + self.new_event('thread', reason='started', threadId=1), + #self.new_event('exited', exitCode=0), + #self.new_event('terminated'), + ]) + ################################## # "normal operation" tests class VSCFlowTest(TestBase): - @contextlib.contextmanager def launched(self, port=8888, **kwargs): kwargs.setdefault('process', False) @@ -177,7 +182,23 @@ class VSCFlowTest(TestBase): with self.lifecycle.launched(port=port, hide=True, **kwargs): yield self.fix.binder.done(close=False) - self.fix.binder.wait_until_done() + + try: + self.fix.binder.wait_until_done() + except Exception as ex: + formatted_ex = traceback.format_exc() + if hasattr(self, 'vsc') and hasattr(self.vsc, 'received'): + message = """ + Session Messages: + ----------------- + {} + + Original Error: + --------------- + {}""".format(os.linesep.join(self.vsc.received), formatted_ex) + raise Exception(message) + else: + raise class BreakpointTests(VSCFlowTest, unittest.TestCase): @@ -301,9 +322,13 @@ class BreakpointTests(VSCFlowTest, unittest.TestCase): self.lifecycle.requests = [] # Trigger capture. config = { 'breakpoints': [{ - 'source': {'path': self.filename}, + 'source': { + 'path': self.filename + }, 'breakpoints': [ - {'line': lineno}, + { + 'line': lineno + }, ], }], 'excbreakpoints': [], @@ -317,18 +342,21 @@ class BreakpointTests(VSCFlowTest, unittest.TestCase): done1() with self.wait_for_event('stopped'): with self.wait_for_event('continued'): - req_continue1 = self.send_request('continue', { - 'threadId': tid, - }) + req_continue1 = self.send_request( + 'continue', { + 'threadId': tid, + }) with self.wait_for_event('stopped'): with self.wait_for_event('continued'): - req_continue2 = self.send_request('continue', { + req_continue2 = self.send_request( + 'continue', { + 'threadId': tid, + }) + with self.wait_for_event('continued'): + req_continue_last = self.send_request( + 'continue', { 'threadId': tid, }) - with self.wait_for_event('continued'): - req_continue_last = self.send_request('continue', { - 'threadId': tid, - }) # Allow the script to run to completion. received = self.vsc.received @@ -344,36 +372,33 @@ class BreakpointTests(VSCFlowTest, unittest.TestCase): self.assertEqual(got, config['breakpoints']) self.assert_vsc_received_fixing_events(received, [ - ('stopped', dict( - reason='breakpoint', - threadId=tid, - text=None, - description=None, - )), + ('stopped', + dict( + reason='breakpoint', + threadId=tid, + text=None, + description=None, + )), req_continue1, - ('continued', dict( - threadId=tid, - )), - ('stopped', dict( - reason='breakpoint', - threadId=tid, - text=None, - description=None, - )), + ('continued', dict(threadId=tid, )), + ('stopped', + dict( + reason='breakpoint', + threadId=tid, + text=None, + description=None, + )), req_continue2, - ('continued', dict( - threadId=tid, - )), - ('stopped', dict( - reason='breakpoint', - threadId=tid, - text=None, - description=None, - )), + ('continued', dict(threadId=tid, )), + ('stopped', + dict( + reason='breakpoint', + threadId=tid, + text=None, + description=None, + )), req_continue_last, - ('continued', dict( - threadId=tid, - )), + ('continued', dict(threadId=tid, )), ]) self.assertIn('2 4 4', out) self.assertIn('ka-boom', err) @@ -386,7 +411,9 @@ class BreakpointTests(VSCFlowTest, unittest.TestCase): config = { 'breakpoints': [], 'excbreakpoints': [ - {'filters': ['raised']}, + { + 'filters': ['raised'] + }, ], } with captured_stdio() as (stdout, _): @@ -412,12 +439,12 @@ class BreakpointTests(VSCFlowTest, unittest.TestCase): else: description = "MyError('ka-boom',)" self.assert_vsc_received_fixing_events(received, [ - ('stopped', dict( - reason='exception', - threadId=tid, - text='MyError', - description=description - )), + ('stopped', + dict( + reason='exception', + threadId=tid, + text='MyError', + description=description)), ]) self.assertIn('2 4 4', out) self.assertIn('ka-boom', out) @@ -439,7 +466,6 @@ class LogpointTests(TestBase, unittest.TestCase): @contextlib.contextmanager def closing(self, exit=True): - def handle_msg(msg, _): with self.wait_for_event('output'): self.req_disconnect = self.send_request('disconnect') @@ -455,7 +481,7 @@ class LogpointTests(TestBase, unittest.TestCase): def running(self): addr = (None, 8888) with self.fake.start(addr): - yield + yield def test_basic(self): with open(self.filename) as scriptfile: @@ -473,18 +499,20 @@ class LogpointTests(TestBase, unittest.TestCase): req_initialize = self.send_request('initialize', { 'adapterID': 'spam', }) - req_attach = self.send_request('attach', { - 'debugOptions': ['RedirectOutput'] - }) - req_breakpoints = self.send_request('setBreakpoints', { - 'source': {'path': self.filename}, - 'breakpoints': [ - { - 'line': '4', - 'logMessage': '{a}+{b}=3' + req_attach = self.send_request( + 'attach', {'debugOptions': ['RedirectOutput']}) + req_breakpoints = self.send_request( + 'setBreakpoints', { + 'source': { + 'path': self.filename }, - ], - }) + 'breakpoints': [ + { + 'line': '4', + 'logMessage': '{a}+{b}=3' + }, + ], + }) with self.vsc.wait_for_event('output'): # 1+2=3 with self.vsc.wait_for_event('thread'): req_config = self.send_request('configurationDone') @@ -507,19 +535,27 @@ class LogpointTests(TestBase, unittest.TestCase): self.new_response(req_initialize, **INITIALIZE_RESPONSE), self.new_event('initialized'), self.new_response(req_attach), - self.new_response(req_breakpoints, **dict( - breakpoints=[{'id': 1, 'verified': True, 'line': '4'}] - )), + self.new_response( + req_breakpoints, + **dict(breakpoints=[{ + 'id': 1, + 'verified': True, + 'line': '4' + }])), self.new_response(req_config), - self.new_event('process', **dict( - name=sys.argv[0], - systemProcessId=os.getpid(), - isLocalProcess=True, - startMethod='attach', - )), + self.new_event( + 'process', + **dict( + name=sys.argv[0], + systemProcessId=os.getpid(), + isLocalProcess=True, + startMethod='attach', + )), self.new_event('thread', reason='started', threadId=1), - self.new_event('output', **dict( - category='stdout', - output='1+2=3' + os.linesep, - )), + self.new_event( + 'output', + **dict( + category='stdout', + output='1+2=3' + os.linesep, + )), ]) diff --git a/tests/system_tests/__init__.py b/tests/system_tests/__init__.py index aa64edbe..ddff23ef 100644 --- a/tests/system_tests/__init__.py +++ b/tests/system_tests/__init__.py @@ -249,28 +249,16 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase): except Exception: pass - fmt = { - "sep": os.linesep, - "messages": os.linesep.join(messages), - "error": ''.join(traceback.format_exception_only(exc_type, exc_value)) # noqa - } message = """ - Session Messages: ----------------- -%(messages)s +{} Original Error: --------------- -%(error)s""" % fmt +{}""".format(os.linesep.join(messages), formatted_ex) - try: - # Chain the original exception for py3. - exec('raise Exception(message) from ex', globals(), locals()) - except SyntaxError: - # This happens when using py27. - message = message + os.linesep + formatted_ex - exec("raise Exception(message)", globals(), locals()) + raise Exception(message) def _handle_exception(ex, adapter, session): exc_type, exc_value, exc_traceback = sys.exc_info()