def test_wait_for_attach(self): addr = Address('localhost', PORT) filename = self.write_script( 'spam.py', """ import sys sys.path.insert(0, {!r}) import ptvsd ptvsd.enable_attach({}, redirect_output=False) ptvsd.wait_for_attach() # <ready> # <wait> """.format(PROJECT_ROOT, tuple(addr)), ) lockfile1 = self.workspace.lockfile() _, wait = set_release(filename, lockfile1, 'ready') lockfile2 = self.workspace.lockfile() done, _ = set_lock(filename, lockfile2, 'wait') adapter = DebugAdapter.start_embedded(addr, filename) with adapter: with DebugClient() as editor: session = editor.attach_socket(addr, adapter, timeout=1) # Ensure that it really does wait. with self.assertRaises(LockTimeoutError): wait(timeout=0.5) lifecycle_handshake(session, 'attach') wait(timeout=1) done() adapter.wait()
def test_attach_started_separately(self): lockfile = self.workspace.lockfile() done, waitscript = lockfile.wait_in_script() filename = self.write_script('spam.py', waitscript) addr = Address('localhost', 8888) with DebugAdapter.start_for_attach(addr, filename) as adapter: with DebugClient() as editor: session = editor.attach_socket(addr, adapter) with session.wait_for_event('thread'): (req_initialize, req_launch, req_config, _, _, _ ) = lifecycle_handshake(session, 'attach') done() adapter.wait() received = list(_strip_newline_output_events(session.received)) self.assert_received(received[:7], [ self.new_version_event(session.received), self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), self.new_event('initialized'), self.new_response(req_launch.req), self.new_response(req_config.req), self.new_event('process', **{ 'isLocalProcess': True, 'systemProcessId': adapter.pid, 'startMethod': 'attach', 'name': filename, }), self.new_event('thread', reason='started', threadId=1), #self.new_event('thread', reason='exited', threadId=1), #self.new_event('exited', exitCode=0), #self.new_event('terminated'), ])
def test_attach_embedded(self): lockfile = self.workspace.lockfile() done, waitscript = lockfile.wait_in_script() addr = Address('localhost', 8888) script = dedent(""" from __future__ import print_function import sys sys.path.insert(0, {!r}) import ptvsd ptvsd.enable_attach({}, redirect_output={}) ptvsd.wait_for_attach() print('success!', end='') %s """).format(os.getcwd(), tuple(addr), True) filename = self.write_script('spam.py', script % waitscript) with DebugAdapter.start_embedded(addr, filename) as adapter: with DebugClient() as editor: session = editor.attach_socket(addr, adapter) (req_initialize, req_launch, req_config, _, _, _) = lifecycle_handshake(session, 'attach') Awaitable.wait_all(req_initialize, req_launch) done() adapter.wait() for i in range(10): # It could take some additional time for the adapter # to actually get the success output, so, wait for the # expected condition in a busy loop. out = adapter.output.decode('utf-8') if 'success!' in out: break import time time.sleep(.1) received = list(_strip_newline_output_events(session.received)) self.assert_contains(received, [ self.new_version_event(session.received), self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), self.new_event('initialized'), self.new_response(req_launch.req), self.new_response(req_config.req), self.new_event( 'process', **{ 'isLocalProcess': True, 'systemProcessId': adapter.pid, 'startMethod': 'attach', 'name': filename, }), self.new_event('output', output='success!', category='stdout'), self.new_event('exited', exitCode=0), self.new_event('terminated'), ]) self.assertIn('success!', out)
def test_does_not_block(self): addr = Address('localhost', PORT) filename = self.write_script('spam.py', """ import sys sys.path.insert(0, {!r}) import ptvsd ptvsd.enable_attach({}, redirect_output=False) # <ready> """.format(PROJECT_ROOT, tuple(addr)), ) lockfile = self.workspace.lockfile() _, wait = set_release(filename, lockfile, 'ready') #DebugAdapter.VERBOSE = True adapter = DebugAdapter.start_embedded(addr, filename) with adapter: wait(timeout=3) adapter.wait()
def test_never_call_wait_for_attach(self): addr = Address('localhost', PORT) filename = self.write_script( 'spam.py', """ import sys import threading import time sys.path.insert(0, {!r}) import ptvsd ptvsd.enable_attach({}, redirect_output=False) # <ready> print('== ready ==') # Allow tracing to be triggered. def wait(): # <wait> pass t = threading.Thread(target=wait) t.start() for _ in range(100): # 10 seconds print('-----') t.join(0.1) if not t.is_alive(): break t.join() print('== starting ==') # <bp> print('== done ==') """.format(PROJECT_ROOT, tuple(addr)), ) lockfile1 = self.workspace.lockfile('ready.lock') _, wait = set_release(filename, lockfile1, 'ready') lockfile2 = self.workspace.lockfile('wait.log') done, script = set_lock(filename, lockfile2, 'wait') bp = find_line(script, 'bp') breakpoints = [{ 'source': { 'path': filename }, 'breakpoints': [ { 'line': bp }, ], }] #DebugAdapter.VERBOSE = True #DebugClient.SESSION.VERBOSE = True adapter = DebugAdapter.start_embedded( addr, filename, srvtimeout=None, ) with adapter: # Wait longer that WAIT_TIMEOUT, so that debugging isn't # immediately enabled in the script's thread. wait(timeout=3.0) with DebugClient() as editor: session = editor.attach_socket(addr, adapter, timeout=1) stopped = session.get_awaiter_for_event('stopped') with session.wait_for_event('thread') as result: lifecycle_handshake(session, 'attach', breakpoints=breakpoints, threads=True) event = result['msg'] tid = event.body['threadId'] stopped.wait(timeout=5.0) done() session.send_request('continue', threadId=tid) adapter.wait() out = str(adapter.output) self.assertIn('== ready ==', out) self.assertIn('== starting ==', out)
def test_attach_breakpoints(self): # See https://github.com/Microsoft/ptvsd/issues/448. addr = Address('localhost', 8888) filename = self.write_script('spam.py', """ import sys sys.path.insert(0, {!r}) import ptvsd addr = {} ptvsd.enable_attach(addr) print('== waiting for attach ==') # <waiting> ptvsd.wait_for_attach() # <attached> print('== attached! ==') # <bp 2> print('== done waiting ==') """.format(ROOT, tuple(addr))) lockfile1 = self.workspace.lockfile() done1, _ = set_lock(filename, lockfile1, 'waiting') lockfile2 = self.workspace.lockfile() done2, script = set_lock(filename, lockfile2, 'bp 2') bp1 = find_line(script, 'attached') bp2 = find_line(script, 'bp 2') breakpoints = [{ 'source': {'path': filename}, 'breakpoints': [ {'line': bp1}, {'line': bp2}, ], }] options = { 'pathMappings': [ { 'localRoot': os.path.dirname(filename), 'remoteRoot': os.path.dirname(filename) }, # This specific mapping is for Mac. # For some reason temp paths on Mac get prefixed with # `private` when returned from ptvsd. { 'localRoot': os.path.dirname(filename), 'remoteRoot': '/private' + os.path.dirname(filename) } ] } #DebugAdapter.VERBOSE = True adapter = DebugAdapter.start_embedded(addr, filename) with adapter: with DebugClient() as editor: session = editor.attach_socket(addr, adapter, timeout=5) with session.wait_for_event('thread') as result: with session.wait_for_event('process'): (req_init, req_attach, req_config, reqs_bps, _, req_threads1, ) = lifecycle_handshake(session, 'attach', breakpoints=breakpoints, options=options, threads=True) Awaitable.wait_all(req_init, req_attach, req_config) req_bps, = reqs_bps # There should only be one. event = result['msg'] tid = event.body['threadId'] # Grab the initial output. out1 = next(adapter.output) # "waiting for attach" line = adapter.output.readline() while line: out1 += line line = adapter.output.readline() with session.wait_for_event('stopped'): # Tell the script to proceed (at "# <waiting>"). # This leads to the first breakpoint. done1() req_threads2, req_stacktrace1 = react_to_stopped(session, tid) out2 = str(adapter.output) # "" # Tell the script to proceed (at "# <bp 2>"). This # leads to the second breakpoint. At this point # execution is still stopped at the first breakpoint. done2() with session.wait_for_event('stopped'): with session.wait_for_event('continued'): req_continue1 = session.send_request( 'continue', threadId=tid, ) req_continue1.wait() req_threads3, req_stacktrace2 = react_to_stopped(session, tid) out3 = str(adapter.output) # "attached!" with session.wait_for_event('continued'): req_continue2 = session.send_request( 'continue', threadId=tid, ) req_continue2.wait() adapter.wait() out4 = str(adapter.output) # "done waiting" # Output between enable_attach() and wait_for_attach() may # be sent at a relatively arbitrary time (or not at all). # So we ignore it by removing it from the message list. received = list(_strip_output_event(session.received, u'== waiting for attach ==')) received = list(_strip_newline_output_events(received)) # There's an ordering race with continue/continued that pops # up occasionally. We work around that by manually fixing the # order. for pos, msg in _find_events(received, 'continued'): prev = received[pos-1] if prev.type != 'response' or prev.command != 'continue': received.pop(pos-1) received.insert(pos + 1, prev) # Sometimes the proc ends before the exited and terminated # events are received. received = list(_strip_exit(received)) self.assert_contains(received, [ self.new_version_event(session.received), self.new_response(req_init.req, **INITIALIZE_RESPONSE), self.new_event('initialized'), self.new_response(req_attach.req), self.new_event( 'thread', threadId=tid, reason='started', ), self.new_response(req_threads1.req, **{ 'threads': [{ 'id': 1, 'name': 'MainThread', }], }), self.new_response(req_bps.req, **{ 'breakpoints': [{ 'id': 1, 'line': bp1, 'verified': True, }, { 'id': 2, 'line': bp2, 'verified': True, }], }), self.new_response(req_config.req), self.new_event('process', **{ 'isLocalProcess': True, 'systemProcessId': adapter.pid, 'startMethod': 'attach', 'name': filename, }), self.new_event( 'stopped', threadId=tid, reason='breakpoint', description=None, text=None, ), self.new_response(req_threads2.req, **{ 'threads': [{ 'id': 1, 'name': 'MainThread', }], }), self.new_event( 'module', module={ 'id': 1, 'name': '__main__', 'path': filename, 'package': None, }, reason='new', ), self.new_response(req_stacktrace1.req, **{ 'totalFrames': 1, 'stackFrames': [{ 'id': 1, 'name': '<module>', 'source': { 'path': filename, 'sourceReference': 0, }, 'line': bp1, 'column': 1, }], }), self.new_response(req_continue1.req, **{ 'allThreadsContinued': True }), self.new_event('continued', threadId=tid), self.new_event( 'output', category='stdout', output='== attached! ==', ), self.new_event( 'stopped', threadId=tid, reason='breakpoint', description=None, text=None, ), self.new_response(req_threads3.req, **{ 'threads': [{ 'id': 1, 'name': 'MainThread', }], }), self.new_response(req_stacktrace2.req, **{ 'totalFrames': 1, 'stackFrames': [{ 'id': 2, # TODO: Isn't this the same frame as before? 'name': '<module>', 'source': { 'path': filename, 'sourceReference': 0, }, 'line': bp2, 'column': 1, }], }), self.new_response(req_continue2.req, **{ 'allThreadsContinued': True }), self.new_event('continued', threadId=tid), self.new_event( 'output', category='stdout', output='== done waiting ==', ), #self.new_event( # 'thread', # threadId=tid, # reason='exited', #), #self.new_event('exited', exitCode=0), #self.new_event('terminated'), ]) # before attaching self.assertIn(b'waiting for attach', out1) self.assertNotIn(b'attached!', out1) # after attaching self.assertNotIn('attached!', out2) # after bp1 continue self.assertIn('attached!', out3) self.assertNotIn('done waiting', out3) # after bp2 continue self.assertIn('done waiting', out4)
def test_detach_clear_and_resume(self): addr = Address('localhost', 8888) filename = self.write_script('spam.py', """ import sys sys.path.insert(0, {!r}) import ptvsd addr = {} ptvsd.enable_attach(addr) ptvsd.wait_for_attach() # <before> print('==before==') # <after> print('==after==') # <done> """.format(ROOT, tuple(addr))) lockfile2 = self.workspace.lockfile() done1, _ = set_lock(filename, lockfile2, 'before') lockfile3 = self.workspace.lockfile() _, wait2 = set_release(filename, lockfile3, 'done') lockfile4 = self.workspace.lockfile() done2, script = set_lock(filename, lockfile4, 'done') bp1 = find_line(script, 'before') bp2 = find_line(script, 'after') #DebugAdapter.VERBOSE = True adapter = DebugAdapter.start_embedded(addr, filename) with adapter: with DebugClient() as editor: session1 = editor.attach_socket(addr, adapter, timeout=5) with session1.wait_for_event('thread') as result: with session1.wait_for_event('process'): (req_init1, req_attach1, req_config1, _, _, req_threads1, ) = lifecycle_handshake(session1, 'attach', threads=True) event = result['msg'] tid1 = event.body['threadId'] stopped_event = session1.get_awaiter_for_event('stopped') req_bps = session1.send_request( 'setBreakpoints', source={'path': filename}, breakpoints=[ {'line': bp1}, {'line': bp2}, ], ) req_bps.wait() done1() stopped_event.wait() req_threads2 = session1.send_request('threads') req_stacktrace1 = session1.send_request( 'stackTrace', threadId=tid1, ) out1 = str(adapter.output) # Detach with execution stopped and 1 breakpoint left. req_disconnect = session1.send_request('disconnect') Awaitable.wait_all(req_threads2, req_stacktrace1, req_disconnect) # noqa editor.detach(adapter) try: wait2() except LockTimeoutError: self.fail('execution never resumed upon detach ' 'or breakpoints never cleared') out2 = str(adapter.output) import time time.sleep(2) session2 = editor.attach_socket(addr, adapter, timeout=5) #session2.VERBOSE = True with session2.wait_for_event('thread') as result: with session2.wait_for_event('process'): (req_init2, req_attach2, req_config2, _, _, req_threads3, ) = lifecycle_handshake(session2, 'attach', threads=True) event = result['msg'] tid2 = event.body['threadId'] done2() adapter.wait() out3 = str(adapter.output) received = list(_strip_newline_output_events(session1.received)) self.assert_contains(received, [ self.new_version_event(session1.received), self.new_response(req_init1.req, **INITIALIZE_RESPONSE), self.new_event('initialized'), self.new_response(req_attach1.req), self.new_event( 'thread', threadId=tid1, reason='started', ), self.new_response(req_threads1.req, **{ 'threads': [{ 'id': 1, 'name': 'MainThread', }], }), self.new_response(req_config1.req), self.new_event('process', **{ 'isLocalProcess': True, 'systemProcessId': adapter.pid, 'startMethod': 'attach', 'name': filename, }), self.new_response(req_bps.req, **{ 'breakpoints': [{ 'id': 1, 'line': bp1, 'verified': True, }, { 'id': 2, 'line': bp2, 'verified': True, }], }), self.new_event( 'stopped', threadId=tid1, reason='breakpoint', description=None, text=None, ), self.new_response(req_threads2.req, **{ 'threads': [{ 'id': 1, 'name': 'MainThread', }], }), self.new_response(req_disconnect.req), ]) self.messages.reset_all() received = list(_strip_newline_output_events(session2.received)) # Sometimes the proc ends before the exited and terminated # events are received. received = list(_strip_exit(received)) self.assert_contains(received, [ self.new_version_event(session2.received), self.new_response(req_init2.req, **INITIALIZE_RESPONSE), self.new_event('initialized'), self.new_response(req_attach2.req), self.new_event( 'thread', threadId=tid2, reason='started', ), self.new_response(req_threads3.req, **{ 'threads': [{ 'id': 1, 'name': 'MainThread', }], }), self.new_response(req_config2.req), self.new_event('process', **{ 'isLocalProcess': True, 'systemProcessId': adapter.pid, 'startMethod': 'attach', 'name': filename, }), #self.new_event( # 'thread', # threadId=tid2, # reason='exited', #), #self.new_event('exited', exitCode=0), #self.new_event('terminated'), ]) # at breakpoint self.assertEqual(out1, '') # after detaching self.assertIn('==before==', out2) self.assertIn('==after==', out2) # after reattach self.assertEqual(out3, out2)
def test_reattach(self): lockfile1 = self.workspace.lockfile() done1, waitscript1 = lockfile1.wait_in_script(timeout=5) lockfile2 = self.workspace.lockfile() done2, waitscript2 = lockfile2.wait_in_script(timeout=5) filename = self.write_script('spam.py', waitscript1 + waitscript2) addr = Address('localhost', 8888) #DebugAdapter.VERBOSE = True with DebugAdapter.start_for_attach(addr, filename) as adapter: with DebugClient() as editor: # Attach initially. session1 = editor.attach_socket(addr, adapter) with session1.wait_for_event('thread'): reqs = lifecycle_handshake(session1, 'attach') reqs[1].wait() done1() req_disconnect = session1.send_request('disconnect') req_disconnect.wait() editor.detach(adapter) # Re-attach session2 = editor.attach_socket(addr, adapter) (req_initialize, req_launch, req_config, _, _, _ ) = lifecycle_handshake(session2, 'attach') req_launch.wait() done2() adapter.wait() received = list(_strip_newline_output_events(session1.received)) self.assert_contains(received, [ self.new_version_event(session1.received), self.new_response(reqs[0].req, **INITIALIZE_RESPONSE), self.new_event('initialized'), self.new_response(reqs[1].req), self.new_response(reqs[2].req), self.new_event('process', **{ 'isLocalProcess': True, 'systemProcessId': adapter.pid, 'startMethod': 'attach', 'name': filename, }), self.new_event('thread', reason='started', threadId=1), self.new_response(req_disconnect.req), ]) self.messages.reset_all() received = list(_strip_newline_output_events(session2.received)) self.assert_contains(received, [ self.new_version_event(session2.received), self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), self.new_event('initialized'), self.new_response(req_launch.req), self.new_response(req_config.req), self.new_event('process', **{ 'isLocalProcess': True, 'systemProcessId': adapter.pid, 'startMethod': 'attach', 'name': filename, }), self.new_event('exited', exitCode=0), self.new_event('terminated'), ])
def start_debugging(self, debug_info): addr = Address('localhost', debug_info.port) cwd = debug_info.cwd env = debug_info.env wait_for_port_to_free(debug_info.port) def _kill_proc(pid): """If debugger does not end gracefully, then kill proc and wait for socket connections to die out. """ try: os.kill(pid, signal.SIGTERM) except Exception: pass time.sleep(1) # wait for socket connections to die out. def _wrap_and_reraise(session, ex, exc_type, exc_value, exc_traceback): """If we have connetion errors, then re-raised wrapped in ConnectionTimeoutError. If using py3, then chain exceptions so we do not loose the original exception, else try hack approach for py27.""" messages = [] formatted_ex = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)) # noqa try: messages = [str(msg) for msg in _strip_newline_output_events(session.received)] except Exception: pass message = """ Session Messages: ----------------- {} Original Error: --------------- {}""".format(os.linesep.join(messages), formatted_ex) raise Exception(message) def _handle_exception(ex, adapter, session): exc_type, exc_value, exc_traceback = sys.exc_info() _kill_proc(adapter.pid) _wrap_and_reraise(session, ex, exc_type, exc_value, exc_traceback) if debug_info.verbose: DebugAdapter.VERBOSE = True if debug_info.attachtype == 'import' and \ debug_info.modulename is not None: argv = debug_info.argv with DebugAdapter.start_wrapper_module( debug_info.modulename, argv, env=env, cwd=cwd) as adapter: with DebugClient() as editor: time.sleep(DELAY_WAITING_FOR_SOCKETS) session = editor.attach_socket(addr, adapter) try: yield Debugger(session=session, adapter=adapter) adapter.wait() except Exception as ex: _handle_exception(ex, adapter, session) elif debug_info.attachtype == 'import' and \ debug_info.starttype == 'attach' and \ debug_info.filename is not None: argv = debug_info.argv adapter = DebugAdapter.start_embedded( addr, debug_info.filename, argv=argv, env=env, cwd=cwd, ) with adapter: with DebugClient() as editor: time.sleep(DELAY_WAITING_FOR_SOCKETS) session = editor.attach_socket(addr, adapter) try: yield Debugger(session=session, adapter=adapter) adapter.wait() except Exception as ex: _handle_exception(ex, adapter, session) elif debug_info.starttype == 'attach': if debug_info.modulename is None: name = debug_info.filename kind = 'script' else: name = debug_info.modulename kind = 'module' argv = debug_info.argv adapter = DebugAdapter.start_for_attach( addr, name=name, extra=argv, kind=kind, env=env, cwd=cwd, ) with adapter: with DebugClient() as editor: time.sleep(DELAY_WAITING_FOR_SOCKETS) session = editor.attach_socket(addr, adapter) try: yield Debugger(session=session, adapter=adapter) adapter.wait() except Exception as ex: _handle_exception(ex, adapter, session) else: if debug_info.filename is None: argv = ['-m', debug_info.modulename] + debug_info.argv else: argv = [debug_info.filename] + debug_info.argv with DebugClient( port=debug_info.port, connecttimeout=CONNECT_TIMEOUT) as editor: time.sleep(DELAY_WAITING_FOR_SOCKETS) adapter, session = editor.host_local_debugger( argv, cwd=cwd, env=env) try: yield Debugger(session=session, adapter=adapter) adapter.wait() except Exception as ex: _handle_exception(ex, adapter, session)