Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Accept new style properties for debugger options. Fixes #2001 #2005

Merged
merged 3 commits into from
Dec 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,102 @@
import sys
import platform
import json
try:
import urllib
urllib.unquote # noqa
except Exception:
import urllib.parse as urllib


class DebugOptions(object):

__slots__ = [
'debug_stdlib',
'redirect_output',
'show_return_value',
'break_system_exit_zero',
'django_debug',
'flask_debug',
'stop_on_entry',
'max_exception_stack_frames',
]

def __init__(self):
self.debug_stdlib = False
self.redirect_output = False
self.show_return_value = False
self.break_system_exit_zero = False
self.django_debug = False
self.flask_debug = False
self.stop_on_entry = False
self.max_exception_stack_frames = 0

def to_json(self):
dct = {}
for s in self.__slots__:
dct[s] = getattr(self, s)
return json.dumps(dct)

def update_fom_debug_options(self, debug_options):
if 'DEBUG_STDLIB' in debug_options:
self.debug_stdlib = debug_options.get('DEBUG_STDLIB')

if 'REDIRECT_OUTPUT' in debug_options:
self.redirect_output = debug_options.get('REDIRECT_OUTPUT')

if 'SHOW_RETURN_VALUE' in debug_options:
self.show_return_value = debug_options.get('SHOW_RETURN_VALUE')

if 'BREAK_SYSTEMEXIT_ZERO' in debug_options:
self.break_system_exit_zero = debug_options.get('BREAK_SYSTEMEXIT_ZERO')

if 'DJANGO_DEBUG' in debug_options:
self.django_debug = debug_options.get('DJANGO_DEBUG')

if 'FLASK_DEBUG' in debug_options:
self.flask_debug = debug_options.get('FLASK_DEBUG')

if 'STOP_ON_ENTRY' in debug_options:
self.stop_on_entry = debug_options.get('STOP_ON_ENTRY')

# Note: _max_exception_stack_frames cannot be set by debug options.

def update_from_args(self, args):
if 'debugStdLib' in args:
self.debug_stdlib = bool_parser(args['debugStdLib'])

if 'redirectOutput' in args:
self.redirect_output = bool_parser(args['redirectOutput'])

if 'showReturnValue' in args:
self.show_return_value = bool_parser(args['showReturnValue'])

if 'breakOnSystemExitZero' in args:
self.break_system_exit_zero = bool_parser(args['breakOnSystemExitZero'])

if 'django' in args:
self.django_debug = bool_parser(args['django'])

if 'flask' in args:
self.flask_debug = bool_parser(args['flask'])
fabioz marked this conversation as resolved.
Show resolved Hide resolved

if 'jinja' in args:
self.flask_debug = bool_parser(args['jinja'])

if 'stopOnEntry' in args:
self.stop_on_entry = bool_parser(args['stopOnEntry'])

self.max_exception_stack_frames = int_parser(args.get('maxExceptionStackFrames', 0))


def int_parser(s, default_value=0):
try:
return int(s)
except Exception:
return default_value


def bool_parser(s):
return s in ("True", "true", "1")
return s in ("True", "true", "1", True, 1)


if sys.version_info >= (3,):
Expand All @@ -35,9 +123,6 @@ def unquote(s):
'WAIT_ON_NORMAL_EXIT': bool_parser,
'BREAK_SYSTEMEXIT_ZERO': bool_parser,
'REDIRECT_OUTPUT': bool_parser,
'VERSION': unquote,
'INTERPRETER_OPTIONS': unquote,
'WEB_BROWSER_URL': unquote,
'DJANGO_DEBUG': bool_parser,
'FLASK_DEBUG': bool_parser,
'FIX_FILE_PATH_CASE': bool_parser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
CMD_STEP_INTO_MY_CODE, CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, file_system_encoding,
CMD_STEP_RETURN_MY_CODE, CMD_STEP_RETURN)
from _pydevd_bundle.pydevd_filtering import ExcludeFilter
from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options
from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options, DebugOptions
from _pydevd_bundle.pydevd_net_command import NetCommand
from _pydevd_bundle.pydevd_utils import convert_dap_log_message_to_expression
from _pydevd_bundle.pydevd_constants import (PY_IMPL_NAME, DebugInfoHolder, PY_VERSION_STR,
Expand Down Expand Up @@ -117,7 +117,7 @@ class PyDevJsonCommandProcessor(object):
def __init__(self, from_json):
self.from_json = from_json
self.api = PyDevdAPI()
self._debug_options = {}
self._options = DebugOptions()
self._next_breakpoint_id = partial(next, itertools.count(0))
self._goto_targets_map = IDMap()
self._launch_or_attach_request_done = False
Expand Down Expand Up @@ -325,14 +325,14 @@ def _set_debug_options(self, py_db, args, start_reason):

self.api.set_exclude_filters(py_db, exclude_filters)

self._debug_options = _extract_debug_options(
debug_options = _extract_debug_options(
args.get('options'),
args.get('debugOptions'),
)
self._debug_options['args'] = args
self._options.update_fom_debug_options(debug_options)
self._options.update_from_args(args)

debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False)
self.api.set_use_libraries_filter(py_db, not debug_stdlib)
self.api.set_use_libraries_filter(py_db, not self._options.debug_stdlib)

path_mappings = []
for pathMapping in args.get('pathMappings', []):
Expand All @@ -345,21 +345,21 @@ def _set_debug_options(self, py_db, args, start_reason):
if bool(path_mappings):
pydevd_file_utils.setup_client_server_paths(path_mappings)

if self._debug_options.get('REDIRECT_OUTPUT', False):
if self._options.redirect_output:
py_db.enable_output_redirection(True, True)
else:
py_db.enable_output_redirection(False, False)

self.api.set_show_return_values(py_db, self._debug_options.get('SHOW_RETURN_VALUE', False))
self.api.set_show_return_values(py_db, self._options.show_return_value)

if not self._debug_options.get('BREAK_SYSTEMEXIT_ZERO', False):
if not self._options.break_system_exit_zero:
ignore_system_exit_codes = [0]
if self._debug_options.get('DJANGO_DEBUG', False):
if self._options.django_debug:
ignore_system_exit_codes += [3]

self.api.set_ignore_system_exit_codes(py_db, ignore_system_exit_codes)

if self._debug_options.get('STOP_ON_ENTRY', False) and start_reason == 'launch':
if self._options.stop_on_entry and start_reason == 'launch':
self.api.stop_on_entry()

def _send_process_event(self, py_db, start_method):
Expand Down Expand Up @@ -557,9 +557,9 @@ def on_setbreakpoints_request(self, py_db, request):
suspend_policy = 'ALL'

if not filename.lower().endswith('.py'): # Note: check based on original file, not mapping.
if self._debug_options.get('DJANGO_DEBUG', False):
if self._options.django_debug:
btype = 'django-line'
elif self._debug_options.get('FLASK_DEBUG', False):
elif self._options.flask_debug:
btype = 'jinja2-line'

breakpoints_set = []
Expand Down Expand Up @@ -688,9 +688,9 @@ def on_setexceptionbreakpoints_request(self, py_db, request):

if break_raised or break_uncaught:
btype = None
if self._debug_options.get('DJANGO_DEBUG', False):
if self._options.django_debug:
btype = 'django'
elif self._debug_options.get('FLASK_DEBUG', False):
elif self._options.flask_debug:
btype = 'jinja2'

if btype:
Expand Down Expand Up @@ -723,7 +723,7 @@ def on_exceptioninfo_request(self, py_db, request):
# : :type exception_into_arguments: ExceptionInfoArguments
exception_into_arguments = request.arguments
thread_id = exception_into_arguments.threadId
max_frames = int(self._debug_options['args'].get('maxExceptionStackFrames', 0))
max_frames = self._options.max_exception_stack_frames
self.api.request_exception_info_json(py_db, request, thread_id, max_frames)

def on_scopes_request(self, py_db, request):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

import pydevd
# Some hackery to get the PyDevJsonCommandProcessor which is not exposed.
try:
json_command_processor = pydevd.get_global_debugger().reader.process_net_command_json.__self__
except:
json_command_processor = pydevd.get_global_debugger().reader.process_net_command_json.im_self

print(json_command_processor._options.to_json())

print('TEST SUCEEDED!')
42 changes: 42 additions & 0 deletions src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -3432,6 +3432,48 @@ def update_command_line_args(self, args):
writer.finished_ok = True


@pytest.mark.parametrize('val', [True, False])
def test_debug_options(case_setup, val):
with case_setup.test_file('_debugger_case_debug_options.py') as writer:
json_facade = JsonFacade(writer)
args = dict(
debugStdLib=val,
redirectOutput=True, # Always redirect the output regardless of other values.
showReturnValue=val,
breakOnSystemExitZero=val,
django=val,
flask=val,
stopOnEntry=val,
maxExceptionStackFrames=4 if val else 5,
)
json_facade.write_launch(**args)

json_facade.write_make_initial_run()
if args['stopOnEntry']:
json_facade.wait_for_thread_stopped('entry')
json_facade.write_continue()

output = json_facade.wait_for_json_message(
OutputEvent, lambda msg: msg.body.category == 'stdout' and msg.body.output.startswith('{')and msg.body.output.endswith('}'))

# The values printed are internal values from _pydevd_bundle.pydevd_json_debug_options.DebugOptions,
# not the parameters we passed.
translation = {
'django': 'django_debug',
'flask': 'flask_debug',
'debugStdLib': 'debug_stdlib',
'redirectOutput': 'redirect_output',
'showReturnValue': 'show_return_value',
'breakOnSystemExitZero': 'break_system_exit_zero',
'stopOnEntry': 'stop_on_entry',
'maxExceptionStackFrames': 'max_exception_stack_frames',
}

assert json.loads(output.body.output) == dict((translation[key], val) for key, val in args.items())
json_facade.wait_for_terminated()
writer.finished_ok = True


def test_send_json_message(case_setup):

with case_setup.test_file('_debugger_case_custom_message.py') as writer:
Expand Down
16 changes: 11 additions & 5 deletions src/ptvsd/adapter/ide.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def initialize_request(self, request):
# See https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
# for the sequence of request and events necessary to orchestrate the start.
def _start_message_handler(f):

@components.Component.message_handler
def handle(self, request):
assert request.is_request("launch", "attach")
Expand All @@ -179,11 +180,16 @@ def handle(self, request):
self._initialize_request = None

arguments = request.arguments
if self.launcher and "RedirectOutput" in debug_options:
# The launcher is doing output redirection, so we don't need the
# server to do it, as well.
arguments = dict(arguments)
arguments["debugOptions"] = list(debug_options - {"RedirectOutput"})
if self.launcher:
if "RedirectOutput" in debug_options:
# The launcher is doing output redirection, so we don't need the
# server to do it, as well.
arguments = dict(arguments)
arguments["debugOptions"] = list(debug_options - {"RedirectOutput"})

if arguments.get("redirectOutput"):
arguments = dict(arguments)
del arguments["redirectOutput"]

# pydevd doesn't send "initialized", and responds to the start request
# immediately, without waiting for "configurationDone". If it changes
Expand Down