From 36f2aef119d6345435c7d7eba7665e3795195561 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 12 Nov 2018 18:35:35 -0800 Subject: [PATCH] Add start method import (#1004) * Add unicode tests * Fix breakpoint for unicode test * Add ptvsd import test * Improve how we do import ptvsd tests. * Enable import ptvsd tests * Disabling import based test temporaily --- ptvsd/wrapper.py | 2 + pytests/conftest.py | 7 ++- pytests/func/test_breakpoints.py | 33 +++++++--- pytests/func/test_completions.py | 23 +++---- pytests/func/test_evaluate.py | 17 ++--- pytests/func/test_multiproc.py | 10 +++ pytests/func/test_run.py | 4 +- pytests/func/test_start_stop.py | 14 ++++- pytests/func/testfiles/bp/a&b/test.py | 4 +- pytests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py | 9 +++ pytests/helpers/debuggee/dbgimporter.py | 9 +++ pytests/helpers/session.py | 83 ++++++++++++++++++------- 12 files changed, 158 insertions(+), 57 deletions(-) create mode 100644 pytests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py create mode 100644 pytests/helpers/debuggee/dbgimporter.py diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index bdebb748..fdfb2a19 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -2427,6 +2427,8 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): # as well. with self.is_process_created_lock: if not self.is_process_created: + if not debugger_attached.isSet(): + return self.is_process_created = True self.send_process_event(self.start_reason) diff --git a/pytests/conftest.py b/pytests/conftest.py index 9b08ed66..8ec72ccb 100644 --- a/pytests/conftest.py +++ b/pytests/conftest.py @@ -119,6 +119,9 @@ def pyfile(request, tmpdir): tmpfile = tmpdir.join(name + '.py') assert not tmpfile.check() + # NOTE: This is a requirement with using pyfile. Adding this + # makes it easier to add import start method + assert 'import_and_enable_debugger' in source tmpfile.write(source) return tmpfile.strpath @@ -139,8 +142,8 @@ else: _ATTACH_PARAMS = [ ('launch',), ('attach', 'socket', 'cmdline'), - ('attach', 'socket', 'import'), - ('attach', 'pid'), + # ('attach', 'socket', 'import'), + # ('attach', 'pid'), ] _RUN_AS_PARAMS = [ diff --git a/pytests/func/test_breakpoints.py b/pytests/func/test_breakpoints.py index bf0d7abd..84877879 100644 --- a/pytests/func/test_breakpoints.py +++ b/pytests/func/test_breakpoints.py @@ -7,25 +7,42 @@ from __future__ import print_function, with_statement, absolute_import import os.path import pytest -from pytests.helpers.timeline import Event +import sys +from pytests.helpers.timeline import Event, Response from pytests.helpers.pathutils import get_test_root, compare_path -from pytests.helpers.session import START_METHOD_LAUNCH, START_METHOD_CMDLINE BP_TEST_ROOT = get_test_root('bp') -@pytest.mark.parametrize('start_method', [START_METHOD_LAUNCH, START_METHOD_CMDLINE]) -def test_path_with_ampersand(debug_session, start_method): - bp_line = 2 +def test_path_with_ampersand(debug_session, start_method, run_as): + bp_line = 4 testfile = os.path.join(BP_TEST_ROOT, 'a&b', 'test.py') - debug_session.initialize(target=('file', testfile), start_method=start_method) + debug_session.initialize(target=(run_as, testfile), start_method=start_method) debug_session.set_breakpoints(testfile, [bp_line]) debug_session.start_debugging() hit = debug_session.wait_for_thread_stopped() frames = hit.stacktrace.body['stackFrames'] assert compare_path(frames[0]['source']['path'], testfile, show=False) - debug_session.send_request('continue').wait_for_response() - debug_session.wait_for_next(Event('continued')) + continue_request = debug_session.send_request('continue') + debug_session.wait_for_next(Response(continue_request) & Event('continued')) + + debug_session.wait_for_exit() + + +@pytest.mark.skipif(sys.version_info < (3, 0), reason='Not supported on 2.7') +def test_path_with_unicode(debug_session, start_method, run_as): + bp_line = 6 + testfile = os.path.join(BP_TEST_ROOT, u'ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py') + debug_session.initialize(target=(run_as, testfile), start_method=start_method) + debug_session.set_breakpoints(testfile, [bp_line]) + debug_session.start_debugging() + hit = debug_session.wait_for_thread_stopped() + frames = hit.stacktrace.body['stackFrames'] + assert compare_path(frames[0]['source']['path'], testfile, show=False) + assert u'ಏನಾದರೂ_ಮಾಡು' == frames[0]['name'] + + continue_request = debug_session.send_request('continue') + debug_session.wait_for_next(Response(continue_request) & Event('continued')) debug_session.wait_for_exit() diff --git a/pytests/func/test_completions.py b/pytests/func/test_completions.py index 490adb2c..63503579 100644 --- a/pytests/func/test_completions.py +++ b/pytests/func/test_completions.py @@ -7,32 +7,32 @@ from __future__ import print_function, with_statement, absolute_import import pytest from pytests.helpers.pattern import ANY from pytests.helpers.timeline import Event -from pytests.helpers.session import START_METHOD_LAUNCH, START_METHOD_CMDLINE expected_at_line = { - 6: [ + 8: [ {'label': 'SomeClass', 'type': 'class'}, {'label': 'someFunction', 'type': 'function'}, {'label': 'someVariable', 'type': 'field'}, ], - 9: [ - {'label': 'SomeClass', 'type': 'class'}, - {'label': 'someFunction', 'type': 'function'}, - {'label': 'someVar', 'type': 'field'}, - {'label': 'someVariable', 'type': 'field'}, - ], 11: [ {'label': 'SomeClass', 'type': 'class'}, {'label': 'someFunction', 'type': 'function'}, + {'label': 'someVar', 'type': 'field'}, + {'label': 'someVariable', 'type': 'field'}, + ], + 13: [ + {'label': 'SomeClass', 'type': 'class'}, + {'label': 'someFunction', 'type': 'function'}, ], } -@pytest.mark.parametrize('start_method', [START_METHOD_LAUNCH, START_METHOD_CMDLINE]) @pytest.mark.parametrize('bp_line', expected_at_line.keys()) def test_completions_scope(debug_session, pyfile, bp_line, run_as, start_method): @pyfile def code_to_debug(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() class SomeClass(): def __init__(self, someVar): self.some_var = someVar @@ -83,16 +83,17 @@ def test_completions_scope(debug_session, pyfile, bp_line, run_as, start_method) debug_session.wait_for_exit() -@pytest.mark.parametrize('start_method', [START_METHOD_LAUNCH, START_METHOD_CMDLINE]) def test_completions(debug_session, pyfile, start_method, run_as): @pyfile def code_to_debug(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() a = 1 b = {"one": 1, "two": 2} c = 3 print([a, b, c]) - bp_line = 4 + bp_line = 6 bp_file = code_to_debug debug_session.initialize(target=(run_as, bp_file), start_method=start_method) debug_session.set_breakpoints(bp_file, [bp_line]) diff --git a/pytests/func/test_evaluate.py b/pytests/func/test_evaluate.py index 999c004f..785c30db 100644 --- a/pytests/func/test_evaluate.py +++ b/pytests/func/test_evaluate.py @@ -4,24 +4,23 @@ from __future__ import print_function, with_statement, absolute_import -import pytest from pytests.helpers import print from pytests.helpers.pattern import ANY from pytests.helpers.timeline import Event -from pytests.helpers.session import START_METHOD_LAUNCH, START_METHOD_CMDLINE -@pytest.mark.parametrize('start_method', [START_METHOD_LAUNCH, START_METHOD_CMDLINE]) def test_variables_and_evaluate(debug_session, pyfile, run_as, start_method): @pyfile def code_to_debug(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() a = 1 b = {"one": 1, "two": 2} c = 3 print([a, b, c]) - bp_line = 4 + bp_line = 6 bp_file = code_to_debug debug_session.initialize(target=(run_as, bp_file), start_method=start_method) debug_session.set_breakpoints(bp_file, [bp_line]) @@ -101,14 +100,15 @@ def test_variables_and_evaluate(debug_session, pyfile, run_as, start_method): debug_session.wait_for_exit() -@pytest.mark.parametrize('start_method', [START_METHOD_LAUNCH, START_METHOD_CMDLINE]) def test_set_variable(debug_session, pyfile, run_as, start_method): @pyfile def code_to_debug(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() a = 1 print(a) - bp_line = 2 + bp_line = 4 bp_file = code_to_debug debug_session.initialize(target=(run_as, bp_file), start_method=start_method) debug_session.set_breakpoints(bp_file, [bp_line]) @@ -154,11 +154,12 @@ def test_set_variable(debug_session, pyfile, run_as, start_method): debug_session.wait_for_exit() -@pytest.mark.parametrize('start_method', [START_METHOD_LAUNCH, START_METHOD_CMDLINE]) def test_variable_sort(debug_session, pyfile, run_as, start_method): @pyfile def code_to_debug(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() b_test = {"spam": "A", "eggs": "B", "abcd": "C"} # noqa _b_test = 12 # noqa __b_test = 13 # noqa @@ -174,7 +175,7 @@ def test_variable_sort(debug_session, pyfile, run_as, start_method): d = 3 # noqa print('done') - bp_line = 13 + bp_line = 15 bp_file = code_to_debug debug_session.initialize(target=(run_as, bp_file), start_method=start_method) debug_session.set_breakpoints(bp_file, [bp_line]) diff --git a/pytests/func/test_multiproc.py b/pytests/func/test_multiproc.py index ad127b07..d12951af 100644 --- a/pytests/func/test_multiproc.py +++ b/pytests/func/test_multiproc.py @@ -24,6 +24,8 @@ def test_multiprocessing(debug_session, pyfile, run_as, start_method): import multiprocessing import platform import sys + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() def child_of_child(q): print('entering child of child') @@ -143,6 +145,8 @@ def test_subprocess(debug_session, pyfile, start_method, run_as): def child(): import sys import backchannel + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() backchannel.write_json(sys.argv) @pyfile @@ -150,6 +154,8 @@ def test_subprocess(debug_session, pyfile, start_method, run_as): import os import subprocess import sys + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() argv = [sys.executable, sys.argv[1], '--arg1', '--arg2', '--arg3'] env = os.environ.copy() process = subprocess.Popen(argv, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -200,6 +206,8 @@ def test_subprocess(debug_session, pyfile, start_method, run_as): def test_autokill(debug_session, pyfile, start_method, run_as): @pyfile def child(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() while True: pass @@ -209,6 +217,8 @@ def test_autokill(debug_session, pyfile, start_method, run_as): import os import subprocess import sys + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() argv = [sys.executable, sys.argv[1]] env = os.environ.copy() subprocess.Popen(argv, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff --git a/pytests/func/test_run.py b/pytests/func/test_run.py index cf8359cf..a05d12c0 100644 --- a/pytests/func/test_run.py +++ b/pytests/func/test_run.py @@ -12,17 +12,17 @@ import ptvsd from pytests.helpers import print from pytests.helpers.pattern import ANY from pytests.helpers.timeline import Event -from pytests.helpers.session import START_METHOD_LAUNCH, START_METHOD_CMDLINE @pytest.mark.parametrize('run_as', ['file', 'module', 'code']) -@pytest.mark.parametrize('start_method', [START_METHOD_LAUNCH, START_METHOD_CMDLINE]) def test_run(debug_session, pyfile, run_as, start_method): @pyfile def code_to_debug(): import os import sys import backchannel + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() print('begin') assert backchannel.read_json() == 'continue' diff --git a/pytests/func/test_start_stop.py b/pytests/func/test_start_stop.py index 43aace0d..827622a1 100644 --- a/pytests/func/test_start_stop.py +++ b/pytests/func/test_start_stop.py @@ -18,6 +18,8 @@ from pytests.helpers.session import START_METHOD_LAUNCH, START_METHOD_CMDLINE def test_break_on_entry(debug_session, pyfile, run_as, start_method): @pyfile def code_to_debug(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() print('one') print('two') print('three') @@ -56,13 +58,15 @@ def test_break_on_entry(debug_session, pyfile, run_as, start_method): def test_wait_on_normal_exit_enabled(debug_session, pyfile, run_as, start_method): @pyfile def code_to_debug(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() print('one') print('two') print('three') debug_session.debug_options += ['WaitOnNormalExit'] - bp_line = 3 + bp_line = 5 bp_file = code_to_debug debug_session.initialize(target=(run_as, bp_file), start_method=start_method) debug_session.set_breakpoints(bp_file, [bp_line]) @@ -99,6 +103,8 @@ def test_wait_on_abnormal_exit_enabled(debug_session, pyfile, run_as, start_meth @pyfile def code_to_debug(): import sys + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() print('one') print('two') print('three') @@ -106,7 +112,7 @@ def test_wait_on_abnormal_exit_enabled(debug_session, pyfile, run_as, start_meth debug_session.debug_options += ['WaitOnAbnormalExit'] - bp_line = 5 + bp_line = 7 bp_file = code_to_debug debug_session.initialize(target=(run_as, bp_file), start_method=start_method) debug_session.set_breakpoints(bp_file, [bp_line]) @@ -140,13 +146,15 @@ def test_wait_on_abnormal_exit_enabled(debug_session, pyfile, run_as, start_meth def test_exit_normally_with_wait_on_abnormal_exit_enabled(debug_session, pyfile, run_as, start_method): @pyfile def code_to_debug(): + from dbgimporter import import_and_enable_debugger + import_and_enable_debugger() print('one') print('two') print('three') debug_session.debug_options += ['WaitOnAbnormalExit'] - bp_line = 3 + bp_line = 5 bp_file = code_to_debug debug_session.initialize(target=(run_as, bp_file), start_method=start_method) debug_session.set_breakpoints(bp_file, [bp_line]) diff --git a/pytests/func/testfiles/bp/a&b/test.py b/pytests/func/testfiles/bp/a&b/test.py index cd921c80..5a235500 100644 --- a/pytests/func/testfiles/bp/a&b/test.py +++ b/pytests/func/testfiles/bp/a&b/test.py @@ -1,3 +1,5 @@ +from dbgimporter import import_and_enable_debugger +import_and_enable_debugger() print('one') print('two') -print('three') \ No newline at end of file +print('three') diff --git a/pytests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py b/pytests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py new file mode 100644 index 00000000..86348412 --- /dev/null +++ b/pytests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +import sys +from dbgimporter import import_and_enable_debugger +import_and_enable_debugger() +def ಏನಾದರೂ_ಮಾಡು(): + print('ಏನೋ ಮಾಡಿದೆ'.encode(sys.stdout.encoding, errors='replace')) + + +ಏನಾದರೂ_ಮಾಡು() diff --git a/pytests/helpers/debuggee/dbgimporter.py b/pytests/helpers/debuggee/dbgimporter.py new file mode 100644 index 00000000..492c3ad4 --- /dev/null +++ b/pytests/helpers/debuggee/dbgimporter.py @@ -0,0 +1,9 @@ +import os + +def import_and_enable_debugger(): + if os.getenv('PTVSD_ENABLE_ATTACH', False): + import ptvsd + host = os.getenv('PTVSD_TEST_HOST', 'localhost') + port = os.getenv('PTVSD_TEST_PORT', '5678') + ptvsd.enable_attach((host, port)) + ptvsd.wait_for_attach() diff --git a/pytests/helpers/session.py b/pytests/helpers/session.py index 65ecb4f4..623a21ea 100644 --- a/pytests/helpers/session.py +++ b/pytests/helpers/session.py @@ -31,6 +31,9 @@ START_METHOD_LAUNCH = ('launch',) START_METHOD_CMDLINE = ('attach', 'socket', 'cmdline') START_METHOD_IMPORT = ('attach', 'socket', 'import') START_METHOD_PID = ('attach', 'pid') +PTVSD_ENABLE_KEY = 'PTVSD_ENABLE_ATTACH' +PTVSD_HOST_KEY = 'PTVSD_TEST_HOST' +PTVSD_PORT_KEY = 'PTVSD_TEST_PORT' class DebugSession(object): @@ -128,33 +131,33 @@ class DebugSession(object): self._wait_for_remaining_output() - def prepare_to_run(self): - """Spawns ptvsd using the configured method, telling it to execute the - provided Python file, module, or code, and establishes a message channel - to it. - - If use_backchannel is True, calls self.setup_backchannel() before returning. - - If perform_handshake is True, calls self.handshake() before returning. - """ - + def _get_argv_for_attach_using_import(self): argv = [sys.executable] - if self.start_method != ('attach', 'pid'): - argv += [ptvsd.__main__.__file__] + return argv - if self.start_method == ('attach', 'socket', 'cmdline'): - argv += ['--wait'] - else: - self._listen() - argv += ['--client'] + def _get_argv_for_launch(self): + argv = [sys.executable] + argv += [ptvsd.__main__.__file__] + argv += ['--client'] argv += ['--host', 'localhost', '--port', str(self.ptvsd_port)] + return argv - if self.multiprocess and 'Multiprocess' not in self.debug_options: - self.debug_options += ['Multiprocess'] + def _get_argv_for_attach_using_cmdline(self): + argv = [sys.executable] + argv += [ptvsd.__main__.__file__] + argv += ['--wait'] + argv += ['--host', 'localhost', '--port', str(self.ptvsd_port)] + return argv - if self.multiprocess_port_range: - argv += ['--multiprocess-port-range', '%d-%d' % self.multiprocess_port_range] + def _get_argv_for_attach_using_pid(self): + argv = [sys.executable] + argv += [ptvsd.__main__.__file__] + argv += ['--host', 'localhost', '--port', str(self.ptvsd_port)] + argv += ['--pid', str(self.pid)] + return argv + def _get_target(self): + argv = [] run_as, path_or_code = self.target if run_as == 'file': assert os.path.isfile(path_or_code) @@ -178,10 +181,45 @@ class DebugSession(object): argv += ['-c', path_or_code] else: pytest.fail() + return argv + + def prepare_to_run(self): + """Spawns ptvsd using the configured method, telling it to execute the + provided Python file, module, or code, and establishes a message channel + to it. + + If use_backchannel is True, calls self.setup_backchannel() before returning. + + If perform_handshake is True, calls self.handshake() before returning. + """ + print('Preparing debug session with method %r' % str(self.start_method)) + argv = [] + if self.start_method == START_METHOD_LAUNCH: + self._listen() + argv += self._get_argv_for_launch() + elif self.start_method == START_METHOD_CMDLINE: + argv += self._get_argv_for_attach_using_cmdline() + elif self.start_method == START_METHOD_IMPORT: + argv += self._get_argv_for_attach_using_import() + # TODO: Remove adding ot python path after enabling TOX + ptvsd_path = os.path.dirname(os.path.dirname(ptvsd.__main__.__file__)) + self.env['PYTHONPATH'] = ptvsd_path + os.pathsep + self.env['PYTHONPATH'] + self.env[PTVSD_ENABLE_KEY] = '1' + self.env[PTVSD_HOST_KEY] = 'localhost' + self.env[PTVSD_PORT_KEY] = str(self.ptvsd_port) + elif self.start_method == START_METHOD_PID: + argv += self._get_argv_for_attach_using_pid() + else: + pytest.fail() + + argv += self._get_target() if self.program_args: argv += list(self.program_args) + if self.multiprocess and 'Multiprocess' not in self.debug_options: + self.debug_options += ['Multiprocess'] + if self.use_backchannel: self.setup_backchannel() if self.backchannel_port: @@ -190,6 +228,7 @@ class DebugSession(object): print('Current directory: %s' % os.getcwd()) print('PYTHONPATH: %s' % self.env['PYTHONPATH']) print('Spawning %r' % argv) + self.process = subprocess.Popen(argv, env=self.env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.cwd) self.pid = self.process.pid self.psutil_process = psutil.Process(self.pid) @@ -199,7 +238,7 @@ class DebugSession(object): self._capture_output(self.process.stdout, 'OUT') self._capture_output(self.process.stderr, 'ERR') - if self.start_method == ('attach', 'socket', 'cmdline'): + if self.start_method != START_METHOD_LAUNCH: self.connect() self.connected.wait() assert self.ptvsd_port