John Harrison e7483770c7
[lldb-dap] Correct attaching by program basename. (#188886)
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
},
```
2026-03-31 06:41:35 -07:00

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}"
)