Fixes an issue where attaching by program would fail if the program name
was a partial name (e.g. "foobar" instead of "/path/to/foobar").
We failed to create the target which caused the attach to fail. Now we
fallback to the dummy target and update to the real target after the
attach completes.
Here is an example launch configuration that fail:
```
{
"type": "lldb-dap",
"name": "Attach (wait)",
"request": "attach",
"program": "foobar",
"waitFor": true
},
```
161 lines
5.4 KiB
Python
161 lines
5.4 KiB
Python
"""
|
|
Test lldb-dap attach request
|
|
"""
|
|
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
import lldbdap_testcase
|
|
import os
|
|
import subprocess
|
|
import threading
|
|
import time
|
|
|
|
|
|
# Often fails on Arm Linux, but not specifically because it's Arm, something in
|
|
# process scheduling can cause a massive (minutes) delay during this test.
|
|
@skipIf(oslist=["linux"], archs=["arm$"])
|
|
class TestDAP_attach(lldbdap_testcase.DAPTestCaseBase):
|
|
SHARED_BUILD_TESTCASE = False
|
|
|
|
def spawn(self, program, args=None):
|
|
return self.spawnSubprocess(
|
|
executable=program,
|
|
args=args,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
universal_newlines=True,
|
|
)
|
|
|
|
def spawn_and_wait(self, program, delay):
|
|
time.sleep(delay)
|
|
proc = self.spawn(program=program)
|
|
start_time = time.time()
|
|
# Wait for either the process to exit or the event to be set.
|
|
while proc.poll() is None and not self.spawn_event.is_set():
|
|
elapsed = time.time() - start_time
|
|
if elapsed >= self.DEFAULT_TIMEOUT:
|
|
break
|
|
time.sleep(0.1)
|
|
proc.kill()
|
|
proc.wait()
|
|
|
|
def continue_and_verify_pid(self):
|
|
self.do_continue()
|
|
proc = self.lastSubprocess
|
|
if proc is None:
|
|
self.fail(f"lastSubprocess is None")
|
|
out, _ = proc.communicate("foo")
|
|
self.assertIn(f"pid = {proc.pid}", out)
|
|
|
|
def test_by_pid(self):
|
|
"""
|
|
Tests attaching to a process by process ID.
|
|
"""
|
|
program = self.build_and_create_debug_adapter_for_attach()
|
|
proc = self.spawn(program=program)
|
|
self.attach(pid=proc.pid)
|
|
self.continue_and_verify_pid()
|
|
|
|
def test_by_name(self):
|
|
"""
|
|
Tests attaching to a process by process name.
|
|
"""
|
|
program = self.build_and_create_debug_adapter_for_attach()
|
|
|
|
# Use a file as a synchronization point between test and inferior.
|
|
pid_file_path = lldbutil.append_to_process_working_directory(
|
|
self, "pid_file_%d" % (int(time.time()))
|
|
)
|
|
self.spawn(program=program, args=[pid_file_path])
|
|
lldbutil.wait_for_file_on_target(self, pid_file_path)
|
|
|
|
self.attach(program=program)
|
|
self.continue_and_verify_pid()
|
|
|
|
@expectedFailureWindows
|
|
def test_by_name_waitFor(self):
|
|
"""
|
|
Tests waiting for, and attaching to a process by process name that
|
|
doesn't exist yet.
|
|
"""
|
|
program = self.build_and_create_debug_adapter_for_attach()
|
|
self.spawn_event = threading.Event()
|
|
self.spawn_thread = threading.Thread(
|
|
target=self.spawn_and_wait,
|
|
args=(
|
|
program,
|
|
1.0,
|
|
),
|
|
)
|
|
self.spawn_thread.start()
|
|
try:
|
|
self.attach(program=program, waitFor=True)
|
|
self.continue_and_verify_pid()
|
|
finally:
|
|
self.spawn_event.set()
|
|
if self.spawn_thread.is_alive():
|
|
self.spawn_thread.join(timeout=10)
|
|
|
|
@expectedFailureWindows
|
|
def test_by_partial_name_waitFor(self):
|
|
"""
|
|
Tests waiting for and attaching to a process by partial process name
|
|
that doesn't exist yet.
|
|
"""
|
|
program = self.build_and_create_debug_adapter_for_attach()
|
|
self.spawn_event = threading.Event()
|
|
self.spawn_thread = threading.Thread(
|
|
target=self.spawn_and_wait,
|
|
args=(
|
|
program,
|
|
1.0,
|
|
),
|
|
)
|
|
self.spawn_thread.start()
|
|
try:
|
|
self.attach(program=os.path.basename(program), waitFor=True)
|
|
self.continue_and_verify_pid()
|
|
finally:
|
|
self.spawn_event.set()
|
|
if self.spawn_thread.is_alive():
|
|
self.spawn_thread.join(timeout=10)
|
|
|
|
def test_attach_with_missing_session_debugger(self):
|
|
"""
|
|
Test that attaching with only one of debuggerId/targetId specified
|
|
fails with the expected error message.
|
|
"""
|
|
self.build_and_create_debug_adapter()
|
|
|
|
# Test with only targetId specified (no debuggerId)
|
|
session = {"targetId": 99999}
|
|
attach_seq = self.attach(session=session)
|
|
resp = self.dap_server.receive_response(attach_seq)
|
|
self.assertFalse(resp["success"])
|
|
self.assertIn(
|
|
"missing value at arguments.session.debuggerId",
|
|
resp["body"]["error"]["format"],
|
|
)
|
|
|
|
def test_attach_with_invalid_session(self):
|
|
"""
|
|
Test that attaching with both debuggerId and targetId specified but
|
|
invalid fails with an appropriate error message.
|
|
"""
|
|
self.build_and_create_debug_adapter()
|
|
|
|
# Attach with both debuggerId=9999 and targetId=99999 (both invalid).
|
|
# Since debugger ID 9999 likely doesn't exist in the global registry,
|
|
# we expect a validation error.
|
|
session = {"debuggerId": 9999, "targetId": 9999}
|
|
resp = self.attach_and_configurationDone(session=session)
|
|
self.assertFalse(resp["success"])
|
|
error_msg = resp["body"]["error"]["format"]
|
|
# Either error is acceptable - both indicate the debugger reuse
|
|
# validation is working correctly
|
|
self.assertTrue(
|
|
"Unable to find existing debugger" in error_msg
|
|
or f"Expected debugger/target not found error, got: {error_msg}"
|
|
)
|