303 lines
10 KiB
Python
303 lines
10 KiB
Python
"""
|
|
Test lldb-dap runInTerminal reverse request
|
|
"""
|
|
|
|
from contextlib import contextmanager
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import line_number
|
|
import lldbdap_testcase
|
|
import os
|
|
import subprocess
|
|
import json
|
|
|
|
|
|
@contextmanager
|
|
def fifo(*args, **kwargs):
|
|
if sys.platform == "win32":
|
|
import ctypes
|
|
|
|
comm_file = r"\\.\pipe\lldb-dap-run-in-terminal-comm"
|
|
PIPE_ACCESS_DUPLEX = 0x00000003
|
|
PIPE_TYPE_MESSAGE = 0x00000004
|
|
PIPE_READMODE_MESSAGE = 0x00000002
|
|
PIPE_WAIT = 0x00000000
|
|
PIPE_UNLIMITED_INSTANCES = 255
|
|
kernel32 = ctypes.windll.kernel32
|
|
|
|
pipe = kernel32.CreateNamedPipeW(
|
|
comm_file,
|
|
PIPE_ACCESS_DUPLEX,
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
4096,
|
|
4096,
|
|
0,
|
|
None,
|
|
)
|
|
else:
|
|
comm_file = os.path.join(kwargs["directory"], "comm-file")
|
|
pipe = None
|
|
os.mkfifo(comm_file)
|
|
|
|
try:
|
|
yield comm_file, pipe
|
|
finally:
|
|
if pipe is not None:
|
|
kernel32.DisconnectNamedPipe(pipe)
|
|
kernel32.CloseHandle(pipe)
|
|
|
|
|
|
def read_pipe_message(pipe):
|
|
import ctypes
|
|
|
|
ERROR_MORE_DATA = 234
|
|
kernel32 = ctypes.windll.kernel32
|
|
buffer = b""
|
|
while True:
|
|
chunk = ctypes.create_string_buffer(4096)
|
|
bytes_read = ctypes.wintypes.DWORD()
|
|
success = kernel32.ReadFile(pipe, chunk, 4096, ctypes.byref(bytes_read), None)
|
|
buffer += chunk.raw[: bytes_read.value]
|
|
if success:
|
|
break
|
|
if ctypes.GetLastError() != ERROR_MORE_DATA:
|
|
break
|
|
return buffer.decode()
|
|
|
|
|
|
@skipIfBuildType(["debug"])
|
|
class TestDAP_runInTerminal(lldbdap_testcase.DAPTestCaseBase):
|
|
SHARED_BUILD_TESTCASE = False
|
|
|
|
def read_pid_message(self, fifo_file, pipe):
|
|
if sys.platform == "win32":
|
|
import ctypes
|
|
|
|
ctypes.windll.kernel32.ConnectNamedPipe(pipe, None)
|
|
self.assertIn("pid", read_pipe_message(pipe))
|
|
else:
|
|
with open(fifo_file, "r") as file:
|
|
self.assertIn("pid", file.readline())
|
|
|
|
@staticmethod
|
|
def send_did_attach_message(fifo_file, pipe=None):
|
|
message = json.dumps({"kind": "didAttach"}) + "\n"
|
|
if sys.platform == "win32":
|
|
import ctypes
|
|
|
|
kernel32 = ctypes.windll.kernel32
|
|
bytes_written = ctypes.wintypes.DWORD()
|
|
kernel32.ConnectNamedPipe(pipe, None)
|
|
kernel32.WriteFile(
|
|
pipe, message.encode(), len(message), ctypes.byref(bytes_written), None
|
|
)
|
|
else:
|
|
with open(fifo_file, "w") as file:
|
|
file.write(message)
|
|
|
|
@staticmethod
|
|
def read_error_message(fifo_file, pipe=None):
|
|
if sys.platform == "win32":
|
|
import ctypes
|
|
|
|
ctypes.windll.kernel32.ConnectNamedPipe(pipe, None)
|
|
return read_pipe_message(pipe)
|
|
else:
|
|
with open(fifo_file, "r") as file:
|
|
return file.readline()
|
|
|
|
@skipIfAsan
|
|
def test_runInTerminal(self):
|
|
"""
|
|
Tests the "runInTerminal" reverse request. It makes sure that the IDE can
|
|
launch the inferior with the correct environment variables and arguments.
|
|
"""
|
|
program = self.getBuildArtifact("a.out")
|
|
source = "main.c"
|
|
self.build_and_launch(
|
|
program, console="integratedTerminal", args=["foobar"], env=["FOO=bar"]
|
|
)
|
|
self.dap_server.wait_for_initialized()
|
|
|
|
self.assertEqual(
|
|
len(self.dap_server.reverse_requests),
|
|
1,
|
|
"make sure we got a reverse request",
|
|
)
|
|
|
|
request = self.dap_server.reverse_requests[0]
|
|
self.assertIn(self.lldbDAPExec, request["arguments"]["args"])
|
|
self.assertIn(program, request["arguments"]["args"])
|
|
self.assertIn("foobar", request["arguments"]["args"])
|
|
self.assertIn("FOO", request["arguments"]["env"])
|
|
|
|
breakpoint_line = line_number(source, "// breakpoint")
|
|
|
|
self.set_source_breakpoints(source, [breakpoint_line])
|
|
self.continue_to_next_stop()
|
|
|
|
# We verify we actually stopped inside the loop
|
|
counter = int(self.dap_server.get_local_variable_value("counter"))
|
|
self.assertEqual(counter, 1)
|
|
|
|
# We verify we were able to set the launch arguments
|
|
argc = int(self.dap_server.get_local_variable_value("argc"))
|
|
self.assertEqual(argc, 2)
|
|
|
|
argv1 = self.dap_server.request_evaluate("argv[1]")["body"]["result"]
|
|
self.assertIn("foobar", argv1)
|
|
|
|
# We verify we were able to set the environment
|
|
env = self.dap_server.request_evaluate("foo")["body"]["result"]
|
|
self.assertIn("bar", env)
|
|
|
|
self.continue_to_exit()
|
|
|
|
@skipIfAsan
|
|
def test_runInTerminalWithObjectEnv(self):
|
|
"""
|
|
Tests the "runInTerminal" reverse request. It makes sure that the IDE can
|
|
launch the inferior with the correct environment variables using an object.
|
|
"""
|
|
program = self.getBuildArtifact("a.out")
|
|
self.build_and_launch(program, console="integratedTerminal", env={"FOO": "BAR"})
|
|
self.dap_server.wait_for_initialized()
|
|
|
|
self.assertEqual(
|
|
len(self.dap_server.reverse_requests),
|
|
1,
|
|
"make sure we got a reverse request",
|
|
)
|
|
|
|
request = self.dap_server.reverse_requests[0]
|
|
request_envs = request["arguments"]["env"]
|
|
|
|
self.assertIn("FOO", request_envs)
|
|
self.assertEqual("BAR", request_envs["FOO"])
|
|
|
|
self.continue_to_exit()
|
|
|
|
def test_runInTerminalInvalidTarget(self):
|
|
self.build_and_create_debug_adapter()
|
|
response = self.launch_and_configurationDone(
|
|
"INVALIDPROGRAM",
|
|
console="integratedTerminal",
|
|
args=["foobar"],
|
|
env=["FOO=bar"],
|
|
)
|
|
self.assertFalse(response["success"])
|
|
self.assertIn(
|
|
"'INVALIDPROGRAM' does not exist",
|
|
response["body"]["error"]["format"],
|
|
)
|
|
|
|
def test_missingArgInRunInTerminalLauncher(self):
|
|
proc = subprocess.run(
|
|
[self.lldbDAPExec, "--launch-target", "INVALIDPROGRAM"],
|
|
capture_output=True,
|
|
universal_newlines=True,
|
|
)
|
|
self.assertNotEqual(proc.returncode, 0)
|
|
self.assertIn(
|
|
'"--launch-target" requires "--comm-file" to be specified', proc.stderr
|
|
)
|
|
|
|
def test_FakeAttachedRunInTerminalLauncherWithInvalidProgram(self):
|
|
with fifo(directory=self.getBuildDir()) as (comm_file, pipe):
|
|
proc = subprocess.Popen(
|
|
[
|
|
self.lldbDAPExec,
|
|
"--comm-file",
|
|
comm_file,
|
|
"--launch-target",
|
|
"INVALIDPROGRAM",
|
|
],
|
|
universal_newlines=True,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
if sys.platform == "win32":
|
|
_, stderr = proc.communicate()
|
|
self.assertIn("Failed to launch target process", stderr)
|
|
else:
|
|
self.read_pid_message(comm_file, pipe)
|
|
self.send_did_attach_message(comm_file, pipe)
|
|
self.assertIn(
|
|
"No such file or directory",
|
|
self.read_error_message(comm_file, pipe),
|
|
)
|
|
|
|
_, stderr = proc.communicate()
|
|
self.assertIn("No such file or directory", stderr)
|
|
|
|
def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self):
|
|
with fifo(directory=self.getBuildDir()) as (comm_file, pipe):
|
|
proc = subprocess.Popen(
|
|
[
|
|
self.lldbDAPExec,
|
|
"--comm-file",
|
|
comm_file,
|
|
"--launch-target",
|
|
"echo",
|
|
"foo",
|
|
],
|
|
universal_newlines=True,
|
|
stdout=subprocess.PIPE,
|
|
)
|
|
|
|
self.read_pid_message(comm_file, pipe)
|
|
self.send_did_attach_message(comm_file, pipe)
|
|
|
|
stdout, _ = proc.communicate()
|
|
|
|
self.assertIn("foo", stdout)
|
|
|
|
def test_FakeAttachedRunInTerminalLauncherAndCheckEnvironment(self):
|
|
with fifo(directory=self.getBuildDir()) as (comm_file, pipe):
|
|
proc = subprocess.Popen(
|
|
[self.lldbDAPExec, "--comm-file", comm_file, "--launch-target", "env"],
|
|
universal_newlines=True,
|
|
stdout=subprocess.PIPE,
|
|
env={**os.environ, "FOO": "BAR"},
|
|
)
|
|
|
|
self.read_pid_message(comm_file, pipe)
|
|
self.send_did_attach_message(comm_file, pipe)
|
|
|
|
stdout, _ = proc.communicate()
|
|
|
|
self.assertIn("FOO=BAR", stdout)
|
|
|
|
def test_NonAttachedRunInTerminalLauncher(self):
|
|
with fifo(directory=self.getBuildDir()) as (comm_file, pipe):
|
|
proc = subprocess.Popen(
|
|
[
|
|
self.lldbDAPExec,
|
|
"--comm-file",
|
|
comm_file,
|
|
"--launch-target",
|
|
"echo",
|
|
"foo",
|
|
],
|
|
universal_newlines=True,
|
|
stderr=subprocess.PIPE,
|
|
env={**os.environ, "LLDB_DAP_RIT_TIMEOUT_IN_MS": "1000"},
|
|
)
|
|
|
|
self.read_pid_message(comm_file, pipe)
|
|
|
|
_, stderr = proc.communicate()
|
|
|
|
self.assertIn("Timed out trying to get messages from the debug adapter", stderr)
|
|
|
|
def test_client_missing_runInTerminal_feature(self):
|
|
program = self.getBuildArtifact("a.out")
|
|
self.build_and_create_debug_adapter()
|
|
response = self.launch_and_configurationDone(
|
|
program,
|
|
console="integratedTerminal",
|
|
client_features={"supportsRunInTerminalRequest": False},
|
|
)
|
|
self.assertFalse(response["success"], f"Expected failure got {response!r}")
|
|
error_message = response["body"]["error"]["format"]
|
|
self.assertIn("Client does not support RunInTerminal.", error_message)
|