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

Commit

Permalink
Fix #2026: "Press any key" prompt appears when using Stop Debugging
Browse files Browse the repository at this point in the history
Fix #2040: Code continues running after Stop Debugging

Force-kill debuggee before disconnecting from the debug server if termination was requested or implied.

Don't show the wait prompt if debuggee was explicitly terminated.

Refactor tests for wait prompt to test all possible permutations in a single test.
  • Loading branch information
int19h committed Jan 15, 2020
1 parent 0149646 commit 80bd2ed
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 120 deletions.
13 changes: 12 additions & 1 deletion src/ptvsd/adapter/launchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,20 @@ def exited_event(self, event):

@message_handler
def terminated_event(self, event):
self.ide.channel.send_event("exited", {"exitCode": self.exit_code})
try:
self.ide.channel.send_event("exited", {"exitCode": self.exit_code})
except Exception:
pass
self.channel.close()

def terminate_debuggee(self):
with self.session:
if self.exit_code is None:
try:
self.channel.request("terminate")
except Exception:
pass


def spawn_debuggee(session, start_request, sudo, args, console, console_title):
cmdline = ["sudo"] if sudo else []
Expand Down
24 changes: 14 additions & 10 deletions src/ptvsd/adapter/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,19 @@ def _finalize(self, why, terminate_debuggee):

if self.server:
if self.server.is_connected:
try:
self.server.channel.request(
"disconnect", {"terminateDebuggee": terminate_debuggee}
)
except Exception:
pass
if terminate_debuggee and self.launcher and self.launcher.is_connected:
# If we were specifically asked to terminate the debuggee, and we
# can ask the launcher to kill it, do so instead of disconnecting
# from the server to prevent debuggee from running any more code.
self.launcher.terminate_debuggee()
else:
# Otherwise, let the server handle it the best it can.
try:
self.server.channel.request(
"disconnect", {"terminateDebuggee": terminate_debuggee}
)
except Exception:
pass
self.server.detach_from_session()

if self.launcher and self.launcher.is_connected:
Expand All @@ -240,10 +247,7 @@ def _finalize(self, why, terminate_debuggee):
# Terminate the debuggee process if it's still alive for any reason -
# whether it's because there was no server to handle graceful shutdown,
# or because the server couldn't handle it for some reason.
try:
self.launcher.channel.request("terminate")
except Exception:
pass
self.launcher.terminate_debuggee()

# Wait until the launcher message queue fully drains. There is no timeout
# here, because the final "terminated" event will only come after reading
Expand Down
7 changes: 6 additions & 1 deletion src/ptvsd/launcher/debuggee.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,17 @@ def wait_for_exit():

log.info("{0} exited with code {1}", describe(), code)
output.wait_for_remaining_output()

# Determine whether we should wait or not before sending "exited", so that any
# follow-up "terminate" requests don't affect the predicates.
should_wait = any(pred(code) for pred in wait_on_exit_predicates)

try:
launcher.channel.send_event("exited", {"exitCode": code})
except Exception:
pass

if any(pred(code) for pred in wait_on_exit_predicates):
if should_wait:
_wait_for_user_input()

try:
Expand Down
2 changes: 2 additions & 0 deletions src/ptvsd/launcher/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,11 @@ def property_or_debug_option(prop_name, flag_name):


def terminate_request(request):
del debuggee.wait_on_exit_predicates[:]
request.respond({})
debuggee.kill()


def disconnect():
del debuggee.wait_on_exit_predicates[:]
debuggee.kill()
83 changes: 82 additions & 1 deletion tests/ptvsd/server/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import os
import pytest
import re
import sys
import time

import ptvsd
from ptvsd.common import messaging
from ptvsd.common import log, messaging
from tests import debug, test_data
from tests.debug import runners, targets
from tests.patterns import some
Expand Down Expand Up @@ -82,3 +84,82 @@ def code_to_debug():
pass

assert "ok" in session.output("stdout")


@pytest.mark.parametrize(
# Can't test "internalConsole", because we don't have debuggee stdin to press the key.
"run",
[runners.launch["integratedTerminal"], runners.launch["externalTerminal"]],
)
@pytest.mark.parametrize("exit_code", [0, 42])
@pytest.mark.parametrize("wait_on_normal", ["", "wait_on_normal"])
@pytest.mark.parametrize("wait_on_abnormal", ["", "wait_on_abnormal"])
@pytest.mark.parametrize(
"process_lifetime",
["run_to_completion", "request_terminate", "request_disconnect", "drop_connection"],
)
def test_wait_on_exit(
pyfile, target, run, exit_code, wait_on_normal, wait_on_abnormal, process_lifetime
):
@pyfile
def code_to_debug():
from debug_me import ptvsd
import sys

ptvsd.break_into_debugger()
print() # line on which it'll actually break
sys.exit(int(sys.argv[1]))

expect_wait = (process_lifetime == "run_to_completion") and (
(wait_on_normal and exit_code == 0) or (wait_on_abnormal and exit_code != 0)
)
if expect_wait and sys.version_info < (3, 0):
pytest.skip("https://github.com/microsoft/ptvsd/issues/1819")

with debug.Session() as session:
session.expected_exit_code = (
None if process_lifetime == "drop_connection" else some.int
)
if wait_on_normal:
session.config["waitOnNormalExit"] = True
if wait_on_abnormal:
session.config["waitOnAbnormalExit"] = True

with run(session, target(code_to_debug, args=[str(exit_code)])):
pass

session.wait_for_stop()
if process_lifetime == "run_to_completion":
session.request_continue()
elif process_lifetime == "request_terminate":
session.request("terminate", freeze=False)
elif process_lifetime == "request_disconnect":
session.disconnect()
elif process_lifetime == "drop_connection":
session.disconnect(force=True)
else:
pytest.fail(process_lifetime)

def has_waited():
lines = session.captured_output.stdout_lines()
return any(
s == some.bytes.matching(br"Press .* to continue . . .\s*")
for s in lines
)

if expect_wait:
log.info("Waiting for keypress prompt...")
while not has_waited():
time.sleep(0.1)

# Wait a bit to simulate the user reaction time, and test that debuggee does
# not exit all by itself.
time.sleep(1)
assert session.debuggee.poll() is None

log.info("Simulating keypress.")
session.debuggee.stdin.write(b"\n")

else:
session.debuggee.wait()
assert not has_waited()
107 changes: 0 additions & 107 deletions tests/ptvsd/server/test_start_stop.py

This file was deleted.

0 comments on commit 80bd2ed

Please sign in to comment.