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

Commit

Permalink
Accept new style properties for debugger options. Fixes #2001 (#2005)
Browse files Browse the repository at this point in the history
* Accept new style properties for debugger options. Fixes #2001

* Don't pass redirectOutput to pydevd in launch.

* Also accept 'jinja' to enable flask debugging.
  • Loading branch information
fabioz authored and karthiknadig committed Dec 25, 2019
1 parent a8ffe4a commit 17d3056
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 26 deletions.
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'])

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

0 comments on commit 17d3056

Please sign in to comment.