mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
* Changes from pushing to pydevd * Update some time outs to get flakey tests to pass * Fix string failure * String case backwards. Fixup test in pydevd * Using callstack for exception check not sufficient * Too restrictive on pydevd matching * Try somethign better than just checking 'pydev' * Retry a flakey test * Disable flakey tests * Another flakey test * Increase timeout for attach * Try upping timeout * Up watchdog timeout * Up some more timeouts * Try delaying shutdown of test apps * Don't output extra things that tests don't expect * Fix output differences in 3.9? Not sure what that's about * Fixup line differences in 3.9 with extra sleep * Fix linter errors * Fix breakpoint bugs
392 lines
14 KiB
Python
392 lines
14 KiB
Python
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
# Licensed under the MIT License. See LICENSE in the project root
|
|
# for license information.
|
|
|
|
import pytest
|
|
import sys
|
|
|
|
from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER
|
|
from tests import debug
|
|
from tests.debug import runners, targets
|
|
from tests.patterns import some
|
|
from tests.timeline import Event
|
|
|
|
str_matching_ArithmeticError = some.str.matching(r"(.+\.)?ArithmeticError")
|
|
|
|
|
|
@pytest.mark.parametrize("raised", ["raised", ""])
|
|
@pytest.mark.parametrize("uncaught", ["uncaught", ""])
|
|
def test_vsc_exception_options_raise_with_except(pyfile, target, run, raised, uncaught):
|
|
@pyfile
|
|
def code_to_debug():
|
|
import debuggee
|
|
|
|
debuggee.setup()
|
|
|
|
def raise_with_except():
|
|
try:
|
|
raise ArithmeticError("bad code") # @exc
|
|
except Exception:
|
|
pass
|
|
|
|
raise_with_except()
|
|
|
|
with debug.Session() as session:
|
|
session.expected_exit_code = some.int
|
|
with run(session, target(code_to_debug)):
|
|
session.request(
|
|
"setExceptionBreakpoints", {"filters": list({raised, uncaught} - {""})}
|
|
)
|
|
|
|
expected = some.dict.containing(
|
|
{
|
|
"exceptionId": str_matching_ArithmeticError,
|
|
"description": "bad code",
|
|
"breakMode": "always" if raised else "unhandled",
|
|
"details": some.dict.containing(
|
|
{
|
|
"typeName": str_matching_ArithmeticError,
|
|
"message": "bad code",
|
|
"source": some.path(code_to_debug),
|
|
}
|
|
),
|
|
}
|
|
)
|
|
|
|
if raised:
|
|
stop = session.wait_for_stop(
|
|
"exception",
|
|
expected_text=str_matching_ArithmeticError,
|
|
expected_description="bad code",
|
|
expected_frames=[some.dap.frame(code_to_debug, line="exc")],
|
|
)
|
|
exc_info = session.request("exceptionInfo", {"threadId": stop.thread_id})
|
|
assert exc_info == expected
|
|
session.request_continue()
|
|
|
|
if uncaught:
|
|
# Exception is caught by try..except, so there should be no stop.
|
|
pass
|
|
|
|
|
|
@pytest.mark.parametrize("raised", ["raised", ""])
|
|
@pytest.mark.parametrize("uncaught", ["uncaught", ""])
|
|
def test_vsc_exception_options_raise_without_except(
|
|
pyfile, target, run, raised, uncaught
|
|
):
|
|
@pyfile
|
|
def code_to_debug():
|
|
import debuggee
|
|
|
|
debuggee.setup()
|
|
|
|
def raise_without_except():
|
|
raise ArithmeticError("bad code") # @exc
|
|
|
|
raise_without_except()
|
|
|
|
with debug.Session() as session:
|
|
session.ignore_unobserved.append(Event("stopped"))
|
|
session.expected_exit_code = some.int
|
|
|
|
with run(session, target(code_to_debug)):
|
|
session.request(
|
|
"setExceptionBreakpoints", {"filters": list({raised, uncaught} - {""})}
|
|
)
|
|
|
|
expected_exc_info = some.dict.containing(
|
|
{
|
|
"exceptionId": str_matching_ArithmeticError,
|
|
"description": "bad code",
|
|
"breakMode": "always" if raised else "unhandled",
|
|
"details": some.dict.containing(
|
|
{
|
|
"typeName": str_matching_ArithmeticError,
|
|
"message": "bad code",
|
|
"source": some.path(code_to_debug),
|
|
}
|
|
),
|
|
}
|
|
)
|
|
|
|
if raised:
|
|
stop = session.wait_for_stop(
|
|
"exception", expected_frames=[some.dap.frame(code_to_debug, line="exc")]
|
|
)
|
|
exc_info = session.request("exceptionInfo", {"threadId": stop.thread_id})
|
|
assert expected_exc_info == exc_info
|
|
session.request_continue()
|
|
|
|
# NOTE: debugger stops at each frame if raised and is uncaught
|
|
# This behavior can be changed by updating 'notify_on_handled_exceptions'
|
|
# setting we send to pydevd to notify only once. In our test code, we have
|
|
# two frames, hence two stops.
|
|
session.wait_for_stop("exception")
|
|
session.request_continue()
|
|
|
|
if uncaught:
|
|
stop = session.wait_for_stop(
|
|
"exception", expected_frames=[some.dap.frame(code_to_debug, line="exc")]
|
|
)
|
|
expected_exc_info = some.dict.containing(
|
|
{
|
|
"exceptionId": str_matching_ArithmeticError,
|
|
"description": "bad code",
|
|
"breakMode": "unhandled", # Only difference from previous expected is breakMode.
|
|
"details": some.dict.containing(
|
|
{
|
|
"typeName": str_matching_ArithmeticError,
|
|
"message": "bad code",
|
|
"source": some.path(code_to_debug),
|
|
}
|
|
),
|
|
}
|
|
)
|
|
exc_info = session.request("exceptionInfo", {"threadId": stop.thread_id})
|
|
assert expected_exc_info == exc_info
|
|
session.request_continue()
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "darwin",
|
|
reason="https://github.com/microsoft/ptvsd/issues/1988",
|
|
)
|
|
@pytest.mark.flaky(retries=2, delay=1)
|
|
@pytest.mark.parametrize("target", targets.all_named)
|
|
@pytest.mark.parametrize("run", runners.all)
|
|
@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, target, run, raised, uncaught, zero, exit_code):
|
|
@pyfile
|
|
def code_to_debug():
|
|
import debuggee
|
|
import sys
|
|
|
|
debuggee.setup()
|
|
exit_code = eval(sys.argv[1])
|
|
print("sys.exit(%r)" % (exit_code,))
|
|
try:
|
|
sys.exit(exit_code) # @handled
|
|
except SystemExit:
|
|
pass
|
|
sys.exit(exit_code) # @unhandled
|
|
|
|
filters = []
|
|
if raised:
|
|
filters += ["raised"]
|
|
if uncaught:
|
|
filters += ["uncaught"]
|
|
|
|
with debug.Session() as session:
|
|
session.expected_exit_code = some.int
|
|
session.config["breakOnSystemExitZero"] = bool(zero)
|
|
|
|
with run(session, target(code_to_debug, args=[repr(exit_code)])):
|
|
session.request("setExceptionBreakpoints", {"filters": filters})
|
|
|
|
# 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):
|
|
session.wait_for_stop(
|
|
"exception",
|
|
expected_frames=[some.dap.frame(code_to_debug, line="handled")],
|
|
)
|
|
session.request_continue()
|
|
|
|
session.wait_for_stop(
|
|
"exception",
|
|
expected_frames=[some.dap.frame(code_to_debug, line="unhandled")],
|
|
)
|
|
session.request_continue()
|
|
|
|
# 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):
|
|
session.wait_for_stop(
|
|
"exception",
|
|
expected_frames=[some.dap.frame(code_to_debug, line="unhandled")],
|
|
)
|
|
session.request_continue()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"break_mode", ["always", "never", "unhandled", "userUnhandled"]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"exceptions",
|
|
[
|
|
["RuntimeError"],
|
|
["AssertionError"],
|
|
["RuntimeError", "AssertionError"],
|
|
[], # Add the whole Python Exceptions category.
|
|
],
|
|
)
|
|
def test_raise_exception_options(pyfile, target, run, exceptions, break_mode):
|
|
if break_mode in ("never", "unhandled", "userUnhandled"):
|
|
expect_exceptions = []
|
|
if break_mode != "never" and (not exceptions or "AssertionError" in exceptions):
|
|
# Only AssertionError is raised in this use-case.
|
|
expect_exceptions = ["AssertionError"]
|
|
|
|
@pyfile
|
|
def code_to_debug():
|
|
import debuggee
|
|
|
|
debuggee.setup()
|
|
raise AssertionError() # @AssertionError
|
|
|
|
else:
|
|
expect_exceptions = exceptions[:]
|
|
if not expect_exceptions:
|
|
# Deal with the Python Exceptions category
|
|
expect_exceptions = ["RuntimeError", "AssertionError", "IndexError"]
|
|
|
|
@pyfile
|
|
def code_to_debug():
|
|
import debuggee
|
|
|
|
debuggee.setup()
|
|
|
|
try:
|
|
raise RuntimeError() # @RuntimeError
|
|
except RuntimeError:
|
|
pass
|
|
try:
|
|
raise AssertionError() # @AssertionError
|
|
except AssertionError:
|
|
pass
|
|
try:
|
|
raise IndexError() # @IndexError
|
|
except IndexError:
|
|
pass
|
|
|
|
with debug.Session() as session:
|
|
session.ignore_unobserved.append(Event("stopped"))
|
|
session.expected_exit_code = some.int
|
|
|
|
with run(session, target(code_to_debug)):
|
|
path = [{"names": ["Python Exceptions"]}]
|
|
if exceptions:
|
|
path.append({"names": exceptions})
|
|
session.request(
|
|
"setExceptionBreakpoints",
|
|
{
|
|
"filters": [], # Unused when exceptionOptions is passed.
|
|
"exceptionOptions": [
|
|
{
|
|
"path": path,
|
|
"breakMode": break_mode, # Can be "never", "always", "unhandled", "userUnhandled"
|
|
}
|
|
],
|
|
},
|
|
)
|
|
|
|
for expected_exception in expect_exceptions:
|
|
session.wait_for_stop(
|
|
"exception",
|
|
expected_frames=[
|
|
some.dap.frame(code_to_debug, line=expected_exception)
|
|
],
|
|
)
|
|
session.request_continue()
|
|
|
|
|
|
@pytest.mark.parametrize("target", targets.all_named)
|
|
@pytest.mark.parametrize("run", runners.all)
|
|
@pytest.mark.parametrize("exit_code", [0, 3])
|
|
@pytest.mark.parametrize("break_on_system_exit_zero", ["break_on_system_exit_zero", ""])
|
|
@pytest.mark.parametrize("django", ["django", ""])
|
|
@pytest.mark.skipif(sys.platform == 'win32' and IS_PY312_OR_GREATER, reason="Flakey test")
|
|
def test_success_exitcodes(
|
|
pyfile, target, run, exit_code, break_on_system_exit_zero, django
|
|
):
|
|
@pyfile
|
|
def code_to_debug():
|
|
import debuggee
|
|
import sys
|
|
|
|
debuggee.setup()
|
|
exit_code = eval(sys.argv[1])
|
|
print("sys.exit(%r)" % (exit_code,))
|
|
sys.exit(exit_code)
|
|
|
|
with debug.Session() as session:
|
|
session.expected_exit_code = some.int
|
|
session.config["breakOnSystemExitZero"] = bool(break_on_system_exit_zero)
|
|
session.config["django"] = bool(django)
|
|
|
|
with run(session, target(code_to_debug, args=[repr(exit_code)])):
|
|
session.request("setExceptionBreakpoints", {"filters": ["uncaught"]})
|
|
|
|
if break_on_system_exit_zero or (not django and exit_code == 3):
|
|
# If "breakOnSystemExitZero" was specified, we should always break.
|
|
# Otherwise, we should not break if the exit code indicates successful
|
|
# exit. 0 always indicates success, and 3 indicates failure only if
|
|
# Django debugging wasn't enabled.
|
|
session.wait_for_stop("exception")
|
|
session.request_continue()
|
|
|
|
|
|
@pytest.mark.parametrize("max_frames", ["default", "all", 10])
|
|
def test_exception_stack(pyfile, target, run, max_frames):
|
|
@pyfile
|
|
def code_to_debug():
|
|
import debuggee
|
|
|
|
debuggee.setup()
|
|
|
|
def do_something(n):
|
|
if n <= 0:
|
|
raise ArithmeticError("bad code") # @unhandled
|
|
do_something2(n - 1)
|
|
|
|
def do_something2(n):
|
|
do_something(n - 1)
|
|
|
|
do_something(100)
|
|
|
|
with debug.Session() as session:
|
|
session.expected_exit_code = some.int
|
|
|
|
max_frames, (min_expected_lines, max_expected_lines) = {
|
|
"all": (0, (100, 308)),
|
|
"default": (None, (100, 308)),
|
|
10: (10, (10, 32)),
|
|
}[max_frames]
|
|
if max_frames is not None:
|
|
session.config["maxExceptionStackFrames"] = max_frames
|
|
|
|
with run(session, target(code_to_debug)):
|
|
session.request("setExceptionBreakpoints", {"filters": ["uncaught"]})
|
|
|
|
stop = session.wait_for_stop(
|
|
"exception",
|
|
expected_frames=[some.dap.frame(code_to_debug, line="unhandled")],
|
|
)
|
|
exc_info = session.request("exceptionInfo", {"threadId": stop.thread_id})
|
|
expected_exc_info = some.dict.containing(
|
|
{
|
|
"exceptionId": str_matching_ArithmeticError,
|
|
"description": "bad code",
|
|
"breakMode": "unhandled",
|
|
"details": some.dict.containing(
|
|
{
|
|
"typeName": str_matching_ArithmeticError,
|
|
"message": "bad code",
|
|
"source": some.path(code_to_debug),
|
|
}
|
|
),
|
|
}
|
|
)
|
|
assert expected_exc_info == exc_info
|
|
stack_str = exc_info["details"]["stackTrace"]
|
|
stack_line_count = len(stack_str.split("\n"))
|
|
assert min_expected_lines <= stack_line_count <= max_expected_lines
|
|
|
|
session.request_continue()
|