From 60da1fee417a7d37bec6e3628cd2eea4285432cf Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Fri, 19 Apr 2019 09:09:52 -0300 Subject: [PATCH] Port stopped and continued events to pydevd. Fixes #1340, #1341 (#1373) * Port stopped and continued events to pydevd. Fixes #1340, #1341 * Use 'ptvsd.log.exception' instead of traceback.print_exc. --- .../pydevd/_pydevd_bundle/pydevd_comm.py | 184 ++++++++-------- .../pydevd_net_command_factory_json.py | 70 +++++- .../pydevd_net_command_factory_xml.py | 2 +- .../pydevd_process_net_command_json.py | 3 + src/ptvsd/_vendored/pydevd/pydevd.py | 5 +- .../_debugger_case_dont_trace_test.py | 14 +- .../pydevd/tests_python/test_debugger_json.py | 207 +++++++++--------- .../pydevd/tests_python/test_utilities.py | 13 +- src/ptvsd/session.py | 13 +- src/ptvsd/wrapper.py | 115 +++------- tests/func/test_exception.py | 13 +- tests/helpers/session.py | 13 +- 12 files changed, 363 insertions(+), 289 deletions(-) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index 0c8864c3..cb7233fd 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -1079,98 +1079,106 @@ def internal_get_description(dbg, seq, thread_id, frame_id, expression): dbg.writer.add_command(cmd) +def build_exception_info_response(dbg, thread_id, request_seq, set_additional_thread_info, iter_visible_frames_info, max_frames): + ''' + :return ExceptionInfoResponse + ''' + thread = pydevd_find_thread_by_id(thread_id) + additional_info = set_additional_thread_info(thread) + topmost_frame = additional_info.get_topmost_frame(thread) + + frames = [] + exc_type = None + exc_desc = None + if topmost_frame is not None: + frame_id_to_lineno = {} + try: + trace_obj = None + frame = topmost_frame + while frame is not None: + if frame.f_code.co_name == 'do_wait_suspend' and frame.f_code.co_filename.endswith('pydevd.py'): + arg = frame.f_locals.get('arg', None) + if arg is not None: + exc_type, exc_desc, trace_obj = arg + break + frame = frame.f_back + + while trace_obj.tb_next is not None: + trace_obj = trace_obj.tb_next + + info = dbg.suspended_frames_manager.get_topmost_frame_and_frame_id_to_line(thread_id) + if info is not None: + topmost_frame, frame_id_to_lineno = info + + if trace_obj is not None: + for frame_id, frame, method_name, original_filename, filename_in_utf8, lineno in iter_visible_frames_info( + dbg, trace_obj.tb_frame, frame_id_to_lineno): + + line_text = linecache.getline(original_filename, lineno) + + # Never filter out plugin frames! + if not getattr(frame, 'IS_PLUGIN_FRAME', False): + if not dbg.in_project_scope(original_filename): + if not dbg.get_use_libraries_filter(): + continue + frames.append((filename_in_utf8, lineno, method_name, line_text)) + finally: + topmost_frame = None + + name = 'exception: type unknown' + if exc_type is not None: + try: + name = exc_type.__qualname__ + except: + try: + name = exc_type.__name__ + except: + try: + name = str(exc_type) + except: + pass + + description = 'exception: no description' + if exc_desc is not None: + try: + description = str(exc_desc) + except: + pass + + stack_str = ''.join(traceback.format_list(frames[-max_frames:])) + + # This is an extra bit of data used by Visual Studio + source_path = frames[0][0] if frames else '' + + if thread.stop_reason == CMD_STEP_CAUGHT_EXCEPTION: + break_mode = pydevd_schema.ExceptionBreakMode.ALWAYS + else: + break_mode = pydevd_schema.ExceptionBreakMode.UNHANDLED + + response = pydevd_schema.ExceptionInfoResponse( + request_seq=request_seq, + success=True, + command='exceptionInfo', + body=pydevd_schema.ExceptionInfoResponseBody( + exceptionId=name, + description=description, + breakMode=break_mode, + details=pydevd_schema.ExceptionDetails( + message=description, + typeName=name, + stackTrace=stack_str, + source=source_path + ) + ) + ) + return response + + def internal_get_exception_details_json(dbg, request, thread_id, max_frames, set_additional_thread_info=None, iter_visible_frames_info=None): ''' Fetch exception details ''' try: - thread = pydevd_find_thread_by_id(thread_id) - additional_info = set_additional_thread_info(thread) - topmost_frame = additional_info.get_topmost_frame(thread) - - frames = [] - exc_type = None - exc_desc = None - if topmost_frame is not None: - frame_id_to_lineno = {} - try: - trace_obj = None - frame = topmost_frame - while frame is not None: - if frame.f_code.co_name == 'do_wait_suspend' and frame.f_code.co_filename.endswith('pydevd.py'): - arg = frame.f_locals.get('arg', None) - if arg is not None: - exc_type, exc_desc, trace_obj = arg - break - frame = frame.f_back - - while trace_obj.tb_next is not None: - trace_obj = trace_obj.tb_next - - info = dbg.suspended_frames_manager.get_topmost_frame_and_frame_id_to_line(thread_id) - if info is not None: - topmost_frame, frame_id_to_lineno = info - - if trace_obj is not None: - for frame_id, frame, method_name, original_filename, filename_in_utf8, lineno in iter_visible_frames_info( - dbg, trace_obj.tb_frame, frame_id_to_lineno): - - line_text = linecache.getline(original_filename, lineno) - - # Never filter out plugin frames! - if not getattr(frame, 'IS_PLUGIN_FRAME', False): - if not dbg.in_project_scope(original_filename): - if not dbg.get_use_libraries_filter(): - continue - frames.append((filename_in_utf8, lineno, method_name, line_text)) - finally: - topmost_frame = None - - name = 'exception: type unknown' - if exc_type is not None: - try: - name = exc_type.__qualname__ - except: - try: - name = exc_type.__name__ - except: - try: - name = str(exc_type) - except: - pass - - description = 'exception: no description' - if exc_desc is not None: - try: - description = str(exc_desc) - except: - pass - - stack_str = ''.join(traceback.format_list(frames[-max_frames:])) - - # This is an extra bit of data used by Visual Studio - source_path = frames[0][0] if frames else '' - - if thread.stop_reason == CMD_STEP_CAUGHT_EXCEPTION: - break_mode = pydevd_schema.ExceptionBreakMode.ALWAYS - else: - break_mode = pydevd_schema.ExceptionBreakMode.UNHANDLED - - response = pydevd_schema.ExceptionInfoResponse( - request_seq=request.seq, - success=True, - command='exceptionInfo', - body=pydevd_schema.ExceptionInfoResponseBody( - exceptionId=name, - description=description, - breakMode=break_mode, - details=pydevd_schema.ExceptionDetails( - message=description, - typeName=name, - stackTrace=stack_str, - source=source_path - ) - ) - ) + response = build_exception_info_response(dbg, thread_id, request.seq, set_additional_thread_info, iter_visible_frames_info, max_frames) except: exc = get_exception_traceback_str() response = pydevd_base_schema.build_response(request, kwargs={ diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py index 1949e8d9..4f3b3600 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py @@ -1,22 +1,27 @@ +from functools import partial +import itertools import os from _pydev_bundle._pydev_imports_tipper import TYPE_IMPORT, TYPE_CLASS, TYPE_FUNCTION, TYPE_ATTR, \ TYPE_BUILTIN, TYPE_PARAM from _pydev_bundle.pydev_is_thread_alive import is_thread_alive from _pydev_bundle.pydev_override import overrides +from _pydev_imps._pydev_saved_modules import threading from _pydevd_bundle._debug_adapter import pydevd_schema +from _pydevd_bundle._debug_adapter.pydevd_schema import ModuleEvent, ModuleEventBody, Module, \ + OutputEventBody, OutputEvent, ContinuedEventBody from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_CREATE, CMD_RETURN, CMD_MODULE_EVENT, \ - CMD_WRITE_TO_CONSOLE + CMD_WRITE_TO_CONSOLE, CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, \ + CMD_STEP_RETURN, CMD_STEP_CAUGHT_EXCEPTION, CMD_ADD_EXCEPTION_BREAK, CMD_SET_BREAK, \ + CMD_SET_NEXT_STATEMENT, CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, \ + CMD_THREAD_RESUME_SINGLE_NOTIFICATION from _pydevd_bundle.pydevd_constants import get_thread_id, dict_values from _pydevd_bundle.pydevd_net_command import NetCommand from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory from _pydevd_bundle.pydevd_utils import get_non_pydevd_threads -from _pydev_imps._pydev_saved_modules import threading -from _pydevd_bundle._debug_adapter.pydevd_schema import ModuleEvent, ModuleEventBody, Module, \ - OutputEventBody, OutputEvent -from functools import partial -import itertools import pydevd_file_utils +from _pydevd_bundle.pydevd_comm import pydevd_find_thread_by_id, build_exception_info_response +from _pydevd_bundle.pydevd_additional_thread_info_regular import set_additional_thread_info class ModulesManager(object): @@ -224,3 +229,56 @@ class NetCommandFactoryJson(NetCommandFactory): body = OutputEventBody(v, category) event = OutputEvent(body) return NetCommand(CMD_WRITE_TO_CONSOLE, 0, event, is_json=True) + + _STEP_REASONS = set([ + CMD_STEP_INTO, + CMD_STEP_INTO_MY_CODE, + CMD_STEP_OVER, + CMD_STEP_OVER_MY_CODE, + CMD_STEP_RETURN, + CMD_STEP_INTO_MY_CODE, + ]) + _EXCEPTION_REASONS = set([ + CMD_STEP_CAUGHT_EXCEPTION, + CMD_ADD_EXCEPTION_BREAK, + ]) + + @overrides(NetCommandFactory.make_thread_suspend_single_notification) + def make_thread_suspend_single_notification(self, py_db, thread_id, stop_reason): + exc_desc = None + exc_name = None + if stop_reason in self._STEP_REASONS: + stop_reason = 'step' + elif stop_reason in self._EXCEPTION_REASONS: + stop_reason = 'exception' + elif stop_reason == CMD_SET_BREAK: + stop_reason = 'breakpoint' + elif stop_reason == CMD_SET_NEXT_STATEMENT: + stop_reason = 'goto' + else: + stop_reason = 'pause' + + if stop_reason == 'exception': + exception_info_response = build_exception_info_response( + py_db, thread_id, -1, set_additional_thread_info, self._iter_visible_frames_info, max_frames=-1) + exception_info_response + + exc_name = exception_info_response.body.exceptionId + exc_desc = exception_info_response.body.description + + body = pydevd_schema.StoppedEventBody( + reason=stop_reason, + description=exc_desc, + threadId=thread_id, + text=exc_name, + allThreadsStopped=True, + preserveFocusHint=stop_reason not in ['step', 'exception', 'breakpoint'], + ) + event = pydevd_schema.StoppedEvent(body) + return NetCommand(CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, 0, event, is_json=True) + + @overrides(NetCommandFactory.make_thread_resume_single_notification) + def make_thread_resume_single_notification(self, thread_id): + body = ContinuedEventBody(threadId=thread_id, allThreadsContinued=True) + event = pydevd_schema.ContinuedEvent(body) + return NetCommand(CMD_THREAD_RESUME_SINGLE_NOTIFICATION, 0, event, is_json=True) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py index e15441c8..32a94af1 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py @@ -273,7 +273,7 @@ class NetCommandFactory(object): except: return self.make_error_message(0, get_exception_traceback_str()) - def make_thread_suspend_single_notification(self, thread_id, stop_reason): + def make_thread_suspend_single_notification(self, py_db, thread_id, stop_reason): try: return NetCommand(CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, 0, json.dumps( {'thread_id': thread_id, 'stop_reason':stop_reason})) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index db6cdd20..f8f0e169 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -759,10 +759,12 @@ class _PyDevJsonCommandProcessor(object): start_patterns = tuple(args['dontTraceStartPatterns']) end_patterns = tuple(args['dontTraceEndPatterns']) if self._can_set_dont_trace_pattern(py_db, start_patterns, end_patterns): + def dont_trace_files_property_request(abs_path): result = abs_path.startswith(start_patterns) or \ abs_path.endswith(end_patterns) return result + dont_trace_files_property_request.start_patterns = start_patterns dont_trace_files_property_request.end_patterns = end_patterns py_db.dont_trace_external_files = dont_trace_files_property_request @@ -788,4 +790,5 @@ class _PyDevJsonCommandProcessor(object): response = pydevd_base_schema.build_response(request, kwargs={'body': {}}) return NetCommand(CMD_RETURN, 0, response, is_json=True) + process_net_command_json = _PyDevJsonCommandProcessor(pydevd_base_schema.from_json).process_net_command_json diff --git a/src/ptvsd/_vendored/pydevd/pydevd.py b/src/ptvsd/_vendored/pydevd/pydevd.py index e4f2db3e..4d9c19f2 100644 --- a/src/ptvsd/_vendored/pydevd/pydevd.py +++ b/src/ptvsd/_vendored/pydevd/pydevd.py @@ -139,6 +139,7 @@ file_system_encoding = getfilesystemencoding() _CACHE_FILE_TYPE = {} + #======================================================================================================================= # PyDBCommandThread #======================================================================================================================= @@ -338,7 +339,7 @@ class ThreadsSuspendedSingleNotification(AbstractSingleNotificationBehavior): def send_suspend_notification(self, thread_id, stop_reason): py_db = self._py_db() if py_db is not None: - py_db.writer.add_command(py_db.cmd_factory.make_thread_suspend_single_notification(thread_id, stop_reason)) + py_db.writer.add_command(py_db.cmd_factory.make_thread_suspend_single_notification(py_db, thread_id, stop_reason)) @overrides(AbstractSingleNotificationBehavior.notify_thread_suspended) @contextmanager @@ -598,7 +599,7 @@ class PyDB(object): return file_type def is_cache_file_type_empty(self): - return bool(_CACHE_FILE_TYPE) + return not _CACHE_FILE_TYPE def get_thread_local_trace_func(self): try: diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_dont_trace_test.py b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_dont_trace_test.py index fb5055ea..60ca74ed 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_dont_trace_test.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_dont_trace_test.py @@ -1,7 +1,17 @@ -from _debugger_case_dont_trace import call_me_back +import sys +import os + +try: + from _debugger_case_dont_trace import call_me_back +except ImportError: + sys.path.append(os.path.dirname(__file__)) + from _debugger_case_dont_trace import call_me_back + + def my_callback(): print('trace me') # Break here + if __name__ == '__main__': call_me_back(my_callback) - print('TEST SUCEEDED!') \ No newline at end of file + print('TEST SUCEEDED!') diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py index 57a299c2..2a341257 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py @@ -4,22 +4,24 @@ import pytest from _pydevd_bundle._debug_adapter import pydevd_schema, pydevd_base_schema from _pydevd_bundle._debug_adapter.pydevd_base_schema import from_json from tests_python.debugger_unittest import IS_JYTHON, REASON_STEP_INTO, REASON_STEP_OVER, \ - REASON_CAUGHT_EXCEPTION, REASON_THREAD_SUSPEND, REASON_STEP_RETURN, IS_APPVEYOR, overrides, \ - REASON_UNCAUGHT_EXCEPTION + REASON_CAUGHT_EXCEPTION, REASON_STEP_RETURN, IS_APPVEYOR, overrides from _pydevd_bundle._debug_adapter.pydevd_schema import ThreadEvent, ModuleEvent, OutputEvent, \ - ExceptionOptions, Response + ExceptionOptions, Response, StoppedEvent, ContinuedEvent from tests_python import debugger_unittest import json from collections import namedtuple from _pydevd_bundle.pydevd_constants import int_types from tests_python.debug_constants import * # noqa import time +from os.path import normcase pytest_plugins = [ str('tests_python.debugger_fixtures'), ] -_JsonHit = namedtuple('_JsonHit', 'frameId, stack_trace_response') +_JsonHit = namedtuple('_JsonHit', 'thread_id, frame_id, stack_trace_response') + +pytestmark = pytest.mark.skipif(IS_JYTHON, reason='Single notification is not OK in Jython (investigate).') # Note: in reality must be < int32, but as it's created sequentially this should be # a reasonable number for tests. @@ -30,6 +32,8 @@ class JsonFacade(object): def __init__(self, writer): self.writer = writer + writer.write_set_protocol('http_json') + writer.write_multi_threads_single_notification(True) def wait_for_json_message(self, expected_class, accept_message=lambda obj:True): @@ -75,6 +79,14 @@ class JsonFacade(object): def write_list_threads(self): return self.wait_for_response(self.write_request(pydevd_schema.ThreadsRequest())) + def wait_for_thread_stopped(self, reason='breakpoint', line=None): + stopped_event = self.wait_for_json_message(StoppedEvent) + assert stopped_event.body.reason == reason + json_hit = self.get_stack_as_json_hit(stopped_event.body.threadId) + if line is not None: + assert json_hit.stack_trace_response.body.stackFrames[0]['line'] == line + return json_hit + def write_set_breakpoints(self, lines, filename=None, line_to_info=None): ''' Adds a breakpoint. @@ -162,7 +174,8 @@ class JsonFacade(object): stack_frame = next(iter(stack_trace_response_body.stackFrames)) - return _JsonHit(frameId=stack_frame['id'], stack_trace_response=stack_trace_response) + return _JsonHit( + thread_id=thread_id, frame_id=stack_frame['id'], stack_trace_response=stack_trace_response) def get_variables_response(self, variables_reference): assert variables_reference < MAX_EXPECTED_ID @@ -192,12 +205,31 @@ class JsonFacade(object): references.append(reference) return references + def write_continue(self): + continue_request = self.write_request( + pydevd_schema.ContinueRequest(pydevd_schema.ContinueArguments('*'))) + + # The continued event is received before the response. + assert self.wait_for_json_message(ContinuedEvent).body.allThreadsContinued + + continue_response = self.wait_for_response(continue_request) + assert continue_response.body.allThreadsContinued + + def write_pause(self): + pause_request = self.write_request( + pydevd_schema.PauseRequest(pydevd_schema.PauseArguments('*'))) + pause_response = self.wait_for_response(pause_request) + assert pause_response.success + + def write_step_in(self, thread_id): + arguments = pydevd_schema.StepInArguments(threadId=thread_id) + self.wait_for_response(self.write_request(pydevd_schema.StepInRequest(arguments))) + def test_case_json_logpoints(case_setup): with case_setup.test_file('_debugger_case_change_breaks.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') json_facade.write_launch() break_2 = writer.get_line_index_with_content('break 2') break_3 = writer.get_line_index_with_content('break 3') @@ -224,29 +256,24 @@ def test_case_json_logpoints(case_setup): break # Just one hit at the end (break 3). - hit = writer.wait_for_breakpoint_hit() - writer.write_run_thread(hit.thread_id) + json_facade.wait_for_thread_stopped(line=break_3) + json_facade.write_continue() writer.finished_ok = True -@pytest.mark.skipif(IS_JYTHON, reason='Must check why it is failing in Jython.') def test_case_json_change_breaks(case_setup): with case_setup.test_file('_debugger_case_change_breaks.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') json_facade.write_launch() - json_facade.write_set_breakpoints(writer.get_line_index_with_content('break 1')) + break1_line = writer.get_line_index_with_content('break 1') + json_facade.write_set_breakpoints(break1_line) json_facade.write_make_initial_run() - hit = writer.wait_for_breakpoint_hit() - writer.write_run_thread(hit.thread_id) - - hit = writer.wait_for_breakpoint_hit() - writer.write_run_thread(hit.thread_id) + json_facade.wait_for_thread_stopped(line=break1_line) json_facade.write_set_breakpoints([]) - writer.write_run_thread(hit.thread_id) + json_facade.write_continue() writer.finished_ok = True @@ -255,21 +282,20 @@ def test_case_handled_exception_breaks(case_setup): with case_setup.test_file('_debugger_case_exceptions.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') json_facade.write_launch() json_facade.write_set_exception_breakpoints(['raised']) json_facade.write_make_initial_run() - hit = writer.wait_for_breakpoint_hit( - reason=REASON_CAUGHT_EXCEPTION, line=writer.get_line_index_with_content('raise indexerror line')) - writer.write_run_thread(hit.thread_id) + json_facade.wait_for_thread_stopped( + reason='exception', line=writer.get_line_index_with_content('raise indexerror line')) + json_facade.write_continue() - hit = writer.wait_for_breakpoint_hit( - reason=REASON_CAUGHT_EXCEPTION, line=writer.get_line_index_with_content('reraise on method2')) + json_facade.wait_for_thread_stopped( + reason='exception', line=writer.get_line_index_with_content('reraise on method2')) # Clear so that the last one is not hit. json_facade.write_set_exception_breakpoints([]) - writer.write_run_thread(hit.thread_id) + json_facade.write_continue() writer.finished_ok = True @@ -278,7 +304,6 @@ def test_case_handled_exception_breaks_by_type(case_setup): with case_setup.test_file('_debugger_case_exceptions.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') json_facade.write_launch() json_facade.write_set_exception_breakpoints(exception_options=[ ExceptionOptions(breakMode='always', path=[ @@ -288,8 +313,8 @@ def test_case_handled_exception_breaks_by_type(case_setup): ]) json_facade.write_make_initial_run() - hit = writer.wait_for_breakpoint_hit( - reason=REASON_CAUGHT_EXCEPTION, line=writer.get_line_index_with_content('raise indexerror line')) + json_facade.wait_for_thread_stopped( + reason='exception', line=writer.get_line_index_with_content('raise indexerror line')) # Deal only with RuntimeErorr now. json_facade.write_set_exception_breakpoints(exception_options=[ @@ -299,7 +324,7 @@ def test_case_handled_exception_breaks_by_type(case_setup): ]) ]) - writer.write_run_thread(hit.thread_id) + json_facade.write_continue() writer.finished_ok = True @@ -309,16 +334,14 @@ def test_case_json_protocol(case_setup): with case_setup.test_file('_debugger_case_print.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') json_facade.write_launch() - json_facade.write_set_breakpoints(writer.get_line_index_with_content('Break here')) + break_line = writer.get_line_index_with_content('Break here') + json_facade.write_set_breakpoints(break_line) json_facade.write_make_initial_run() json_facade.wait_for_json_message(ThreadEvent, lambda event: event.body.reason == 'started') - hit = writer.wait_for_breakpoint_hit() - thread_id = hit.thread_id - frame_id = hit.frame_id + json_facade.wait_for_thread_stopped(line=break_line) # : :type response: ThreadsResponse response = json_facade.write_list_threads() @@ -360,8 +383,6 @@ def test_case_path_translation_not_skipped(case_setup): with case_setup.test_file('my_code/my_code.py', get_environ=get_environ) as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') - bp_line = writer.get_line_index_with_content('break here') json_facade.write_set_breakpoints( bp_line, @@ -369,11 +390,11 @@ def test_case_path_translation_not_skipped(case_setup): ) json_facade.write_make_initial_run() - hit = writer.wait_for_breakpoint_hit(line=bp_line) - json_hit = json_facade.get_stack_as_json_hit(hit.thread_id) + json_hit = json_facade.wait_for_thread_stopped(line=bp_line) + assert json_hit.stack_trace_response.body.stackFrames[-1]['source']['path'] == \ os.path.join(sys_folder, 'my_code.py') - writer.write_run_thread(hit.thread_id) + json_facade.write_continue() writer.finished_ok = True @@ -390,7 +411,6 @@ def test_case_skipping_filters(case_setup, custom_setup): with case_setup.test_file('my_code/my_code.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') if custom_setup == 'set_exclude_launch_path_match_filename': json_facade.write_launch( debugOptions=['DebugStdLib'], @@ -440,12 +460,13 @@ def test_case_skipping_filters(case_setup, custom_setup): else: raise AssertionError('Unhandled: %s' % (custom_setup,)) - json_facade.write_set_breakpoints(writer.get_line_index_with_content('break here')) + break_line = writer.get_line_index_with_content('break here') + json_facade.write_set_breakpoints(break_line) json_facade.write_make_initial_run() json_facade.wait_for_json_message(ThreadEvent, lambda event: event.body.reason == 'started') - hit = writer.wait_for_breakpoint_hit() + hit = writer.wait_for_breakpoint_hit(line=break_line) writer.write_step_in(hit.thread_id) hit = writer.wait_for_breakpoint_hit(reason=REASON_STEP_INTO) @@ -481,7 +502,6 @@ def test_case_completions_json(case_setup): with case_setup.test_file('_debugger_case_completions.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() @@ -495,7 +515,7 @@ def test_case_completions_json(case_setup): first_hit = json_hit completions_arguments = pydevd_schema.CompletionsArguments( - 'dict.', 6, frameId=json_hit.frameId, line=0) + 'dict.', 6, frameId=json_hit.frame_id, line=0) completions_request = json_facade.write_request( pydevd_schema.CompletionsRequest(completions_arguments)) @@ -505,7 +525,7 @@ def test_case_completions_json(case_setup): assert set(labels).issuperset(set(['__contains__', 'items', 'keys', 'values'])) completions_arguments = pydevd_schema.CompletionsArguments( - 'dict.item', 10, frameId=json_hit.frameId) + 'dict.item', 10, frameId=json_hit.frame_id) completions_request = json_facade.write_request( pydevd_schema.CompletionsRequest(completions_arguments)) @@ -520,9 +540,9 @@ def test_case_completions_json(case_setup): if i == 1: # Check with a previously existing frameId. - assert first_hit.frameId != json_hit.frameId + assert first_hit.frame_id != json_hit.frame_id completions_arguments = pydevd_schema.CompletionsArguments( - 'dict.item', 10, frameId=first_hit.frameId) + 'dict.item', 10, frameId=first_hit.frame_id) completions_request = json_facade.write_request( pydevd_schema.CompletionsRequest(completions_arguments)) @@ -549,8 +569,6 @@ def test_modules(case_setup): with case_setup.test_file('_debugger_case_local_variables.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') - writer.write_add_breakpoint(writer.get_line_index_with_content('Break 2 here')) json_facade.write_make_initial_run() @@ -580,14 +598,12 @@ def test_stack_and_variables_dict(case_setup): with case_setup.test_file('_debugger_case_local_variables.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') - writer.write_add_breakpoint(writer.get_line_index_with_content('Break 2 here')) json_facade.write_make_initial_run() hit = writer.wait_for_breakpoint_hit() json_hit = json_facade.get_stack_as_json_hit(hit.thread_id) - variables_response = json_facade.get_variables_response(json_hit.frameId) + variables_response = json_facade.get_variables_response(json_hit.frame_id) variables_references = json_facade.pop_variables_reference(variables_response.body.variables) dict_variable_reference = variables_references[2] @@ -632,7 +648,6 @@ def test_stack_and_variables_dict(case_setup): def test_return_value(case_setup): with case_setup.test_file('_debugger_case_return_value.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') break_line = writer.get_line_index_with_content('break here') writer.write_add_breakpoint(break_line) @@ -644,7 +659,7 @@ def test_return_value(case_setup): hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, name='', line=break_line + 1) json_hit = json_facade.get_stack_as_json_hit(hit.thread_id) - variables_response = json_facade.get_variables_response(json_hit.frameId) + variables_response = json_facade.get_variables_response(json_hit.frame_id) return_variables = json_facade.filter_return_variables(variables_response.body.variables) assert return_variables == [{ 'name': '(return) method1', @@ -662,14 +677,12 @@ def test_stack_and_variables_set_and_list(case_setup): with case_setup.test_file('_debugger_case_local_variables2.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') - writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() hit = writer.wait_for_breakpoint_hit() json_hit = json_facade.get_stack_as_json_hit(hit.thread_id) - variables_response = json_facade.get_variables_response(json_hit.frameId) + variables_response = json_facade.get_variables_response(json_hit.frame_id) variables_references = json_facade.pop_variables_reference(variables_response.body.variables) if IS_PY2: @@ -714,8 +727,6 @@ def test_evaluate_unicode(case_setup): with case_setup.test_file('_debugger_case_local_variables.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') - writer.write_add_breakpoint(writer.get_line_index_with_content('Break 2 here')) json_facade.write_make_initial_run() @@ -723,7 +734,7 @@ def test_evaluate_unicode(case_setup): json_hit = json_facade.get_stack_as_json_hit(hit.thread_id) evaluate_response = json_facade.wait_for_response( - json_facade.write_request(EvaluateRequest(EvaluateArguments(u'\u16A0', json_hit.frameId)))) + json_facade.write_request(EvaluateRequest(EvaluateArguments(u'\u16A0', json_hit.frame_id)))) evaluate_response_body = evaluate_response.body.to_dict() @@ -769,8 +780,6 @@ def test_evaluate_variable_references(case_setup): with case_setup.test_file('_debugger_case_local_variables2.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') - writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() @@ -778,7 +787,7 @@ def test_evaluate_variable_references(case_setup): json_hit = json_facade.get_stack_as_json_hit(hit.thread_id) evaluate_response = json_facade.wait_for_response( - json_facade.write_request(EvaluateRequest(EvaluateArguments('variable_for_test_2', json_hit.frameId)))) + json_facade.write_request(EvaluateRequest(EvaluateArguments('variable_for_test_2', json_hit.frame_id)))) evaluate_response_body = evaluate_response.body.to_dict() @@ -821,8 +830,6 @@ def test_set_expression(case_setup): with case_setup.test_file('_debugger_case_local_variables2.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') - writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() @@ -831,11 +838,11 @@ def test_set_expression(case_setup): set_expression_response = json_facade.wait_for_response( json_facade.write_request(SetExpressionRequest( - SetExpressionArguments('bb', '20', frameId=json_hit.frameId)))) + SetExpressionArguments('bb', '20', frameId=json_hit.frame_id)))) assert set_expression_response.to_dict()['body'] == { 'value': '20', 'type': 'int', 'presentationHint': {}, 'variablesReference': 0} - variables_response = json_facade.get_variables_response(json_hit.frameId) + variables_response = json_facade.get_variables_response(json_hit.frame_id) assert {'name': 'bb', 'value': '20', 'type': 'int', 'evaluateName': 'bb'} in \ variables_response.to_dict()['body']['variables'] @@ -849,7 +856,6 @@ def test_stack_and_variables(case_setup): with case_setup.test_file('_debugger_case_local_variables.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() @@ -975,7 +981,6 @@ def test_hex_variables(case_setup): with case_setup.test_file('_debugger_case_local_variables_hex.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() @@ -1051,33 +1056,40 @@ def test_hex_variables(case_setup): writer.finished_ok = True -@pytest.mark.skipif(IS_JYTHON, reason='Flaky on Jython.') -def test_pause_and_continue(case_setup): - with case_setup.test_file('_debugger_case_pause_continue.py') as writer: +def test_stopped_event(case_setup): + with case_setup.test_file('_debugger_case_print.py') as writer: json_facade = JsonFacade(writer) - writer.write_multi_threads_single_notification(True) - writer.write_set_protocol('http_json') writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() - hit = writer.wait_for_breakpoint_hit() + json_hit = json_facade.wait_for_thread_stopped() + assert json_hit.thread_id - continue_request = json_facade.write_request( - pydevd_schema.ContinueRequest(pydevd_schema.ContinueArguments('*'))) - continue_response = json_facade.wait_for_response(continue_request) - assert continue_response.body.allThreadsContinued + json_facade.write_continue() - pause_request = json_facade.write_request( - pydevd_schema.PauseRequest(pydevd_schema.PauseArguments('*'))) - pause_response = json_facade.wait_for_response(pause_request) - hit = writer.wait_for_breakpoint_hit(reason=REASON_THREAD_SUSPEND) + writer.finished_ok = True - stack_trace_request = json_facade.write_request( - pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=hit.thread_id))) - stack_trace_response = json_facade.wait_for_response(stack_trace_request) - stack_frame = next(iter(stack_trace_response.body.stackFrames)) + +@pytest.mark.skipif(IS_JYTHON, reason='Not Jython compatible (fails on set variable).') +def test_pause_and_continue(case_setup): + with case_setup.test_file('_debugger_case_pause_continue.py') as writer: + json_facade = JsonFacade(writer) + + json_facade.write_set_breakpoints(writer.get_line_index_with_content('Break here')) + + json_facade.write_make_initial_run() + + json_facade.wait_for_thread_stopped() + + json_facade.write_continue() + + json_facade.write_pause() + + json_hit = json_facade.wait_for_thread_stopped(reason="pause") + + stack_frame = next(iter(json_hit.stack_trace_response.body.stackFrames)) scopes_request = json_facade.write_request(pydevd_schema.ScopesRequest( pydevd_schema.ScopesArguments(stack_frame['id']))) @@ -1093,10 +1105,7 @@ def test_pause_and_continue(case_setup): set_variable_response_as_dict = set_variable_response.to_dict()['body'] assert set_variable_response_as_dict == {'value': "False", 'type': 'bool'} - continue_request = json_facade.write_request( - pydevd_schema.ContinueRequest(pydevd_schema.ContinueArguments('*'))) - continue_response = json_facade.wait_for_response(continue_request) - assert continue_response.body.allThreadsContinued + json_facade.write_continue() writer.finished_ok = True @@ -1105,7 +1114,6 @@ def test_stepping(case_setup): with case_setup.test_file('_debugger_case_stepping.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') writer.write_add_breakpoint(writer.get_line_index_with_content('Break here 1')) writer.write_add_breakpoint(writer.get_line_index_with_content('Break here 2')) @@ -1176,7 +1184,6 @@ def test_evaluate(case_setup): with case_setup.test_file('_debugger_case_evaluate.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() @@ -1220,7 +1227,6 @@ def test_exception_details(case_setup, max_frames): with case_setup.test_file('_debugger_case_large_exception_stack.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') if max_frames == 'all': json_facade.write_launch(maxExceptionStackFrames=0) # trace back compresses repeated text @@ -1248,7 +1254,7 @@ def test_exception_details(case_setup, max_frames): body = exc_info_response.body assert body.exceptionId.endswith('IndexError') assert body.description == 'foo' - assert body.details.kwargs['source'] == writer.TEST_FILE + assert normcase(body.details.kwargs['source']) == normcase(writer.TEST_FILE) stack_line_count = len(body.details.stackTrace.split('\n')) assert min_expected_lines <= stack_line_count <= max_expected_lines @@ -1262,7 +1268,6 @@ def test_stack_levels(case_setup): with case_setup.test_file('_debugger_case_deep_stacks.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() @@ -1331,7 +1336,6 @@ def test_goto(case_setup): with case_setup.test_file('_debugger_case_set_next_statement.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') break_line = writer.get_line_index_with_content('Break here') step_line = writer.get_line_index_with_content('Step here') writer.write_add_breakpoint(break_line) @@ -1382,12 +1386,12 @@ def test_goto(case_setup): writer.finished_ok = True + @pytest.mark.parametrize('dbg_property', ['dont_trace', 'trace', 'change_pattern', 'dont_trace_after_start']) def test_set_debugger_property(case_setup, dbg_property): with case_setup.test_file('_debugger_case_dont_trace_test.py') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) if dbg_property in ('dont_trace', 'change_pattern', 'dont_trace_after_start'): @@ -1473,7 +1477,6 @@ def test_path_translation_and_source_reference(case_setup): json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') bp_line = writer.get_line_index_with_content('break here') writer.write_add_breakpoint( bp_line, 'call_this', filename=file_in_client) @@ -1530,7 +1533,7 @@ def test_source_reference_no_file(case_setup, tmpdir): with case_setup.test_file('_debugger_case_source_reference.py', get_environ=get_environ) as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') + writer.write_add_breakpoint(writer.get_line_index_with_content('breakpoint')) json_facade.write_make_initial_run() @@ -1591,7 +1594,6 @@ def test_case_django_no_attribute_exception_breakpoint(case_setup_django, jmc): with case_setup_django.test_file(EXPECTED_RETURNCODE='any') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') if jmc: writer.write_set_project_roots([debugger_unittest._get_debugger_test_file('my_code')]) @@ -1627,7 +1629,7 @@ def test_case_django_no_attribute_exception_breakpoint(case_setup_django, jmc): assert stack_frame['source']['path'].endswith('template_error.html') json_hit = json_facade.get_stack_as_json_hit(hit.thread_id) - variables_response = json_facade.get_variables_response(json_hit.frameId) + variables_response = json_facade.get_variables_response(json_hit.frame_id) entries = [x for x in variables_response.to_dict()['body']['variables'] if x['name'] == 'entry'] assert len(entries) == 1 variables_response = json_facade.get_variables_response(entries[0]['variablesReference']) @@ -1645,7 +1647,6 @@ def test_case_django_no_attribute_exception_breakpoint(case_setup_django, jmc): def test_case_flask_exceptions(case_setup_flask, jmc): with case_setup_flask.test_file(EXPECTED_RETURNCODE='any') as writer: json_facade = JsonFacade(writer) - writer.write_set_protocol('http_json') if jmc: writer.write_set_project_roots([debugger_unittest._get_debugger_test_file('my_code')]) @@ -1684,7 +1685,7 @@ def test_redirect_output(case_setup): with case_setup.test_file('_debugger_case_redirect.py', get_environ=get_environ) as writer: original_ignore_stderr_line = writer._ignore_stderr_line - writer.write_set_protocol('http_json') + json_facade = JsonFacade(writer) @overrides(writer._ignore_stderr_line) diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_utilities.py b/src/ptvsd/_vendored/pydevd/tests_python/test_utilities.py index ce62a87c..07156252 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_utilities.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_utilities.py @@ -7,7 +7,18 @@ from tests_python.debug_constants import IS_PY26, IS_PY3K def test_is_main_thread(): from _pydevd_bundle.pydevd_utils import is_current_thread_main_thread - assert is_current_thread_main_thread() + if not is_current_thread_main_thread(): + error_msg = 'Current thread does not seem to be a main thread. Details:\n' + current_thread = threading.current_thread() + error_msg += 'Current thread: %s\n' % (current_thread,) + + if hasattr(threading, 'main_thread'): + error_msg += 'Main thread found: %s\n' % (threading.main_thread(),) + else: + error_msg += 'Current main thread not instance of: %s (%s)' % ( + threading._MainThread, current_thread.__class__.__mro__,) + + raise AssertionError(error_msg) class NonMainThread(threading.Thread): diff --git a/src/ptvsd/session.py b/src/ptvsd/session.py index 4e1e3591..a405b046 100644 --- a/src/ptvsd/session.py +++ b/src/ptvsd/session.py @@ -24,7 +24,7 @@ class DebugSession(Startable, Closeable): return raw if not is_socket(raw): # TODO: Create a new client socket from a remote address? - #addr = Address.from_raw(raw) + # addr = Address.from_raw(raw) raise NotImplementedError client = raw return cls(client, **kwargs) @@ -42,9 +42,11 @@ class DebugSession(Startable, Closeable): super(DebugSession, self).__init__() if notify_closing is not None: + def handle_closing(before): if before: notify_closing(self) + self.add_close_handler(handle_closing) if notify_disconnecting is None: @@ -54,6 +56,7 @@ class DebugSession(Startable, Closeable): self._sock = sock self._pre_socket_close = None if ownsock: + # Close the socket *after* calling sys.exit() (via notify_closing). def handle_closing(before): if before: @@ -68,6 +71,7 @@ class DebugSession(Startable, Closeable): except TimeoutError: ptvsd.log.exception('timed out waiting for disconnect', category='D') close_socket(self._sock) + self.add_close_handler(handle_closing) self._msgprocessor = None @@ -171,6 +175,7 @@ class PyDevdDebugSession(DebugSession): self._notified_debugger_ready = True if _notify is not None: _notify(session) + self._notified_debugger_ready = False self._notify_debugger_ready = notify_debugger_ready @@ -178,7 +183,11 @@ class PyDevdDebugSession(DebugSession): if self._msgprocessor is None: # TODO: Do more than ignore? return - return self._msgprocessor.on_pydevd_event(cmdid, seq, text) + try: + return self._msgprocessor.on_pydevd_event(cmdid, seq, text) + except: + ptvsd.log.exception('Error handling pydevd message: {0}', text) + raise # internal methods diff --git a/src/ptvsd/wrapper.py b/src/ptvsd/wrapper.py index 1bef14ef..2651c7de 100644 --- a/src/ptvsd/wrapper.py +++ b/src/ptvsd/wrapper.py @@ -51,19 +51,6 @@ from ptvsd.socket import TimeoutError # noqa WAIT_FOR_THREAD_FINISH_TIMEOUT = 1 # seconds -STEP_REASONS = { - pydevd_comm.CMD_STEP_INTO, - pydevd_comm.CMD_STEP_INTO_MY_CODE, - pydevd_comm.CMD_STEP_OVER, - pydevd_comm.CMD_STEP_OVER_MY_CODE, - pydevd_comm.CMD_STEP_RETURN, - pydevd_comm.CMD_STEP_INTO_MY_CODE, -} -EXCEPTION_REASONS = { - pydevd_comm.CMD_STEP_CAUGHT_EXCEPTION, - pydevd_comm.CMD_ADD_EXCEPTION_BREAK -} - debugger_attached = threading.Event() @@ -78,12 +65,14 @@ def path_to_unicode(s): PTVSD_DIR_PATH = os.path.dirname(os.path.abspath(get_abs_path_real_path_and_base_from_file(__file__)[0])) + os.path.sep NORM_PTVSD_DIR_PATH = os.path.normcase(PTVSD_DIR_PATH) + def dont_trace_ptvsd_files(py_db, file_path): """ Returns true if the file should not be traced. """ return file_path.startswith(PTVSD_DIR_PATH) or file_path.endswith('ptvsd_launcher.py') + pydevd.PyDB.dont_trace_external_files = dont_trace_ptvsd_files @@ -1333,11 +1322,11 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): # Don't trace files under ptvsd, and ptvsd_launcher.py files # TODO: un-comment this code after fixing https://github.com/Microsoft/ptvsd/issues/1355 - #dont_trace_request = self._get_new_setDebuggerProperty_request( + # dont_trace_request = self._get_new_setDebuggerProperty_request( # dontTraceStartPatterns=[PTVSD_DIR_PATH], # dontTraceEndPatterns=['ptvsd_launcher.py'] - #) - #yield self.pydevd_request(-1, dont_trace_request, is_json=True) + # ) + # yield self.pydevd_request(-1, dont_trace_request, is_json=True) def _handle_detach(self): ptvsd.log.info('Detaching ...') @@ -1745,84 +1734,50 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): pass # We only care about the thread suspend single notification. @pydevd_events.handler(pydevd_comm_constants.CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION) - @async_handler def on_pydevd_thread_suspend_single_notification(self, seq, args): # NOTE: We should add the thread to VSC thread map only if the # thread is seen here for the first time in 'attach' scenario. # If we are here in 'launch' scenario and we get KeyError then # there is an issue in reporting of thread creation. - suspend_info = json.loads(args) - pyd_tid = suspend_info['thread_id'] - reason = suspend_info['stop_reason'] + body = args.get('body', {}) + + pyd_tid = body['threadId'] autogen = self.start_reason == 'attach' vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=autogen) - exc_desc = None - exc_name = None - extra = {} - if reason in STEP_REASONS: - reason = 'step' - elif reason in EXCEPTION_REASONS: - reason = 'exception' - elif reason == pydevd_comm.CMD_SET_BREAK: - reason = 'breakpoint' - elif reason == pydevd_comm.CMD_SET_NEXT_STATEMENT: - reason = 'goto' - else: - reason = 'pause' - - extra['preserveFocusHint'] = \ - reason not in ['step', 'exception', 'breakpoint'] - + reason = body['reason'] if reason == 'exception': - pydevd_request = { - 'type': 'request', - 'command': 'exceptionInfo', - 'arguments': { - 'threadId': pyd_tid - }, - } + exc_name = body['text'] + exc_desc = body['description'] - _, _, resp_args = yield self.pydevd_request( - pydevd_comm.CMD_GET_EXCEPTION_DETAILS, - pydevd_request, - is_json=True) - exc_name = resp_args['body']['exceptionId'] - exc_desc = resp_args['body']['description'] + if not self.debug_options.get('BREAK_SYSTEMEXIT_ZERO', False) and exc_name == 'SystemExit': + ptvsd.log.info('{0}({1!r})', exc_name, exc_desc) + try: + exit_code = int(exc_desc) + except ValueError: + # It is legal to invoke exit() with a non-integer argument, and SystemExit will + # pass that through. It's considered an error exit, same as non-zero integer. + ptvsd.log.info('Exit code {0!r} cannot be converted to int, treating as failure', exc_desc) + ignore = False + else: + ignore = exit_code in self._success_exitcodes + ptvsd.log.info( + 'Process exiting with {0} exit code {1}', + 'success' if ignore else 'failure', + exc_desc, + ) + if ignore: + self._resume_all_threads() + return - if not self.debug_options.get('BREAK_SYSTEMEXIT_ZERO', False) and exc_name == 'SystemExit': - ptvsd.log.info('{0}({1!r})', exc_name, exc_desc) - try: - exit_code = int(exc_desc) - except ValueError: - # It is legal to invoke exit() with a non-integer argument, and SystemExit will - # pass that through. It's considered an error exit, same as non-zero integer. - ptvsd.log.info('Exit code {0!r} cannot be converted to int, treating as failure', exc_desc) - ignore = False - else: - ignore = exit_code in self._success_exitcodes - ptvsd.log.info( - 'Process exiting with {0} exit code {1}', - 'success' if ignore else 'failure', - exc_desc, - ) - if ignore: - self._resume_all_threads() - return - - extra['allThreadsStopped'] = True - self.send_event( - 'stopped', - reason=reason, - threadId=vsc_tid, - text=exc_name, - description=exc_desc, - **extra) + body = body.copy() + body['threadId'] = vsc_tid + self.send_event('stopped', **body) @pydevd_events.handler(pydevd_comm_constants.CMD_THREAD_RESUME_SINGLE_NOTIFICATION) def on_pydevd_thread_resume_single_notification(self, seq, args): - resumed_info = json.loads(args) - pyd_tid = resumed_info['thread_id'] + body = args.get('body', {}) + pyd_tid = body['threadId'] try: vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=False) diff --git a/tests/func/test_exception.py b/tests/func/test_exception.py index 2c5f11a7..0eb5c424 100644 --- a/tests/func/test_exception.py +++ b/tests/func/test_exception.py @@ -56,7 +56,11 @@ def test_vsc_exception_options_raise_with_except(pyfile, run_as, start_method, r }) if raised == 'raisedOn': - hit = session.wait_for_thread_stopped(reason='exception') + hit = session.wait_for_thread_stopped( + reason='exception', + text=ANY.such_that(lambda s: s.endswith('ArithmeticError')), + description='bad code', + ) frames = hit.stacktrace.body['stackFrames'] assert ex_line == frames[0]['line'] @@ -313,6 +317,7 @@ def test_raise_exception_options(pyfile, run_as, start_method, exceptions, break @pytest.mark.parametrize('exit_code', [0, 3]) def test_success_exitcodes(pyfile, run_as, start_method, exit_code): + @pyfile def code_to_debug(): from dbgimporter import import_and_enable_debugger @@ -344,17 +349,19 @@ def test_success_exitcodes(pyfile, run_as, start_method, exit_code): @pytest.mark.parametrize('max_frames', ['default', 'all', 10]) def test_exception_stack(pyfile, run_as, start_method, max_frames): + @pyfile def code_to_debug(): from dbgimporter import import_and_enable_debugger import_and_enable_debugger() + def do_something(n): if n <= 0: - raise ArithmeticError('bad code') # @unhandled + raise ArithmeticError('bad code') # @unhandled do_something2(n - 1) def do_something2(n): - do_something(n-1) + do_something(n - 1) do_something(100) diff --git a/tests/helpers/session.py b/tests/helpers/session.py index a4f027d0..7cee02d2 100644 --- a/tests/helpers/session.py +++ b/tests/helpers/session.py @@ -636,10 +636,21 @@ class DebugSession(object): 'breakpoints': [{'line': bp_line} for bp_line in lines], }).wait_for_response().body.get('breakpoints', None) - def wait_for_thread_stopped(self, reason=ANY): + def wait_for_thread_stopped(self, reason=ANY, text=None, description=None): thread_stopped = self.wait_for_next(Event('stopped', ANY.dict_with({'reason': reason}))) + if text is not None: + assert text == thread_stopped.body['text'] + + if description is not None: + assert description == thread_stopped.body['description'] + tid = thread_stopped.body['threadId'] + + assert thread_stopped.body['allThreadsStopped'] + assert thread_stopped.body['preserveFocusHint'] == \ + (thread_stopped.body['reason'] not in ['step', 'exception', 'breakpoint']) + assert tid is not None resp_stacktrace = self.send_request('stackTrace', arguments={