From 13df4d7b59fc39a82abb42597901bfa470026fe9 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 1 Mar 2018 00:46:04 +0000 Subject: [PATCH 01/16] Use the vendored pydevd in tests. --- tests/__main__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/__main__.py b/tests/__main__.py index 93baf17e..496b36cc 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -58,6 +58,15 @@ def convert_argv(argv): return cmd + args +def fix_sys_path(): + pydevdroot = os.path.join(PROJECT_ROOT, 'ptvsd', 'pydevd') + if not sys.path[0] or sys.path[0] == '.': + sys.path.insert(1, pydevdroot) + else: + sys.path.insert(0, pydevdroot) + + if __name__ == '__main__': argv = convert_argv(sys.argv[1:]) + fix_sys_path() unittest.main(module=None, argv=argv) From fa15f3bb7c85109a2f2aededc1d8c19efa7ccd85 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 1 Mar 2018 01:03:32 +0000 Subject: [PATCH 02/16] lint --- ptvsd/ipcjson.py | 2 +- ptvsd/wrapper.py | 52 +++++++++++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/ptvsd/ipcjson.py b/ptvsd/ipcjson.py index 8ee55ba5..6700dbc0 100644 --- a/ptvsd/ipcjson.py +++ b/ptvsd/ipcjson.py @@ -287,7 +287,7 @@ class IpcChannel(object): self._wait_for_message() except OSError as exc: if exc.errno == errno.EBADF or self.__exit: # socket closed - return self.__exit + return self.__exit raise except Exception: if self.__exit: diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index e9c9068c..24d15dc7 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -41,6 +41,7 @@ ptvsd_sys_exit_code = 0 WAIT_FOR_DISCONNECT_REQUEST_TIMEOUT = 2 WAIT_FOR_THREAD_FINISH_TIMEOUT = 1 + def unquote(s): if s is None: return None @@ -326,7 +327,7 @@ class ExceptionsManager(object): notify_on_terminate, ignore_libraries, ) - break_mode = 'never' + break_mode = 'never' if break_raised: break_mode = 'always' elif break_uncaught: @@ -425,7 +426,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): self._closed = False self.event_loop_thread = threading.Thread(target=self.loop.run_forever, - name='ptvsd.EventLoop') + name='ptvsd.EventLoop') self.event_loop_thread.daemon = True self.event_loop_thread.start() @@ -449,7 +450,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): self.send_response(self.disconnect_request) self.disconnect_request = None - self.set_exit() + self.set_exit() self.loop.stop() self.event_loop_thread.join(WAIT_FOR_THREAD_FINISH_TIMEOUT) @@ -543,13 +544,16 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): self.process_launch_arguments() self.pydevd_request(pydevd_comm.CMD_RUN, '') self.send_process_event(self.start_reason) - + def process_launch_arguments(self): """Process the launch arguments to configure the debugger""" if self.launch_arguments is None: return - redirect_stdout = 'STDOUT\tSTDERR' if self.launch_arguments.get('redirectOutput', False) == True else '' + if self.launch_arguments.get('redirectOutput', False): + redirect_stdout = 'STDOUT\tSTDERR' + else: + redirect_stdout = '' self.pydevd_request(pydevd_comm.CMD_REDIRECT_OUTPUT, redirect_stdout) def on_disconnect(self, request, args): @@ -562,7 +566,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): if killProcess: os.kill(os.getpid(), signal.SIGTERM) else: - self.send_response(request) + self.send_response(request) @async_handler def on_attach(self, request, args): @@ -612,7 +616,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): name = unquote(xthread['name']) except KeyError: name = None - + if not self.is_debugger_internal_thread(name): pyd_tid = xthread['id'] try: @@ -620,7 +624,8 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): except KeyError: # This is a previously unseen thread vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=True) - self.send_event('thread', reason='started', threadId=vsc_tid) + self.send_event('thread', reason='started', + threadId=vsc_tid) threads.append({'id': vsc_tid, 'name': name}) @@ -638,7 +643,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): try: xframes = self.stack_traces[pyd_tid] except KeyError: - # This means the stack was requested before the + # This means the stack was requested before the # thread was suspended xframes = [] totalFrames = len(xframes) @@ -712,7 +717,8 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): } if bool(xvar['isContainer']): pyd_child = pyd_var + (var['name'],) - var['variablesReference'] = self.var_map.to_vscode(pyd_child, autogen=True) + var['variablesReference'] = self.var_map.to_vscode( + pyd_child, autogen=True) variables.append(var) self.send_response(request, variables=variables) @@ -872,7 +878,9 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): ) def get_exceptionInfo(self, pyd_tid): - """Gets the exception name and description for the given PyDev Thread id""" + """ + Gets the exception name and description for the given PyDev Thread id. + """ with self.active_exceptions_lock: try: exc = self.active_exceptions[pyd_tid] @@ -880,7 +888,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): exc = ExceptionInfo('BaseException', 'exception: no description') return (exc.name, exc.description) - + @pydevd_events.handler(pydevd_comm.CMD_THREAD_CREATE) def on_pydevd_thread_create(self, seq, args): # TODO: docstring @@ -926,7 +934,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=False) except KeyError: return - + text = None if reason in STEP_REASONS: reason = 'step' @@ -941,7 +949,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): with self.stack_traces_lock: self.stack_traces[pyd_tid] = xml.thread.frame - + self.send_event('stopped', reason=reason, threadId=vsc_tid, text=text) @pydevd_events.handler(pydevd_comm.CMD_THREAD_RUN) @@ -965,7 +973,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): for pyd_var, vsc_var in self.var_map.pairs(): if pyd_var[0] == pyd_tid: self.var_map.remove(pyd_var, vsc_var) - + try: vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=False) except KeyError: @@ -1000,7 +1008,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): category = 'stdout' if ctx == '1' else 'stderr' content = unquote(xml.io['s']) self.send_event('output', category=category, output=content) - + ######################## # lifecycle @@ -1046,10 +1054,12 @@ def exit_handler(proc, server_thread): if server_thread.is_alive(): server_thread.join(WAIT_FOR_THREAD_FINISH_TIMEOUT) + def signal_handler(signum, frame, proc): proc.close() sys.exit(0) + def start_server(port): """Return a socket to a (new) local pydevd-handling daemon. @@ -1063,7 +1073,10 @@ def start_server(port): pydevd, proc, server_thread = _start(client, server) atexit.register(lambda: exit_handler(proc, server_thread)) if platform.system() != 'Windows': - signal.signal(signal.SIGHUP, lambda signum, frame: signal_handler(signum, frame, proc)) + signal.signal( + signal.SIGHUP, + (lambda signum, frame: signal_handler(signum, frame, proc)), + ) return pydevd @@ -1080,7 +1093,10 @@ def start_client(host, port): pydevd, proc, server_thread = _start(client, None) atexit.register(lambda: exit_handler(proc, server_thread)) if platform.system() != 'Windows': - signal.signal(signal.SIGHUP, lambda signum, frame: signal_handler(signum, frame, proc)) + signal.signal( + signal.SIGHUP, + (lambda signum, frame: signal_handler(signum, frame, proc)), + ) return pydevd From a1dcecf99faa565f6bb49bcfcfd91da5e465399b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 1 Mar 2018 01:13:48 +0000 Subject: [PATCH 03/16] Do not kill-on-close during tests. --- ptvsd/wrapper.py | 10 ++++++---- tests/helpers/pydevd/_fake.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index 24d15dc7..062ea90e 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -402,12 +402,13 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): protocol. """ - def __init__(self, socket, pydevd, logfile=None): + def __init__(self, socket, pydevd, logfile=None, killonclose=True): super(VSCodeMessageProcessor, self).__init__(socket=socket, own_socket=False, logfile=logfile) self.socket = socket self.pydevd = pydevd + self.killonclose = killonclose self.stack_traces = {} self.stack_traces_lock = threading.Lock() self.active_exceptions = {} @@ -563,7 +564,7 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): self.disconnect_request_event.set() killProcess = not self._closed self.close() - if killProcess: + if killProcess and self.killonclose: os.kill(os.getpid(), signal.SIGTERM) else: self.send_response(request) @@ -1032,11 +1033,12 @@ def _new_sock(): return sock -def _start(client, server): +def _start(client, server, killonclose=True): name = 'ptvsd.Client' if server is None else 'ptvsd.Server' pydevd = PydevdSocket(lambda *args: proc.on_pydevd_event(*args)) - proc = VSCodeMessageProcessor(client, pydevd) + proc = VSCodeMessageProcessor(client, pydevd, + killonclose=killonclose) server_thread = threading.Thread(target=proc.process_messages, name=name) diff --git a/tests/helpers/pydevd/_fake.py b/tests/helpers/pydevd/_fake.py index d5e364f5..1c353628 100644 --- a/tests/helpers/pydevd/_fake.py +++ b/tests/helpers/pydevd/_fake.py @@ -19,7 +19,7 @@ def _bind(address): def connect(_connect=connect): client, server = _connect() - pydevd, _, _ = _ptvsd._start(client, server) + pydevd, _, _ = _ptvsd._start(client, server, killonclose=False) return socket.Connection(pydevd, server) return connect, remote From b0a71d0105678509ea768f02406fe8392fb01941 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 1 Mar 2018 19:35:37 +0000 Subject: [PATCH 04/16] Expect thread events during initialization. --- tests/ptvsd/highlevel/__init__.py | 29 ++++++++++++++++++++------ tests/ptvsd/highlevel/test_messages.py | 4 +++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/tests/ptvsd/highlevel/__init__.py b/tests/ptvsd/highlevel/__init__.py index 49d3464c..32a92a6f 100644 --- a/tests/ptvsd/highlevel/__init__.py +++ b/tests/ptvsd/highlevel/__init__.py @@ -230,8 +230,9 @@ class VSCLifecycle(object): self._initialize(**initargs) self._fix.send_request(command, **kwargs) - self._fix.set_threads(*threads or (), - **dict(default_threads=default_threads)) + if threads: + self._fix.set_threads(*threads, + **dict(default_threads=default_threads)) self._handle_config(**config or {}) with self._fix.expect_debugger_command(CMD_RUN): @@ -385,11 +386,13 @@ class HighlevelFixture(object): self.send_debugger_event(cmdid, text) return None - def set_threads(self, *threads, **kwargs): + def set_threads(self, _thread, *threads, **kwargs): + threads = (_thread,) + threads return self._set_threads(threads, **kwargs) def set_thread(self, thread): - return self.set_threads(thread)[thread] + threads = (thread,) + return self._set_threads(threads)[thread] def _set_threads(self, threads, default_threads=True): request = {t[1]: t for t in threads} @@ -398,9 +401,23 @@ class HighlevelFixture(object): threads = self._add_default_threads(threads) text = self.debugger_msgs.format_threads(*threads) self.set_debugger_response(CMD_RETURN, text, reqid=CMD_LIST_THREADS) - self.send_request('threads') + with self.wait_for_event('thread'): + self.send_request('threads') + if self._hidden: + # The first event was handled by wait_for_event(). + for _, name in tuple(threads)[1:]: + if name.startswith(('ptvsd.', 'pydevd.')): + continue + next(self.vsc_msgs.event_seq) - for tinfo in self.vsc.received[-1].data['body']['threads']: + for msg in reversed(self.vsc.received): + if msg.data['type'] == 'response': + if msg.data['command'] == 'threads': + break + else: + assert False, 'we waited for the response in send_request()' + + for tinfo in msg.data['body']['threads']: try: thread = request[tinfo['name']] except KeyError: diff --git a/tests/ptvsd/highlevel/test_messages.py b/tests/ptvsd/highlevel/test_messages.py index a71378ae..cae2469a 100644 --- a/tests/ptvsd/highlevel/test_messages.py +++ b/tests/ptvsd/highlevel/test_messages.py @@ -191,11 +191,13 @@ class ThreadsTests(NormalRequestTest, unittest.TestCase): received = self.vsc.received self.assert_vsc_received(received, [ + self.new_event('thread', threadId=1, reason='started'), + self.new_event('thread', threadId=2, reason='started'), self.expected_response( threads=[ {'id': 1, 'name': 'spam'}, # Threads named 'pydevd.*' are ignored. - {'id': 3, 'name': ''}, + {'id': 2, 'name': ''}, ], ), # no events From 87fffa7bdb8a5458e6c730995ad34766b248ea34 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 1 Mar 2018 20:18:17 +0000 Subject: [PATCH 05/16] Factor out Request, Response, and Event. --- tests/helpers/vsc/_fake.py | 18 ++- tests/helpers/vsc/_vsc.py | 185 ++++++++++++++++++++++++- tests/ptvsd/highlevel/__init__.py | 10 +- tests/ptvsd/highlevel/test_messages.py | 6 +- 4 files changed, 203 insertions(+), 16 deletions(-) diff --git a/tests/helpers/vsc/_fake.py b/tests/helpers/vsc/_fake.py index c7cecaa0..4c65bf17 100644 --- a/tests/helpers/vsc/_fake.py +++ b/tests/helpers/vsc/_fake.py @@ -89,18 +89,26 @@ class FakeVSC(protocol.Daemon): command = req['command'] def match(msg): - msg = msg.data - if msg['type'] != 'response' or msg['request_seq'] != reqseq: + #msg = parse_message(msg) + try: + actual = msg.request_seq + except AttributeError: return False - assert(msg['command'] == command) + if actual != reqseq: + return False + assert(msg.command == command) return True return self._wait_for_message(match, req, **kwargs) def wait_for_event(self, event, **kwargs): def match(msg): - msg = msg.data - if msg['type'] != 'event' or msg['event'] != event: + #msg = parse_message(msg) + try: + actual = msg.event + except AttributeError: + return False + if actual != event: return False return True diff --git a/tests/helpers/vsc/_vsc.py b/tests/helpers/vsc/_vsc.py index 9b214a74..6aa5d6a1 100644 --- a/tests/helpers/vsc/_vsc.py +++ b/tests/helpers/vsc/_vsc.py @@ -7,6 +7,12 @@ from tests.helpers.protocol import StreamFailure # TODO: Use more of the code from debugger_protocol. +class ProtocolMessageError(Exception): pass # noqa +class MalformedMessageError(ProtocolMessageError): pass # noqa +class IncompleteMessageError(MalformedMessageError): pass # noqa +class UnsupportedMessageTypeError(ProtocolMessageError): pass # noqa + + def parse_message(msg): """Return a message object for the given "msg" data.""" if type(msg) is str: @@ -14,10 +20,29 @@ def parse_message(msg): elif isinstance(msg, bytes): data = json.loads(msg.decode('utf-8')) elif type(msg) is RawMessage: - return msg + try: + msg.data['seq'] + msg.data['type'] + except KeyError: + return msg + return parse_message(msg.data) + elif isinstance(msg, ProtocolMessage): + if msg.TYPE is not None: + return msg + try: + ProtocolMessage._look_up(msg.type) + except UnsupportedMessageTypeError: + return msg + data = msg.as_data() else: data = msg - return RawMessage.from_data(**data) + + cls = look_up(data) + try: + return cls.from_data(**data) + except IncompleteMessageError: + # TODO: simply fail? + return RawMessage.from_data(**data) def encode_message(msg): @@ -29,7 +54,8 @@ def iter_messages(stream, stop=lambda: False): """Yield the correct message for each line-formatted one found.""" while not stop(): try: - msg = wireformat.read(stream, lambda _: RawMessage) + #msg = wireformat.read(stream, lambda _: RawMessage) + msg = wireformat.read(stream, look_up) if msg is None: # EOF break yield msg @@ -37,6 +63,20 @@ def iter_messages(stream, stop=lambda: False): yield StreamFailure('recv', None, exc) +def look_up(data): + """Return the message type to use.""" + try: + msgtype = data['type'] + except KeyError: + # TODO: return RawMessage? + ProtocolMessage._check_data(data) + try: + return ProtocolMessage._look_up(msgtype) + except UnsupportedMessageTypeError: + # TODO: return Message? + raise + + class RawMessage(namedtuple('RawMessage', 'data')): """A wrapper around a line-formatted debugger protocol message.""" @@ -54,3 +94,142 @@ class RawMessage(namedtuple('RawMessage', 'data')): def as_data(self): """Return the corresponding data, ready to be JSON-encoded.""" return self.data + + +class ProtocolMessage(object): + """The base type for VSC debug adapter protocol message.""" + + TYPE = None + + @classmethod + def from_data(cls, **data): + """Return a message for the given JSON-decoded data.""" + try: + return cls(**data) + except TypeError: + cls._check_data(data) + raise + + @classmethod + def _check_data(cls, data): + missing = set(cls._fields) - set(data) + if missing: + raise IncompleteMessageError(','.join(missing)) + + @classmethod + def _look_up(cls, msgtype): + if msgtype == 'request': + return Request + elif msgtype == 'response': + return Response + elif msgtype == 'event': + return Event + else: + raise UnsupportedMessageTypeError(msgtype) + + def __new__(cls, seq, type, **kwargs): + if cls is ProtocolMessage: + return Message(seq, type, **kwargs) + seq = int(seq) + type = str(type) if type else None + unused = {k: kwargs.pop(k) + for k in tuple(kwargs) + if k not in cls._fields} + self = super(ProtocolMessage, cls).__new__(cls, seq, type, **kwargs) + self._unused = unused + return self + + def __init__(self, *args, **kwargs): + if self.TYPE is None: + if self.type is None: + raise TypeError('missing type') + elif self.type != self.TYPE: + msg = 'wrong type (expected {!r}, go {!r}' + raise ValueError(msg.format(self.TYPE, self.type)) + + def __repr__(self): + raw = super(ProtocolMessage, self).__repr__() + if self.TYPE is None: + return raw + return ', '.join(part + for part in raw.split(', ') + if not part.startswith('type=')) + + @property + def unused(self): + return dict(self._unused) + + def as_data(self): + """Return the corresponding data, ready to be JSON-encoded.""" + data = self._asdict() + data.update(self._unused) + return data + + +class Message(ProtocolMessage, namedtuple('Message', 'seq type')): + """A generic DAP message.""" + + def __getattr__(self, name): + try: + return self._unused[name] + except KeyError: + raise AttributeError(name) + + +class Request(ProtocolMessage, + namedtuple('Request', 'seq type command arguments')): + """A DAP request message.""" + + TYPE = 'request' + + def __new__(cls, seq, type, command, arguments, **unused): + # TODO: Make "arguments" immutable? + return super(Request, cls).__new__( + cls, + seq, + type, + command=command, + arguments=arguments, + **unused + ) + + +class Response(ProtocolMessage, + namedtuple('Response', + 'seq type request_seq command success message body'), + ): + """A DAP response message.""" + + TYPE = 'response' + + def __new__(cls, seq, type, request_seq, command, success, message, body, + **unused): + # TODO: Make "body" immutable? + return super(Response, cls).__new__( + cls, + seq, + type, + request_seq=request_seq, + command=command, + success=success, + message=message, + body=body, + **unused + ) + + +class Event(ProtocolMessage, namedtuple('Event', 'seq type event body')): + """A DAP event message.""" + + TYPE = 'event' + + def __new__(cls, seq, type, event, body, **unused): + # TODO: Make "body" immutable? + return super(Event, cls).__new__( + cls, + seq, + type, + event=event, + body=body, + **unused + ) diff --git a/tests/ptvsd/highlevel/__init__.py b/tests/ptvsd/highlevel/__init__.py index 32a92a6f..93a51037 100644 --- a/tests/ptvsd/highlevel/__init__.py +++ b/tests/ptvsd/highlevel/__init__.py @@ -249,7 +249,7 @@ class VSCLifecycle(object): See https://code.visualstudio.com/docs/extensionAPI/api-debugging#_the-vs-code-debug-protocol-in-a-nutshell """ # noqa def handle_response(resp, _): - self._capabilities = resp.data['body'] + self._capabilities = resp.body version = self._fix.debugger.VERSION self._fix.set_debugger_response(CMD_VERSION, version) self._fix.send_request( @@ -411,13 +411,13 @@ class HighlevelFixture(object): next(self.vsc_msgs.event_seq) for msg in reversed(self.vsc.received): - if msg.data['type'] == 'response': - if msg.data['command'] == 'threads': + if msg.type == 'response': + if msg.command == 'threads': break else: assert False, 'we waited for the response in send_request()' - for tinfo in msg.data['body']['threads']: + for tinfo in msg.body['threads']: try: thread = request[tinfo['name']] except KeyError: @@ -541,7 +541,7 @@ class HighlevelTest(object): failure = received[-1] expected = self.vsc.protocol.parse( - self.fix.vsc_msgs.new_failure(req, failure.data['message'])) + self.fix.vsc_msgs.new_failure(req, failure.message)) self.assertEqual(failure, expected) def assert_received(self, daemon, expected): diff --git a/tests/ptvsd/highlevel/test_messages.py b/tests/ptvsd/highlevel/test_messages.py index cae2469a..9cdef6ff 100644 --- a/tests/ptvsd/highlevel/test_messages.py +++ b/tests/ptvsd/highlevel/test_messages.py @@ -1507,7 +1507,7 @@ class ThreadEventTest(PyDevdEventTest): def send_event(self, *args, **kwargs): def handler(msg, _): - self._tid = msg.data['body']['threadId'] + self._tid = msg.body['threadId'] kwargs['handler'] = handler super(ThreadEventTest, self).send_event(*args, **kwargs) return self._tid @@ -1844,8 +1844,8 @@ class SendCurrExcTraceTests(PyDevdEventTest, unittest.TestCase): self.assert_vsc_received(received, []) self.assert_received(self.debugger, []) - self.assertTrue(resp.data['success'], resp.data['message']) - self.assertEqual(resp.data['body'], dict( + self.assertTrue(resp.success, resp.message) + self.assertEqual(resp.body, dict( exceptionId='RuntimeError', description='something went wrong', breakMode='unhandled', From 04abc78a1cbe092adbd1df1f7e9bd7bda62f9f56 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 1 Mar 2018 22:37:43 +0000 Subject: [PATCH 06/16] Add a Counter class. --- tests/helpers/counter.py | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/helpers/counter.py diff --git a/tests/helpers/counter.py b/tests/helpers/counter.py new file mode 100644 index 00000000..5a3928de --- /dev/null +++ b/tests/helpers/counter.py @@ -0,0 +1,60 @@ + +class Counter(object): + """An introspectable, dynamic alternative to itertools.count().""" + + def __init__(self, start=0, step=1): + self._start = int(start) + self._step = int(step) + + def __repr__(self): + if self._last is None: + start = self._start + else: + start = self._last + self._step + return '{}(start={}, step={})'.format( + type(self).__name__, + start, + self._step, + ) + + def __iter__(self): + return self + + def __next__(self): + try: + self._last += self._step + except AttributeError: + self._last = self._start + return self._last + + @property + def start(self): + return self._start + + @property + def step(self): + return self._step + + @property + def last(self): + try: + return self._last + except AttributeError: + return None + + def peek(self, iterations=1): + """Return the value that will be used next.""" + try: + last = self._last + except AttributeError: + last = self._start - self._step + return last + self._step * iterations + + def reset(self, start=None): + """Set the next value to the given one. + + If no value is provided then the previous start value is used. + """ + if start is not None: + self._start = int(start) + self._next = self._start From c18b9a388f4ad5e7cb15f8ccc5662d5a2ba09198 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 1 Mar 2018 22:39:23 +0000 Subject: [PATCH 07/16] Add a MessageCounters helper. --- tests/helpers/protocol.py | 37 +++++++++++++++++++++++ tests/ptvsd/highlevel/__init__.py | 49 +++++++++++++++++-------------- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/tests/helpers/protocol.py b/tests/helpers/protocol.py index cdc43389..8a6fc660 100644 --- a/tests/helpers/protocol.py +++ b/tests/helpers/protocol.py @@ -3,6 +3,7 @@ import errno import threading from . import socket +from .counter import Counter class StreamFailure(Exception): @@ -40,6 +41,42 @@ class MessageProtocol(namedtuple('Protocol', 'parse encode iter')): yield self.parse(msg) +class MessageCounters(namedtuple('MessageCounters', + 'request response event')): + """Track the next "seq" value for the protocol message types.""" + + REQUEST_INC = 1 + RESPONSE_INC = 1 + EVENT_INC = 1 + + def __new__(cls, request=0, response=0, event=None): + request = Counter(request, cls.REQUEST_INC) + if response is None: + response = request + else: + response = Counter(response, cls.RESPONSE_INC) + if event is None: + event = response + else: + event = Counter(event, cls.EVENT_INC) + self = super(MessageCounters, cls).__new__( + cls, + request, + response, + event, + ) + return self + + def next_request(self): + return next(self.request) + + def next_response(self): + return next(self.response) + + def next_event(self): + return next(self.event) + + class Started(object): """A simple wrapper around a started message protocol daemon.""" diff --git a/tests/ptvsd/highlevel/__init__.py b/tests/ptvsd/highlevel/__init__.py index 93a51037..9fa2a080 100644 --- a/tests/ptvsd/highlevel/__init__.py +++ b/tests/ptvsd/highlevel/__init__.py @@ -17,6 +17,7 @@ from _pydevd_bundle.pydevd_comm import ( CMD_SEND_CURR_EXCEPTION_TRACE, ) +from tests.helpers.protocol import MessageCounters from tests.helpers.pydevd import FakePyDevd from tests.helpers.vsc import FakeVSC @@ -33,18 +34,20 @@ class PyDevdMessages(object): response_seq=0, # PyDevd responses/events to ptvsd event_seq=None, ): - self.request_seq = itertools.count(request_seq) - self.response_seq = itertools.count(response_seq) - if event_seq is None: - self.event_seq = self.response_seq - else: - self.event_seq = itertools.count(event_seq) + self.counters = MessageCounters( + request_seq, + response_seq, + event_seq, + ) + + def __getattr__(self, name): + return getattr(self.counters, name) def new_request(self, cmdid, *args, **kwargs): """Return a new PyDevd request message.""" seq = kwargs.pop('seq', None) if seq is None: - seq = next(self.request_seq) + seq = self.counters.next_request() return self._new_message(cmdid, seq, args, **kwargs) def new_response(self, req, *args): @@ -59,7 +62,7 @@ class PyDevdMessages(object): """Return a new VSC event message.""" seq = kwargs.pop('seq', None) if seq is None: - seq = next(self.event_seq) + seq = self.counters.next_event() return self._new_message(cmdid, seq, args, **kwargs) def _new_message(self, cmdid, seq, args=()): @@ -120,17 +123,19 @@ class VSCMessages(object): response_seq=0, # ptvsd responses/events to VSC event_seq=None, ): - self.request_seq = itertools.count(request_seq) - self.response_seq = itertools.count(response_seq) - if event_seq is None: - self.event_seq = self.response_seq - else: - self.event_seq = itertools.count(event_seq) + self.counters = MessageCounters( + request_seq, + response_seq, + event_seq, + ) + + def __getattr__(self, name): + return getattr(self.counters, name) def new_request(self, command, seq=None, **args): """Return a new VSC request message.""" if seq is None: - seq = next(self.request_seq) + seq = self.counters.next_request() return { 'type': 'request', 'seq': seq, @@ -148,7 +153,7 @@ class VSCMessages(object): def _new_response(self, req, err=None, seq=None, body=None): if seq is None: - seq = next(self.response_seq) + seq = self.counters.next_response() return { 'type': 'response', 'seq': seq, @@ -162,7 +167,7 @@ class VSCMessages(object): def new_event(self, eventname, seq=None, **body): """Return a new VSC event message.""" if seq is None: - seq = next(self.event_seq) + seq = self.counters.next_event() return { 'type': 'event', 'seq': seq, @@ -354,25 +359,25 @@ class HighlevelFixture(object): with self.vsc.wait_for_response(req, handler=handler): yield req if self._hidden: - next(self.vsc_msgs.response_seq) + self.vsc_msgs.next_response() @contextlib.contextmanager def wait_for_event(self, event, *args, **kwargs): with self.vsc.wait_for_event(event, *args, **kwargs): yield if self._hidden: - next(self.vsc_msgs.event_seq) + self.vsc_msgs.next_event() @contextlib.contextmanager def expect_debugger_command(self, cmdid): yield if self._hidden: - next(self.debugger_msgs.request_seq) + self.debugger_msgs.next_request() def set_debugger_response(self, cmdid, payload, **kwargs): self.debugger.add_pending_response(cmdid, payload, **kwargs) if self._hidden: - next(self.debugger_msgs.request_seq) + self.debugger_msgs.next_request() def send_debugger_event(self, cmdid, payload): event = self.debugger_msgs.new_event(cmdid, payload) @@ -408,7 +413,7 @@ class HighlevelFixture(object): for _, name in tuple(threads)[1:]: if name.startswith(('ptvsd.', 'pydevd.')): continue - next(self.vsc_msgs.event_seq) + self.vsc_msgs.next_event() for msg in reversed(self.vsc.received): if msg.type == 'response': From 98eec95cf24e3eec81768b51d253bd5e945619ef Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 00:07:34 +0000 Subject: [PATCH 08/16] Fix the default threads. --- tests/ptvsd/highlevel/__init__.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/tests/ptvsd/highlevel/__init__.py b/tests/ptvsd/highlevel/__init__.py index 9fa2a080..22ad7f02 100644 --- a/tests/ptvsd/highlevel/__init__.py +++ b/tests/ptvsd/highlevel/__init__.py @@ -297,6 +297,7 @@ class HighlevelFixture(object): self.debugger_msgs = PyDevdMessages() self._hidden = False + self._default_threads = None @property def vsc(self): @@ -368,6 +369,15 @@ class HighlevelFixture(object): if self._hidden: self.vsc_msgs.next_event() + @contextlib.contextmanager + def _wait_for_events(self, events): + if not events: + yield + return + with self._wait_for_events(events[1:]): + with self.wait_for_event(events[0]): + yield + @contextlib.contextmanager def expect_debugger_command(self, cmdid): yield @@ -404,16 +414,13 @@ class HighlevelFixture(object): response = {t: None for t in threads} if default_threads: threads = self._add_default_threads(threads) + active = [name + for _, name in threads + if not name.startswith(('ptvsd.', 'pydevd.'))] text = self.debugger_msgs.format_threads(*threads) self.set_debugger_response(CMD_RETURN, text, reqid=CMD_LIST_THREADS) - with self.wait_for_event('thread'): + with self._wait_for_events(['thread' for _ in active]): self.send_request('threads') - if self._hidden: - # The first event was handled by wait_for_event(). - for _, name in tuple(threads)[1:]: - if name.startswith(('ptvsd.', 'pydevd.')): - continue - self.vsc_msgs.next_event() for msg in reversed(self.vsc.received): if msg.type == 'response': @@ -431,6 +438,8 @@ class HighlevelFixture(object): return response def _add_default_threads(self, threads): + if self._default_threads is not None: + return threads defaults = { 'MainThread', 'ptvsd.Server', @@ -449,6 +458,7 @@ class HighlevelFixture(object): tid = next(ids) thread = tid, tname allthreads.append(thread) + self._default_threads = list(allthreads) allthreads.extend(threads) return allthreads From 5452de8c66a51163c759b87c7b7613491835ba30 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 00:18:52 +0000 Subject: [PATCH 09/16] Fix thread IDs. --- tests/ptvsd/highlevel/test_messages.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/ptvsd/highlevel/test_messages.py b/tests/ptvsd/highlevel/test_messages.py index 9cdef6ff..6b7c32ee 100644 --- a/tests/ptvsd/highlevel/test_messages.py +++ b/tests/ptvsd/highlevel/test_messages.py @@ -586,14 +586,15 @@ class PauseTests(NormalRequestTest, unittest.TestCase): PYDEVD_RESP = None def test_pause_one(self): + thread = (10, 'spam') with self.launched(): with self.hidden(): - self.set_threads( - (10, 'spam'), + tids = self.set_threads( + thread, (11, ''), ) self.send_request( - threadId=5, # matches our first thread + threadId=tids[thread], ) received = self.vsc.received @@ -621,11 +622,11 @@ class ContinueTests(NormalRequestTest, unittest.TestCase): thread = (10, 'x') with self.launched(): with self.hidden(): - self.pause(thread, *[ + tid = self.pause(thread, *[ (2, 'spam', 'abc.py', 10), ]) self.send_request( - threadId=5, # matches our thread + threadId=tid, ) received = self.vsc.received @@ -648,11 +649,11 @@ class NextTests(NormalRequestTest, unittest.TestCase): thread = (10, 'x') with self.launched(): with self.hidden(): - self.pause(thread, *[ + tid = self.pause(thread, *[ (2, 'spam', 'abc.py', 10), ]) self.send_request( - threadId=5, # matches our thread + threadId=tid, ) received = self.vsc.received @@ -675,11 +676,11 @@ class StepInTests(NormalRequestTest, unittest.TestCase): thread = (10, 'x') with self.launched(): with self.hidden(): - self.pause(thread, *[ + tid = self.pause(thread, *[ (2, 'spam', 'abc.py', 10), ]) self.send_request( - threadId=5, # matches our thread + threadId=tid, ) received = self.vsc.received @@ -702,11 +703,11 @@ class StepOutTests(NormalRequestTest, unittest.TestCase): thread = (10, 'x') with self.launched(): with self.hidden(): - self.pause(thread, *[ + tid = self.pause(thread, *[ (2, 'spam', 'abc.py', 10), ]) self.send_request( - threadId=5, # matches our thread + threadId=tid, ) received = self.vsc.received From 3df19db59fa27c92050fbac59abe81cfca19fcfe Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 00:24:27 +0000 Subject: [PATCH 10/16] Un-skip tests for fixed bugs. --- tests/ptvsd/highlevel/test_messages.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/ptvsd/highlevel/test_messages.py b/tests/ptvsd/highlevel/test_messages.py index 6b7c32ee..c4684bee 100644 --- a/tests/ptvsd/highlevel/test_messages.py +++ b/tests/ptvsd/highlevel/test_messages.py @@ -1574,8 +1574,6 @@ class ThreadKillTests(ThreadEventTest, unittest.TestCase): def pydevd_payload(self, threadid): return str(threadid) - # TODO: https://github.com/Microsoft/ptvsd/issues/138 - @unittest.skip('broken') def test_known(self): thread = (10, 'x') with self.launched(): @@ -1600,8 +1598,6 @@ class ThreadKillTests(ThreadEventTest, unittest.TestCase): self.assert_vsc_received(received, []) self.assert_received(self.debugger, []) - # TODO: https://github.com/Microsoft/ptvsd/issues/137 - @unittest.skip('broken') def test_pydevd_name(self): thread = (10, 'pydevd.spam') with self.launched(): @@ -1613,8 +1609,6 @@ class ThreadKillTests(ThreadEventTest, unittest.TestCase): self.assert_vsc_received(received, []) self.assert_received(self.debugger, []) - # TODO: https://github.com/Microsoft/ptvsd/issues/137 - @unittest.skip('broken') def test_ptvsd_name(self): thread = (10, 'ptvsd.spam') with self.launched(): @@ -1862,8 +1856,6 @@ class SendCurrExcTraceProceededTests(PyDevdEventTest, unittest.TestCase): CMD = CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED EVENT = None - # See https://github.com/Microsoft/ptvsd/issues/141. - def pydevd_payload(self, threadid): return str(threadid) @@ -1874,10 +1866,24 @@ class SendCurrExcTraceProceededTests(PyDevdEventTest, unittest.TestCase): text = self.debugger_msgs.format_exception(thread[0], exc, frame) with self.launched(): with self.hidden(): - self.set_thread(thread) + tid = self.set_thread(thread) self.fix.send_event(CMD_SEND_CURR_EXCEPTION_TRACE, text) + self.send_request('exceptionInfo', dict( + threadId=tid, + )) + before = self.vsc.received[-1] + self.send_event(10) received = self.vsc.received + self.send_request('exceptionInfo', dict( + threadId=tid, + )) + after = self.vsc.received[-1] + self.assert_vsc_received(received, []) self.assert_received(self.debugger, []) + # The exception got cleared so we do not see RuntimeError. + self.assertEqual(after.body['exceptionId'], 'BaseException') + self.assertNotEqual(after.body['exceptionId'], + before.body['exceptionId']) From bf57745d846505084d0bde3a4339e24fc62a6a92 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 00:35:50 +0000 Subject: [PATCH 11/16] Fix the lifecycle tests. --- tests/ptvsd/highlevel/test_lifecycle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ptvsd/highlevel/test_lifecycle.py b/tests/ptvsd/highlevel/test_lifecycle.py index 93f01a26..60fbee2a 100644 --- a/tests/ptvsd/highlevel/test_lifecycle.py +++ b/tests/ptvsd/highlevel/test_lifecycle.py @@ -138,8 +138,9 @@ class LifecycleTests(HighlevelTest, unittest.TestCase): isLocalProcess=True, startMethod='launch', )), - self.new_response(req_disconnect), self.new_event('exited', exitCode=0), + self.new_event('terminated'), + self.new_response(req_disconnect), ]) self.assert_received(self.debugger, [ self.debugger_msgs.new_request(CMD_VERSION, From b8620c4dcfc435c31fe083db65a7201c10256a9e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 01:14:13 +0000 Subject: [PATCH 12/16] Add the "text" message for exception events. --- tests/ptvsd/highlevel/test_messages.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/ptvsd/highlevel/test_messages.py b/tests/ptvsd/highlevel/test_messages.py index c4684bee..98074549 100644 --- a/tests/ptvsd/highlevel/test_messages.py +++ b/tests/ptvsd/highlevel/test_messages.py @@ -1647,6 +1647,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='step', threadId=tid, + text=None, ), ]) self.assert_received(self.debugger, []) @@ -1663,6 +1664,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='step', threadId=tid, + text=None, ), ]) self.assert_received(self.debugger, []) @@ -1679,6 +1681,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='step', threadId=tid, + text=None, ), ]) self.assert_received(self.debugger, []) @@ -1695,6 +1698,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='exception', threadId=tid, + text='BaseException, exception: no description', ), ]) self.assert_received(self.debugger, []) @@ -1711,6 +1715,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='exception', threadId=tid, + text='BaseException, exception: no description', ), ]) self.assert_received(self.debugger, []) @@ -1727,6 +1732,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='pause', threadId=tid, + text=None, ), ]) self.assert_received(self.debugger, []) @@ -1744,6 +1750,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='pause', threadId=tid, + text=None, ), ]) self.assert_received(self.debugger, []) @@ -1762,6 +1769,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='pause', threadId=tid, + text=None, ), ]) self.assert_received(self.debugger, []) @@ -1781,6 +1789,7 @@ class ThreadSuspendTests(ThreadEventTest, unittest.TestCase): self.expected_event( reason='pause', threadId=tid, + text=None, ), ]) self.assert_received(self.debugger, []) From 4b6a557ecc126c25cb0364d6ecb0523c712d9e0c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 01:35:22 +0000 Subject: [PATCH 13/16] Fix Counter.__repr__(). --- tests/helpers/counter.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/helpers/counter.py b/tests/helpers/counter.py index 5a3928de..2c4a3dd9 100644 --- a/tests/helpers/counter.py +++ b/tests/helpers/counter.py @@ -7,13 +7,9 @@ class Counter(object): self._step = int(step) def __repr__(self): - if self._last is None: - start = self._start - else: - start = self._last + self._step return '{}(start={}, step={})'.format( type(self).__name__, - start, + self.peek(), self._step, ) From e1d1e5a062dc10709e622039b73986cd4a0a308c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 01:48:28 +0000 Subject: [PATCH 14/16] Handle CMD_REDIRECT_OUTPUT in tests. --- tests/ptvsd/highlevel/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/ptvsd/highlevel/__init__.py b/tests/ptvsd/highlevel/__init__.py index 22ad7f02..0c2a2d37 100644 --- a/tests/ptvsd/highlevel/__init__.py +++ b/tests/ptvsd/highlevel/__init__.py @@ -11,6 +11,7 @@ from _pydevd_bundle.pydevd_comm import ( CMD_VERSION, CMD_LIST_THREADS, CMD_THREAD_SUSPEND, + CMD_REDIRECT_OUTPUT, CMD_RETURN, CMD_RUN, CMD_STEP_CAUGHT_EXCEPTION, @@ -240,9 +241,10 @@ class VSCLifecycle(object): **dict(default_threads=default_threads)) self._handle_config(**config or {}) - with self._fix.expect_debugger_command(CMD_RUN): - with self._fix.wait_for_event('process'): - self._fix.send_request('configurationDone') + with self._fix.expect_debugger_command(CMD_REDIRECT_OUTPUT): + with self._fix.expect_debugger_command(CMD_RUN): + with self._fix.wait_for_event('process'): + self._fix.send_request('configurationDone') if reset: self._fix.reset() From 0502d0496edcae7109027d4b0ba5b8a638a5e91f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 01:54:17 +0000 Subject: [PATCH 15/16] Fix a dict ordering failure in a test. --- tests/ptvsd/highlevel/test_messages.py | 28 ++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/ptvsd/highlevel/test_messages.py b/tests/ptvsd/highlevel/test_messages.py index 98074549..deffc17a 100644 --- a/tests/ptvsd/highlevel/test_messages.py +++ b/tests/ptvsd/highlevel/test_messages.py @@ -1172,10 +1172,16 @@ class SetExceptionBreakpointsTests(NormalRequestTest, unittest.TestCase): self.expected_response(), ]) self.PYDEVD_CMD = CMD_REMOVE_EXCEPTION_BREAK - removed = [ - self.expected_pydevd_request('python-ImportError'), - self.expected_pydevd_request('python-BaseException'), - ] + if self.debugger.received[0].payload == 'python-ImportError': + removed = [ + self.expected_pydevd_request('python-ImportError'), + self.expected_pydevd_request('python-BaseException'), + ] + else: + removed = [ + self.expected_pydevd_request('python-BaseException'), + self.expected_pydevd_request('python-ImportError'), + ] self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK self.assert_received(self.debugger, removed + [ self.expected_pydevd_request('python-ImportError\t0\t0\t0'), @@ -1328,10 +1334,16 @@ class SetExceptionBreakpointsTests(NormalRequestTest, unittest.TestCase): self.expected_response(), ]) self.PYDEVD_CMD = CMD_REMOVE_EXCEPTION_BREAK - removed = [ - self.expected_pydevd_request('python-ImportError'), - self.expected_pydevd_request('python-BaseException'), - ] + if self.debugger.received[0].payload == 'python-ImportError': + removed = [ + self.expected_pydevd_request('python-ImportError'), + self.expected_pydevd_request('python-BaseException'), + ] + else: + removed = [ + self.expected_pydevd_request('python-BaseException'), + self.expected_pydevd_request('python-ImportError'), + ] self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK self.assert_received(self.debugger, removed + [ self.expected_pydevd_request('python-BaseException\t3\t0\t0'), From 89fce50c1f50ac47615e9b3fe6aedbc1d9f21869 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Mar 2018 01:56:10 +0000 Subject: [PATCH 16/16] Fix the lifecycle tests. --- tests/ptvsd/highlevel/test_lifecycle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ptvsd/highlevel/test_lifecycle.py b/tests/ptvsd/highlevel/test_lifecycle.py index 60fbee2a..5d26f953 100644 --- a/tests/ptvsd/highlevel/test_lifecycle.py +++ b/tests/ptvsd/highlevel/test_lifecycle.py @@ -3,6 +3,7 @@ import sys import unittest from _pydevd_bundle.pydevd_comm import ( + CMD_REDIRECT_OUTPUT, CMD_RUN, CMD_VERSION, ) @@ -145,5 +146,6 @@ class LifecycleTests(HighlevelTest, unittest.TestCase): self.assert_received(self.debugger, [ self.debugger_msgs.new_request(CMD_VERSION, *['1.1', OS_ID, 'ID']), + self.debugger_msgs.new_request(CMD_REDIRECT_OUTPUT), self.debugger_msgs.new_request(CMD_RUN), ])