fix(debug): accept directory programs for dlv and auto-select dlv mode
The debug tool ran validateLaunchProgram before adapter selection and rejected any directory program with `launch program resolves to a directory`, while dlv's default `mode=debug` requires a Go package path (a directory or .go source file). Every Go-module launch failed: passing the module dir was rejected outright, and passing the compiled binary failed at dlv with `not a valid go module`. - Add `acceptsDirectoryProgram` to DapAdapterConfig/DapResolvedAdapter and flag dlv in dap/defaults.json. - In DebugTool.execute(launch), resolve the adapter first, then call validateLaunchProgram with the resolved adapter — the directory rejection only fires when the adapter does not advertise the flag. - Add resolveLaunchOverrides in dap/config.ts: for dlv, derive `mode` from the program shape (directory or .go file → debug; other file → exec). Plumbed through DapLaunchSessionOptions.extraLaunchArguments and spread between adapter.launchDefaults and the hard-coded launch fields in session.ts. - Refresh tests: cover the no-dir-support adapter rejection, dlv on a package directory keeping mode=debug, and dlv on a binary switching to mode=exec. Fixes #2020
This commit is contained in:
@@ -20,6 +20,7 @@ const TEST_ADAPTER: DapResolvedAdapter = {
|
||||
launchDefaults: {},
|
||||
attachDefaults: {},
|
||||
connectMode: "stdio",
|
||||
acceptsDirectoryProgram: false,
|
||||
};
|
||||
|
||||
const DELAYED_UNIX_SOCKET_ADAPTER = `
|
||||
@@ -338,24 +339,116 @@ describe("DAP launch failure handling", () => {
|
||||
});
|
||||
|
||||
describe("DebugTool launch validation", () => {
|
||||
it("rejects directory-valued launch programs before adapter selection", async () => {
|
||||
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "omp-debug-program-"));
|
||||
it("rejects directory programs when the selected adapter cannot debug a directory", async () => {
|
||||
const dapModule = await import("../../src/dap");
|
||||
const launchSpy = spyOn(dapModule, "selectLaunchAdapter").mockReturnValue(TEST_ADAPTER);
|
||||
try {
|
||||
await fs.mkdir(path.join(cwd, "python"));
|
||||
const session: ToolSession = {
|
||||
cwd,
|
||||
hasUI: false,
|
||||
getSessionFile: () => null,
|
||||
getSessionSpawns: () => "*",
|
||||
settings: Settings.isolated({ "debug.enabled": true }),
|
||||
};
|
||||
const tool = new DebugTool(session);
|
||||
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "omp-debug-program-"));
|
||||
try {
|
||||
await fs.mkdir(path.join(cwd, "python"));
|
||||
const session: ToolSession = {
|
||||
cwd,
|
||||
hasUI: false,
|
||||
getSessionFile: () => null,
|
||||
getSessionSpawns: () => "*",
|
||||
settings: Settings.isolated({ "debug.enabled": true }),
|
||||
};
|
||||
const tool = new DebugTool(session);
|
||||
|
||||
await expect(tool.execute("call", { action: "launch", program: "python" })).rejects.toThrow(
|
||||
/launch program resolves to a directory.*python/,
|
||||
);
|
||||
await expect(tool.execute("call", { action: "launch", program: "python" })).rejects.toThrow(
|
||||
/launch program resolves to a directory.*python/,
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(cwd, { recursive: true, force: true });
|
||||
}
|
||||
} finally {
|
||||
await fs.rm(cwd, { recursive: true, force: true });
|
||||
launchSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("allows directory programs when the selected adapter accepts them (dlv on a Go package)", async () => {
|
||||
const dapModule = await import("../../src/dap");
|
||||
const dlvAdapter: DapResolvedAdapter = {
|
||||
...TEST_ADAPTER,
|
||||
name: "dlv",
|
||||
command: "dlv",
|
||||
resolvedCommand: "dlv",
|
||||
launchDefaults: { request: "launch", mode: "debug", stopOnEntry: true },
|
||||
acceptsDirectoryProgram: true,
|
||||
};
|
||||
const launchSpy = spyOn(dapModule, "selectLaunchAdapter").mockReturnValue(dlvAdapter);
|
||||
const sessionLaunchSpy = spyOn(dapModule.dapSessionManager, "launch").mockImplementation(async opts => {
|
||||
throw Object.assign(new Error("captured launch"), { capturedOptions: opts });
|
||||
});
|
||||
try {
|
||||
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "omp-debug-dlv-dir-"));
|
||||
try {
|
||||
await fs.mkdir(path.join(cwd, "cmd"));
|
||||
const session: ToolSession = {
|
||||
cwd,
|
||||
hasUI: false,
|
||||
getSessionFile: () => null,
|
||||
getSessionSpawns: () => "*",
|
||||
settings: Settings.isolated({ "debug.enabled": true }),
|
||||
};
|
||||
const tool = new DebugTool(session);
|
||||
|
||||
// Validation must pass and propagate to dapSessionManager.launch; we
|
||||
// stop the actual spawn there and inspect the launch arguments.
|
||||
await expect(tool.execute("call", { action: "launch", program: "cmd", adapter: "dlv" })).rejects.toThrow(
|
||||
/captured launch/,
|
||||
);
|
||||
expect(sessionLaunchSpy).toHaveBeenCalledTimes(1);
|
||||
const [opts] = sessionLaunchSpy.mock.calls[0]!;
|
||||
expect(opts.extraLaunchArguments).toEqual({ mode: "debug" });
|
||||
expect(opts.program).toBe(path.join(cwd, "cmd"));
|
||||
} finally {
|
||||
await fs.rm(cwd, { recursive: true, force: true });
|
||||
}
|
||||
} finally {
|
||||
sessionLaunchSpy.mockRestore();
|
||||
launchSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("dlv launch with a compiled binary switches mode from debug to exec", async () => {
|
||||
const dapModule = await import("../../src/dap");
|
||||
const dlvAdapter: DapResolvedAdapter = {
|
||||
...TEST_ADAPTER,
|
||||
name: "dlv",
|
||||
command: "dlv",
|
||||
resolvedCommand: "dlv",
|
||||
launchDefaults: { request: "launch", mode: "debug", stopOnEntry: true },
|
||||
acceptsDirectoryProgram: true,
|
||||
};
|
||||
const launchSpy = spyOn(dapModule, "selectLaunchAdapter").mockReturnValue(dlvAdapter);
|
||||
const sessionLaunchSpy = spyOn(dapModule.dapSessionManager, "launch").mockImplementation(async opts => {
|
||||
throw Object.assign(new Error("captured launch"), { capturedOptions: opts });
|
||||
});
|
||||
try {
|
||||
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "omp-debug-dlv-exec-"));
|
||||
try {
|
||||
await fs.writeFile(path.join(cwd, "hello"), "#!/usr/bin/env sh\necho hi\n");
|
||||
const session: ToolSession = {
|
||||
cwd,
|
||||
hasUI: false,
|
||||
getSessionFile: () => null,
|
||||
getSessionSpawns: () => "*",
|
||||
settings: Settings.isolated({ "debug.enabled": true }),
|
||||
};
|
||||
const tool = new DebugTool(session);
|
||||
|
||||
await expect(tool.execute("call", { action: "launch", program: "hello", adapter: "dlv" })).rejects.toThrow(
|
||||
/captured launch/,
|
||||
);
|
||||
const [opts] = sessionLaunchSpy.mock.calls[0]!;
|
||||
expect(opts.extraLaunchArguments).toEqual({ mode: "exec" });
|
||||
} finally {
|
||||
await fs.rm(cwd, { recursive: true, force: true });
|
||||
}
|
||||
} finally {
|
||||
sessionLaunchSpy.mockRestore();
|
||||
launchSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user