From ed565b2d05fbbd7226c3fbf30c12ad71e67c22d5 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 7 Aug 2018 11:40:33 -0700 Subject: [PATCH] Turning suspend all policy and using get stack trace command (#731) * Continue resumes all threads * Enable suspend policy ALL * stacktraces and active exceptions are no longer stored * stacktraces and active exceptions are no longer stored * Fix set breakpoints tests * Fix exception info * Fix breakpoint tests * Fix exception breakpoint tests * Fix syntax * Fix exception info * Fix syntax * Fix Stack trace tests * Fix for 2.7 * Minor refactor * Fix ContinueTests * Fix ExceptionInfoTests * Enable more tests * Remove tests that are now no-ops * Minor tweaks * Fix test_attach_breakpoints * typo * More tweaks * Send error response for unknown threads * Use assert_contains in continue tests * Add some TODOs --- ptvsd/wrapper.py | 172 +++++++++-------- tests/highlevel/__init__.py | 15 +- tests/highlevel/test_live_pydevd.py | 95 +++------- tests/highlevel/test_messages.py | 283 ++++++++++++---------------- tests/system_tests/test_main.py | 8 +- 5 files changed, 249 insertions(+), 324 deletions(-) diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index 000f9ff11..98a348409 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -563,16 +563,12 @@ def append(self, var): if var_name.startswith('__'): if var_name.endswith('__'): self.dunder.append(var) - #print('Apended dunder: %s' % var_name) else: self.double_underscore.append(var) - #print('Apended double under: %s' % var_name) elif var_name.startswith('_'): self.single_underscore.append(var) - #print('Apended single under: %s' % var_name) else: self.variables.append(var) - #print('Apended variable: %s' % var_name) def get_sorted_variables(self): def get_sort_key(o): @@ -581,7 +577,6 @@ def get_sort_key(o): self.single_underscore.sort(key=get_sort_key) self.double_underscore.sort(key=get_sort_key) self.dunder.sort(key=get_sort_key) - #print('sorted') return self.variables + self.single_underscore + self.double_underscore + self.dunder # noqa @@ -1218,10 +1213,6 @@ def __init__(self, socket, pydevd_notify, pydevd_request, # debugger state self.is_process_created = False self.is_process_created_lock = threading.Lock() - self.stack_traces = {} - self.stack_traces_lock = threading.Lock() - self.active_exceptions = {} - self.active_exceptions_lock = threading.Lock() self.thread_map = IDMap() self.frame_map = IDMap() self.var_map = IDMap() @@ -1487,8 +1478,9 @@ def _clear_breakpoints(self): # TODO: Wait until the last request has been handled? def _resume_all_threads(self): - for tid in self.stack_traces: - self.pydevd_notify(pydevd_comm.CMD_THREAD_RUN, tid) + # TODO: Replace this with resume all command after #732 is fixed + for pyd_tid in self.thread_map.pydevd_ids(): + self.pydevd_notify(pydevd_comm.CMD_THREAD_RUN, pyd_tid) def send_process_event(self, start_method): # TODO: docstring @@ -1588,16 +1580,22 @@ def on_stackTrace(self, request, args): levels = int(args.get('levels', 0)) fmt = args.get('format', {}) - pyd_tid = self.thread_map.to_pydevd(vsc_tid) - with self.stack_traces_lock: - try: - xframes = self.stack_traces[pyd_tid] - except KeyError: - # This means the stack was requested before the - # thread was suspended - xframes = [] - totalFrames = len(xframes) + try: + pyd_tid = self.thread_map.to_pydevd(vsc_tid) + except KeyError: + # Unknown thread, nothing much we cna do about it here + self.send_error_response(request) + return + + try: + cmd = pydevd_comm.CMD_GET_THREAD_STACK + _, _, resp_args = yield self.pydevd_request(cmd, pyd_tid) + xml = self.parse_xml_response(resp_args) + xframes = list(xml.thread.frame) + except Exception: + xframes = [] + totalFrames = len(xframes) if levels == 0: levels = totalFrames @@ -1641,8 +1639,9 @@ def on_stackTrace(self, request, args): user_frames = [] for frame in stackFrames: - if not self.internals_filter.is_internal_path( - frame['source']['path']): + path = frame['source']['path'] + if not self.internals_filter.is_internal_path(path) and \ + self._should_debug(path): user_frames.append(frame) totalFrames = len(user_frames) @@ -1988,6 +1987,7 @@ def on_pause(self, request, args): return # Always suspend all threads. + # TODO: Replace this with suspend all command after #732 is fixed for pyd_tid in self.thread_map.pydevd_ids(): self.pydevd_notify(pydevd_comm.CMD_THREAD_SUSPEND, pyd_tid) self.send_response(request) @@ -1995,9 +1995,11 @@ def on_pause(self, request, args): @async_handler def on_continue(self, request, args): # TODO: docstring - tid = self.thread_map.to_pydevd(int(args['threadId'])) - self.pydevd_notify(pydevd_comm.CMD_THREAD_RUN, tid) - self.send_response(request) + # Always continue all threads. + # TODO: Replace this with resume all command after #732 is fixed + for pyd_tid in self.thread_map.pydevd_ids(): + self.pydevd_notify(pydevd_comm.CMD_THREAD_RUN, pyd_tid) + self.send_response(request, allThreadsContinued=True) @async_handler def on_next(self, request, args): @@ -2081,7 +2083,7 @@ def on_setBreakpoints(self, request, args): self.bp_map.remove(pyd_bpid, vsc_bpid) cmd = pydevd_comm.CMD_SET_BREAK - msgfmt = '{}\t{}\t{}\t{}\tNone\t{}\t{}\t{}\t{}' + msgfmt = '{}\t{}\t{}\t{}\tNone\t{}\t{}\t{}\t{}\tALL' if needs_unicode(path): msgfmt = unicode(msgfmt) # noqa for src_bp in src_bps: @@ -2147,13 +2149,59 @@ def on_setExceptionBreakpoints(self, request, args): def on_exceptionInfo(self, request, args): # TODO: docstring pyd_tid = self.thread_map.to_pydevd(args['threadId']) - with self.active_exceptions_lock: - try: - exc = self.active_exceptions[pyd_tid] - except KeyError: - exc = ExceptionInfo('BaseException', - 'exception: no description', - None, None) + + # For exception cases both raise and uncaught, pydevd adds a + # __exception__ object to the top most frame. Extracting the + # exception name and description from that frame gives accurate + # exception information. Get exception info from frame + try: + cmd = pydevd_comm.CMD_GET_THREAD_STACK + _, _, resp_args = yield self.pydevd_request(cmd, pyd_tid) + xml = self.parse_xml_response(resp_args) + xframes = list(xml.thread.frame) + + xframe = xframes[0] + pyd_fid = xframe['id'] + + cmdargs = '{}\t{}\tFRAME\t__exception__'.format(pyd_tid, pyd_fid) + cmdid = pydevd_comm.CMD_GET_VARIABLE + _, _, resp_args = yield self.pydevd_request(cmdid, cmdargs) + xml = self.parse_xml_response(resp_args) + + name = unquote(xml.var[1]['type']) + description = unquote(xml.var[1]['value']) + + frame_data = [] + for f in xframes: + file_path = unquote(f['file']) + if not self.internals_filter.is_internal_path(file_path) and \ + self._should_debug(file_path): + line_no = int(f['line']) + func_name = unquote(f['name']) + if _util.is_py34(): + # NOTE: In 3.4.* format_list requires the text + # to be passed in the tuple list. + line_text = _util.get_line_for_traceback(file_path, + line_no) + frame_data.append((file_path, line_no, + func_name, line_text)) + else: + frame_data.append((file_path, line_no, + func_name, None)) + + stack = ''.join(traceback.format_list(frame_data)) + + source = unquote(xframe['file']) + if self.internals_filter.is_internal_path(source) or \ + not self._should_debug(source): + source = None + except Exception: + name = 'BaseException' + description = 'exception: no description' + stack = None + source = None + + exc = ExceptionInfo(name, description, stack, source) self.send_response( request, exceptionId=exc.name, @@ -2318,9 +2366,6 @@ def on_pydevd_thread_suspend(self, seq, args): autogen = self.start_reason == 'attach' vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=autogen) - with self.stack_traces_lock: - self.stack_traces[pyd_tid] = list(xml.thread.frame) - description = None text = None if reason in STEP_REASONS: @@ -2332,12 +2377,7 @@ def on_pydevd_thread_suspend(self, seq, args): else: reason = 'pause' - # For exception cases both raise and uncaught, pydevd adds a - # __exception__ object to the top most frame. Extracting the - # exception name and description from that frame gives accurate - # exception information. if reason == 'exception': - # Get exception info from frame try: pyd_fid = xframe['id'] cmdargs = '{}\t{}\tFRAME\t__exception__'.format(pyd_tid, @@ -2345,39 +2385,12 @@ def on_pydevd_thread_suspend(self, seq, args): cmdid = pydevd_comm.CMD_GET_VARIABLE _, _, resp_args = yield self.pydevd_request(cmdid, cmdargs) xml = self.parse_xml_response(resp_args) + text = unquote(xml.var[1]['type']) description = unquote(xml.var[1]['value']) - frame_data = [] - for f in xframes: - file_path = unquote(f['file']) - if not self.internals_filter.is_internal_path(file_path): - line_no = int(f['line']) - func_name = unquote(f['name']) - if _util.is_py34(): - # NOTE: In 3.4.* format_list requires the text - # to be passed in the tuple list. - line_text = _util.get_line_for_traceback(file_path, - line_no) - frame_data.append((file_path, line_no, - func_name, line_text)) - else: - frame_data.append((file_path, line_no, - func_name, None)) - stack = ''.join(traceback.format_list(frame_data)) - source = unquote(xframe['file']) - if self.internals_filter.is_internal_path(source): - source = None except Exception: text = 'BaseException' description = 'exception: no description' - stack = None - source = None - - with self.active_exceptions_lock: - self.active_exceptions[pyd_tid] = ExceptionInfo(text, - description, - stack, - source) self.send_event( 'stopped', @@ -2392,20 +2405,8 @@ def on_pydevd_thread_run(self, seq, args): pyd_tid, _ = args.split('\t') pyd_tid = pyd_tid.strip() - # Stack trace, active exception, all frames, and variables for + # All frames, and variables for # this thread are now invalid; clear their IDs. - with self.stack_traces_lock: - try: - del self.stack_traces[pyd_tid] - except KeyError: - pass - - with self.active_exceptions_lock: - try: - del self.active_exceptions[pyd_tid] - except KeyError: - pass - for pyd_fid, vsc_fid in self.frame_map.pairs(): if pyd_fid[0] == pyd_tid: self.frame_map.remove(pyd_fid, vsc_fid) @@ -2429,12 +2430,7 @@ def on_pydevd_send_curr_exception_trace(self, seq, args): @pydevd_events.handler(pydevd_comm.CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED) def on_pydevd_send_curr_exception_trace_proceeded(self, seq, args): # TODO: docstring - pyd_tid = args.strip() - with self.active_exceptions_lock: - try: - del self.active_exceptions[pyd_tid] - except KeyError: - pass + pass @pydevd_events.handler(pydevd_comm.CMD_WRITE_TO_CONSOLE) def on_pydevd_cmd_write_to_console2(self, seq, args): diff --git a/tests/highlevel/__init__.py b/tests/highlevel/__init__.py index 5263282aa..f6b679178 100644 --- a/tests/highlevel/__init__.py +++ b/tests/highlevel/__init__.py @@ -17,6 +17,7 @@ CMD_THREAD_CREATE, CMD_GET_VARIABLE, CMD_SET_PROJECT_ROOTS, + CMD_GET_THREAD_STACK, ) from ptvsd._util import new_hidden_thread @@ -833,6 +834,8 @@ def pause(self, threadname, *stack): self._pydevd.send_pause_event(thread, *stack) if self._vsc._hidden: self._vsc.msgs.next_event() + payload = self.debugger_msgs.format_frames(thread.id, 'pause', *stack) + self.set_debugger_response(CMD_GET_THREAD_STACK, payload) self.send_request('stackTrace', {'threadId': tid}) self.send_request('scopes', {'frameId': 1}) return tid, thread @@ -909,8 +912,9 @@ def assert_vsc_failure(self, received, expected, req): self.assertEqual(received[:-1], expected) failure = received[-1] if len(received) > 0 else [] - expected = self.vsc.protocol.parse( - self.fix.vsc_msgs.new_failure(req, failure.message)) + if failure: + expected = self.vsc.protocol.parse( + self.fix.vsc_msgs.new_failure(req, failure.message)) self.assertEqual(failure, expected) def assert_received(self, daemon, expected): @@ -919,10 +923,11 @@ def assert_received(self, daemon, expected): expected = list(daemon.protocol.parse_each(expected)) self.assertEqual(received, expected) - def assert_contains(self, received, expected): + def assert_contains(self, received, expected, parser='vsc'): + parser = self.vsc.protocol if parser == 'vsc' else parser from tests.helpers.message import assert_contains_messages - received = list(self.vsc.protocol.parse_each(received)) - expected = list(self.vsc.protocol.parse_each(expected)) + received = list(parser.parse_each(received)) + expected = list(parser.parse_each(expected)) assert_contains_messages(received, expected) def assert_received_unordered_payload(self, daemon, expected): diff --git a/tests/highlevel/test_live_pydevd.py b/tests/highlevel/test_live_pydevd.py index e4ad6fdc7..6238025ac 100644 --- a/tests/highlevel/test_live_pydevd.py +++ b/tests/highlevel/test_live_pydevd.py @@ -249,46 +249,6 @@ def inc(value, count=1): raise MyError('ka-boom') """) - def assert_vsc_received_fixing_events(self, received, specs): - ''' - Assert that the received messages match the spec but considering that - some events may be unordered. - ''' - iter_specs = iter(specs) - expected_msgs = [] - new_received = [] - for msg in received: - if msg.type == 'event': - if msg.event == 'process': - # Just check that 'process' is correct, don't add to the - # check list later on. - self.assertEqual(msg.body['name'], sys.argv[0]) - self.assertEqual(msg.body['systemProcessId'], os.getpid()) - self.assertEqual(msg.body['isLocalProcess'], True) - self.assertEqual(msg.body['startMethod'], 'launch') - continue - - if msg.event == 'thread': - # When thread is found, just consider it ok, no - # matter where it was found. - continue - - new_received.append(msg) - - try: - spec = next(iter_specs) - except StopIteration: - continue - - if isinstance(spec, tuple): - event, body = spec - expected = self.new_event(event, seq=msg.seq, **body) - else: - expected = self.new_response(spec, seq=msg.seq) - expected_msgs.append(expected) - - self.assert_vsc_received(new_received, expected_msgs) - def _set_lock(self, label=None, script=None): if script is None: if not os.path.exists(self.filename): @@ -370,34 +330,31 @@ def test_breakpoints_single_file(self): self.assertNotEqual(req['command'], 'setExceptionBreakpoints') self.assertEqual(got, config['breakpoints']) - self.assert_vsc_received_fixing_events(received, [ - ('stopped', - dict( - reason='breakpoint', - threadId=tid, - text=None, - description=None, - )), - req_continue1, - ('continued', dict(threadId=tid, )), - ('stopped', - dict( - reason='breakpoint', - threadId=tid, - text=None, - description=None, - )), - req_continue2, - ('continued', dict(threadId=tid, )), - ('stopped', - dict( - reason='breakpoint', - threadId=tid, - text=None, - description=None, - )), - req_continue_last, - ('continued', dict(threadId=tid, )), + self.assert_contains(received, [ + self.new_event('stopped', + reason='breakpoint', + threadId=tid, + text=None, + description=None, + ), + self.new_response(req_continue1, allThreadsContinued=True), + self.new_event('continued', threadId=tid), + self.new_event('stopped', + reason='breakpoint', + threadId=tid, + text=None, + description=None, + ), + self.new_response(req_continue2, allThreadsContinued=True), + self.new_event('continued', threadId=tid), + self.new_event('stopped', + reason='breakpoint', + threadId=tid, + text=None, + description=None, + ), + self.new_response(req_continue_last, allThreadsContinued=True), + self.new_event('continued', threadId=tid), ]) self.assertIn('2 4 4', out) self.assertIn('ka-boom', err) @@ -444,7 +401,7 @@ def test_exception_breakpoints(self): threadId=tid, text='MyError', description=description)), - self.new_response(req_continue_last), + self.new_response(req_continue_last, allThreadsContinued=True), self.new_event('continued', **dict(threadId=tid, )), ]) self.assertIn('2 4 4', out) diff --git a/tests/highlevel/test_messages.py b/tests/highlevel/test_messages.py index 427c5f251..5d2553df4 100644 --- a/tests/highlevel/test_messages.py +++ b/tests/highlevel/test_messages.py @@ -18,8 +18,6 @@ CMD_REMOVE_BREAK, CMD_REMOVE_EXCEPTION_BREAK, CMD_RETURN, - CMD_SEND_CURR_EXCEPTION_TRACE, - CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED, CMD_SET_BREAK, CMD_SHOW_CONSOLE, CMD_STEP_CAUGHT_EXCEPTION, @@ -33,6 +31,7 @@ CMD_VERSION, CMD_WRITE_TO_CONSOLE, CMD_STEP_INTO_MY_CODE, + CMD_GET_THREAD_STACK, ) from . import RunningTest @@ -248,16 +247,22 @@ def test_none(self): class StackTraceTests(NormalRequestTest, unittest.TestCase): COMMAND = 'stackTrace' + PYDEVD_CMD = CMD_GET_THREAD_STACK + + def pydevd_payload(self, threadid, *frames): + return self.debugger_msgs.format_frames(threadid, 'pause', *frames) def test_basic(self): + frames = [ + # (pfid, func, file, line) + (2, 'spam', 'abc.py', 10), + (5, 'eggs', 'xyz.py', 2), + ] with self.launched(): with self.hidden(): tid, thread = self.set_thread('x') - self.suspend(thread, CMD_THREAD_SUSPEND, *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), - (5, 'eggs', 'xyz.py', 2), - ]) + self.suspend(thread, CMD_THREAD_SUSPEND, *frames) + self.set_debugger_response(thread.id, *frames) self.send_request( threadId=tid, #startFrame=1, @@ -287,16 +292,20 @@ def test_basic(self): ), # no events ]) - self.assert_received(self.debugger, []) + self.assert_received(self.debugger, [ + self.debugger_msgs.new_request(self.PYDEVD_CMD, str(thread.id)), + ]) def test_one_frame(self): + frames = [ + # (pfid, func, file, line) + (2, 'spam', 'abc.py', 10), + ] with self.launched(): with self.hidden(): tid, thread = self.set_thread('x') - self.suspend(thread, CMD_THREAD_SUSPEND, *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), - ]) + self.suspend(thread, CMD_THREAD_SUSPEND, *frames) + self.set_debugger_response(thread.id, *frames) self.send_request( threadId=tid, ) @@ -317,17 +326,21 @@ def test_one_frame(self): ), # no events ]) - self.assert_received(self.debugger, []) + self.assert_received(self.debugger, [ + self.debugger_msgs.new_request(self.PYDEVD_CMD, str(thread.id)), + ]) def test_with_frame_format(self): + frames = [ + # (pfid, func, file, line) + (2, 'spam', 'abc.py', 10), + (5, 'eggs', 'xyz.py', 2), + ] with self.launched(): with self.hidden(): tid, thread = self.set_thread('x') - self.suspend(thread, CMD_THREAD_SUSPEND, *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), - (5, 'eggs', 'xyz.py', 2), - ]) + self.suspend(thread, CMD_THREAD_SUSPEND, *frames) + self.set_debugger_response(thread.id, *frames) self.send_request( threadId=tid, format={ @@ -361,7 +374,9 @@ def test_with_frame_format(self): ), # no events ]) - self.assert_received(self.debugger, []) + self.assert_received(self.debugger, [ + self.debugger_msgs.new_request(self.PYDEVD_CMD, str(thread.id)), + ]) def test_no_threads(self): with self.launched(): @@ -842,23 +857,27 @@ class ContinueTests(NormalRequestTest, unittest.TestCase): PYDEVD_RESP = None def test_basic(self): + frames = [ + (2, 'spam', 'abc.py', 10), + ] with self.launched(): with self.hidden(): - tid, thread = self.pause('x', *[ - (2, 'spam', 'abc.py', 10), - ]) + tid, thread = self.pause('x', *frames) self.send_request( threadId=tid, ) received = self.vsc.received self.assert_vsc_received(received, [ - self.expected_response(), + self.expected_response(allThreadsContinued=True), # no events ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request(str(thread.id)), - ]) + thread_ids = list(t.id for t in self._known_threads) + expected = list(self.debugger_msgs.new_request( + self.PYDEVD_CMD, str(t)) + for t in thread_ids) + self.assert_contains(self.debugger.received, expected, + parser=self.debugger.protocol) class NextTests(NormalRequestTest, unittest.TestCase): @@ -1045,9 +1064,9 @@ def test_initial(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone'), + '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.expected_pydevd_request( - '2\tpython-line\tspam.py\t15\tNone\ti == 3\tNone\tNone\tNone'), + '2\tpython-line\tspam.py\t15\tNone\ti == 3\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1095,15 +1114,15 @@ def test_with_hit_condition(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\t@HIT@ == 5\tNone'), # noqa + '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\t@HIT@ == 5\tNone\tALL'), # noqa self.expected_pydevd_request( - '2\tpython-line\tspam.py\t15\tNone\tNone\tNone\t@HIT@ ==5\tNone'), # noqa + '2\tpython-line\tspam.py\t15\tNone\tNone\tNone\t@HIT@ ==5\tNone\tALL'), # noqa self.expected_pydevd_request( - '3\tpython-line\tspam.py\t20\tNone\tNone\tNone\t@HIT@ > 5\tNone'), # noqa + '3\tpython-line\tspam.py\t20\tNone\tNone\tNone\t@HIT@ > 5\tNone\tALL'), # noqa self.expected_pydevd_request( - '4\tpython-line\tspam.py\t25\tNone\tNone\tNone\t@HIT@ % 5 == 0\tNone'), # noqa + '4\tpython-line\tspam.py\t25\tNone\tNone\tNone\t@HIT@ % 5 == 0\tNone\tALL'), # noqa self.expected_pydevd_request( - '5\tpython-line\tspam.py\t30\tNone\tNone\tNone\tx\tNone'), + '5\tpython-line\tspam.py\t30\tNone\tNone\tNone\tx\tNone\tALL'), self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1146,13 +1165,13 @@ def test_with_logpoint(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("5") + '\tNone\tTrue'), # noqa + '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("5") + '\tNone\tTrue\tALL'), # noqa self.expected_pydevd_request( - '2\tpython-line\tspam.py\t15\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue'), # noqa + '2\tpython-line\tspam.py\t15\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue\tALL'), # noqa self.expected_pydevd_request( - '3\tpython-line\tspam.py\t20\tNone\tNone\t"{}".format(a)\tNone\tTrue'), # noqa + '3\tpython-line\tspam.py\t20\tNone\tNone\t"{}".format(a)\tNone\tTrue\tALL'), # noqa self.expected_pydevd_request( - '4\tpython-line\tspam.py\t25\tNone\tNone\t"{}+{}=Something".format(a, b)\tNone\tTrue'), # noqa + '4\tpython-line\tspam.py\t25\tNone\tNone\t"{}+{}=Something".format(a, b)\tNone\tTrue\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1161,9 +1180,9 @@ def test_with_existing(self): with self.hidden(): self.PYDEVD_CMD = CMD_SET_BREAK p1 = self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone') # noqa + '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL') # noqa p2 = self.expected_pydevd_request( - '2\tpython-line\tspam.py\t17\tNone\tNone\tNone\tNone\tNone') # noqa + '2\tpython-line\tspam.py\t17\tNone\tNone\tNone\tNone\tNone\tALL') # noqa with self.expect_debugger_command(CMD_VERSION): self.fix.send_request('setBreakpoints', dict( source={'path': 'spam.py'}, @@ -1213,11 +1232,11 @@ def test_with_existing(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, removed + [ self.expected_pydevd_request( - '3\tpython-line\tspam.py\t113\tNone\tNone\tNone\tNone\tNone'), + '3\tpython-line\tspam.py\t113\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.expected_pydevd_request( - '4\tpython-line\tspam.py\t2\tNone\tNone\tNone\tNone\tNone'), + '4\tpython-line\tspam.py\t2\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.expected_pydevd_request( - '5\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone'), + '5\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1254,10 +1273,10 @@ def test_multiple_files(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone'), + '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), self.expected_pydevd_request( - '2\tpython-line\teggs.py\t17\tNone\tNone\tNone\tNone\tNone'), + '2\tpython-line\teggs.py\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1293,10 +1312,10 @@ def test_vs_django(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone'), + '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), self.expected_pydevd_request( - '2\tdjango-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone'), # noqa + '2\tdjango-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1332,10 +1351,10 @@ def test_vs_django_logpoint(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue'), # noqa + '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), self.expected_pydevd_request( - '2\tdjango-line\teggs.html\t17\tNone\tNone\t' + repr("Hello Django World") + '\tNone\tTrue'), # noqa + '2\tdjango-line\teggs.html\t17\tNone\tNone\t' + repr("Hello Django World") + '\tNone\tTrue\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1371,10 +1390,10 @@ def test_vs_flask_jinja2(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone'), + '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), self.expected_pydevd_request( - '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone'), # noqa + '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1410,10 +1429,10 @@ def test_vs_flask_jinja2_logpoint(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue'), # noqa + '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), self.expected_pydevd_request( - '2\tjinja2-line\teggs.html\t17\tNone\tNone\t' + repr("Hello Jinja World") + '\tNone\tTrue'), # noqa + '2\tjinja2-line\teggs.html\t17\tNone\tNone\t' + repr("Hello Jinja World") + '\tNone\tTrue\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1449,10 +1468,10 @@ def test_vsc_flask_jinja2(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone'), + '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), self.expected_pydevd_request( - '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone'), # noqa + '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -1488,10 +1507,10 @@ def test_vsc_jinja2(self): self.PYDEVD_CMD = CMD_SET_BREAK self.assert_received(self.debugger, [ self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone'), + '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), self.expected_pydevd_request( - '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone'), # noqa + '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), ]) @@ -2094,13 +2113,27 @@ class ExceptionInfoTests(NormalRequestTest, unittest.TestCase): # ], # ), + PYDEVD_CMD = CMD_GET_THREAD_STACK + + def pydevd_payload(self, threadid, *args): + if self.PYDEVD_CMD == CMD_GET_THREAD_STACK: + return self.debugger_msgs.format_frames(threadid, 'pause', *args) + else: + return self.debugger_msgs.format_variables(*args) + def test_active_exception(self): exc = RuntimeError('something went wrong') lineno = fail.__code__.co_firstlineno + 1 frame = (2, 'fail', __file__, lineno) # (pfid, func, file, line) with self.launched(): with self.hidden(): - tid, _ = self.error('x', exc, frame) + tid, thread = self.error('x', exc, frame) + self.set_debugger_response(thread.id, frame) + self.PYDEVD_CMD = CMD_GET_VARIABLE + self.set_debugger_response( + thread.id, + ('type', exc.__class__.__name__), + ('value', exc)) self.send_request( threadId=tid, ) @@ -2108,7 +2141,7 @@ def test_active_exception(self): # TODO: Is this the right str? excstr = "RuntimeError('something went wrong')" - if sys.version_info[1] < 7: + if sys.version_info < (3, 7): excstr = excstr[:-1] + ',)' self.assert_vsc_received(received, [ self.expected_response( @@ -2128,37 +2161,27 @@ def test_active_exception(self): ), ), ]) - self.assert_received(self.debugger, []) - - # TODO: verify behavior - @unittest.skip('poorly specified (broken?)') - def test_no_exception(self): - with self.launched(): - with self.hidden(): - tid, _ = self.pause('x') - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - ), + self.assert_received(self.debugger, [ + self.debugger_msgs.new_request( + CMD_GET_THREAD_STACK, + str(thread.id)), + self.debugger_msgs.new_request( + CMD_GET_VARIABLE, + '{}\t2\tFRAME\t__exception__'.format(thread.id)), ]) - self.assert_received(self.debugger, []) - # TODO: verify behavior - @unittest.skip('poorly specified (broken?)') - def test_exception_cleared(self): + def test_no_exception(self): exc = RuntimeError('something went wrong') - frame = (2, 'spam', 'abc.py', 10) # (pfid, func, file, line) + lineno = fail.__code__.co_firstlineno + 1 + frame = (2, 'fail', __file__, lineno) # (pfid, func, file, line) with self.launched(): with self.hidden(): tid, thread = self.error('x', exc, frame) - self.send_debugger_event( - CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED, - str(thread.id), - ) + self.set_debugger_response(thread.id, frame) + self.PYDEVD_CMD = CMD_GET_VARIABLE + # Don't provide exception info to simulate no exception + self.set_debugger_response( + thread.id) self.send_request( threadId=tid, ) @@ -2166,9 +2189,25 @@ def test_exception_cleared(self): self.assert_vsc_received(received, [ self.expected_response( + exceptionId='BaseException', + description='exception: no description', + breakMode='unhandled', + details=dict( + typeName='BaseException', + message='exception: no description', + stackTrace=None, + source=None + ), ), ]) - self.assert_received(self.debugger, []) + self.assert_received(self.debugger, [ + self.debugger_msgs.new_request( + CMD_GET_THREAD_STACK, + str(thread.id)), + self.debugger_msgs.new_request( + CMD_GET_VARIABLE, + '{}\t2\tFRAME\t__exception__'.format(thread.id)), + ]) class RunInTerminalTests(NormalRequestTest, unittest.TestCase): @@ -2789,82 +2828,6 @@ def test_basic(self): self.assert_received(self.debugger, []) -class SendCurrExcTraceEventTests(PyDevdEventTest, unittest.TestCase): - - CMD = CMD_SEND_CURR_EXCEPTION_TRACE - EVENT = None - - def pydevd_payload(self, thread, exc, frame): - return self.debugger_msgs.format_exception(thread[0], exc, frame) - - # TODO: Is this right? - @unittest.skip('now a no-op') - def test_basic(self): - exc = RuntimeError('something went wrong') - frame = (2, 'spam', 'abc.py', 10) # (pfid, func, file, line) - with self.launched(): - with self.hidden(): - tid, thread = self.set_thread('x') - self.send_event(thread, exc, frame) - received = self.vsc.received - - self.send_request('exceptionInfo', dict( - threadId=tid, - )) - resp = self.vsc.received[-1] - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - self.assertTrue(resp.success, resp.message) - self.assertEqual(resp.body, dict( - exceptionId='RuntimeError', - description='something went wrong', - breakMode='unhandled', - details=dict( - message='something went wrong', - typeName='RuntimeError', - ), - )) - - -class SendCurrExcTraceProceededEventTests(PyDevdEventTest, unittest.TestCase): - - CMD = CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED - EVENT = None - - def pydevd_payload(self, threadid): - return str(threadid) - - def test_basic(self): - exc = RuntimeError('something went wrong') - frame = (2, 'spam', 'abc.py', 10) # (pfid, func, file, line) - #text = self.debugger_msgs.format_exception(thread[0], exc, frame) - with self.launched(): - with self.hidden(): - #tid, thread = self.set_thread('x') - #self.fix.send_event(CMD_SEND_CURR_EXCEPTION_TRACE, text) - tid, thread = self.error('x', exc, frame) - self.send_request('exceptionInfo', dict( - threadId=tid, - )) - before = self.vsc.received[-1] - - self.send_event(thread.id) - received = self.vsc.received - - self.send_request('exceptionInfo', dict( - threadId=tid, - )) - after = self.vsc.received[-1] - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - # The exception got cleared so we do not see RuntimeError. - self.assertEqual(after.body['exceptionId'], 'BaseException') - self.assertNotEqual(after.body['exceptionId'], - before.body['exceptionId']) - - class GetExceptionBreakpointEventTests(PyDevdEventTest, unittest.TestCase): CMD = CMD_GET_BREAKPOINT_EXCEPTION diff --git a/tests/system_tests/test_main.py b/tests/system_tests/test_main.py index 76b42fc63..9e6735bd1 100644 --- a/tests/system_tests/test_main.py +++ b/tests/system_tests/test_main.py @@ -756,7 +756,9 @@ def test_attach_breakpoints(self): 'column': 1, }], }), - self.new_response(req_continue1.req), + self.new_response(req_continue1.req, **{ + 'allThreadsContinued': True + }), self.new_event('continued', threadId=tid), self.new_event( 'output', @@ -789,7 +791,9 @@ def test_attach_breakpoints(self): 'column': 1, }], }), - self.new_response(req_continue2.req), + self.new_response(req_continue2.req, **{ + 'allThreadsContinued': True + }), self.new_event('continued', threadId=tid), self.new_event( 'output',