diff --git a/README.md b/README.md index bb01ec8d..48bc749e 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio "Jinja", // Enables Jinja (Flask) Template debugging "FixFilePathCase", // See FIX_FILE_PATH_CASE in wrapper.py "DebugStdLib" // Whether to enable debugging of standard library functions - "WindowsClient" // Whether client OS is Windows or not + "WindowsClient" // Whether client OS is Windows + "UnixClient" // Whether client OS is Unix ], "pathMappings": [ { diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index 77473d01..aaa2e159 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -713,7 +713,7 @@ DEBUG_OPTIONS_PARSER = { 'DJANGO_DEBUG': bool_parser, 'FLASK_DEBUG': bool_parser, 'FIX_FILE_PATH_CASE': bool_parser, - 'WINDOWS_CLIENT': bool_parser, + 'CLIENT_OS_TYPE': unquote, 'DEBUG_STDLIB': bool_parser, } @@ -727,7 +727,8 @@ DEBUG_OPTIONS_BY_FLAG = { 'Jinja': 'FLASK_DEBUG=True', 'FixFilePathCase': 'FIX_FILE_PATH_CASE=True', 'DebugStdLib': 'DEBUG_STDLIB=True', - 'WindowsClient': 'WINDOWS_CLIENT=True', + 'WindowsClient': 'CLIENT_OS_TYPE=WINDOWS', + 'UnixClient': 'CLIENT_OS_TYPE=UNIX', } @@ -776,7 +777,7 @@ def _parse_debug_options(opts): INTERPRETER_OPTIONS=string WEB_BROWSER_URL=string url DJANGO_DEBUG=True|False - WINDOWS_CLIENT=True|False + CLIENT_OS_TYPE=WINDOWS|UNIX DEBUG_STDLIB=True|False """ options = {} @@ -793,8 +794,8 @@ def _parse_debug_options(opts): except KeyError: continue - if 'WINDOWS_CLIENT' not in options: - options['WINDOWS_CLIENT'] = platform.system() == 'Windows' # noqa + if 'CLIENT_OS_TYPE' not in options: + options['CLIENT_OS_TYPE'] = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX' # noqa return options @@ -1224,6 +1225,7 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): self.source_map = IDMap() self.enable_source_references = False self.next_var_ref = 0 + self._path_mappings = [] self.exceptions_mgr = ExceptionsManager(self) self.modules_mgr = ModulesManager(self) self.internals_filter = InternalsFilter() @@ -1419,34 +1421,34 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): return True def _initialize_path_maps(self, args): - pathMaps = [] + self._path_mappings = [] for pathMapping in args.get('pathMappings', []): localRoot = pathMapping.get('localRoot', '') remoteRoot = pathMapping.get('remoteRoot', '') if (len(localRoot) > 0 and len(remoteRoot) > 0): - pathMaps.append((localRoot, remoteRoot)) + self._path_mappings.append((localRoot, remoteRoot)) - if len(pathMaps) > 0: - pydevd_file_utils.setup_client_server_paths(pathMaps) + if len(self._path_mappings) > 0: + pydevd_file_utils.setup_client_server_paths(self._path_mappings) def _send_cmd_version_command(self): cmd = pydevd_comm.CMD_VERSION - windows_client = self.debug_options.get( - 'WINDOWS_CLIENT', - platform.system() == 'Windows') - os_id = 'WINDOWS' if windows_client else 'UNIX' + default_os_type = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX' # noqa + client_os_type = self.debug_options.get( + 'CLIENT_OS_TYPE', default_os_type) + os_id = client_os_type msg = '1.1\t{}\tID'.format(os_id) return self.pydevd_request(cmd, msg) @async_handler def _handle_attach(self, args): - self._initialize_path_maps(args) yield self._send_cmd_version_command() + self._initialize_path_maps(args) @async_handler def _handle_launch(self, args): - self._initialize_path_maps(args) yield self._send_cmd_version_command() + self._initialize_path_maps(args) def _handle_detach(self): debug('detaching') @@ -1557,34 +1559,20 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): if self.start_reason == 'launch': return 0 + # If we have no path mappings, then always enable source references. + autogen = len(self._path_mappings) == 0 + try: - return self.source_map.to_vscode(filename, autogen=False) + return self.source_map.to_vscode(filename, autogen=autogen) except KeyError: - # If attaching to a local process (then remote and local are same) - for local_prefix, remote_prefix in pydevd_file_utils.PATHS_FROM_ECLIPSE_TO_PYTHON: # noqa - if local_prefix != remote_prefix: - continue - if filename.startswith(local_prefix): # noqa - return 0 - if platform.system() == 'Windows' and filename.upper().startswith(local_prefix.upper()): # noqa - return 0 + pass - client_filename = pydevd_file_utils.norm_file_to_client(filename) + # If file has been mapped, then source is available on client. + for local_prefix, remote_prefix in self._path_mappings: + if filename.startswith(local_prefix): + return 0 - # If the mapped file is the same as the file we provided, - # then we can generate a soure reference. - if client_filename == filename: - return 0 - elif platform.system() == 'Windows' and \ - client_filename.upper() == filename.upper(): - return 0 - elif client_filename.replace('\\', '/') == filename.replace('\\', '/'): - # If remote is Unix and local is Windows, then PyDevD will - # replace the path separator in remote with with - # the os path separator of remote client - return 0 - else: - return self.source_map.to_vscode(filename, autogen=True) + return self.source_map.to_vscode(filename, autogen=True) @async_handler def on_stackTrace(self, request, args): diff --git a/tests/helpers/debugadapter.py b/tests/helpers/debugadapter.py index 900b2e00..aca6f248 100644 --- a/tests/helpers/debugadapter.py +++ b/tests/helpers/debugadapter.py @@ -53,6 +53,13 @@ COPIED_ENV = [ ] +try: + ConnectionRefusedError +except Exception: + class ConnectionRefusedError(Exception): + pass + + def _copy_env(verbose=False, env=None): variables = {k: v for k, v in os.environ.items() if k in COPIED_ENV} # TODO: Be smarter about the seed? diff --git a/tests/highlevel/test_lifecycle.py b/tests/highlevel/test_lifecycle.py index ae6cc0ca..1fc7f86d 100644 --- a/tests/highlevel/test_lifecycle.py +++ b/tests/highlevel/test_lifecycle.py @@ -125,12 +125,20 @@ class LifecycleTests(HighlevelTest, unittest.TestCase): # TODO: Ensure we see the "terminated" and "exited" events. raise NotImplementedError - def test_attach_from_unix_os(self): - attach_args = {'options': 'WINDOWS_CLIENT=False'} + def test_attach_from_unix_os_vsc(self): + attach_args = {'debugOptions': ['UnixClient']} self.attach(expected_os_id='UNIX', attach_args=attach_args) + def test_attach_from_unix_os(self): + attach_args = {'options': 'CLIENT_OS_TYPE=UNIX'} + self.attach(expected_os_id='UNIX', attach_args=attach_args) + + def test_attach_from_win_os_vsc(self): + attach_args = {'debugOptions': ['WindowsClient']} + self.attach(expected_os_id='WINDOWS', attach_args=attach_args) + def test_attach_from_windows_os(self): - attach_args = {'options': 'WINDOWS_CLIENT=True'} + attach_args = {'options': 'CLIENT_OS_TYPE=WINDOWS'} self.attach(expected_os_id='WINDOWS', attach_args=attach_args) def test_launch(self): diff --git a/tests/resources/system_tests/test_basic/test_args/mymod_launch1/__init__.py b/tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_args/mymod_launch1/__init__.py rename to tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__init__.py diff --git a/tests/resources/system_tests/test_basic/test_args/mymod_launch1/__main__.py b/tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_args/mymod_launch1/__main__.py rename to tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__main__.py diff --git a/tests/resources/system_tests/test_basic/test_output/mymod_attach1/__init__.py b/tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_output/mymod_attach1/__init__.py rename to tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__init__.py diff --git a/tests/resources/system_tests/test_basic/test_output/mymod_attach1/__main__.py b/tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_output/mymod_attach1/__main__.py rename to tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__main__.py diff --git a/tests/resources/system_tests/test_basic/test_output/mymod_launch1/__init__.py b/tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_output/mymod_launch1/__init__.py rename to tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__init__.py diff --git a/tests/resources/system_tests/test_basic/test_output/mymod_launch1/__main__.py b/tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_output/mymod_launch1/__main__.py rename to tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__main__.py diff --git a/tests/resources/system_tests/test_basic/test_without_output/mymod_attach1/__init__.py b/tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_without_output/mymod_attach1/__init__.py rename to tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__init__.py diff --git a/tests/resources/system_tests/test_basic/test_without_output/mymod_attach1/__main__.py b/tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_without_output/mymod_attach1/__main__.py rename to tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__main__.py diff --git a/tests/resources/system_tests/test_basic/test_without_output/mymod_launch1/__init__.py b/tests/resources/system_tests/test_basic/test_without_output/mypkg_launch1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_without_output/mymod_launch1/__init__.py rename to tests/resources/system_tests/test_basic/test_without_output/mypkg_launch1/__init__.py diff --git a/tests/resources/system_tests/test_basic/test_without_output/mymod_launch1/__main__.py b/tests/resources/system_tests/test_basic/test_without_output/mypkg_launch1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_basic/test_without_output/mymod_launch1/__main__.py rename to tests/resources/system_tests/test_basic/test_without_output/mypkg_launch1/__main__.py diff --git a/tests/resources/system_tests/test_breakpoints/mymod_foo/__init__.py b/tests/resources/system_tests/test_breakpoints/mymod_foo/__init__.py deleted file mode 100644 index 4c33a5ce..00000000 --- a/tests/resources/system_tests/test_breakpoints/mymod_foo/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -import mymod_bar.bar - - -def do_foo(): - mymod_bar.bar.do_bar() - - -do_foo() diff --git a/tests/resources/system_tests/test_breakpoints/mymod_attach1/__init__.py b/tests/resources/system_tests/test_breakpoints/mypkg_attach1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_breakpoints/mymod_attach1/__init__.py rename to tests/resources/system_tests/test_breakpoints/mypkg_attach1/__init__.py diff --git a/tests/resources/system_tests/test_breakpoints/mymod_attach1/__main__.py b/tests/resources/system_tests/test_breakpoints/mypkg_attach1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_breakpoints/mymod_attach1/__main__.py rename to tests/resources/system_tests/test_breakpoints/mypkg_attach1/__main__.py diff --git a/tests/resources/system_tests/test_breakpoints/mymod_bar/__init__.py b/tests/resources/system_tests/test_breakpoints/mypkg_bar/__init__.py similarity index 100% rename from tests/resources/system_tests/test_breakpoints/mymod_bar/__init__.py rename to tests/resources/system_tests/test_breakpoints/mypkg_bar/__init__.py diff --git a/tests/resources/system_tests/test_breakpoints/mymod_bar/__main__.py b/tests/resources/system_tests/test_breakpoints/mypkg_bar/__main__.py similarity index 100% rename from tests/resources/system_tests/test_breakpoints/mymod_bar/__main__.py rename to tests/resources/system_tests/test_breakpoints/mypkg_bar/__main__.py diff --git a/tests/resources/system_tests/test_breakpoints/mymod_bar/bar.py b/tests/resources/system_tests/test_breakpoints/mypkg_bar/bar.py similarity index 100% rename from tests/resources/system_tests/test_breakpoints/mymod_bar/bar.py rename to tests/resources/system_tests/test_breakpoints/mypkg_bar/bar.py diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_foo/__init__.py b/tests/resources/system_tests/test_breakpoints/mypkg_foo/__init__.py new file mode 100644 index 00000000..ff77fe66 --- /dev/null +++ b/tests/resources/system_tests/test_breakpoints/mypkg_foo/__init__.py @@ -0,0 +1,8 @@ +import mypkg_bar.bar + + +def do_foo(): + mypkg_bar.bar.do_bar() + + +do_foo() diff --git a/tests/resources/system_tests/test_breakpoints/mymod_foo/__main__.py b/tests/resources/system_tests/test_breakpoints/mypkg_foo/__main__.py similarity index 100% rename from tests/resources/system_tests/test_breakpoints/mymod_foo/__main__.py rename to tests/resources/system_tests/test_breakpoints/mypkg_foo/__main__.py diff --git a/tests/resources/system_tests/test_breakpoints/mymod_launch1/__init__.py b/tests/resources/system_tests/test_breakpoints/mypkg_launch1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_breakpoints/mymod_launch1/__init__.py rename to tests/resources/system_tests/test_breakpoints/mypkg_launch1/__init__.py diff --git a/tests/resources/system_tests/test_breakpoints/mymod_launch1/__main__.py b/tests/resources/system_tests/test_breakpoints/mypkg_launch1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_breakpoints/mymod_launch1/__main__.py rename to tests/resources/system_tests/test_breakpoints/mypkg_launch1/__main__.py diff --git a/tests/resources/system_tests/test_exceptions/mymod_attach1/__init__.py b/tests/resources/system_tests/test_exceptions/mypkg_attach1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_exceptions/mymod_attach1/__init__.py rename to tests/resources/system_tests/test_exceptions/mypkg_attach1/__init__.py diff --git a/tests/resources/system_tests/test_exceptions/mymod_attach1/__main__.py b/tests/resources/system_tests/test_exceptions/mypkg_attach1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_exceptions/mymod_attach1/__main__.py rename to tests/resources/system_tests/test_exceptions/mypkg_attach1/__main__.py diff --git a/tests/resources/system_tests/test_exceptions/mymod_launch1/__init__.py b/tests/resources/system_tests/test_exceptions/mypkg_launch1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_exceptions/mymod_launch1/__init__.py rename to tests/resources/system_tests/test_exceptions/mypkg_launch1/__init__.py diff --git a/tests/resources/system_tests/test_exceptions/mymod_launch1/__main__.py b/tests/resources/system_tests/test_exceptions/mypkg_launch1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_exceptions/mymod_launch1/__main__.py rename to tests/resources/system_tests/test_exceptions/mypkg_launch1/__main__.py diff --git a/tests/resources/system_tests/test_forever/attach_forever.py b/tests/resources/system_tests/test_forever/attach_forever.py new file mode 100644 index 00000000..a6f395ff --- /dev/null +++ b/tests/resources/system_tests/test_forever/attach_forever.py @@ -0,0 +1,11 @@ +import ptvsd +import sys +import time + +ptvsd.enable_attach((sys.argv[1], sys.argv[2])) + +i = 0 +while True: + time.sleep(0.1) + print(i) + i += 1 diff --git a/tests/resources/system_tests/test_terminate/mymod_launch1/__init__.py b/tests/resources/system_tests/test_terminate/mypkg_launch1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_terminate/mymod_launch1/__init__.py rename to tests/resources/system_tests/test_terminate/mypkg_launch1/__init__.py diff --git a/tests/resources/system_tests/test_terminate/mymod_launch1/__main__.py b/tests/resources/system_tests/test_terminate/mypkg_launch1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_terminate/mymod_launch1/__main__.py rename to tests/resources/system_tests/test_terminate/mypkg_launch1/__main__.py diff --git a/tests/resources/system_tests/test_variables/mymod_attach1/__init__.py b/tests/resources/system_tests/test_variables/mypkg_attach1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_variables/mymod_attach1/__init__.py rename to tests/resources/system_tests/test_variables/mypkg_attach1/__init__.py diff --git a/tests/resources/system_tests/test_variables/mymod_attach1/__main__.py b/tests/resources/system_tests/test_variables/mypkg_attach1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_variables/mymod_attach1/__main__.py rename to tests/resources/system_tests/test_variables/mypkg_attach1/__main__.py diff --git a/tests/resources/system_tests/test_variables/mymod_launch1/__init__.py b/tests/resources/system_tests/test_variables/mypkg_launch1/__init__.py similarity index 100% rename from tests/resources/system_tests/test_variables/mymod_launch1/__init__.py rename to tests/resources/system_tests/test_variables/mypkg_launch1/__init__.py diff --git a/tests/resources/system_tests/test_variables/mymod_launch1/__main__.py b/tests/resources/system_tests/test_variables/mypkg_launch1/__main__.py similarity index 100% rename from tests/resources/system_tests/test_variables/mymod_launch1/__main__.py rename to tests/resources/system_tests/test_variables/mypkg_launch1/__main__.py diff --git a/tests/system_tests/__init__.py b/tests/system_tests/__init__.py index 18b0e30e..5574bc1f 100644 --- a/tests/system_tests/__init__.py +++ b/tests/system_tests/__init__.py @@ -2,7 +2,9 @@ import contextlib import os import ptvsd import signal +import sys import time +import traceback import unittest from collections import namedtuple @@ -234,24 +236,46 @@ class LifecycleTestsBase(TestsBase, unittest.TestCase): pass time.sleep(1) # wait for socket connections to die out. - def _wrap_and_reraise(ex, session): + def _wrap_and_reraise(session, ex, exc_type, exc_value, exc_traceback): + """If we have connetion errors, then re-raised wrapped in + ConnectionTimeoutError. If using py3, then chain exceptions so + we do not loose the original exception, else try hack approach + for py27.""" messages = [] + formatted_ex = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)) # noqa try: messages = [str(msg) for msg in _strip_newline_output_events(session.received)] except Exception: pass - messages = os.linesep.join(messages) + fmt = { + "sep": os.linesep, + "messages": os.linesep.join(messages), + "error": ''.join(traceback.format_exception_only(exc_type, exc_value)) # noqa + } + message = """ + +Session Messages: +----------------- +%(messages)s + +Original Error: +--------------- +%(error)s""" % fmt + try: - raise Exception(messages) from ex - except Exception: - print(messages) - raise ex + # Chain the original exception for py3. + exec('raise Exception(message) from ex', globals(), locals()) + except SyntaxError: + # This happens when using py27. + message = message + os.linesep + formatted_ex + exec("raise Exception(message)", globals(), locals()) def _handle_exception(ex, adapter, session): + exc_type, exc_value, exc_traceback = sys.exc_info() _kill_proc(adapter.pid) - _wrap_and_reraise(ex, session) + _wrap_and_reraise(session, ex, exc_type, exc_value, exc_traceback) if debug_info.attachtype == 'import' and \ debug_info.modulename is not None: diff --git a/tests/system_tests/test_basic.py b/tests/system_tests/test_basic.py index c7b86624..e83d34e3 100644 --- a/tests/system_tests/test_basic.py +++ b/tests/system_tests/test_basic.py @@ -111,10 +111,10 @@ class LaunchFileTests(BasicTests): ) -class LaunchModuleTests(BasicTests): +class LaunchPackageTests(BasicTests): def test_with_output(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' cwd = WITH_OUTPUT.root env = WITH_OUTPUT.env_with_py_path() self.run_test_output( @@ -122,7 +122,7 @@ class LaunchModuleTests(BasicTests): ) def test_without_output(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' cwd = WITHOUT_OUTPUT.root env = WITHOUT_OUTPUT.env_with_py_path() self.run_test_without_output( @@ -131,7 +131,7 @@ class LaunchModuleTests(BasicTests): @unittest.skip('Broken') def test_termination(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' cwd = TEST_TERMINATION_FILES.root env = TEST_TERMINATION_FILES.env_with_py_path() self.run_test_output( @@ -143,7 +143,7 @@ class LaunchModuleTests(BasicTests): @unittest.skip('Broken') def test_arguments(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' cwd = WITH_ARGS.root env = WITH_ARGS.env_with_py_path() argv = ['arg1', 'arg2'] @@ -213,10 +213,10 @@ class PTVSDAttachTests(BasicTests): ) -class ServerAttachModuleTests(BasicTests): +class ServerAttachPackageTests(BasicTests): def test_with_output(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' cwd = WITH_OUTPUT.root env = WITH_OUTPUT.env_with_py_path() argv = ['localhost', str(PORT)] @@ -231,7 +231,7 @@ class ServerAttachModuleTests(BasicTests): ) def test_without_output(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' cwd = WITHOUT_OUTPUT.root env = WITHOUT_OUTPUT.env_with_py_path() argv = ['localhost', str(PORT)] @@ -246,11 +246,11 @@ class ServerAttachModuleTests(BasicTests): ) -class PTVSDAttachModuleTests(BasicTests): +class PTVSDAttachPackageTests(BasicTests): def test_with_output(self): #self.enable_verbose() - module_name = 'mymod_attach1' + module_name = 'mypkg_attach1' cwd = WITH_OUTPUT.root env = WITH_OUTPUT.env_with_py_path() argv = ['localhost', str(PORT)] @@ -266,7 +266,7 @@ class PTVSDAttachModuleTests(BasicTests): ) def test_without_output(self): - module_name = 'mymod_attach1' + module_name = 'mypkg_attach1' cwd = WITHOUT_OUTPUT.root env = WITHOUT_OUTPUT.env_with_py_path() argv = ['localhost', str(PORT)] diff --git a/tests/system_tests/test_breakpoints.py b/tests/system_tests/test_breakpoints.py index 20e48906..344e0bb3 100644 --- a/tests/system_tests/test_breakpoints.py +++ b/tests/system_tests/test_breakpoints.py @@ -15,7 +15,17 @@ TEST_FILES = TestResources.from_module(__name__) class BreakpointTests(LifecycleTestsBase): def run_test_with_break_points(self, debug_info, bp_filename, bp_line): - options = {'debugOptions': ['RedirectOutput']} + pathMappings = [] + # Required to ensure sourceReference = 0 + if (debug_info.starttype == 'attach'): + pathMappings.append({ + 'localRoot': debug_info.cwd, + 'remoteRoot': debug_info.cwd + }) + options = { + 'debugOptions': ['RedirectOutput'], + 'pathMappings': pathMappings + } breakpoints = [{ 'source': { 'path': bp_filename @@ -82,7 +92,7 @@ class BreakpointTests(LifecycleTestsBase): def run_test_with_break_points_across_files( self, debug_info, first_file, second_file, second_file_line, - expected_modules, expected_stacktrace): + expected_stacktrace): breakpoints = [{ 'source': { 'path': second_file @@ -111,14 +121,6 @@ class BreakpointTests(LifecycleTestsBase): session.send_request('continue', threadId=tid) - received = list(_strip_newline_output_events(session.received)) - - for mod in expected_modules: - found_mod = self.find_events(received, 'module', mod) - self.assertEqual(len(found_mod), - 1, - 'Modul not found {}'.format(mod)) - self.assert_is_subset(stacktrace, expected_stacktrace) def run_test_conditional_break_points(self, debug_info): @@ -289,19 +291,6 @@ class LaunchFileTests(BreakpointTests): first_file = TEST_FILES.resolve('foo.py') second_file = TEST_FILES.resolve('bar.py') cwd = os.path.dirname(first_file) - expected_modules = [{ - 'reason': 'new', - 'module': { - 'path': second_file, - 'name': 'bar' - } - }, { - 'reason': 'new', - 'module': { - 'path': first_file, - 'name': '__main__' - } - }] expected_stacktrace = { 'stackFrames': [{ 'name': 'do_bar', @@ -335,7 +324,6 @@ class LaunchFileTests(BreakpointTests): first_file, second_file, 2, - expected_modules, expected_stacktrace, ) @@ -422,10 +410,10 @@ class LaunchFileTests(BreakpointTests): ) -class LaunchModuleTests(BreakpointTests): +class LaunchPackageTests(BreakpointTests): def test_with_break_points(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.root bp_filename = os.path.join(cwd, module_name, '__init__.py') @@ -436,25 +424,11 @@ class LaunchModuleTests(BreakpointTests): ) def test_with_break_points_across_files(self): - module_name = 'mymod_foo' + module_name = 'mypkg_foo' first_file = TEST_FILES.resolve(module_name, '__init__.py') - second_file = TEST_FILES.resolve('mymod_bar', 'bar.py') + second_file = TEST_FILES.resolve('mypkg_bar', 'bar.py') env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.root - expected_modules = [{ - 'reason': 'new', - 'module': { - 'package': 'mymod_bar', - 'path': second_file, - 'name': 'mymod_bar.bar' - } - }, { - 'reason': 'new', - 'module': { - 'path': first_file, - 'name': '__main__' - } - }] expected_stacktrace = { 'stackFrames': [{ 'name': 'do_bar', @@ -488,7 +462,6 @@ class LaunchModuleTests(BreakpointTests): first_file, second_file, 2, - expected_modules, expected_stacktrace, ) @@ -530,10 +503,10 @@ class PTVSDAttachTests(BreakpointTests): ) -class ServerAttachModuleTests(BreakpointTests): +class ServerAttachPackageTests(BreakpointTests): def test_with_break_points(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.root argv = ['localhost', str(PORT)] @@ -552,10 +525,10 @@ class ServerAttachModuleTests(BreakpointTests): @unittest.skip('Needs fixing') -class PTVSDAttachModuleTests(BreakpointTests): +class PTVSDAttachPackageTests(BreakpointTests): def test_with_break_points(self): - module_name = 'mymod_attach1' + module_name = 'mypkg_attach1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.root argv = ['localhost', str(PORT)] diff --git a/tests/system_tests/test_exceptions.py b/tests/system_tests/test_exceptions.py index 94fc40aa..6c8abb38 100644 --- a/tests/system_tests/test_exceptions.py +++ b/tests/system_tests/test_exceptions.py @@ -100,14 +100,14 @@ class LaunchFileTests(ExceptionTests): class LaunchModuleExceptionLifecycleTests(ExceptionTests): def test_breaking_into_handled_exceptions(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.parent.root self.run_test_breaking_into_handled_exceptions( DebugInfo(modulename=module_name, env=env, cwd=cwd)) def test_not_breaking_into_handled_exceptions(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.parent.root self.run_test_not_breaking_into_handled_exceptions( @@ -174,7 +174,7 @@ class PTVSDAttachExceptionLifecycleTests(ExceptionTests): class ServerAttachModuleExceptionLifecycleTests(ExceptionTests): def test_breaking_into_handled_exceptions(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.root argv = ['localhost', str(PORT)] @@ -188,7 +188,7 @@ class ServerAttachModuleExceptionLifecycleTests(ExceptionTests): )) def test_not_breaking_into_handled_exceptions(self): - module_name = 'mymod_launch1' + module_name = 'mypkg_launch1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.root argv = ['localhost', str(PORT)] @@ -206,7 +206,7 @@ class ServerAttachModuleExceptionLifecycleTests(ExceptionTests): class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests): def test_breaking_into_handled_exceptions(self): - module_name = 'mymod_attach1' + module_name = 'mypkg_attach1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.root argv = ['localhost', str(PORT)] @@ -221,7 +221,7 @@ class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests): )) def test_not_breaking_into_handled_exceptions(self): - module_name = 'mymod_attach1' + module_name = 'mypkg_attach1' env = TEST_FILES.env_with_py_path() cwd = TEST_FILES.root argv = ['localhost', str(PORT)] diff --git a/tests/system_tests/test_main.py b/tests/system_tests/test_main.py index f53e633d..fea89847 100644 --- a/tests/system_tests/test_main.py +++ b/tests/system_tests/test_main.py @@ -474,29 +474,6 @@ class LifecycleTests(LifecycleTestsBase): 'name': 'MainThread', }], }), - self.new_event( - 'module', - module={ - 'id': 1, - 'name': '__main__', - 'path': filename, - 'package': None, - }, - reason='new', - ), - self.new_response(req_stacktrace1.req, **{ - 'totalFrames': 1, - 'stackFrames': [{ - 'id': 1, - 'name': '', - 'source': { - 'path': filename, - 'sourceReference': 0, - }, - 'line': bp1, - 'column': 1, - }], - }), self.new_response(req_disconnect.req), ]) self.messages.reset_all() diff --git a/tests/system_tests/test_remote.py b/tests/system_tests/test_remote.py index 255238e0..36dc22c0 100644 --- a/tests/system_tests/test_remote.py +++ b/tests/system_tests/test_remote.py @@ -1,6 +1,10 @@ import os import os.path +import signal +import sys +import time +from tests.helpers.debugsession import Awaitable from tests.helpers.resource import TestResources from tests.helpers.socket import resolve_hostname from . import ( @@ -11,9 +15,20 @@ from . import ( TEST_FILES = TestResources.from_module('tests.system_tests.test_basic') WITH_OUTPUT = TEST_FILES.sub('test_output') +SYSTEM_TEST_FILES = TestResources.from_module('tests.system_tests') +WITH_TEST_FORVER = SYSTEM_TEST_FILES.sub('test_forever') class RemoteTests(LifecycleTestsBase): + def _assert_stacktrace_is_subset(self, stacktrace, expected_stacktrace): + # Ignore path case on Windows. + if sys.platform == 'win32': + for frame in stacktrace.get('stackFrames'): + frame['source']['path'] = frame['source'].get('path', '').upper() # noqa + for frame in expected_stacktrace.get('stackFrames'): + frame['source']['path'] = frame['source'].get('path', '').upper() # noqa + + self.assert_is_subset(stacktrace, expected_stacktrace) def run_test_attach(self, debug_info): options = {'debugOptions': ['RedirectOutput']} @@ -28,6 +43,48 @@ class RemoteTests(LifecycleTestsBase): self.new_event('output', category='stderr', output='no'), ]) + def run_test_source_references(self, + debug_info, + expected_stacktrace, + path_mappings=[], + debug_options=[]): + options = { + 'debugOptions': debug_options, + 'pathMappings': path_mappings + } + + with self.start_debugging(debug_info) as dbg: + (_, req_attach, _, _, _, req_threads) = lifecycle_handshake( + dbg.session, + debug_info.starttype, + options=options, + threads=True) + + # wait till we enter the for loop. + time.sleep(1) + Awaitable.wait_all(req_attach, req_threads) + with dbg.session.wait_for_event('stopped') as result: + arguments = { + 'source': { + 'name': os.path.basename(debug_info.filename), + 'path': debug_info.filename + }, + 'lines': [9], + 'breakpoints': [{'line': 9}] + } + dbg.session.send_request('setBreakpoints', **arguments) + + tid = result['msg'].body['threadId'] + stacktrace = dbg.session.send_request('stackTrace', threadId=tid) + stacktrace.wait() + dbg.session.send_request('continue', threadId=tid).wait() + + # Kill remove program. + os.kill(dbg.adapter.pid, signal.SIGTERM) + + self._assert_stacktrace_is_subset(stacktrace.resp.body, + expected_stacktrace) + class AttachFileTests(RemoteTests): @@ -86,6 +143,129 @@ class AttachFileTests(RemoteTests): host=ip, cwd=cwd, starttype='attach', - argv=argv, - ), - ) + argv=argv)) + + def test_source_references_should_be_returned_without_path_mappings(self): + filename = WITH_TEST_FORVER.resolve('attach_forever.py') + cwd = os.path.dirname(filename) + argv = ['localhost', str(PORT)] + expected_stacktrace = { + 'stackFrames': [{ + 'source': { + 'path': filename, + 'sourceReference': 1 + } + }], + } + self.run_test_source_references( + DebugInfo( + filename=filename, + attachtype='import', + cwd=cwd, + starttype='attach', + argv=argv), expected_stacktrace) + + def test_source_references_should_not_be_returned_with_path_mappings(self): + filename = WITH_TEST_FORVER.resolve('attach_forever.py') + cwd = os.path.dirname(filename) + argv = ['localhost', str(PORT)] + path_mappings = [{ + 'localRoot': os.path.dirname(filename), + 'remoteRoot': os.path.dirname(filename) + }] + expected_stacktrace = { + 'stackFrames': [{ + 'source': { + 'path': filename, + 'sourceReference': 0 + } + }], + } + self.run_test_source_references( + DebugInfo( + filename=filename, + attachtype='import', + cwd=cwd, + starttype='attach', + argv=argv), expected_stacktrace, path_mappings) + + def test_source_references_should_be_returned_with_invalid_path_mappings( + self): + filename = WITH_TEST_FORVER.resolve('attach_forever.py') + cwd = os.path.dirname(filename) + argv = ['localhost', str(PORT)] + path_mappings = [{ + 'localRoot': os.path.dirname(__file__), + 'remoteRoot': os.path.dirname(__file__) + }] + expected_stacktrace = { + 'stackFrames': [{ + 'source': { + 'path': filename, + 'sourceReference': 1 + } + }], + } + self.run_test_source_references( + DebugInfo( + filename=filename, + attachtype='import', + cwd=cwd, + starttype='attach', + argv=argv), expected_stacktrace, path_mappings) + + def test_source_references_should_be_returned_with_win_client(self): + filename = WITH_TEST_FORVER.resolve('attach_forever.py') + cwd = os.path.dirname(filename) + argv = ['localhost', str(PORT)] + client_dir = 'C:\\Development\\Projects\\src\\sub dir' + path_mappings = [{ + 'localRoot': client_dir, + 'remoteRoot': os.path.dirname(filename) + }] + expected_stacktrace = { + 'stackFrames': [{ + 'source': { + 'path': client_dir + '\\' + os.path.basename(filename), + 'sourceReference': 0 + } + }], + } + self.run_test_source_references( + DebugInfo( + filename=filename, + attachtype='import', + cwd=cwd, + starttype='attach', + argv=argv), + expected_stacktrace, + path_mappings=path_mappings, + debug_options=['WindowsClient']) + + def test_source_references_should_be_returned_with_unix_client(self): + filename = WITH_TEST_FORVER.resolve('attach_forever.py') + cwd = os.path.dirname(filename) + argv = ['localhost', str(PORT)] + client_dir = '/Users/PeterSmith/projects/src/sub dir' + path_mappings = [{ + 'localRoot': client_dir, + 'remoteRoot': os.path.dirname(filename) + }] + expected_stacktrace = { + 'stackFrames': [{ + 'source': { + 'path': client_dir + '/' + os.path.basename(filename), + 'sourceReference': 0 + } + }], + } + self.run_test_source_references( + DebugInfo( + filename=filename, + attachtype='import', + cwd=cwd, + starttype='attach', + argv=argv), + expected_stacktrace, + path_mappings=path_mappings, + debug_options=['UnixClient'])