mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Fix #841: BreakOnSystemExitZero debug option is not respected
This commit is contained in:
parent
32a2527291
commit
a8309a3742
2 changed files with 87 additions and 1 deletions
|
|
@ -819,6 +819,7 @@ def bool_parser(str):
|
|||
DEBUG_OPTIONS_PARSER = {
|
||||
'WAIT_ON_ABNORMAL_EXIT': bool_parser,
|
||||
'WAIT_ON_NORMAL_EXIT': bool_parser,
|
||||
'BREAK_SYSTEMEXIT_ZERO': bool_parser,
|
||||
'REDIRECT_OUTPUT': bool_parser,
|
||||
'VERSION': unquote,
|
||||
'INTERPRETER_OPTIONS': unquote,
|
||||
|
|
@ -837,6 +838,7 @@ DEBUG_OPTIONS_BY_FLAG = {
|
|||
'RedirectOutput': 'REDIRECT_OUTPUT=True',
|
||||
'WaitOnNormalExit': 'WAIT_ON_NORMAL_EXIT=True',
|
||||
'WaitOnAbnormalExit': 'WAIT_ON_ABNORMAL_EXIT=True',
|
||||
'BreakOnSystemExitZero': 'BREAK_SYSTEMEXIT_ZERO=True',
|
||||
'Django': 'DJANGO_DEBUG=True',
|
||||
'Flask': 'FLASK_DEBUG=True',
|
||||
'Jinja': 'FLASK_DEBUG=True',
|
||||
|
|
@ -890,6 +892,7 @@ def _parse_debug_options(opts):
|
|||
"""Debug options are semicolon separated key=value pairs
|
||||
WAIT_ON_ABNORMAL_EXIT=True|False
|
||||
WAIT_ON_NORMAL_EXIT=True|False
|
||||
BREAK_SYSTEMEXIT_ZERO=True|False
|
||||
REDIRECT_OUTPUT=True|False
|
||||
VERSION=string
|
||||
INTERPRETER_OPTIONS=string
|
||||
|
|
@ -2570,8 +2573,23 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
exc_name, exc_desc, _, _ = \
|
||||
self._parse_exception_details(resp_args, include_stack=False)
|
||||
|
||||
extra['allThreadsStopped'] = True
|
||||
if not self.debug_options.get('BREAK_SYSTEMEXIT_ZERO', False):
|
||||
# SystemExit is qualified on Python 2, and unqualified on Python 3
|
||||
sysexit_exc_name = 'exceptions.SystemExit' if sys.version_info < (3,) else 'SystemExit'
|
||||
if exc_name == sysexit_exc_name:
|
||||
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.
|
||||
ignore = False
|
||||
else:
|
||||
ignore = (exit_code == 0)
|
||||
if ignore:
|
||||
self._resume_all_threads()
|
||||
return
|
||||
|
||||
extra['allThreadsStopped'] = True
|
||||
self.send_event(
|
||||
'stopped',
|
||||
reason=reason,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from __future__ import print_function, with_statement, absolute_import
|
|||
|
||||
import pytest
|
||||
|
||||
from tests.helpers import print
|
||||
from tests.helpers.session import DebugSession
|
||||
from tests.helpers.timeline import Event
|
||||
from tests.helpers.pattern import ANY, Path
|
||||
|
|
@ -138,3 +139,70 @@ def test_vsc_exception_options_raise_without_except(pyfile, run_as, start_method
|
|||
session.send_request('continue').wait_for_response(freeze=False)
|
||||
|
||||
session.wait_for_exit()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('raised', ['raised', ''])
|
||||
@pytest.mark.parametrize('uncaught', ['uncaught', ''])
|
||||
@pytest.mark.parametrize('zero', ['zero', ''])
|
||||
@pytest.mark.parametrize('exit_code', [0, 1, 'nan'])
|
||||
def test_systemexit(pyfile, run_as, start_method, raised, uncaught, zero, exit_code):
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
from dbgimporter import import_and_enable_debugger
|
||||
import_and_enable_debugger()
|
||||
import sys
|
||||
exit_code = eval(sys.argv[1])
|
||||
print('sys.exit(%r)' % (exit_code,))
|
||||
try:
|
||||
sys.exit(exit_code)
|
||||
except SystemExit:
|
||||
pass
|
||||
sys.exit(exit_code)
|
||||
|
||||
filters = []
|
||||
if raised:
|
||||
filters += ['raised']
|
||||
if uncaught:
|
||||
filters += ['uncaught']
|
||||
|
||||
with DebugSession() as session:
|
||||
session.program_args = [repr(exit_code)]
|
||||
if zero:
|
||||
session.debug_options += ['BreakOnSystemExitZero']
|
||||
session.initialize(
|
||||
target=(run_as, code_to_debug),
|
||||
start_method=start_method,
|
||||
ignore_unobserved=[Event('continued'), Event('stopped')],
|
||||
expected_returncode=ANY.int,
|
||||
)
|
||||
session.send_request('setExceptionBreakpoints', {
|
||||
'filters': filters
|
||||
}).wait_for_response()
|
||||
session.start_debugging()
|
||||
|
||||
# When breaking on raised exceptions, we'll stop on both lines,
|
||||
# unless it's SystemExit(0) and we asked to ignore that.
|
||||
if raised and (zero or exit_code != 0):
|
||||
hit = session.wait_for_thread_stopped(reason='exception')
|
||||
frames = hit.stacktrace.body['stackFrames']
|
||||
assert frames[0]['line'] == 7
|
||||
session.send_request('continue').wait_for_response(freeze=False)
|
||||
|
||||
hit = session.wait_for_thread_stopped(reason='exception')
|
||||
frames = hit.stacktrace.body['stackFrames']
|
||||
assert frames[0]['line'] == 10
|
||||
session.send_request('continue').wait_for_response(freeze=False)
|
||||
|
||||
# When breaking on uncaught exceptions, we'll stop on the second line,
|
||||
# unless it's SystemExit(0) and we asked to ignore that.
|
||||
# Note that if both raised and uncaught filters are set, there will be
|
||||
# two stop for the second line - one for exception being raised, and one
|
||||
# for it unwinding the stack without finding a handler. The block above
|
||||
# takes care of the first stop, so here we just take care of the second.
|
||||
if uncaught and (zero or exit_code != 0):
|
||||
hit = session.wait_for_thread_stopped(reason='exception')
|
||||
frames = hit.stacktrace.body['stackFrames']
|
||||
assert frames[0]['line'] == 10
|
||||
session.send_request('continue').wait_for_response(freeze=False)
|
||||
|
||||
session.wait_for_exit()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue