From 270e668bde1120f695f382f75c6621cab1c3df43 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 11 Apr 2018 15:41:52 -0700 Subject: [PATCH 01/18] no debug launcher --- .../PythonTools/visualstudio_py_launcher.py | 52 +- pythonFiles/experimental/message_processor.py | 259 +++++++ pythonFiles/experimental/nodebug_launcher.py | 128 ++++ .../experimental/visualstudio_py_util.py | 631 ++++++++++++++++++ .../debugger/DebugClients/DebugFactory.ts | 6 +- .../debugger/DebugClients/LocalDebugClient.ts | 6 +- .../debugger/DebugClients/launcherProvider.ts | 6 + .../DebugClients/localDebugClientV2.ts | 4 + .../debugger/DebugClients/nonDebugClientV2.ts | 26 +- 9 files changed, 1064 insertions(+), 54 deletions(-) create mode 100644 pythonFiles/experimental/message_processor.py create mode 100644 pythonFiles/experimental/nodebug_launcher.py create mode 100644 pythonFiles/experimental/visualstudio_py_util.py diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 12b2140c4f72..0d13964dd660 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. @@ -45,19 +45,16 @@ # Arguments are: # 1. Working directory. # 2. VS debugger port to connect to. -# 3. GUID for the debug session. -# 4. Debug options (as integer - see enum PythonDebugOptions). -# 5. '-m' or '-c' to override the default run-as mode. [optional] -# 6. Startup script name. -# 7. Script arguments. +# 3. '-m' or '-c' to override the default run-as mode. [optional] +# 4. Startup script name. +# 5. Script arguments. # change to directory we expected to start from os.chdir(sys.argv[1]) port_num = int(sys.argv[2]) -debug_id = sys.argv[3] -debug_options = vspd.parse_debug_options(sys.argv[4]) -del sys.argv[0:5] + +del sys.argv[0:3] # set run_as mode appropriately run_as = 'script' @@ -71,22 +68,23 @@ # preserve filename before we del sys filename = sys.argv[0] -# fix sys.path to be the script file dir -sys.path[0] = '' - -# exclude ourselves from being debugged -vspd.DONT_DEBUG.append(os.path.normcase(__file__)) - -## Begin modification by Don Jayamanne -# Get current Process id to pass back to debugger -currentPid = os.getpid() -## End Modification by Don Jayamanne +# Load the debugger package +try: + import ptvsd + import ptvsd.debugger as vspd + vspd.DONT_DEBUG.append(os.path.normcase(__file__)) +except: + traceback.print_exc() + print(''' +Internal error detected. Please copy the above traceback and report at +https://github.com/Microsoft/vscode-python/issues/new -# remove all state we imported -del sys, os +Press Enter to close. . .''') + try: + raw_input() + except NameError: + input() + sys.exit(1) # and start debugging -## Begin modification by Don Jayamanne -# Pass current Process id to pass back to debugger -vspd.debug(filename, port_num, debug_id, debug_options, currentPid, run_as) -## End Modification by Don Jayamanne +vspd.debug(filename, port_num, '34806ad9-833a-4524-8cd6-18ca4aa74f14', '', run_as) diff --git a/pythonFiles/experimental/message_processor.py b/pythonFiles/experimental/message_processor.py new file mode 100644 index 000000000000..f953003f982c --- /dev/null +++ b/pythonFiles/experimental/message_processor.py @@ -0,0 +1,259 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import atexit +import os +import platform +import signal +import socket +import threading +import warnings + +from ptvsd.daemon import DaemonClosedError +from ptvsd import ipcjson, __version__ +from ptvsd.socket import close_socket +from ptvsd.wrapper import WAIT_FOR_DISCONNECT_REQUEST_TIMEOUT, WAIT_FOR_THREAD_FINISH_TIMEOUT + + +class Daemon(object): + """The process-level manager for the VSC protocol debug adapter.""" + + exitcode = 0 + exiting_via_exit_handler = False + + def __init__(self, notify_launch=lambda : None, addhandlers=True, killonclose=True): + self.addhandlers = addhandlers + self.killonclose = killonclose + self._notify_launch = notify_launch + + self._closed = False + self._client = None + self._adapter = None + + def start(self, server=None): + if self._closed: + raise DaemonClosedError() + return None + + def set_connection(self, client): + """Set the client socket to use for the debug adapter. + + A VSC message loop is started for the client. + """ + if self._closed: + raise DaemonClosedError() + if self._client is not None: + raise RuntimeError('connection already set') + self._client = client + + self._adapter = VSCodeMessageProcessor( + client, + self._notify_launch, + self._handle_vsc_disconnect, + self._handle_vsc_close, + ) + self._adapter.start() + if self.addhandlers: + self._add_atexit_handler() + self._set_signal_handlers() + return self._adapter + + def close(self): + """Stop all loops and release all resources.""" + if self._closed: + raise DaemonClosedError('already closed') + self._closed = True + + if self._client is not None: + self._release_connection() + + # internal methods + + def _add_atexit_handler(self): + def handler(): + self.exiting_via_exit_handler = True + if not self._closed: + self.close() + if self._adapter is not None: + self._adapter._wait_for_server_thread() + atexit.register(handler) + + def _set_signal_handlers(self): + if platform.system() == 'Windows': + return None + + def handler(signum, frame): + if not self._closed: + self.close() + sys.exit(0) + signal.signal(signal.SIGHUP, handler) + + def _release_connection(self): + if self._adapter is not None: + self._adapter.handle_stopped(self.exitcode) + self._adapter.close() + close_socket(self._client) + + # internal methods for VSCodeMessageProcessor + + def _handle_vsc_disconnect(self, kill=False): + if not self._closed: + self.close() + if kill and self.killonclose and not self.exiting_via_exit_handler: + os.kill(os.getpid(), signal.SIGTERM) + + def _handle_vsc_close(self): + if self._closed: + return + self.close() + + +class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): + """IPC JSON message processor for VSC debugger protocol. + + This translates between the VSC debugger protocol and the pydevd + protocol. + """ + + def __init__(self, socket, + notify_launch=lambda : None, + notify_disconnecting = lambda: None, + notify_closing = lambda: None, + logfile=None, + ): + super(VSCodeMessageProcessor, self).__init__(socket=socket, + own_socket=False, + logfile=logfile) + self._socket = socket + self._notify_launch = notify_launch + self._notify_disconnecting = notify_disconnecting + self._notify_closing = notify_closing + + self.server_thread = None + self._closed = False + + # adapter state + self.disconnect_request = None + self.disconnect_request_event = threading.Event() + self._exited = False + + def start(self): + # VSC msg processing loop + self.server_thread = threading.Thread( + target=self.process_messages, + name='ptvsd.Client1234', + ) + self.server_thread.daemon = True + self.server_thread.start() + + # special initialization + self.send_event( + 'output', + category='telemetry', + output='ptvsd', + data={'version': __version__, 'nodebug': True}, + ) + + # closing the adapter + + def close(self): + """Stop the message processor and release its resources.""" + if self._closed: + return + self._closed = True + + self._notify_closing() + # Close the editor-side socket. + self._stop_vsc_message_loop() + + def _stop_vsc_message_loop(self): + self.set_exit() + if self._socket: + try: + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + except: + pass + + def _wait_for_server_thread(self): + if self.server_thread is None: + return + if not self.server_thread.is_alive(): + return + self.server_thread.join(WAIT_FOR_THREAD_FINISH_TIMEOUT) + + def handle_stopped(self, exitcode): + """Finalize the protocol connection.""" + if self._exited: + return + self._exited = True + + # Notify the editor that the "debuggee" (e.g. script, app) exited. + self.send_event('exited', exitCode=exitcode) + + # Notify the editor that the debugger has stopped. + self.send_event('terminated') + + # The editor will send a "disconnect" request at this point. + self._wait_for_disconnect() + + def _wait_for_disconnect(self, timeout=None): + if timeout is None: + timeout = WAIT_FOR_DISCONNECT_REQUEST_TIMEOUT + + if not self.disconnect_request_event.wait(timeout): + warnings.warn('timed out waiting for disconnect request') + if self.disconnect_request is not None: + self.send_response(self.disconnect_request) + self.disconnect_request = None + + def _handle_disconnect(self, request): + self.disconnect_request = request + self.disconnect_request_event.set() + self._notify_disconnecting(not self._closed) + if not self._closed: + self.close() + + + # VSC protocol handlers + + def on_initialize(self, request, args): + # TODO: docstring + self.send_response( + request, + supportsExceptionInfoRequest=True, + supportsConfigurationDoneRequest=True, + supportsConditionalBreakpoints=True, + supportsSetVariable=True, + supportsExceptionOptions=True, + supportsEvaluateForHovers=True, + supportsValueFormattingOptions=True, + supportsSetExpression=True, + supportsModulesRequest=True, + exceptionBreakpointFilters=[ + { + 'filter': 'raised', + 'label': 'Raised Exceptions', + 'default': False + }, + { + 'filter': 'uncaught', + 'label': 'Uncaught Exceptions', + 'default': True + }, + ], + ) + self.send_event('initialized') + + def on_configurationDone(self, request, args): + self.send_response(request) + + def on_launch(self, request, args): + self._notify_launch() + self.send_response(request) + + def on_disconnect(self, request, args): + self._handle_disconnect(request) + + def on_invalid_request(self, request, args): + self.send_response(request, success=True) diff --git a/pythonFiles/experimental/nodebug_launcher.py b/pythonFiles/experimental/nodebug_launcher.py new file mode 100644 index 000000000000..c2304d892e70 --- /dev/null +++ b/pythonFiles/experimental/nodebug_launcher.py @@ -0,0 +1,128 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""Run a block of code or Python file.""" + +import os.path +import sys +import threading +import time +import traceback + +try: + import ptvsd + from message_processor import Daemon + from ptvsd.pydevd_hooks import start_client + import visualstudio_py_util as _vspu +except: + traceback.print_exc() + print("""Internal error detected. Please copy the above traceback and report at +https://github.com/Microsoft/vscode-python/issues""") + sys.exit(1) + +HOSTNAME = 'localhost' +WAIT_FOR_LAUNCH_REQUEST_TIMEOUT = 10000 + +def parse_argv(): + """Parses arguments for use with the launcher. + Arguments are: + 1. Working directory. + 2. VS debugger port to connect to. + 3. '-m' or '-c' to override the default run-as mode. [optional]. + 4. Startup script name. + 5. Script arguments. + """ + + # Change to directory we expected to start from. + os.chdir(sys.argv[1]) + + port_num = int(sys.argv[2]) + + del sys.argv[:3] + + # Set run_as mode appropriately + run_as = 'script' + if sys.argv and sys.argv[0] == '-m': + run_as = 'module' + del sys.argv[0] + elif sys.argv and sys.argv[0] == '-c': + run_as = 'code' + del sys.argv[0] + + # Preserve filename before we del sys. + filename = sys.argv[0] + + # Fix sys.path to be the script file dir. + sys.path[0] = '' + + return (filename, port_num, run_as) + +def run(file, port_num, run_as='script'): + if not start_message_processor(HOSTNAME, port_num): + return + + # Now execute main file. + globals_obj = {'__name__': '__main__'} + try: + if run_as == 'module': + _vspu.exec_module(file, globals_obj) + elif run_as == 'code': + _vspu.exec_code(file, '', globals_obj) + else: + _vspu.exec_file(file, globals_obj) + except: + exc_type, exc_value, exc_tb = sys.exc_info() + handle_exception(exc_type, exc_value, exc_tb) + +def handle_exception(exc_type, exc_value, exc_tb): + # Specifies list of files not to display in stack trace. + do_not_debug = [__file__, _vspu.__file__] + if sys.version_info >= (3, 3): + do_not_debug.append('') + if sys.version_info >= (3, 5): + do_not_debug.append('') + + # Remove debugger frames from the top and bottom of the traceback. + tb = traceback.extract_tb(exc_tb) + for i in [0, -1]: + while tb: + frame_file = os.path.normcase(tb[i][0]) + if not any(is_same_py_file(frame_file, f) for f in do_not_debug): + break + del tb[i] + + # Print the traceback. + if tb: + sys.stderr.write('Traceback (most recent call last):') + for out in traceback.format_list(tb): + sys.stderr.write(out) + sys.stderr.flush() + + # Print the exception. + for out in traceback.format_exception_only(exc_type, exc_value): + sys.stderr.write(out) + sys.stderr.flush() + + +def is_same_py_file(file_1, file_2): + """Compares 2 filenames accounting for .pyc files.""" + if file_1.endswith('.pyc') or file_1.endswith('.pyo'): + file_1 = file_1[:-1] + if file_2.endswith('.pyc') or file_2.endswith('.pyo'): + file_2 = file_2[:-1] + + return file_1 == file_2 + +def start_message_processor(host, port_num): + launch_notification = threading.Event() + def on_launch(): + launch_notification.set() + + daemon = Daemon(notify_launch=on_launch, addhandlers=True,killonclose=True) + start_client(daemon, host, port_num) + + return launch_notification.wait(WAIT_FOR_LAUNCH_REQUEST_TIMEOUT) + +if __name__ == '__main__': + filename, port_num, run_as = parse_argv() + run(filename, port_num, run_as) diff --git a/pythonFiles/experimental/visualstudio_py_util.py b/pythonFiles/experimental/visualstudio_py_util.py new file mode 100644 index 000000000000..b3ed951e8718 --- /dev/null +++ b/pythonFiles/experimental/visualstudio_py_util.py @@ -0,0 +1,631 @@ +# Python Tools for Visual Studio +# Copyright(c) Microsoft Corporation +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the License); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at http://www.apache.org/licenses/LICENSE-2.0 +# +# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +# MERCHANTABLITY OR NON-INFRINGEMENT. +# +# See the Apache Version 2.0 License for specific language governing +# permissions and limitations under the License. + +__author__ = "Microsoft Corporation " +__version__ = "3.0.0.0" + +# This module MUST NOT import threading in global scope. This is because in a direct (non-ptvsd) +# attach scenario, it is loaded on the injected debugger attach thread, and if threading module +# hasn't been loaded already, it will assume that the thread on which it is being loaded is the +# main thread. This will cause issues when the thread goes away after attach completes. + +import imp +import os +import sys +import struct + +# Import encodings early to avoid import on the debugger thread, which may cause deadlock +from encodings import utf_8, ascii + +# WARNING: Avoid imports beyond this point, specifically on the debugger thread, as this may cause +# deadlock where the debugger thread performs an import while a user thread has the import lock + +# Py3k compat - alias unicode to str, and xrange to range +try: + unicode +except: + unicode = str +try: + xrange +except: + xrange = range + + +if sys.version_info[0] >= 3: + def to_bytes(cmd_str): + return ascii.Codec.encode(cmd_str)[0] +else: + def to_bytes(cmd_str): + return cmd_str + +def exec_code(code, file, global_variables): + '''Executes the provided code as if it were the original script provided + to python.exe. The functionality is similar to `runpy.run_path`, which was + added in Python 2.7/3.2. + + The following values in `global_variables` will be set to the following + values, if they are not already set:: + __name__ = '' + __file__ = file + __package__ = __name__.rpartition('.')[0] # 2.6 and later + __cached__ = None # 3.2 and later + __loader__ = None # 3.3 and later + + The `sys.modules` entry for ``__name__`` will be set to a new module, and + ``sys.path[0]`` will be changed to the value of `file` without the filename. + Both values are restored when this function exits. + ''' + original_main = sys.modules.get('__main__') + + global_variables = dict(global_variables) + mod_name = global_variables.setdefault('__name__', '') + mod = sys.modules[mod_name] = imp.new_module(mod_name) + mod.__dict__.update(global_variables) + global_variables = mod.__dict__ + global_variables.setdefault('__file__', file) + if sys.version_info[0] >= 3 or sys.version_info[1] >= 6: + global_variables.setdefault('__package__', mod_name.rpartition('.')[0]) + if sys.version_info[0] >= 3: + if sys.version_info[1] >= 2: + global_variables.setdefault('__cached__', None) + if sys.version_info[1] >= 3: + try: + global_variables.setdefault('__loader__', original_main.__loader__) + except AttributeError: + pass + + if os.path.isdir(sys.path[0]): + sys.path.insert(0, os.path.split(file)[0]) + else: + sys.path[0] = os.path.split(file)[0] + code_obj = compile(code, file, 'exec') + exec(code_obj, global_variables) + +def exec_file(file, global_variables): + '''Executes the provided script as if it were the original script provided + to python.exe. The functionality is similar to `runpy.run_path`, which was + added in Python 2.7/3.2. + + The following values in `global_variables` will be set to the following + values, if they are not already set:: + __name__ = '' + __file__ = file + __package__ = __name__.rpartition('.')[0] # 2.6 and later + __cached__ = None # 3.2 and later + __loader__ = sys.modules['__main__'].__loader__ # 3.3 and later + + The `sys.modules` entry for ``__name__`` will be set to a new module, and + ``sys.path[0]`` will be changed to the value of `file` without the filename. + Both values are restored when this function exits. + ''' + f = open(file, "rb") + try: + code = f.read().replace(to_bytes('\r\n'), to_bytes('\n')) + to_bytes('\n') + finally: + f.close() + exec_code(code, file, global_variables) + +def exec_module(module, global_variables): + '''Executes the provided module as if it were provided as '-m module'. The + functionality is implemented using `runpy.run_module`, which was added in + Python 2.5. + ''' + import runpy + runpy.run_module(module, global_variables, run_name=global_variables.get('__name__'), alter_sys=True) + +UNICODE_PREFIX = to_bytes('U') +ASCII_PREFIX = to_bytes('A') +NONE_PREFIX = to_bytes('N') + + +def read_bytes(conn, count): + b = to_bytes('') + while len(b) < count: + received_data = conn.recv(count - len(b)) + if received_data is None: + break + b += received_data + return b + + +def write_bytes(conn, b): + conn.sendall(b) + + +def read_int(conn): + return struct.unpack('!q', read_bytes(conn, 8))[0] + + +def write_int(conn, i): + write_bytes(conn, struct.pack('!q', i)) + + +def read_string(conn): + """ reads length of text to read, and then the text encoded in UTF-8, and returns the string""" + strlen = read_int(conn) + if not strlen: + return '' + res = to_bytes('') + while len(res) < strlen: + res = res + conn.recv(strlen - len(res)) + + res = utf_8.decode(res)[0] + if sys.version_info[0] == 2 and sys.platform != 'cli': + # Py 2.x, we want an ASCII string if possible + try: + res = ascii.Codec.encode(res)[0] + except UnicodeEncodeError: + pass + + return res + + +def write_string(conn, s): + if s is None: + write_bytes(conn, NONE_PREFIX) + elif isinstance(s, unicode): + b = utf_8.encode(s)[0] + b_len = len(b) + write_bytes(conn, UNICODE_PREFIX) + write_int(conn, b_len) + if b_len > 0: + write_bytes(conn, b) + else: + s_len = len(s) + write_bytes(conn, ASCII_PREFIX) + write_int(conn, s_len) + if s_len > 0: + write_bytes(conn, s) + +class SafeRepr(object): + # String types are truncated to maxstring_outer when at the outer- + # most level, and truncated to maxstring_inner characters inside + # collections. + maxstring_outer = 2 ** 16 + maxstring_inner = 30 + if sys.version_info >= (3, 0): + string_types = (str, bytes) + set_info = (set, '{', '}', False) + frozenset_info = (frozenset, 'frozenset({', '})', False) + else: + string_types = (str, unicode) + set_info = (set, 'set([', '])', False) + frozenset_info = (frozenset, 'frozenset([', '])', False) + + # Collection types are recursively iterated for each limit in + # maxcollection. + maxcollection = (15, 10) + + # Specifies type, prefix string, suffix string, and whether to include a + # comma if there is only one element. (Using a sequence rather than a + # mapping because we use isinstance() to determine the matching type.) + collection_types = [ + (tuple, '(', ')', True), + (list, '[', ']', False), + frozenset_info, + set_info, + ] + try: + from collections import deque + collection_types.append((deque, 'deque([', '])', False)) + except: + pass + + # type, prefix string, suffix string, item prefix string, item key/value separator, item suffix string + dict_types = [(dict, '{', '}', '', ': ', '')] + try: + from collections import OrderedDict + dict_types.append((OrderedDict, 'OrderedDict([', '])', '(', ', ', ')')) + except: + pass + + # All other types are treated identically to strings, but using + # different limits. + maxother_outer = 2 ** 16 + maxother_inner = 30 + + def __call__(self, obj): + try: + return ''.join(self._repr(obj, 0)) + except: + try: + return 'An exception was raised: %r' % sys.exc_info()[1] + except: + return 'An exception was raised' + + def _repr(self, obj, level): + '''Returns an iterable of the parts in the final repr string.''' + + try: + obj_repr = type(obj).__repr__ + except: + obj_repr = None + + def has_obj_repr(t): + r = t.__repr__ + try: + return obj_repr == r + except: + return obj_repr is r + + for t, prefix, suffix, comma in self.collection_types: + if isinstance(obj, t) and has_obj_repr(t): + return self._repr_iter(obj, level, prefix, suffix, comma) + + for t, prefix, suffix, item_prefix, item_sep, item_suffix in self.dict_types: + if isinstance(obj, t) and has_obj_repr(t): + return self._repr_dict(obj, level, prefix, suffix, item_prefix, item_sep, item_suffix) + + for t in self.string_types: + if isinstance(obj, t) and has_obj_repr(t): + return self._repr_str(obj, level) + + if self._is_long_iter(obj): + return self._repr_long_iter(obj) + + return self._repr_other(obj, level) + + # Determines whether an iterable exceeds the limits set in maxlimits, and is therefore unsafe to repr(). + def _is_long_iter(self, obj, level = 0): + try: + # Strings have their own limits (and do not nest). Because they don't have __iter__ in 2.x, this + # check goes before the next one. + if isinstance(obj, self.string_types): + return len(obj) > self.maxstring_inner + + # If it's not an iterable (and not a string), it's fine. + if not hasattr(obj, '__iter__'): + return False + + # Iterable is its own iterator - this is a one-off iterable like generator or enumerate(). We can't + # really count that, but repr() for these should not include any elements anyway, so we can treat it + # the same as non-iterables. + if obj is iter(obj): + return False + + # xrange reprs fine regardless of length. + if isinstance(obj, xrange): + return False + + # numpy and scipy collections (ndarray etc) have self-truncating repr, so they're always safe. + try: + module = type(obj).__module__.partition('.')[0] + if module in ('numpy', 'scipy'): + return False + except: + pass + + # Iterables that nest too deep are considered long. + if level >= len(self.maxcollection): + return True + + # It is too long if the length exceeds the limit, or any of its elements are long iterables. + if hasattr(obj, '__len__'): + try: + l = len(obj) + except: + l = None + if l is not None and l > self.maxcollection[level]: + return True + return any((self._is_long_iter(item, level + 1) for item in obj)) + return any(i > self.maxcollection[level] or self._is_long_iter(item, level + 1) for i, item in enumerate(obj)) + + except: + # If anything breaks, assume the worst case. + return True + + def _repr_iter(self, obj, level, prefix, suffix, comma_after_single_element = False): + yield prefix + + if level >= len(self.maxcollection): + yield '...' + else: + count = self.maxcollection[level] + yield_comma = False + for item in obj: + if yield_comma: + yield ', ' + yield_comma = True + + count -= 1 + if count <= 0: + yield '...' + break + + for p in self._repr(item, 100 if item is obj else level + 1): + yield p + else: + if comma_after_single_element and count == self.maxcollection[level] - 1: + yield ',' + yield suffix + + def _repr_long_iter(self, obj): + try: + obj_repr = '<%s, len() = %s>' % (type(obj).__name__, len(obj)) + except: + try: + obj_repr = '<' + type(obj).__name__ + '>' + except: + obj_repr = '' + yield obj_repr + + def _repr_dict(self, obj, level, prefix, suffix, item_prefix, item_sep, item_suffix): + if not obj: + yield prefix + suffix + return + if level >= len(self.maxcollection): + yield prefix + '...' + suffix + return + + yield prefix + + count = self.maxcollection[level] + yield_comma = False + + try: + sorted_keys = sorted(obj) + except Exception: + sorted_keys = list(obj) + + for key in sorted_keys: + if yield_comma: + yield ', ' + yield_comma = True + + count -= 1 + if count <= 0: + yield '...' + break + + yield item_prefix + for p in self._repr(key, level + 1): + yield p + + yield item_sep + + try: + item = obj[key] + except Exception: + yield '' + else: + for p in self._repr(item, 100 if item is obj else level + 1): + yield p + yield item_suffix + + yield suffix + + def _repr_str(self, obj, level): + return self._repr_obj(obj, level, self.maxstring_inner, self.maxstring_outer) + + def _repr_other(self, obj, level): + return self._repr_obj(obj, level, self.maxother_inner, self.maxother_outer) + + def _repr_obj(self, obj, level, limit_inner, limit_outer): + try: + obj_repr = repr(obj) + except: + try: + obj_repr = object.__repr__(obj) + except: + try: + obj_repr = '' + except: + obj_repr = '' + + limit = limit_inner if level > 0 else limit_outer + + if limit >= len(obj_repr): + yield obj_repr + return + + # Slightly imprecise calculations - we may end up with a string that is + # up to 3 characters longer than limit. If you need precise formatting, + # you are using the wrong class. + left_count, right_count = max(1, int(2 * limit / 3)), max(1, int(limit / 3)) + + yield obj_repr[:left_count] + yield '...' + yield obj_repr[-right_count:] + + + def _selftest(self): + # Test the string limiting somewhat automatically + tests = [] + tests.append((7, 9, 'A' * (5))) + tests.append((self.maxstring_outer + 3, self.maxstring_inner + 3 + 2, 'A' * (self.maxstring_outer + 10))) + if sys.version_info >= (3, 0): + tests.append((self.maxstring_outer + 4, self.maxstring_inner + 4 + 2, bytes('A', 'ascii') * (self.maxstring_outer + 10))) + else: + tests.append((self.maxstring_outer + 4, self.maxstring_inner + 4 + 2, unicode('A') * (self.maxstring_outer + 10))) + + for limit1, limit2, value in tests: + assert len(self(value)) <= limit1 <= len(repr(value)), (len(self(value)), limit1, len(repr(value)), value) + assert len(self([value])) <= limit2 <= len(repr([value])), (len(self([value])), limit2, len(repr([value])), self([value])) + + def test(source, expected): + actual = self(source) + if actual != expected: + print("Source " + repr(source)) + print("Expect " + expected) + print("Actual " + actual) + print("") + assert False + + def re_test(source, pattern): + import re + actual = self(source) + if not re.match(pattern, actual): + print("Source " + repr(source)) + print("Pattern " + pattern) + print("Actual " + actual) + print("") + assert False + + for ctype, _prefix, _suffix, comma in self.collection_types: + for i in range(len(self.maxcollection)): + prefix = _prefix * (i + 1) + if comma: + suffix = _suffix + ("," + _suffix) * i + else: + suffix = _suffix * (i + 1) + #print("ctype = " + ctype.__name__ + ", maxcollection[" + str(i) + "] == " + str(self.maxcollection[i])) + c1 = ctype(range(self.maxcollection[i] - 1)) + inner_repr = prefix + ', '.join(str(j) for j in c1) + c2 = ctype(range(self.maxcollection[i])) + c3 = ctype(range(self.maxcollection[i] + 1)) + for j in range(i): + c1, c2, c3 = ctype((c1,)), ctype((c2,)), ctype((c3,)) + test(c1, inner_repr + suffix) + test(c2, inner_repr + ", ..." + suffix) + test(c3, inner_repr + ", ..." + suffix) + + if ctype is set: + # Cannot recursively add sets to sets + break + + # Assume that all tests apply equally to all iterable types and only + # test with lists. + c1 = list(range(self.maxcollection[0] * 2)) + c2 = [c1 for _ in range(self.maxcollection[0] * 2)] + c1_expect = '[' + ', '.join(str(j) for j in range(self.maxcollection[0] - 1)) + ', ...]' + test(c1, c1_expect) + c1_expect2 = '[' + ', '.join(str(j) for j in range(self.maxcollection[1] - 1)) + ', ...]' + c2_expect = '[' + ', '.join(c1_expect2 for _ in range(self.maxcollection[0] - 1)) + ', ...]' + test(c2, c2_expect) + + # Ensure dict keys and values are limited correctly + d1 = {} + d1_key = 'a' * self.maxstring_inner * 2 + d1[d1_key] = d1_key + re_test(d1, "{'a+\.\.\.a+': 'a+\.\.\.a+'}") + d2 = {d1_key : d1} + re_test(d2, "{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") + d3 = {d1_key : d2} + if len(self.maxcollection) == 2: + re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") + else: + re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") + + # Ensure empty dicts work + test({}, '{}') + + # Ensure dict keys are sorted + d1 = {} + d1['c'] = None + d1['b'] = None + d1['a'] = None + test(d1, "{'a': None, 'b': None, 'c': None}") + + if sys.version_info >= (3, 0): + # Ensure dicts with unsortable keys do not crash + d1 = {} + for _ in range(100): + d1[object()] = None + try: + list(sorted(d1)) + assert False, "d1.keys() should be unorderable" + except TypeError: + pass + self(d1) + + # Test with objects with broken repr implementations + class TestClass(object): + def __repr__(self): + raise NameError + try: + repr(TestClass()) + assert False, "TestClass().__repr__ should have thrown" + except NameError: + pass + self(TestClass()) + + # Test with objects with long repr implementations + class TestClass(object): + repr_str = '<' + 'A' * self.maxother_outer * 2 + '>' + def __repr__(self): + return self.repr_str + re_test(TestClass(), r'\') + + # Test collections that don't override repr + class TestClass(dict): pass + test(TestClass(), '{}') + class TestClass(list): pass + test(TestClass(), '[]') + + # Test collections that override repr + class TestClass(dict): + def __repr__(self): return 'MyRepr' + test(TestClass(), 'MyRepr') + class TestClass(list): + def __init__(self, iter = ()): list.__init__(self, iter) + def __repr__(self): return 'MyRepr' + test(TestClass(), 'MyRepr') + + # Test collections and iterables with long repr + test(TestClass(xrange(0, 15)), 'MyRepr') + test(TestClass(xrange(0, 16)), '') + test(TestClass([TestClass(xrange(0, 10))]), 'MyRepr') + test(TestClass([TestClass(xrange(0, 11))]), '') + + # Test strings inside long iterables + test(TestClass(['a' * (self.maxcollection[1] + 1)]), 'MyRepr') + test(TestClass(['a' * (self.maxstring_inner + 1)]), '') + + # Test range + if sys.version[0] == '2': + range_name = 'xrange' + else: + range_name = 'range' + test(xrange(1, self.maxcollection[0] + 1), '%s(1, %s)' % (range_name, self.maxcollection[0] + 1)) + + # Test directly recursive collections + c1 = [1, 2] + c1.append(c1) + test(c1, '[1, 2, [...]]') + d1 = {1: None} + d1[2] = d1 + test(d1, '{1: None, 2: {...}}') + + # Find the largest possible repr and ensure it is below our arbitrary + # limit (8KB). + coll = '-' * (self.maxstring_outer * 2) + for limit in reversed(self.maxcollection[1:]): + coll = [coll] * (limit * 2) + dcoll = {} + for i in range(self.maxcollection[0]): + dcoll[str(i) * self.maxstring_outer] = coll + text = self(dcoll) + #try: + # text_repr = repr(dcoll) + #except MemoryError: + # print('Memory error raised while creating repr of test data') + # text_repr = '' + #print('len(SafeRepr()(dcoll)) = ' + str(len(text)) + ', len(repr(coll)) = ' + str(len(text_repr))) + assert len(text) < 8192 + + # Test numpy types - they should all use their native reprs, even arrays exceeding limits + try: + import numpy as np + except ImportError: + print('WARNING! could not import numpy - skipping all numpy tests.') + else: + test(np.int32(123), repr(np.int32(123))) + test(np.float64(123.456), repr(np.float64(123.456))) + test(np.zeros(self.maxcollection[0] + 1), repr(np.zeros(self.maxcollection[0] + 1))); + +if __name__ == '__main__': + print('Running tests...') + SafeRepr()._selftest() \ No newline at end of file diff --git a/src/client/debugger/DebugClients/DebugFactory.ts b/src/client/debugger/DebugClients/DebugFactory.ts index f8c3b6378e9c..f3ae100c08b8 100644 --- a/src/client/debugger/DebugClients/DebugFactory.ts +++ b/src/client/debugger/DebugClients/DebugFactory.ts @@ -2,7 +2,7 @@ import { DebugSession } from 'vscode-debugadapter'; import { AttachRequestArguments, LaunchRequestArguments } from '../Common/Contracts'; import { IDebugLauncherScriptProvider } from '../types'; import { DebugClient } from './DebugClient'; -import { DebuggerLauncherScriptProvider, NoDebugLauncherScriptProvider } from './launcherProvider'; +import { DebuggerLauncherScriptProvider, DebuggerV2LauncherScriptProvider, NoDebugLauncherScriptProvider, NoDebugLauncherScriptProviderV2 } from './launcherProvider'; import { LocalDebugClient } from './LocalDebugClient'; import { LocalDebugClientV2 } from './localDebugClientV2'; import { NonDebugClient } from './NonDebugClient'; @@ -13,10 +13,10 @@ export function CreateLaunchDebugClient(launchRequestOptions: LaunchRequestArgum let launchScriptProvider: IDebugLauncherScriptProvider; let debugClientClass: typeof LocalDebugClient; if (launchRequestOptions.noDebug === true) { - launchScriptProvider = new NoDebugLauncherScriptProvider(); + launchScriptProvider = launchRequestOptions.type === 'pythonExperimental' ? new NoDebugLauncherScriptProviderV2() : new NoDebugLauncherScriptProvider(); debugClientClass = launchRequestOptions.type === 'pythonExperimental' ? NonDebugClientV2 : NonDebugClient; } else { - launchScriptProvider = new DebuggerLauncherScriptProvider(); + launchScriptProvider = launchRequestOptions.type === 'pythonExperimental' ? new DebuggerV2LauncherScriptProvider() : new DebuggerLauncherScriptProvider(); debugClientClass = launchRequestOptions.type === 'pythonExperimental' ? LocalDebugClientV2 : LocalDebugClient; } return new debugClientClass(launchRequestOptions, debugSession, canLaunchTerminal, launchScriptProvider); diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index 117266b91e15..4dfcfa7567ac 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -159,12 +159,12 @@ export class LocalDebugClient extends DebugClient { }); } private buildLaunchArguments(cwd: string, debugPort: number): string[] { - return [...this.buildDebugArguments(cwd, debugPort), ...this.buildStandardArguments()]; + const ptVSToolsFilePath = this.launcherScriptProvider.getLauncherFilePath(); + return [ptVSToolsFilePath, ...this.buildDebugArguments(cwd, debugPort), ...this.buildStandardArguments()]; } // tslint:disable-next-line:member-ordering protected buildDebugArguments(cwd: string, debugPort: number): string[] { - const ptVSToolsFilePath = this.launcherScriptProvider.getLauncherFilePath(); const vsDebugOptions: string[] = [DebugOptions.RedirectOutput]; if (Array.isArray(this.args.debugOptions)) { this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0) @@ -175,7 +175,7 @@ export class LocalDebugClient extends DebugClient { if (djangoIndex >= 0) { vsDebugOptions[djangoIndex] = 'DjangoDebugging'; } - return [ptVSToolsFilePath, cwd, debugPort.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14', vsDebugOptions.join(',')]; + return [cwd, debugPort.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14', vsDebugOptions.join(',')]; } // tslint:disable-next-line:member-ordering protected buildStandardArguments() { diff --git a/src/client/debugger/DebugClients/launcherProvider.ts b/src/client/debugger/DebugClients/launcherProvider.ts index 06ea43e0002e..7641123bef4d 100644 --- a/src/client/debugger/DebugClients/launcherProvider.ts +++ b/src/client/debugger/DebugClients/launcherProvider.ts @@ -14,6 +14,12 @@ export class NoDebugLauncherScriptProvider implements IDebugLauncherScriptProvid } } +export class NoDebugLauncherScriptProviderV2 implements IDebugLauncherScriptProvider { + public getLauncherFilePath(): string { + return path.join(path.dirname(__dirname), '..', '..', '..', 'pythonFiles', 'experimental', 'nodebug_launcher.py'); + } +} + export class DebuggerLauncherScriptProvider implements IDebugLauncherScriptProvider { public getLauncherFilePath(): string { return path.join(path.dirname(__dirname), '..', '..', '..', 'pythonFiles', 'PythonTools', 'visualstudio_py_launcher.py'); diff --git a/src/client/debugger/DebugClients/localDebugClientV2.ts b/src/client/debugger/DebugClients/localDebugClientV2.ts index 417efba39e26..78986c146cb8 100644 --- a/src/client/debugger/DebugClients/localDebugClientV2.ts +++ b/src/client/debugger/DebugClients/localDebugClientV2.ts @@ -13,6 +13,7 @@ export class LocalDebugClientV2 extends LocalDebugClient { super(args, debugSession, canLaunchTerminal, launcherScriptProvider); } protected buildDebugArguments(cwd: string, debugPort: number): string[] { +<<<<<<< 157f392165ac177707587117ecf76faffe5c4062 const noDebugArg = this.args.noDebug ? ['--nodebug'] : []; return ['-m', 'ptvsd', ...noDebugArg, '--host', 'localhost', '--port', debugPort.toString()]; } @@ -25,5 +26,8 @@ export class LocalDebugClientV2 extends LocalDebugClient { return ['--file', this.args.program, ...programArgs]; } return programArgs; +======= + return [cwd, debugPort.toString()]; +>>>>>>> no debug launcher } } diff --git a/src/client/debugger/DebugClients/nonDebugClientV2.ts b/src/client/debugger/DebugClients/nonDebugClientV2.ts index 0d47171e810c..537d7dda5749 100644 --- a/src/client/debugger/DebugClients/nonDebugClientV2.ts +++ b/src/client/debugger/DebugClients/nonDebugClientV2.ts @@ -3,33 +3,17 @@ 'use strict'; -import { ChildProcess } from 'child_process'; import { DebugSession } from 'vscode-debugadapter'; import { LaunchRequestArguments } from '../Common/Contracts'; import { IDebugLauncherScriptProvider } from '../types'; -import { DebugType } from './DebugClient'; -import { LocalDebugClientV2 } from './localDebugClientV2'; +import { NonDebugClient } from './NonDebugClient'; -export class NonDebugClientV2 extends LocalDebugClientV2 { +export class NonDebugClientV2 extends NonDebugClient { + // tslint:disable-next-line:no-any constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean, launcherScriptProvider: IDebugLauncherScriptProvider) { super(args, debugSession, canLaunchTerminal, launcherScriptProvider); } - - public get DebugType(): DebugType { - return DebugType.RunLocal; - } - - public Stop() { - super.Stop(); - if (this.pyProc) { - try { - this.pyProc!.kill(); - // tslint:disable-next-line:no-empty - } catch { } - this.pyProc = undefined; - } - } - protected handleProcessOutput(proc: ChildProcess, _failedToLaunch: (error: Error | string | Buffer) => void) { - // Do nothing + protected buildDebugArguments(cwd: string, debugPort: number): string[] { + return [cwd, debugPort.toString()]; } } From 8c424252ab66c01c5452405e678f756430044f0c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 11 Apr 2018 15:44:46 -0700 Subject: [PATCH 02/18] format py code --- .../PythonTools/visualstudio_py_launcher.py | 4 +-- pythonFiles/experimental/message_processor.py | 32 ++++++++++++------- pythonFiles/experimental/nodebug_launcher.py | 13 ++++++-- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 0d13964dd660..85b5f28be27a 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -13,7 +13,6 @@ # # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. - """ Starts Debugging, expected to start with normal program to start as first argument and directory to run from as @@ -87,4 +86,5 @@ sys.exit(1) # and start debugging -vspd.debug(filename, port_num, '34806ad9-833a-4524-8cd6-18ca4aa74f14', '', run_as) +vspd.debug(filename, port_num, '34806ad9-833a-4524-8cd6-18ca4aa74f14', '', + run_as) diff --git a/pythonFiles/experimental/message_processor.py b/pythonFiles/experimental/message_processor.py index f953003f982c..1d7ddd128fac 100644 --- a/pythonFiles/experimental/message_processor.py +++ b/pythonFiles/experimental/message_processor.py @@ -21,7 +21,10 @@ class Daemon(object): exitcode = 0 exiting_via_exit_handler = False - def __init__(self, notify_launch=lambda : None, addhandlers=True, killonclose=True): + def __init__(self, + notify_launch=lambda: None, + addhandlers=True, + killonclose=True): self.addhandlers = addhandlers self.killonclose = killonclose self._notify_launch = notify_launch @@ -76,6 +79,7 @@ def handler(): self.close() if self._adapter is not None: self._adapter._wait_for_server_thread() + atexit.register(handler) def _set_signal_handlers(self): @@ -86,6 +90,7 @@ def handler(signum, frame): if not self._closed: self.close() sys.exit(0) + signal.signal(signal.SIGHUP, handler) def _release_connection(self): @@ -115,15 +120,16 @@ class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): protocol. """ - def __init__(self, socket, - notify_launch=lambda : None, - notify_disconnecting = lambda: None, - notify_closing = lambda: None, - logfile=None, - ): - super(VSCodeMessageProcessor, self).__init__(socket=socket, - own_socket=False, - logfile=logfile) + def __init__( + self, + socket, + notify_launch=lambda: None, + notify_disconnecting=lambda: None, + notify_closing=lambda: None, + logfile=None, + ): + super(VSCodeMessageProcessor, self).__init__( + socket=socket, own_socket=False, logfile=logfile) self._socket = socket self._notify_launch = notify_launch self._notify_disconnecting = notify_disconnecting @@ -151,7 +157,10 @@ def start(self): 'output', category='telemetry', output='ptvsd', - data={'version': __version__, 'nodebug': True}, + data={ + 'version': __version__, + 'nodebug': True + }, ) # closing the adapter @@ -214,7 +223,6 @@ def _handle_disconnect(self, request): if not self._closed: self.close() - # VSC protocol handlers def on_initialize(self, request, args): diff --git a/pythonFiles/experimental/nodebug_launcher.py b/pythonFiles/experimental/nodebug_launcher.py index c2304d892e70..e388e379b180 100644 --- a/pythonFiles/experimental/nodebug_launcher.py +++ b/pythonFiles/experimental/nodebug_launcher.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. - """Run a block of code or Python file.""" import os.path @@ -16,13 +15,15 @@ import visualstudio_py_util as _vspu except: traceback.print_exc() - print("""Internal error detected. Please copy the above traceback and report at + print( + """Internal error detected. Please copy the above traceback and report at https://github.com/Microsoft/vscode-python/issues""") sys.exit(1) HOSTNAME = 'localhost' WAIT_FOR_LAUNCH_REQUEST_TIMEOUT = 10000 + def parse_argv(): """Parses arguments for use with the launcher. Arguments are: @@ -57,6 +58,7 @@ def parse_argv(): return (filename, port_num, run_as) + def run(file, port_num, run_as='script'): if not start_message_processor(HOSTNAME, port_num): return @@ -74,6 +76,7 @@ def run(file, port_num, run_as='script'): exc_type, exc_value, exc_tb = sys.exc_info() handle_exception(exc_type, exc_value, exc_tb) + def handle_exception(exc_type, exc_value, exc_tb): # Specifies list of files not to display in stack trace. do_not_debug = [__file__, _vspu.__file__] @@ -113,16 +116,20 @@ def is_same_py_file(file_1, file_2): return file_1 == file_2 + def start_message_processor(host, port_num): launch_notification = threading.Event() + def on_launch(): launch_notification.set() - daemon = Daemon(notify_launch=on_launch, addhandlers=True,killonclose=True) + daemon = Daemon( + notify_launch=on_launch, addhandlers=True, killonclose=True) start_client(daemon, host, port_num) return launch_notification.wait(WAIT_FOR_LAUNCH_REQUEST_TIMEOUT) + if __name__ == '__main__': filename, port_num, run_as = parse_argv() run(filename, port_num, run_as) From 729d12200a1bfc21269437f657e7f88ee66ed4cc Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 11 Apr 2018 18:01:19 -0700 Subject: [PATCH 03/18] send output --- src/client/debugger/DebugClients/nonDebugClientV2.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/debugger/DebugClients/nonDebugClientV2.ts b/src/client/debugger/DebugClients/nonDebugClientV2.ts index 537d7dda5749..7a6df643a9c7 100644 --- a/src/client/debugger/DebugClients/nonDebugClientV2.ts +++ b/src/client/debugger/DebugClients/nonDebugClientV2.ts @@ -3,7 +3,8 @@ 'use strict'; -import { DebugSession } from 'vscode-debugadapter'; +import { ChildProcess } from 'child_process'; +import { DebugSession, OutputEvent } from 'vscode-debugadapter'; import { LaunchRequestArguments } from '../Common/Contracts'; import { IDebugLauncherScriptProvider } from '../types'; import { NonDebugClient } from './NonDebugClient'; @@ -16,4 +17,10 @@ export class NonDebugClientV2 extends NonDebugClient { protected buildDebugArguments(cwd: string, debugPort: number): string[] { return [cwd, debugPort.toString()]; } + protected handleProcessOutput(proc: ChildProcess, _failedToLaunch: (error: Error | string | Buffer) => void) { + proc.stdout.on('data', data => { + this.debugSession.sendEvent(new OutputEvent(data.toString(), 'stdout')); + }); + proc.stderr.on('data', data => this.debugSession.sendEvent(new OutputEvent(data.toString(), 'stderr'))); + } } From c46b318e2798e7742ac8c04e95ec6c1953af47ba Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 12 Apr 2018 09:11:15 -0700 Subject: [PATCH 04/18] output redirection --- pythonFiles/experimental/message_processor.py | 70 +++++++++++++++++-- pythonFiles/experimental/nodebug_launcher.py | 1 + .../debugger/DebugClients/nonDebugClientV2.ts | 7 +- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/pythonFiles/experimental/message_processor.py b/pythonFiles/experimental/message_processor.py index 1d7ddd128fac..254ae8488739 100644 --- a/pythonFiles/experimental/message_processor.py +++ b/pythonFiles/experimental/message_processor.py @@ -6,6 +6,8 @@ import platform import signal import socket +import sys +import time import threading import warnings @@ -13,18 +15,66 @@ from ptvsd import ipcjson, __version__ from ptvsd.socket import close_socket from ptvsd.wrapper import WAIT_FOR_DISCONNECT_REQUEST_TIMEOUT, WAIT_FOR_THREAD_FINISH_TIMEOUT +from pydevd import CheckOutputThread, init_stdout_redirect, init_stderr_redirect +from _pydevd_bundle.pydevd_kill_all_pydevd_threads import kill_all_pydev_threads +class OutputRedirection(object): + def __init__(self, on_output=lambda category, output: None): + self._on_output = on_output + self._stopped = False + self._thread = None + + def start(self): + self._enable_redirection() + self._thread = threading.Thread(target=self._run, name='ptvsd.output.redirection') + self._thread.daemon = True + self._thread.start() + + def stop(self): + if self._stopped: + return + + self._stopped = true + self._thread.join(WAIT_FOR_THREAD_FINISH_TIMEOUT) + self._thread.join(10) + + def _enable_redirection(self): + init_stdout_redirect() + init_stderr_redirect() + + def _check_output(self, out, category): + '''Checks the output to see if we have to send some buffered output to the debug server + + @param out: sys.stdout or sys.stderr + @param category: Whether + ''' + + try: + v = out.getvalue() + + if v: + self._on_output(category, v) + except: + traceback.print_exc() + + def _run(self): + import sys + while not self._stopped: + self._check_output(sys.stdoutBuf, 'stdout') + self._check_output(sys.stderrBuf, 'stderr') + time.sleep(0.3) class Daemon(object): """The process-level manager for the VSC protocol debug adapter.""" - exitcode = 0 - exiting_via_exit_handler = False - def __init__(self, notify_launch=lambda: None, addhandlers=True, killonclose=True): + + self.exitcode = 0 + self.exiting_via_exit_handler = False + self.addhandlers = addhandlers self.killonclose = killonclose self._notify_launch = notify_launch @@ -36,6 +86,10 @@ def __init__(self, def start(self, server=None): if self._closed: raise DaemonClosedError() + + self._output_monitor = OutputRedirection(self._send_output) + self._output_monitor.start() + return None def set_connection(self, client): @@ -63,6 +117,7 @@ def set_connection(self, client): def close(self): """Stop all loops and release all resources.""" + self._output_monitor.stop() if self._closed: raise DaemonClosedError('already closed') self._closed = True @@ -112,6 +167,13 @@ def _handle_vsc_close(self): return self.close() + def _send_output(self, category, output): + self._adapter.send_event( + 'output', + category=category, + output=output + ) + class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): """IPC JSON message processor for VSC debugger protocol. @@ -147,7 +209,7 @@ def start(self): # VSC msg processing loop self.server_thread = threading.Thread( target=self.process_messages, - name='ptvsd.Client1234', + name='ptvsd.Client', ) self.server_thread.daemon = True self.server_thread.start() diff --git a/pythonFiles/experimental/nodebug_launcher.py b/pythonFiles/experimental/nodebug_launcher.py index e388e379b180..3f0ed4ea9ec8 100644 --- a/pythonFiles/experimental/nodebug_launcher.py +++ b/pythonFiles/experimental/nodebug_launcher.py @@ -72,6 +72,7 @@ def run(file, port_num, run_as='script'): _vspu.exec_code(file, '', globals_obj) else: _vspu.exec_file(file, globals_obj) + time.sleep(0.4) except: exc_type, exc_value, exc_tb = sys.exc_info() handle_exception(exc_type, exc_value, exc_tb) diff --git a/src/client/debugger/DebugClients/nonDebugClientV2.ts b/src/client/debugger/DebugClients/nonDebugClientV2.ts index 7a6df643a9c7..788578bfbc17 100644 --- a/src/client/debugger/DebugClients/nonDebugClientV2.ts +++ b/src/client/debugger/DebugClients/nonDebugClientV2.ts @@ -4,7 +4,7 @@ 'use strict'; import { ChildProcess } from 'child_process'; -import { DebugSession, OutputEvent } from 'vscode-debugadapter'; +import { DebugSession } from 'vscode-debugadapter'; import { LaunchRequestArguments } from '../Common/Contracts'; import { IDebugLauncherScriptProvider } from '../types'; import { NonDebugClient } from './NonDebugClient'; @@ -18,9 +18,6 @@ export class NonDebugClientV2 extends NonDebugClient { return [cwd, debugPort.toString()]; } protected handleProcessOutput(proc: ChildProcess, _failedToLaunch: (error: Error | string | Buffer) => void) { - proc.stdout.on('data', data => { - this.debugSession.sendEvent(new OutputEvent(data.toString(), 'stdout')); - }); - proc.stderr.on('data', data => this.debugSession.sendEvent(new OutputEvent(data.toString(), 'stderr'))); + // Do nothing } } From 626e8b072dd4e4fcb380d28af1e53930d07de71c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 12 Apr 2018 13:41:30 -0700 Subject: [PATCH 05/18] removed and moved to ptvsd --- pythonFiles/experimental/message_processor.py | 329 --------- pythonFiles/experimental/nodebug_launcher.py | 136 ---- .../experimental/visualstudio_py_util.py | 631 ------------------ 3 files changed, 1096 deletions(-) delete mode 100644 pythonFiles/experimental/message_processor.py delete mode 100644 pythonFiles/experimental/nodebug_launcher.py delete mode 100644 pythonFiles/experimental/visualstudio_py_util.py diff --git a/pythonFiles/experimental/message_processor.py b/pythonFiles/experimental/message_processor.py deleted file mode 100644 index 254ae8488739..000000000000 --- a/pythonFiles/experimental/message_processor.py +++ /dev/null @@ -1,329 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import atexit -import os -import platform -import signal -import socket -import sys -import time -import threading -import warnings - -from ptvsd.daemon import DaemonClosedError -from ptvsd import ipcjson, __version__ -from ptvsd.socket import close_socket -from ptvsd.wrapper import WAIT_FOR_DISCONNECT_REQUEST_TIMEOUT, WAIT_FOR_THREAD_FINISH_TIMEOUT -from pydevd import CheckOutputThread, init_stdout_redirect, init_stderr_redirect -from _pydevd_bundle.pydevd_kill_all_pydevd_threads import kill_all_pydev_threads - -class OutputRedirection(object): - def __init__(self, on_output=lambda category, output: None): - self._on_output = on_output - self._stopped = False - self._thread = None - - def start(self): - self._enable_redirection() - self._thread = threading.Thread(target=self._run, name='ptvsd.output.redirection') - self._thread.daemon = True - self._thread.start() - - def stop(self): - if self._stopped: - return - - self._stopped = true - self._thread.join(WAIT_FOR_THREAD_FINISH_TIMEOUT) - self._thread.join(10) - - def _enable_redirection(self): - init_stdout_redirect() - init_stderr_redirect() - - def _check_output(self, out, category): - '''Checks the output to see if we have to send some buffered output to the debug server - - @param out: sys.stdout or sys.stderr - @param category: Whether - ''' - - try: - v = out.getvalue() - - if v: - self._on_output(category, v) - except: - traceback.print_exc() - - def _run(self): - import sys - while not self._stopped: - self._check_output(sys.stdoutBuf, 'stdout') - self._check_output(sys.stderrBuf, 'stderr') - time.sleep(0.3) - -class Daemon(object): - """The process-level manager for the VSC protocol debug adapter.""" - - def __init__(self, - notify_launch=lambda: None, - addhandlers=True, - killonclose=True): - - self.exitcode = 0 - self.exiting_via_exit_handler = False - - self.addhandlers = addhandlers - self.killonclose = killonclose - self._notify_launch = notify_launch - - self._closed = False - self._client = None - self._adapter = None - - def start(self, server=None): - if self._closed: - raise DaemonClosedError() - - self._output_monitor = OutputRedirection(self._send_output) - self._output_monitor.start() - - return None - - def set_connection(self, client): - """Set the client socket to use for the debug adapter. - - A VSC message loop is started for the client. - """ - if self._closed: - raise DaemonClosedError() - if self._client is not None: - raise RuntimeError('connection already set') - self._client = client - - self._adapter = VSCodeMessageProcessor( - client, - self._notify_launch, - self._handle_vsc_disconnect, - self._handle_vsc_close, - ) - self._adapter.start() - if self.addhandlers: - self._add_atexit_handler() - self._set_signal_handlers() - return self._adapter - - def close(self): - """Stop all loops and release all resources.""" - self._output_monitor.stop() - if self._closed: - raise DaemonClosedError('already closed') - self._closed = True - - if self._client is not None: - self._release_connection() - - # internal methods - - def _add_atexit_handler(self): - def handler(): - self.exiting_via_exit_handler = True - if not self._closed: - self.close() - if self._adapter is not None: - self._adapter._wait_for_server_thread() - - atexit.register(handler) - - def _set_signal_handlers(self): - if platform.system() == 'Windows': - return None - - def handler(signum, frame): - if not self._closed: - self.close() - sys.exit(0) - - signal.signal(signal.SIGHUP, handler) - - def _release_connection(self): - if self._adapter is not None: - self._adapter.handle_stopped(self.exitcode) - self._adapter.close() - close_socket(self._client) - - # internal methods for VSCodeMessageProcessor - - def _handle_vsc_disconnect(self, kill=False): - if not self._closed: - self.close() - if kill and self.killonclose and not self.exiting_via_exit_handler: - os.kill(os.getpid(), signal.SIGTERM) - - def _handle_vsc_close(self): - if self._closed: - return - self.close() - - def _send_output(self, category, output): - self._adapter.send_event( - 'output', - category=category, - output=output - ) - - -class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel): - """IPC JSON message processor for VSC debugger protocol. - - This translates between the VSC debugger protocol and the pydevd - protocol. - """ - - def __init__( - self, - socket, - notify_launch=lambda: None, - notify_disconnecting=lambda: None, - notify_closing=lambda: None, - logfile=None, - ): - super(VSCodeMessageProcessor, self).__init__( - socket=socket, own_socket=False, logfile=logfile) - self._socket = socket - self._notify_launch = notify_launch - self._notify_disconnecting = notify_disconnecting - self._notify_closing = notify_closing - - self.server_thread = None - self._closed = False - - # adapter state - self.disconnect_request = None - self.disconnect_request_event = threading.Event() - self._exited = False - - def start(self): - # VSC msg processing loop - self.server_thread = threading.Thread( - target=self.process_messages, - name='ptvsd.Client', - ) - self.server_thread.daemon = True - self.server_thread.start() - - # special initialization - self.send_event( - 'output', - category='telemetry', - output='ptvsd', - data={ - 'version': __version__, - 'nodebug': True - }, - ) - - # closing the adapter - - def close(self): - """Stop the message processor and release its resources.""" - if self._closed: - return - self._closed = True - - self._notify_closing() - # Close the editor-side socket. - self._stop_vsc_message_loop() - - def _stop_vsc_message_loop(self): - self.set_exit() - if self._socket: - try: - self._socket.shutdown(socket.SHUT_RDWR) - self._socket.close() - except: - pass - - def _wait_for_server_thread(self): - if self.server_thread is None: - return - if not self.server_thread.is_alive(): - return - self.server_thread.join(WAIT_FOR_THREAD_FINISH_TIMEOUT) - - def handle_stopped(self, exitcode): - """Finalize the protocol connection.""" - if self._exited: - return - self._exited = True - - # Notify the editor that the "debuggee" (e.g. script, app) exited. - self.send_event('exited', exitCode=exitcode) - - # Notify the editor that the debugger has stopped. - self.send_event('terminated') - - # The editor will send a "disconnect" request at this point. - self._wait_for_disconnect() - - def _wait_for_disconnect(self, timeout=None): - if timeout is None: - timeout = WAIT_FOR_DISCONNECT_REQUEST_TIMEOUT - - if not self.disconnect_request_event.wait(timeout): - warnings.warn('timed out waiting for disconnect request') - if self.disconnect_request is not None: - self.send_response(self.disconnect_request) - self.disconnect_request = None - - def _handle_disconnect(self, request): - self.disconnect_request = request - self.disconnect_request_event.set() - self._notify_disconnecting(not self._closed) - if not self._closed: - self.close() - - # VSC protocol handlers - - def on_initialize(self, request, args): - # TODO: docstring - self.send_response( - request, - supportsExceptionInfoRequest=True, - supportsConfigurationDoneRequest=True, - supportsConditionalBreakpoints=True, - supportsSetVariable=True, - supportsExceptionOptions=True, - supportsEvaluateForHovers=True, - supportsValueFormattingOptions=True, - supportsSetExpression=True, - supportsModulesRequest=True, - exceptionBreakpointFilters=[ - { - 'filter': 'raised', - 'label': 'Raised Exceptions', - 'default': False - }, - { - 'filter': 'uncaught', - 'label': 'Uncaught Exceptions', - 'default': True - }, - ], - ) - self.send_event('initialized') - - def on_configurationDone(self, request, args): - self.send_response(request) - - def on_launch(self, request, args): - self._notify_launch() - self.send_response(request) - - def on_disconnect(self, request, args): - self._handle_disconnect(request) - - def on_invalid_request(self, request, args): - self.send_response(request, success=True) diff --git a/pythonFiles/experimental/nodebug_launcher.py b/pythonFiles/experimental/nodebug_launcher.py deleted file mode 100644 index 3f0ed4ea9ec8..000000000000 --- a/pythonFiles/experimental/nodebug_launcher.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -"""Run a block of code or Python file.""" - -import os.path -import sys -import threading -import time -import traceback - -try: - import ptvsd - from message_processor import Daemon - from ptvsd.pydevd_hooks import start_client - import visualstudio_py_util as _vspu -except: - traceback.print_exc() - print( - """Internal error detected. Please copy the above traceback and report at -https://github.com/Microsoft/vscode-python/issues""") - sys.exit(1) - -HOSTNAME = 'localhost' -WAIT_FOR_LAUNCH_REQUEST_TIMEOUT = 10000 - - -def parse_argv(): - """Parses arguments for use with the launcher. - Arguments are: - 1. Working directory. - 2. VS debugger port to connect to. - 3. '-m' or '-c' to override the default run-as mode. [optional]. - 4. Startup script name. - 5. Script arguments. - """ - - # Change to directory we expected to start from. - os.chdir(sys.argv[1]) - - port_num = int(sys.argv[2]) - - del sys.argv[:3] - - # Set run_as mode appropriately - run_as = 'script' - if sys.argv and sys.argv[0] == '-m': - run_as = 'module' - del sys.argv[0] - elif sys.argv and sys.argv[0] == '-c': - run_as = 'code' - del sys.argv[0] - - # Preserve filename before we del sys. - filename = sys.argv[0] - - # Fix sys.path to be the script file dir. - sys.path[0] = '' - - return (filename, port_num, run_as) - - -def run(file, port_num, run_as='script'): - if not start_message_processor(HOSTNAME, port_num): - return - - # Now execute main file. - globals_obj = {'__name__': '__main__'} - try: - if run_as == 'module': - _vspu.exec_module(file, globals_obj) - elif run_as == 'code': - _vspu.exec_code(file, '', globals_obj) - else: - _vspu.exec_file(file, globals_obj) - time.sleep(0.4) - except: - exc_type, exc_value, exc_tb = sys.exc_info() - handle_exception(exc_type, exc_value, exc_tb) - - -def handle_exception(exc_type, exc_value, exc_tb): - # Specifies list of files not to display in stack trace. - do_not_debug = [__file__, _vspu.__file__] - if sys.version_info >= (3, 3): - do_not_debug.append('') - if sys.version_info >= (3, 5): - do_not_debug.append('') - - # Remove debugger frames from the top and bottom of the traceback. - tb = traceback.extract_tb(exc_tb) - for i in [0, -1]: - while tb: - frame_file = os.path.normcase(tb[i][0]) - if not any(is_same_py_file(frame_file, f) for f in do_not_debug): - break - del tb[i] - - # Print the traceback. - if tb: - sys.stderr.write('Traceback (most recent call last):') - for out in traceback.format_list(tb): - sys.stderr.write(out) - sys.stderr.flush() - - # Print the exception. - for out in traceback.format_exception_only(exc_type, exc_value): - sys.stderr.write(out) - sys.stderr.flush() - - -def is_same_py_file(file_1, file_2): - """Compares 2 filenames accounting for .pyc files.""" - if file_1.endswith('.pyc') or file_1.endswith('.pyo'): - file_1 = file_1[:-1] - if file_2.endswith('.pyc') or file_2.endswith('.pyo'): - file_2 = file_2[:-1] - - return file_1 == file_2 - - -def start_message_processor(host, port_num): - launch_notification = threading.Event() - - def on_launch(): - launch_notification.set() - - daemon = Daemon( - notify_launch=on_launch, addhandlers=True, killonclose=True) - start_client(daemon, host, port_num) - - return launch_notification.wait(WAIT_FOR_LAUNCH_REQUEST_TIMEOUT) - - -if __name__ == '__main__': - filename, port_num, run_as = parse_argv() - run(filename, port_num, run_as) diff --git a/pythonFiles/experimental/visualstudio_py_util.py b/pythonFiles/experimental/visualstudio_py_util.py deleted file mode 100644 index b3ed951e8718..000000000000 --- a/pythonFiles/experimental/visualstudio_py_util.py +++ /dev/null @@ -1,631 +0,0 @@ -# Python Tools for Visual Studio -# Copyright(c) Microsoft Corporation -# All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the License); you may not use -# this file except in compliance with the License. You may obtain a copy of the -# License at http://www.apache.org/licenses/LICENSE-2.0 -# -# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS -# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY -# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -# MERCHANTABLITY OR NON-INFRINGEMENT. -# -# See the Apache Version 2.0 License for specific language governing -# permissions and limitations under the License. - -__author__ = "Microsoft Corporation " -__version__ = "3.0.0.0" - -# This module MUST NOT import threading in global scope. This is because in a direct (non-ptvsd) -# attach scenario, it is loaded on the injected debugger attach thread, and if threading module -# hasn't been loaded already, it will assume that the thread on which it is being loaded is the -# main thread. This will cause issues when the thread goes away after attach completes. - -import imp -import os -import sys -import struct - -# Import encodings early to avoid import on the debugger thread, which may cause deadlock -from encodings import utf_8, ascii - -# WARNING: Avoid imports beyond this point, specifically on the debugger thread, as this may cause -# deadlock where the debugger thread performs an import while a user thread has the import lock - -# Py3k compat - alias unicode to str, and xrange to range -try: - unicode -except: - unicode = str -try: - xrange -except: - xrange = range - - -if sys.version_info[0] >= 3: - def to_bytes(cmd_str): - return ascii.Codec.encode(cmd_str)[0] -else: - def to_bytes(cmd_str): - return cmd_str - -def exec_code(code, file, global_variables): - '''Executes the provided code as if it were the original script provided - to python.exe. The functionality is similar to `runpy.run_path`, which was - added in Python 2.7/3.2. - - The following values in `global_variables` will be set to the following - values, if they are not already set:: - __name__ = '' - __file__ = file - __package__ = __name__.rpartition('.')[0] # 2.6 and later - __cached__ = None # 3.2 and later - __loader__ = None # 3.3 and later - - The `sys.modules` entry for ``__name__`` will be set to a new module, and - ``sys.path[0]`` will be changed to the value of `file` without the filename. - Both values are restored when this function exits. - ''' - original_main = sys.modules.get('__main__') - - global_variables = dict(global_variables) - mod_name = global_variables.setdefault('__name__', '') - mod = sys.modules[mod_name] = imp.new_module(mod_name) - mod.__dict__.update(global_variables) - global_variables = mod.__dict__ - global_variables.setdefault('__file__', file) - if sys.version_info[0] >= 3 or sys.version_info[1] >= 6: - global_variables.setdefault('__package__', mod_name.rpartition('.')[0]) - if sys.version_info[0] >= 3: - if sys.version_info[1] >= 2: - global_variables.setdefault('__cached__', None) - if sys.version_info[1] >= 3: - try: - global_variables.setdefault('__loader__', original_main.__loader__) - except AttributeError: - pass - - if os.path.isdir(sys.path[0]): - sys.path.insert(0, os.path.split(file)[0]) - else: - sys.path[0] = os.path.split(file)[0] - code_obj = compile(code, file, 'exec') - exec(code_obj, global_variables) - -def exec_file(file, global_variables): - '''Executes the provided script as if it were the original script provided - to python.exe. The functionality is similar to `runpy.run_path`, which was - added in Python 2.7/3.2. - - The following values in `global_variables` will be set to the following - values, if they are not already set:: - __name__ = '' - __file__ = file - __package__ = __name__.rpartition('.')[0] # 2.6 and later - __cached__ = None # 3.2 and later - __loader__ = sys.modules['__main__'].__loader__ # 3.3 and later - - The `sys.modules` entry for ``__name__`` will be set to a new module, and - ``sys.path[0]`` will be changed to the value of `file` without the filename. - Both values are restored when this function exits. - ''' - f = open(file, "rb") - try: - code = f.read().replace(to_bytes('\r\n'), to_bytes('\n')) + to_bytes('\n') - finally: - f.close() - exec_code(code, file, global_variables) - -def exec_module(module, global_variables): - '''Executes the provided module as if it were provided as '-m module'. The - functionality is implemented using `runpy.run_module`, which was added in - Python 2.5. - ''' - import runpy - runpy.run_module(module, global_variables, run_name=global_variables.get('__name__'), alter_sys=True) - -UNICODE_PREFIX = to_bytes('U') -ASCII_PREFIX = to_bytes('A') -NONE_PREFIX = to_bytes('N') - - -def read_bytes(conn, count): - b = to_bytes('') - while len(b) < count: - received_data = conn.recv(count - len(b)) - if received_data is None: - break - b += received_data - return b - - -def write_bytes(conn, b): - conn.sendall(b) - - -def read_int(conn): - return struct.unpack('!q', read_bytes(conn, 8))[0] - - -def write_int(conn, i): - write_bytes(conn, struct.pack('!q', i)) - - -def read_string(conn): - """ reads length of text to read, and then the text encoded in UTF-8, and returns the string""" - strlen = read_int(conn) - if not strlen: - return '' - res = to_bytes('') - while len(res) < strlen: - res = res + conn.recv(strlen - len(res)) - - res = utf_8.decode(res)[0] - if sys.version_info[0] == 2 and sys.platform != 'cli': - # Py 2.x, we want an ASCII string if possible - try: - res = ascii.Codec.encode(res)[0] - except UnicodeEncodeError: - pass - - return res - - -def write_string(conn, s): - if s is None: - write_bytes(conn, NONE_PREFIX) - elif isinstance(s, unicode): - b = utf_8.encode(s)[0] - b_len = len(b) - write_bytes(conn, UNICODE_PREFIX) - write_int(conn, b_len) - if b_len > 0: - write_bytes(conn, b) - else: - s_len = len(s) - write_bytes(conn, ASCII_PREFIX) - write_int(conn, s_len) - if s_len > 0: - write_bytes(conn, s) - -class SafeRepr(object): - # String types are truncated to maxstring_outer when at the outer- - # most level, and truncated to maxstring_inner characters inside - # collections. - maxstring_outer = 2 ** 16 - maxstring_inner = 30 - if sys.version_info >= (3, 0): - string_types = (str, bytes) - set_info = (set, '{', '}', False) - frozenset_info = (frozenset, 'frozenset({', '})', False) - else: - string_types = (str, unicode) - set_info = (set, 'set([', '])', False) - frozenset_info = (frozenset, 'frozenset([', '])', False) - - # Collection types are recursively iterated for each limit in - # maxcollection. - maxcollection = (15, 10) - - # Specifies type, prefix string, suffix string, and whether to include a - # comma if there is only one element. (Using a sequence rather than a - # mapping because we use isinstance() to determine the matching type.) - collection_types = [ - (tuple, '(', ')', True), - (list, '[', ']', False), - frozenset_info, - set_info, - ] - try: - from collections import deque - collection_types.append((deque, 'deque([', '])', False)) - except: - pass - - # type, prefix string, suffix string, item prefix string, item key/value separator, item suffix string - dict_types = [(dict, '{', '}', '', ': ', '')] - try: - from collections import OrderedDict - dict_types.append((OrderedDict, 'OrderedDict([', '])', '(', ', ', ')')) - except: - pass - - # All other types are treated identically to strings, but using - # different limits. - maxother_outer = 2 ** 16 - maxother_inner = 30 - - def __call__(self, obj): - try: - return ''.join(self._repr(obj, 0)) - except: - try: - return 'An exception was raised: %r' % sys.exc_info()[1] - except: - return 'An exception was raised' - - def _repr(self, obj, level): - '''Returns an iterable of the parts in the final repr string.''' - - try: - obj_repr = type(obj).__repr__ - except: - obj_repr = None - - def has_obj_repr(t): - r = t.__repr__ - try: - return obj_repr == r - except: - return obj_repr is r - - for t, prefix, suffix, comma in self.collection_types: - if isinstance(obj, t) and has_obj_repr(t): - return self._repr_iter(obj, level, prefix, suffix, comma) - - for t, prefix, suffix, item_prefix, item_sep, item_suffix in self.dict_types: - if isinstance(obj, t) and has_obj_repr(t): - return self._repr_dict(obj, level, prefix, suffix, item_prefix, item_sep, item_suffix) - - for t in self.string_types: - if isinstance(obj, t) and has_obj_repr(t): - return self._repr_str(obj, level) - - if self._is_long_iter(obj): - return self._repr_long_iter(obj) - - return self._repr_other(obj, level) - - # Determines whether an iterable exceeds the limits set in maxlimits, and is therefore unsafe to repr(). - def _is_long_iter(self, obj, level = 0): - try: - # Strings have their own limits (and do not nest). Because they don't have __iter__ in 2.x, this - # check goes before the next one. - if isinstance(obj, self.string_types): - return len(obj) > self.maxstring_inner - - # If it's not an iterable (and not a string), it's fine. - if not hasattr(obj, '__iter__'): - return False - - # Iterable is its own iterator - this is a one-off iterable like generator or enumerate(). We can't - # really count that, but repr() for these should not include any elements anyway, so we can treat it - # the same as non-iterables. - if obj is iter(obj): - return False - - # xrange reprs fine regardless of length. - if isinstance(obj, xrange): - return False - - # numpy and scipy collections (ndarray etc) have self-truncating repr, so they're always safe. - try: - module = type(obj).__module__.partition('.')[0] - if module in ('numpy', 'scipy'): - return False - except: - pass - - # Iterables that nest too deep are considered long. - if level >= len(self.maxcollection): - return True - - # It is too long if the length exceeds the limit, or any of its elements are long iterables. - if hasattr(obj, '__len__'): - try: - l = len(obj) - except: - l = None - if l is not None and l > self.maxcollection[level]: - return True - return any((self._is_long_iter(item, level + 1) for item in obj)) - return any(i > self.maxcollection[level] or self._is_long_iter(item, level + 1) for i, item in enumerate(obj)) - - except: - # If anything breaks, assume the worst case. - return True - - def _repr_iter(self, obj, level, prefix, suffix, comma_after_single_element = False): - yield prefix - - if level >= len(self.maxcollection): - yield '...' - else: - count = self.maxcollection[level] - yield_comma = False - for item in obj: - if yield_comma: - yield ', ' - yield_comma = True - - count -= 1 - if count <= 0: - yield '...' - break - - for p in self._repr(item, 100 if item is obj else level + 1): - yield p - else: - if comma_after_single_element and count == self.maxcollection[level] - 1: - yield ',' - yield suffix - - def _repr_long_iter(self, obj): - try: - obj_repr = '<%s, len() = %s>' % (type(obj).__name__, len(obj)) - except: - try: - obj_repr = '<' + type(obj).__name__ + '>' - except: - obj_repr = '' - yield obj_repr - - def _repr_dict(self, obj, level, prefix, suffix, item_prefix, item_sep, item_suffix): - if not obj: - yield prefix + suffix - return - if level >= len(self.maxcollection): - yield prefix + '...' + suffix - return - - yield prefix - - count = self.maxcollection[level] - yield_comma = False - - try: - sorted_keys = sorted(obj) - except Exception: - sorted_keys = list(obj) - - for key in sorted_keys: - if yield_comma: - yield ', ' - yield_comma = True - - count -= 1 - if count <= 0: - yield '...' - break - - yield item_prefix - for p in self._repr(key, level + 1): - yield p - - yield item_sep - - try: - item = obj[key] - except Exception: - yield '' - else: - for p in self._repr(item, 100 if item is obj else level + 1): - yield p - yield item_suffix - - yield suffix - - def _repr_str(self, obj, level): - return self._repr_obj(obj, level, self.maxstring_inner, self.maxstring_outer) - - def _repr_other(self, obj, level): - return self._repr_obj(obj, level, self.maxother_inner, self.maxother_outer) - - def _repr_obj(self, obj, level, limit_inner, limit_outer): - try: - obj_repr = repr(obj) - except: - try: - obj_repr = object.__repr__(obj) - except: - try: - obj_repr = '' - except: - obj_repr = '' - - limit = limit_inner if level > 0 else limit_outer - - if limit >= len(obj_repr): - yield obj_repr - return - - # Slightly imprecise calculations - we may end up with a string that is - # up to 3 characters longer than limit. If you need precise formatting, - # you are using the wrong class. - left_count, right_count = max(1, int(2 * limit / 3)), max(1, int(limit / 3)) - - yield obj_repr[:left_count] - yield '...' - yield obj_repr[-right_count:] - - - def _selftest(self): - # Test the string limiting somewhat automatically - tests = [] - tests.append((7, 9, 'A' * (5))) - tests.append((self.maxstring_outer + 3, self.maxstring_inner + 3 + 2, 'A' * (self.maxstring_outer + 10))) - if sys.version_info >= (3, 0): - tests.append((self.maxstring_outer + 4, self.maxstring_inner + 4 + 2, bytes('A', 'ascii') * (self.maxstring_outer + 10))) - else: - tests.append((self.maxstring_outer + 4, self.maxstring_inner + 4 + 2, unicode('A') * (self.maxstring_outer + 10))) - - for limit1, limit2, value in tests: - assert len(self(value)) <= limit1 <= len(repr(value)), (len(self(value)), limit1, len(repr(value)), value) - assert len(self([value])) <= limit2 <= len(repr([value])), (len(self([value])), limit2, len(repr([value])), self([value])) - - def test(source, expected): - actual = self(source) - if actual != expected: - print("Source " + repr(source)) - print("Expect " + expected) - print("Actual " + actual) - print("") - assert False - - def re_test(source, pattern): - import re - actual = self(source) - if not re.match(pattern, actual): - print("Source " + repr(source)) - print("Pattern " + pattern) - print("Actual " + actual) - print("") - assert False - - for ctype, _prefix, _suffix, comma in self.collection_types: - for i in range(len(self.maxcollection)): - prefix = _prefix * (i + 1) - if comma: - suffix = _suffix + ("," + _suffix) * i - else: - suffix = _suffix * (i + 1) - #print("ctype = " + ctype.__name__ + ", maxcollection[" + str(i) + "] == " + str(self.maxcollection[i])) - c1 = ctype(range(self.maxcollection[i] - 1)) - inner_repr = prefix + ', '.join(str(j) for j in c1) - c2 = ctype(range(self.maxcollection[i])) - c3 = ctype(range(self.maxcollection[i] + 1)) - for j in range(i): - c1, c2, c3 = ctype((c1,)), ctype((c2,)), ctype((c3,)) - test(c1, inner_repr + suffix) - test(c2, inner_repr + ", ..." + suffix) - test(c3, inner_repr + ", ..." + suffix) - - if ctype is set: - # Cannot recursively add sets to sets - break - - # Assume that all tests apply equally to all iterable types and only - # test with lists. - c1 = list(range(self.maxcollection[0] * 2)) - c2 = [c1 for _ in range(self.maxcollection[0] * 2)] - c1_expect = '[' + ', '.join(str(j) for j in range(self.maxcollection[0] - 1)) + ', ...]' - test(c1, c1_expect) - c1_expect2 = '[' + ', '.join(str(j) for j in range(self.maxcollection[1] - 1)) + ', ...]' - c2_expect = '[' + ', '.join(c1_expect2 for _ in range(self.maxcollection[0] - 1)) + ', ...]' - test(c2, c2_expect) - - # Ensure dict keys and values are limited correctly - d1 = {} - d1_key = 'a' * self.maxstring_inner * 2 - d1[d1_key] = d1_key - re_test(d1, "{'a+\.\.\.a+': 'a+\.\.\.a+'}") - d2 = {d1_key : d1} - re_test(d2, "{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") - d3 = {d1_key : d2} - if len(self.maxcollection) == 2: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") - else: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") - - # Ensure empty dicts work - test({}, '{}') - - # Ensure dict keys are sorted - d1 = {} - d1['c'] = None - d1['b'] = None - d1['a'] = None - test(d1, "{'a': None, 'b': None, 'c': None}") - - if sys.version_info >= (3, 0): - # Ensure dicts with unsortable keys do not crash - d1 = {} - for _ in range(100): - d1[object()] = None - try: - list(sorted(d1)) - assert False, "d1.keys() should be unorderable" - except TypeError: - pass - self(d1) - - # Test with objects with broken repr implementations - class TestClass(object): - def __repr__(self): - raise NameError - try: - repr(TestClass()) - assert False, "TestClass().__repr__ should have thrown" - except NameError: - pass - self(TestClass()) - - # Test with objects with long repr implementations - class TestClass(object): - repr_str = '<' + 'A' * self.maxother_outer * 2 + '>' - def __repr__(self): - return self.repr_str - re_test(TestClass(), r'\') - - # Test collections that don't override repr - class TestClass(dict): pass - test(TestClass(), '{}') - class TestClass(list): pass - test(TestClass(), '[]') - - # Test collections that override repr - class TestClass(dict): - def __repr__(self): return 'MyRepr' - test(TestClass(), 'MyRepr') - class TestClass(list): - def __init__(self, iter = ()): list.__init__(self, iter) - def __repr__(self): return 'MyRepr' - test(TestClass(), 'MyRepr') - - # Test collections and iterables with long repr - test(TestClass(xrange(0, 15)), 'MyRepr') - test(TestClass(xrange(0, 16)), '') - test(TestClass([TestClass(xrange(0, 10))]), 'MyRepr') - test(TestClass([TestClass(xrange(0, 11))]), '') - - # Test strings inside long iterables - test(TestClass(['a' * (self.maxcollection[1] + 1)]), 'MyRepr') - test(TestClass(['a' * (self.maxstring_inner + 1)]), '') - - # Test range - if sys.version[0] == '2': - range_name = 'xrange' - else: - range_name = 'range' - test(xrange(1, self.maxcollection[0] + 1), '%s(1, %s)' % (range_name, self.maxcollection[0] + 1)) - - # Test directly recursive collections - c1 = [1, 2] - c1.append(c1) - test(c1, '[1, 2, [...]]') - d1 = {1: None} - d1[2] = d1 - test(d1, '{1: None, 2: {...}}') - - # Find the largest possible repr and ensure it is below our arbitrary - # limit (8KB). - coll = '-' * (self.maxstring_outer * 2) - for limit in reversed(self.maxcollection[1:]): - coll = [coll] * (limit * 2) - dcoll = {} - for i in range(self.maxcollection[0]): - dcoll[str(i) * self.maxstring_outer] = coll - text = self(dcoll) - #try: - # text_repr = repr(dcoll) - #except MemoryError: - # print('Memory error raised while creating repr of test data') - # text_repr = '' - #print('len(SafeRepr()(dcoll)) = ' + str(len(text)) + ', len(repr(coll)) = ' + str(len(text_repr))) - assert len(text) < 8192 - - # Test numpy types - they should all use their native reprs, even arrays exceeding limits - try: - import numpy as np - except ImportError: - print('WARNING! could not import numpy - skipping all numpy tests.') - else: - test(np.int32(123), repr(np.int32(123))) - test(np.float64(123.456), repr(np.float64(123.456))) - test(np.zeros(self.maxcollection[0] + 1), repr(np.zeros(self.maxcollection[0] + 1))); - -if __name__ == '__main__': - print('Running tests...') - SafeRepr()._selftest() \ No newline at end of file From 595f1cfab52d39fb0859c9b667308a1f749aa6d0 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 12 Apr 2018 13:42:14 -0700 Subject: [PATCH 06/18] :hammer: refactor and enable launch without debug --- .../debugger/DebugClients/LocalDebugClient.ts | 6 ++--- .../DebugClients/localDebugClientV2.ts | 6 +++++ .../debugger/DebugClients/nonDebugClientV2.ts | 22 ++++++++++++++----- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index 4dfcfa7567ac..117266b91e15 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -159,12 +159,12 @@ export class LocalDebugClient extends DebugClient { }); } private buildLaunchArguments(cwd: string, debugPort: number): string[] { - const ptVSToolsFilePath = this.launcherScriptProvider.getLauncherFilePath(); - return [ptVSToolsFilePath, ...this.buildDebugArguments(cwd, debugPort), ...this.buildStandardArguments()]; + return [...this.buildDebugArguments(cwd, debugPort), ...this.buildStandardArguments()]; } // tslint:disable-next-line:member-ordering protected buildDebugArguments(cwd: string, debugPort: number): string[] { + const ptVSToolsFilePath = this.launcherScriptProvider.getLauncherFilePath(); const vsDebugOptions: string[] = [DebugOptions.RedirectOutput]; if (Array.isArray(this.args.debugOptions)) { this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0) @@ -175,7 +175,7 @@ export class LocalDebugClient extends DebugClient { if (djangoIndex >= 0) { vsDebugOptions[djangoIndex] = 'DjangoDebugging'; } - return [cwd, debugPort.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14', vsDebugOptions.join(',')]; + return [ptVSToolsFilePath, cwd, debugPort.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14', vsDebugOptions.join(',')]; } // tslint:disable-next-line:member-ordering protected buildStandardArguments() { diff --git a/src/client/debugger/DebugClients/localDebugClientV2.ts b/src/client/debugger/DebugClients/localDebugClientV2.ts index 78986c146cb8..5a585005f794 100644 --- a/src/client/debugger/DebugClients/localDebugClientV2.ts +++ b/src/client/debugger/DebugClients/localDebugClientV2.ts @@ -13,7 +13,10 @@ export class LocalDebugClientV2 extends LocalDebugClient { super(args, debugSession, canLaunchTerminal, launcherScriptProvider); } protected buildDebugArguments(cwd: string, debugPort: number): string[] { +<<<<<<< 8020cb03529c53726d3eeb013acde4a63338fb8f <<<<<<< 157f392165ac177707587117ecf76faffe5c4062 +======= +>>>>>>> :hammer: refactor and enable launch without debug const noDebugArg = this.args.noDebug ? ['--nodebug'] : []; return ['-m', 'ptvsd', ...noDebugArg, '--host', 'localhost', '--port', debugPort.toString()]; } @@ -26,8 +29,11 @@ export class LocalDebugClientV2 extends LocalDebugClient { return ['--file', this.args.program, ...programArgs]; } return programArgs; +<<<<<<< 8020cb03529c53726d3eeb013acde4a63338fb8f ======= return [cwd, debugPort.toString()]; >>>>>>> no debug launcher +======= +>>>>>>> :hammer: refactor and enable launch without debug } } diff --git a/src/client/debugger/DebugClients/nonDebugClientV2.ts b/src/client/debugger/DebugClients/nonDebugClientV2.ts index 788578bfbc17..0d47171e810c 100644 --- a/src/client/debugger/DebugClients/nonDebugClientV2.ts +++ b/src/client/debugger/DebugClients/nonDebugClientV2.ts @@ -7,15 +7,27 @@ import { ChildProcess } from 'child_process'; import { DebugSession } from 'vscode-debugadapter'; import { LaunchRequestArguments } from '../Common/Contracts'; import { IDebugLauncherScriptProvider } from '../types'; -import { NonDebugClient } from './NonDebugClient'; +import { DebugType } from './DebugClient'; +import { LocalDebugClientV2 } from './localDebugClientV2'; -export class NonDebugClientV2 extends NonDebugClient { - // tslint:disable-next-line:no-any +export class NonDebugClientV2 extends LocalDebugClientV2 { constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean, launcherScriptProvider: IDebugLauncherScriptProvider) { super(args, debugSession, canLaunchTerminal, launcherScriptProvider); } - protected buildDebugArguments(cwd: string, debugPort: number): string[] { - return [cwd, debugPort.toString()]; + + public get DebugType(): DebugType { + return DebugType.RunLocal; + } + + public Stop() { + super.Stop(); + if (this.pyProc) { + try { + this.pyProc!.kill(); + // tslint:disable-next-line:no-empty + } catch { } + this.pyProc = undefined; + } } protected handleProcessOutput(proc: ChildProcess, _failedToLaunch: (error: Error | string | Buffer) => void) { // Do nothing From 294c8348b36a0a68cb99bca8618f2d0204330bd9 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 12 Apr 2018 20:40:55 -0700 Subject: [PATCH 07/18] :tada: --- src/client/debugger/Common/Contracts.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 989568fa731a..e6fe4a263d53 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -51,6 +51,22 @@ export interface ExceptionHandling { export type DebuggerType = 'python' | 'pythonExperimental'; +export type AdditionalLaunchDebugOptions = { + redirectOutput?: boolean; + django?: boolean; + jinja?: boolean; + debugStdLib?: boolean; + sudo?: boolean; + pyramid?: boolean; +}; + +export type AdditionalAttachDebugOptions = { + redirectOutput?: boolean; + django?: boolean; + jinja?: boolean; + debugStdLib?: boolean; +}; + export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { type?: DebuggerType; /** An absolute path to the program to debug. */ From 4f8111a407e15e75f5c7dff5077a49e454d92f27 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 13 Apr 2018 10:14:56 -0700 Subject: [PATCH 08/18] :sparkles: boolean settings --- package.json | 105 ++++++++++-------- src/client/debugger/Common/Contracts.ts | 35 ++++-- .../DebugClients/RemoteDebugClient.ts | 8 +- .../DebugServers/RemoteDebugServer.ts | 10 +- .../debugger/configProviders/baseProvider.ts | 29 ++--- .../configProviders/pythonProvider.ts | 17 ++- .../configProviders/pythonV2Provider.ts | 65 +++++++---- src/client/extension.ts | 4 +- src/test/debugger/attach.test.ts | 4 +- 9 files changed, 169 insertions(+), 108 deletions(-) diff --git a/package.json b/package.json index 4eade1bf39ca..92924ab601f3 100644 --- a/package.json +++ b/package.json @@ -934,22 +934,6 @@ "description": "Absolute path to the working directory of the program being debugged. Default is the root directory of the file (leave empty).", "default": "${workspaceFolder}" }, - "debugOptions": { - "type": "array", - "description": "Advanced options, view read me for further details.", - "items": { - "type": "string", - "enum": [ - "RedirectOutput", - "DebugStdLib", - "Django", - "Jinja", - "Sudo", - "Pyramid" - ] - }, - "default": [] - }, "env": { "type": "object", "description": "Environment variables defined as a key value pair. Property ends up being the Environment Variable and the value of the property ends up being the value of the Env Variable.", @@ -974,25 +958,48 @@ "type": "boolean", "description": "Enable logging of debugger events to a log file.", "default": false + }, + "redirectOutput": { + "type": "boolean", + "description": "Redirect output.", + "default": true + }, + "debugStdLib": { + "type": "boolean", + "description": "Debug standard library code.", + "default": false + }, + "django": { + "type": "boolean", + "description": "Django debugging.", + "default": false + }, + "jinja": { + "enum": [ + true, + false, + null + ], + "description": "Jinja template debugging (e.g. Flask).", + "default": null + }, + "sudo": { + "type": "boolean", + "description": "Running debug program under elevated permissions (on Unix).", + "default": false + }, + "pyramid": { + "type": "boolean", + "description": "Whether debugging Pyramid applications", + "default": false } } }, "attach": { "required": [ - "port", - "remoteRoot" + "port" ], "properties": { - "localRoot": { - "type": "string", - "description": "Local source root that corrresponds to the 'remoteRoot'.", - "default": "${workspaceFolder}" - }, - "remoteRoot": { - "type": "string", - "description": "The source root of the remote host.", - "default": "" - }, "port": { "type": "number", "description": "Debug port to attach", @@ -1003,20 +1010,6 @@ "description": "IP Address of the of remote server (default is localhost or use 127.0.0.1).", "default": "localhost" }, - "debugOptions": { - "type": "array", - "description": "Advanced options, view read me for further details.", - "items": { - "type": "string", - "enum": [ - "RedirectOutput", - "DebugStdLib", - "Django", - "Jinja" - ] - }, - "default": [] - }, "pathMappings": { "type": "array", "label": "Additional path mappings.", @@ -1031,7 +1024,7 @@ "localRoot": { "type": "string", "label": "Local source root.", - "default": "" + "default": "${workspaceFolder}" }, "remoteRoot": { "type": "string", @@ -1046,7 +1039,31 @@ "type": "boolean", "description": "Enable logging of debugger events to a log file.", "default": false - } + }, + "redirectOutput": { + "type": "boolean", + "description": "Redirect output.", + "default": true + }, + "debugStdLib": { + "type": "boolean", + "description": "Debug standard library code.", + "default": false + }, + "django": { + "type": "boolean", + "description": "Django debugging.", + "default": false + }, + "jinja": { + "enum": [ + true, + false, + null + ], + "description": "Jinja template debugging (e.g. Flask).", + "default": null + } } } }, diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index e6fe4a263d53..08d3fee81833 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -51,23 +51,23 @@ export interface ExceptionHandling { export type DebuggerType = 'python' | 'pythonExperimental'; -export type AdditionalLaunchDebugOptions = { +export interface AdditionalLaunchDebugOptions { redirectOutput?: boolean; django?: boolean; jinja?: boolean; debugStdLib?: boolean; sudo?: boolean; pyramid?: boolean; -}; +} -export type AdditionalAttachDebugOptions = { +export interface AdditionalAttachDebugOptions { redirectOutput?: boolean; django?: boolean; jinja?: boolean; debugStdLib?: boolean; -}; +} -export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { +export interface BaseLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { type?: DebuggerType; /** An absolute path to the program to debug. */ module?: string; @@ -76,31 +76,42 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum /** Automatically stop target after launch. If not specified, target does not stop. */ stopOnEntry?: boolean; args: string[]; - applicationType?: string; cwd?: string; debugOptions?: DebugOptions[]; env?: Object; envFile: string; - exceptionHandling?: ExceptionHandling; console?: 'none' | 'integratedTerminal' | 'externalTerminal'; port?: number; host?: string; logToFile?: boolean; } -export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { +export interface LaunchRequestArgumentsV1 extends BaseLaunchRequestArguments { + exceptionHandling?: ExceptionHandling; +} + +export interface LaunchRequestArguments extends BaseLaunchRequestArguments, AdditionalLaunchDebugOptions { +} + +export interface BaseAttachRequestArguments extends DebugProtocol.AttachRequestArguments { type?: DebuggerType; /** An absolute path to local directory with source. */ - localRoot: string; - remoteRoot: string; port?: number; host?: string; - secret?: string; logToFile?: boolean; - pathMappings?: { localRoot: string; remoteRoot: string }[]; +} +export interface AttachRequestArgumentsV1 extends BaseAttachRequestArguments { + secret?: string; + localRoot: string; + remoteRoot: string; debugOptions?: DebugOptions[]; } +export interface AttachRequestArguments extends BaseAttachRequestArguments, AdditionalAttachDebugOptions { + localRoot?: string; + remoteRoot?: string; + pathMappings?: { localRoot: string; remoteRoot: string }[]; +} export interface IDebugServer { port: number; host?: string; diff --git a/src/client/debugger/DebugClients/RemoteDebugClient.ts b/src/client/debugger/DebugClients/RemoteDebugClient.ts index a7bee8e9f578..172b8ba79bd4 100644 --- a/src/client/debugger/DebugClients/RemoteDebugClient.ts +++ b/src/client/debugger/DebugClients/RemoteDebugClient.ts @@ -1,15 +1,15 @@ import { DebugSession } from 'vscode-debugadapter'; -import { AttachRequestArguments, IPythonProcess } from '../Common/Contracts'; +import { AttachRequestArgumentsV1, BaseAttachRequestArguments, IPythonProcess } from '../Common/Contracts'; import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; import { RemoteDebugServer } from '../DebugServers/RemoteDebugServer'; import { RemoteDebugServerV2 } from '../DebugServers/RemoteDebugServerv2'; import { DebugClient, DebugType } from './DebugClient'; -export class RemoteDebugClient extends DebugClient { +export class RemoteDebugClient extends DebugClient { private pythonProcess?: IPythonProcess; private debugServer?: BaseDebugServer; // tslint:disable-next-line:no-any - constructor(args: AttachRequestArguments, debugSession: DebugSession) { + constructor(args: T, debugSession: DebugSession) { super(args, debugSession); } @@ -19,7 +19,7 @@ export class RemoteDebugClient extends DebugClient { this.debugServer = new RemoteDebugServerV2(this.debugSession, undefined as any, this.args); } else { this.pythonProcess = pythonProcess!; - this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args); + this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args as {} as AttachRequestArgumentsV1); } return this.debugServer!; } diff --git a/src/client/debugger/DebugServers/RemoteDebugServer.ts b/src/client/debugger/DebugServers/RemoteDebugServer.ts index 32d93d004920..b63fa9955484 100644 --- a/src/client/debugger/DebugServers/RemoteDebugServer.ts +++ b/src/client/debugger/DebugServers/RemoteDebugServer.ts @@ -1,8 +1,8 @@ -// tslint:disable:quotemark ordered-imports no-any no-empty curly member-ordering one-line max-func-body-length no-var-self prefer-const cyclomatic-complexity prefer-template +// tslint:disable:quotemark ordered-imports no-any no-empty curly member-ordering one-line max-func-body-length no-var-self prefer-const cyclomatic-complexity prefer-template no-this-assignment "use strict"; import { DebugSession, OutputEvent } from "vscode-debugadapter"; -import { IPythonProcess, IDebugServer, AttachRequestArguments } from "../Common/Contracts"; +import { IPythonProcess, IDebugServer, AttachRequestArgumentsV1 } from "../Common/Contracts"; import * as net from "net"; import { BaseDebugServer } from "./BaseDebugServer"; import { SocketStream } from "../../common/net/socket/SocketStream"; @@ -15,8 +15,8 @@ const AttachCommandBytes: Buffer = new Buffer("ATCH", "ascii"); export class RemoteDebugServer extends BaseDebugServer { private socket?: net.Socket; - private args: AttachRequestArguments; - constructor(debugSession: DebugSession, pythonProcess: IPythonProcess, args: AttachRequestArguments) { + private args: AttachRequestArgumentsV1; + constructor(debugSession: DebugSession, pythonProcess: IPythonProcess, args: AttachRequestArgumentsV1) { super(debugSession, pythonProcess); this.args = args; } @@ -29,7 +29,7 @@ export class RemoteDebugServer extends BaseDebugServer { catch (ex) { } this.socket = undefined; } - private stream: SocketStream; + private stream!: SocketStream; public Start(): Promise { return new Promise((resolve, reject) => { let that = this; diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index 849994a735b1..3e7fb78daac8 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -3,6 +3,8 @@ 'use strict'; +// tslint:disable:no-invalid-template-strings + import { injectable, unmanaged } from 'inversify'; import * as path from 'path'; import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, ProviderResult, Uri, WorkspaceFolder } from 'vscode'; @@ -11,23 +13,21 @@ import { PythonLanguage } from '../../common/constants'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { AttachRequestArguments, DebuggerType, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; +import { BaseAttachRequestArguments, BaseLaunchRequestArguments, DebuggerType, DebugOptions } from '../Common/Contracts'; -// tslint:disable:no-invalid-template-strings - -export type PythonLaunchDebugConfiguration = DebugConfiguration & LaunchRequestArguments; -export type PythonAttachDebugConfiguration = DebugConfiguration & AttachRequestArguments; +export type PythonLaunchDebugConfiguration = DebugConfiguration & T; +export type PythonAttachDebugConfiguration = DebugConfiguration & T; @injectable() -export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { +export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { constructor(@unmanaged() public debugType: DebuggerType, protected serviceContainer: IServiceContainer) { } public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult { const workspaceFolder = this.getWorkspaceFolder(folder); if (debugConfiguration.request === 'attach') { - this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); + this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); } else { - const config = debugConfiguration as PythonLaunchDebugConfiguration; + const config = debugConfiguration as PythonLaunchDebugConfiguration; const numberOfSettings = Object.keys(config); if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) { @@ -44,22 +44,15 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro } return debugConfiguration; } - protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { + protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { if (!Array.isArray(debugConfiguration.debugOptions)) { debugConfiguration.debugOptions = []; } - // Always redirect output. - if (debugConfiguration.debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { - debugConfiguration.debugOptions.push(DebugOptions.RedirectOutput); - } if (!debugConfiguration.host) { debugConfiguration.host = 'localhost'; } - if (!debugConfiguration.localRoot && workspaceFolder) { - debugConfiguration.localRoot = workspaceFolder.fsPath; - } } - protected provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { + protected provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) { debugConfiguration.cwd = workspaceFolder.fsPath; @@ -122,7 +115,7 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro return editor.document.fileName; } } - private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { + private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { if (!debugConfiguration) { return; } diff --git a/src/client/debugger/configProviders/pythonProvider.ts b/src/client/debugger/configProviders/pythonProvider.ts index 1b349bf2c465..69b2fe743319 100644 --- a/src/client/debugger/configProviders/pythonProvider.ts +++ b/src/client/debugger/configProviders/pythonProvider.ts @@ -4,12 +4,25 @@ 'use strict'; import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; -import { BaseConfigurationProvider } from './baseProvider'; +import { AttachRequestArgumentsV1, DebugOptions, LaunchRequestArgumentsV1 } from '../Common/Contracts'; +import { BaseConfigurationProvider, PythonAttachDebugConfiguration } from './baseProvider'; @injectable() -export class PythonDebugConfigurationProvider extends BaseConfigurationProvider { +export class PythonDebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('python', serviceContainer); } + protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { + super.provideAttachDefaults(workspaceFolder, debugConfiguration); + const debugOptions = debugConfiguration.debugOptions!; + // Always redirect output. + if (debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { + debugOptions.push(DebugOptions.RedirectOutput); + } + if (!debugConfiguration.localRoot && workspaceFolder) { + debugConfiguration.localRoot = workspaceFolder.fsPath; + } + } } diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 47e587daabc7..e707598c73d5 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -7,50 +7,75 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; -import { DebugOptions } from '../Common/Contracts'; +import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; import { BaseConfigurationProvider, PythonAttachDebugConfiguration, PythonLaunchDebugConfiguration } from './baseProvider'; @injectable() -export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { +export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('pythonExperimental', serviceContainer); } - protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void { + protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void { super.provideLaunchDefaults(workspaceFolder, debugConfiguration); - - debugConfiguration.stopOnEntry = false; - debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; - - // Add PTVSD specific flags. + const debugOptions = debugConfiguration.debugOptions!; + if (debugConfiguration.debugStdLib) { + debugOptions.push(DebugOptions.DebugStdLib); + } + if (debugConfiguration.django) { + debugOptions.push(DebugOptions.Django); + } + if (debugConfiguration.jinja) { + debugOptions.push(DebugOptions.Jinja); + } + if (debugConfiguration.redirectOutput) { + debugOptions.push(DebugOptions.RedirectOutput); + } + if (debugConfiguration.sudo) { + debugOptions.push(DebugOptions.Sudo); + } if (this.serviceContainer.get(IPlatformService).isWindows) { - debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); + debugOptions.push(DebugOptions.FixFilePathCase); } if (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' - && debugConfiguration.debugOptions.indexOf(DebugOptions.Jinja) === -1) { - debugConfiguration.debugOptions.push(DebugOptions.Jinja); + && debugOptions.indexOf(DebugOptions.Jinja) === -1 + && debugConfiguration.jinja !== false) { + debugOptions.push(DebugOptions.Jinja); } } - protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { + protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { super.provideAttachDefaults(workspaceFolder, debugConfiguration); - - debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; + const debugOptions = debugConfiguration.debugOptions!; + if (debugConfiguration.debugStdLib) { + debugOptions.push(DebugOptions.DebugStdLib); + } + if (debugConfiguration.django) { + debugOptions.push(DebugOptions.Django); + } + if (debugConfiguration.jinja) { + debugOptions.push(DebugOptions.Jinja); + } + if (debugConfiguration.redirectOutput) { + debugOptions.push(DebugOptions.RedirectOutput); + } // We'll need paths to be fixed only in the case where local and remote hosts are the same // I.e. only if hostName === 'localhost' or '127.0.0.1' or '' const isLocalHost = !debugConfiguration.host || debugConfiguration.host === 'localhost' || debugConfiguration.host === '127.0.0.1'; if (this.serviceContainer.get(IPlatformService).isWindows && isLocalHost) { - debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); + debugOptions.push(DebugOptions.FixFilePathCase); } if (this.serviceContainer.get(IPlatformService).isWindows) { - debugConfiguration.debugOptions.push(DebugOptions.WindowsClient); + debugOptions.push(DebugOptions.WindowsClient); } if (!debugConfiguration.pathMappings) { debugConfiguration.pathMappings = []; } - debugConfiguration.pathMappings!.push({ - localRoot: debugConfiguration.localRoot, - remoteRoot: debugConfiguration.remoteRoot - }); + if (debugConfiguration.localRoot && debugConfiguration.remoteRoot) { + debugConfiguration.pathMappings!.push({ + localRoot: debugConfiguration.localRoot, + remoteRoot: debugConfiguration.remoteRoot + }); + } } } diff --git a/src/client/extension.ts b/src/client/extension.ts index ed1b0d09443b..627cf7c39385 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -26,6 +26,7 @@ import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { StopWatch } from './common/stopWatch'; import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; +import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; import { IDebugConfigurationProvider } from './debugger/types'; @@ -153,7 +154,8 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new TerminalProvider(serviceContainer)); context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); - serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { + type ConfigurationProvider = BaseConfigurationProvider; + serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); }); activationDeferred.resolve(); diff --git a/src/test/debugger/attach.test.ts b/src/test/debugger/attach.test.ts index 7747a0450c01..c03e0cdf3573 100644 --- a/src/test/debugger/attach.test.ts +++ b/src/test/debugger/attach.test.ts @@ -11,7 +11,7 @@ import { DebugClient } from 'vscode-debugadapter-testsupport'; import { createDeferred } from '../../client/common/helpers'; import { BufferDecoder } from '../../client/common/process/decoder'; import { ProcessService } from '../../client/common/process/proc'; -import { AttachRequestArguments } from '../../client/debugger/Common/Contracts'; +import { AttachRequestArgumentsV1 } from '../../client/debugger/Common/Contracts'; import { PYTHON_PATH, sleep } from '../common'; import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; @@ -53,7 +53,7 @@ suite('Attach Debugger', () => { } const port = await getFreePort({ host: 'localhost', port: 3000 }); - const args: AttachRequestArguments = { + const args: AttachRequestArgumentsV1 = { localRoot: path.dirname(fileToDebug), remoteRoot: path.dirname(fileToDebug), port: port, From 71304737e264b501df4054c9f8c4dbae288db00a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 13 Apr 2018 13:05:03 -0700 Subject: [PATCH 09/18] Fix linter errors --- src/client/debugger/Main.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index 190330501a52..316b1aed417d 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -14,9 +14,9 @@ import { DebugProtocol } from "vscode-debugprotocol"; import { DEBUGGER } from '../../client/telemetry/constants'; import { DebuggerTelemetry } from '../../client/telemetry/types'; import { isNotInstalledError } from '../common/helpers'; -import { enum_EXCEPTION_STATE, IPythonBreakpoint, IPythonException, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, PythonEvaluationResultReprKind } from "./Common/Contracts"; +import { enum_EXCEPTION_STATE, IPythonBreakpoint, IPythonException, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, PythonEvaluationResultReprKind, LaunchRequestArgumentsV1, AttachRequestArgumentsV1 } from "./Common/Contracts"; import { IDebugServer, IPythonEvaluationResult, IPythonModule, IPythonStackFrame, IPythonThread } from "./Common/Contracts"; -import { AttachRequestArguments, DebugOptions, LaunchRequestArguments, PythonEvaluationResultFlags, TelemetryEvent } from "./Common/Contracts"; +import { DebugOptions, LaunchRequestArguments, PythonEvaluationResultFlags, TelemetryEvent } from "./Common/Contracts"; import { getPythonExecutable, validatePath } from './Common/Utils'; import { DebugClient } from "./DebugClients/DebugClient"; import { CreateAttachDebugClient, CreateLaunchDebugClient } from "./DebugClients/DebugFactory"; @@ -203,8 +203,8 @@ export class PythonDebugger extends LoggingDebugSession { this.sendEvent(new OutputEvent(output, outputChannel)); } private entryResponse?: DebugProtocol.LaunchResponse; - private launchArgs!: LaunchRequestArguments; - private attachArgs!: AttachRequestArguments; + private launchArgs!: LaunchRequestArgumentsV1; + private attachArgs!: AttachRequestArgumentsV1; private canStartDebugger(): Promise { return Promise.resolve(true); } @@ -280,7 +280,7 @@ export class PythonDebugger extends LoggingDebugSession { this.sendErrorResponse(response, 200, errorMsg); }); } - protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments) { + protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArgumentsV1) { if (args.logToFile === true) { logger.setup(LogLevel.Verbose, true); } From 7d5a291c080531212adbfa4045ab176c7cdaed87 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 16 Apr 2018 12:57:39 -0700 Subject: [PATCH 10/18] fixed tests --- package.json | 4 +- .../configProviders/pythonV2Provider.ts | 4 +- .../configProvider/provider.attach.test.ts | 69 +++++++++++++++---- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 92924ab601f3..906ad9e94b14 100644 --- a/package.json +++ b/package.json @@ -1012,7 +1012,7 @@ }, "pathMappings": { "type": "array", - "label": "Additional path mappings.", + "label": "Path mappings.", "items": { "type": "object", "label": "Path mapping", @@ -1063,7 +1063,7 @@ ], "description": "Jinja template debugging (e.g. Flask).", "default": null - } + } } } }, diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index e707598c73d5..08e4e8aef8eb 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -27,7 +27,7 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide if (debugConfiguration.jinja) { debugOptions.push(DebugOptions.Jinja); } - if (debugConfiguration.redirectOutput) { + if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { debugOptions.push(DebugOptions.RedirectOutput); } if (debugConfiguration.sudo) { @@ -54,7 +54,7 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide if (debugConfiguration.jinja) { debugOptions.push(DebugOptions.Jinja); } - if (debugConfiguration.redirectOutput) { + if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { debugOptions.push(DebugOptions.RedirectOutput); } diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index 30d1a4192800..47fec711f938 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -3,7 +3,7 @@ 'use strict'; -// tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion +// tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion no-invalid-this import { expect } from 'chai'; import * as path from 'path'; @@ -83,9 +83,11 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and active file', async () => { const pythonFile = 'xyz.py'; @@ -98,10 +100,12 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and no active file', async () => { setupActiveEditor(undefined, PythonLanguage.language); @@ -111,9 +115,11 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.not.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.not.have.property('localRoot'); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and non python file', async () => { const activeFile = 'xyz.js'; @@ -125,9 +131,9 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); + expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); expect(debugConfig).to.not.have.property('localRoot'); expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); }); test('Defaults should be returned when an empty object is passed without Workspace Folder, with a workspace and an active python file', async () => { const activeFile = 'xyz.py'; @@ -140,10 +146,12 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + } }); test('Ensure \'localRoot\' is left unaltered', async () => { const activeFile = 'xyz.py'; @@ -156,6 +164,43 @@ enum OS { const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, request: 'attach' } as any as DebugConfiguration); expect(debugConfig).to.have.property('localRoot', localRoot); + if (provider.debugType === 'pythonExperimental') { + expect(debugConfig!.pathMappings).to.deep.include({ localRoot: localRoot }); + } + }); + test('Ensure \'localRoot\' and \'remoteRoot\' is used', async function () { + if (provider.debugType !== 'pythonExperimental') { + return this.skip(); + } + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; + const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, remoteRoot, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig!.pathMappings).to.be.lengthOf(1); + expect(debugConfig!.pathMappings).to.deep.include({ localRoot, remoteRoot }); + }); + test('Ensure \'localRoot\' and \'remoteRoot\' is used', async function () { + if (provider.debugType !== 'pythonExperimental') { + return this.skip(); + } + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; + const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, remoteRoot, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig!.pathMappings).to.be.lengthOf(1); + expect(debugConfig!.pathMappings).to.deep.include({ localRoot, remoteRoot }); }); test('Ensure \'remoteRoot\' is left unaltered', async () => { const activeFile = 'xyz.py'; From f022d90bf788415aa940861fb6b13cd38704e91a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 12:44:44 -0700 Subject: [PATCH 11/18] Fix tests --- src/test/debugger/configProvider/provider.attach.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index 47fec711f938..3bdc3d811954 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -165,7 +165,7 @@ enum OS { expect(debugConfig).to.have.property('localRoot', localRoot); if (provider.debugType === 'pythonExperimental') { - expect(debugConfig!.pathMappings).to.deep.include({ localRoot: localRoot }); + expect(debugConfig!.pathMappings).to.be.lengthOf(0); } }); test('Ensure \'localRoot\' and \'remoteRoot\' is used', async function () { From 16b6a50863057a75554b8a2dcf2faa2d158aa703 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 13:15:41 -0700 Subject: [PATCH 12/18] Fixed merge issue --- .../DebugClients/localDebugClientV2.ts | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/client/debugger/DebugClients/localDebugClientV2.ts b/src/client/debugger/DebugClients/localDebugClientV2.ts index 5a585005f794..4fe8a72460cc 100644 --- a/src/client/debugger/DebugClients/localDebugClientV2.ts +++ b/src/client/debugger/DebugClients/localDebugClientV2.ts @@ -13,27 +13,6 @@ export class LocalDebugClientV2 extends LocalDebugClient { super(args, debugSession, canLaunchTerminal, launcherScriptProvider); } protected buildDebugArguments(cwd: string, debugPort: number): string[] { -<<<<<<< 8020cb03529c53726d3eeb013acde4a63338fb8f -<<<<<<< 157f392165ac177707587117ecf76faffe5c4062 -======= ->>>>>>> :hammer: refactor and enable launch without debug - const noDebugArg = this.args.noDebug ? ['--nodebug'] : []; - return ['-m', 'ptvsd', ...noDebugArg, '--host', 'localhost', '--port', debugPort.toString()]; - } - protected buildStandardArguments() { - const programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : []; - if (typeof this.args.module === 'string' && this.args.module.length > 0) { - return ['-m', this.args.module, ...programArgs]; - } - if (this.args.program && this.args.program.length > 0) { - return ['--file', this.args.program, ...programArgs]; - } - return programArgs; -<<<<<<< 8020cb03529c53726d3eeb013acde4a63338fb8f -======= return [cwd, debugPort.toString()]; ->>>>>>> no debug launcher -======= ->>>>>>> :hammer: refactor and enable launch without debug } } From c2b113c1b2315e96bc989d82506d698fdb438584 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 14:11:00 -0700 Subject: [PATCH 13/18] Remove dups --- src/client/debugger/Common/Contracts.ts | 2 +- src/client/debugger/configProviders/baseProvider.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 08d3fee81833..765c6c571caf 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -99,12 +99,12 @@ export interface BaseAttachRequestArguments extends DebugProtocol.AttachRequestA port?: number; host?: string; logToFile?: boolean; + debugOptions?: DebugOptions[]; } export interface AttachRequestArgumentsV1 extends BaseAttachRequestArguments { secret?: string; localRoot: string; remoteRoot: string; - debugOptions?: DebugOptions[]; } export interface AttachRequestArguments extends BaseAttachRequestArguments, AdditionalAttachDebugOptions { diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index 3e7fb78daac8..d1395d8dda6b 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -42,6 +42,11 @@ export abstract class BaseConfigurationProvider dbgConfig.debugOptions!.indexOf(item) === pos); + } return debugConfiguration; } protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { From c7e952aeba2bcc83f49fa8e22e6901196a052fd0 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 14:12:31 -0700 Subject: [PATCH 14/18] Fixed code merge issue --- src/client/debugger/DebugClients/DebugFactory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/debugger/DebugClients/DebugFactory.ts b/src/client/debugger/DebugClients/DebugFactory.ts index f3ae100c08b8..f8c3b6378e9c 100644 --- a/src/client/debugger/DebugClients/DebugFactory.ts +++ b/src/client/debugger/DebugClients/DebugFactory.ts @@ -2,7 +2,7 @@ import { DebugSession } from 'vscode-debugadapter'; import { AttachRequestArguments, LaunchRequestArguments } from '../Common/Contracts'; import { IDebugLauncherScriptProvider } from '../types'; import { DebugClient } from './DebugClient'; -import { DebuggerLauncherScriptProvider, DebuggerV2LauncherScriptProvider, NoDebugLauncherScriptProvider, NoDebugLauncherScriptProviderV2 } from './launcherProvider'; +import { DebuggerLauncherScriptProvider, NoDebugLauncherScriptProvider } from './launcherProvider'; import { LocalDebugClient } from './LocalDebugClient'; import { LocalDebugClientV2 } from './localDebugClientV2'; import { NonDebugClient } from './NonDebugClient'; @@ -13,10 +13,10 @@ export function CreateLaunchDebugClient(launchRequestOptions: LaunchRequestArgum let launchScriptProvider: IDebugLauncherScriptProvider; let debugClientClass: typeof LocalDebugClient; if (launchRequestOptions.noDebug === true) { - launchScriptProvider = launchRequestOptions.type === 'pythonExperimental' ? new NoDebugLauncherScriptProviderV2() : new NoDebugLauncherScriptProvider(); + launchScriptProvider = new NoDebugLauncherScriptProvider(); debugClientClass = launchRequestOptions.type === 'pythonExperimental' ? NonDebugClientV2 : NonDebugClient; } else { - launchScriptProvider = launchRequestOptions.type === 'pythonExperimental' ? new DebuggerV2LauncherScriptProvider() : new DebuggerLauncherScriptProvider(); + launchScriptProvider = new DebuggerLauncherScriptProvider(); debugClientClass = launchRequestOptions.type === 'pythonExperimental' ? LocalDebugClientV2 : LocalDebugClient; } return new debugClientClass(launchRequestOptions, debugSession, canLaunchTerminal, launchScriptProvider); From 10ca82b5906f8a965b46b15d105e5356d79c4f47 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 14:20:58 -0700 Subject: [PATCH 15/18] Fix tests --- .../configProviders/pythonV2Provider.ts | 32 +++++++++++-------- .../configProvider/provider.attach.test.ts | 3 +- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 08e4e8aef8eb..e8953534c1b0 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -19,53 +19,53 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide super.provideLaunchDefaults(workspaceFolder, debugConfiguration); const debugOptions = debugConfiguration.debugOptions!; if (debugConfiguration.debugStdLib) { - debugOptions.push(DebugOptions.DebugStdLib); + this.debugOption(debugOptions, DebugOptions.DebugStdLib); } if (debugConfiguration.django) { - debugOptions.push(DebugOptions.Django); + this.debugOption(debugOptions, DebugOptions.Django); } if (debugConfiguration.jinja) { - debugOptions.push(DebugOptions.Jinja); + this.debugOption(debugOptions, DebugOptions.Jinja); } if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { - debugOptions.push(DebugOptions.RedirectOutput); + this.debugOption(debugOptions, DebugOptions.RedirectOutput); } if (debugConfiguration.sudo) { - debugOptions.push(DebugOptions.Sudo); + this.debugOption(debugOptions, DebugOptions.Sudo); } if (this.serviceContainer.get(IPlatformService).isWindows) { - debugOptions.push(DebugOptions.FixFilePathCase); + this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } if (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' && debugOptions.indexOf(DebugOptions.Jinja) === -1 && debugConfiguration.jinja !== false) { - debugOptions.push(DebugOptions.Jinja); + this.debugOption(debugOptions, DebugOptions.Jinja); } } protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { super.provideAttachDefaults(workspaceFolder, debugConfiguration); const debugOptions = debugConfiguration.debugOptions!; if (debugConfiguration.debugStdLib) { - debugOptions.push(DebugOptions.DebugStdLib); + this.debugOption(debugOptions, DebugOptions.DebugStdLib); } if (debugConfiguration.django) { - debugOptions.push(DebugOptions.Django); + this.debugOption(debugOptions, DebugOptions.Django); } if (debugConfiguration.jinja) { - debugOptions.push(DebugOptions.Jinja); + this.debugOption(debugOptions, DebugOptions.Jinja); } if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { - debugOptions.push(DebugOptions.RedirectOutput); + this.debugOption(debugOptions, DebugOptions.RedirectOutput); } // We'll need paths to be fixed only in the case where local and remote hosts are the same // I.e. only if hostName === 'localhost' or '127.0.0.1' or '' const isLocalHost = !debugConfiguration.host || debugConfiguration.host === 'localhost' || debugConfiguration.host === '127.0.0.1'; if (this.serviceContainer.get(IPlatformService).isWindows && isLocalHost) { - debugOptions.push(DebugOptions.FixFilePathCase); + this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } if (this.serviceContainer.get(IPlatformService).isWindows) { - debugOptions.push(DebugOptions.WindowsClient); + this.debugOption(debugOptions, DebugOptions.WindowsClient); } if (!debugConfiguration.pathMappings) { @@ -78,4 +78,10 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide }); } } + private debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions) { + if (debugOptions.indexOf(debugOption) >= 0) { + return; + } + debugOptions.push(debugOption); + } } diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index 3bdc3d811954..f470616ee7a5 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -234,9 +234,10 @@ enum OS { setupWorkspaces([defaultWorkspace]); const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); + const expectedDebugOptions = debugOptions.slice(); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { debugOptions, request: 'attach' } as any as DebugConfiguration); - expect(debugConfig).to.have.property('debugOptions').to.be.deep.equal(debugOptions); + expect(debugConfig).to.have.property('debugOptions').to.be.deep.equal(expectedDebugOptions); }); }); }); From ffea2d8681fa0a8ac8a6e12269149dbd7d509436 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Apr 2018 09:01:18 -0700 Subject: [PATCH 16/18] Revert changes --- .../PythonTools/visualstudio_py_launcher.py | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 85b5f28be27a..ca5a0ae8d4b3 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -13,6 +13,7 @@ # # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. + """ Starts Debugging, expected to start with normal program to start as first argument and directory to run from as @@ -44,16 +45,19 @@ # Arguments are: # 1. Working directory. # 2. VS debugger port to connect to. -# 3. '-m' or '-c' to override the default run-as mode. [optional] -# 4. Startup script name. -# 5. Script arguments. +# 3. GUID for the debug session. +# 4. Debug options (as integer - see enum PythonDebugOptions). +# 5. '-m' or '-c' to override the default run-as mode. [optional] +# 6. Startup script name. +# 7. Script arguments. # change to directory we expected to start from os.chdir(sys.argv[1]) port_num = int(sys.argv[2]) - -del sys.argv[0:3] +debug_id = sys.argv[3] +debug_options = vspd.parse_debug_options(sys.argv[4]) +del sys.argv[0:5] # set run_as mode appropriately run_as = 'script' @@ -67,24 +71,22 @@ # preserve filename before we del sys filename = sys.argv[0] -# Load the debugger package -try: - import ptvsd - import ptvsd.debugger as vspd - vspd.DONT_DEBUG.append(os.path.normcase(__file__)) -except: - traceback.print_exc() - print(''' -Internal error detected. Please copy the above traceback and report at -https://github.com/Microsoft/vscode-python/issues/new +# fix sys.path to be the script file dir +sys.path[0] = '' -Press Enter to close. . .''') - try: - raw_input() - except NameError: - input() - sys.exit(1) +# exclude ourselves from being debugged +vspd.DONT_DEBUG.append(os.path.normcase(__file__)) + +## Begin modification by Don Jayamanne +# Get current Process id to pass back to debugger +currentPid = os.getpid() +## End Modification by Don Jayamanne + +# remove all state we imported +del sys, os # and start debugging -vspd.debug(filename, port_num, '34806ad9-833a-4524-8cd6-18ca4aa74f14', '', - run_as) +## Begin modification by Don Jayamanne +# Pass current Process id to pass back to debugger +vspd.debug(filename, port_num, debug_id, debug_options, currentPid, run_as) +## End Modification by Don Jayamanne From dec303a32f82355e733185371a3d108b33a544fa Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Apr 2018 10:01:01 -0700 Subject: [PATCH 17/18] Fixed debugger launcher --- .../debugger/DebugClients/launcherProvider.ts | 6 ------ .../debugger/DebugClients/localDebugClientV2.ts | 13 ++++++++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/client/debugger/DebugClients/launcherProvider.ts b/src/client/debugger/DebugClients/launcherProvider.ts index 7641123bef4d..06ea43e0002e 100644 --- a/src/client/debugger/DebugClients/launcherProvider.ts +++ b/src/client/debugger/DebugClients/launcherProvider.ts @@ -14,12 +14,6 @@ export class NoDebugLauncherScriptProvider implements IDebugLauncherScriptProvid } } -export class NoDebugLauncherScriptProviderV2 implements IDebugLauncherScriptProvider { - public getLauncherFilePath(): string { - return path.join(path.dirname(__dirname), '..', '..', '..', 'pythonFiles', 'experimental', 'nodebug_launcher.py'); - } -} - export class DebuggerLauncherScriptProvider implements IDebugLauncherScriptProvider { public getLauncherFilePath(): string { return path.join(path.dirname(__dirname), '..', '..', '..', 'pythonFiles', 'PythonTools', 'visualstudio_py_launcher.py'); diff --git a/src/client/debugger/DebugClients/localDebugClientV2.ts b/src/client/debugger/DebugClients/localDebugClientV2.ts index 4fe8a72460cc..417efba39e26 100644 --- a/src/client/debugger/DebugClients/localDebugClientV2.ts +++ b/src/client/debugger/DebugClients/localDebugClientV2.ts @@ -13,6 +13,17 @@ export class LocalDebugClientV2 extends LocalDebugClient { super(args, debugSession, canLaunchTerminal, launcherScriptProvider); } protected buildDebugArguments(cwd: string, debugPort: number): string[] { - return [cwd, debugPort.toString()]; + const noDebugArg = this.args.noDebug ? ['--nodebug'] : []; + return ['-m', 'ptvsd', ...noDebugArg, '--host', 'localhost', '--port', debugPort.toString()]; + } + protected buildStandardArguments() { + const programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : []; + if (typeof this.args.module === 'string' && this.args.module.length > 0) { + return ['-m', this.args.module, ...programArgs]; + } + if (this.args.program && this.args.program.length > 0) { + return ['--file', this.args.program, ...programArgs]; + } + return programArgs; } } From 543fa1cc3e80dd21c8cf946e8fcf4b4675f2de2b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Apr 2018 10:05:49 -0700 Subject: [PATCH 18/18] removed unnecesary arg (reduces the number of args) --- src/client/debugger/DebugClients/localDebugClientV2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/debugger/DebugClients/localDebugClientV2.ts b/src/client/debugger/DebugClients/localDebugClientV2.ts index 417efba39e26..9028cb10f01e 100644 --- a/src/client/debugger/DebugClients/localDebugClientV2.ts +++ b/src/client/debugger/DebugClients/localDebugClientV2.ts @@ -22,7 +22,7 @@ export class LocalDebugClientV2 extends LocalDebugClient { return ['-m', this.args.module, ...programArgs]; } if (this.args.program && this.args.program.length > 0) { - return ['--file', this.args.program, ...programArgs]; + return [this.args.program, ...programArgs]; } return programArgs; }