Suzy Mueller has uploaded this change for review.
tools/generateDlvDapTest.go: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, this change generates a new file with the modifications
required to run dlv dap.
This change includes adding some comments to the original test file
to make generating the tests easy. The goal will be to simply delete
the old file and generating script once the new implementation is
complete.
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
---
M src/debugAdapter2/goDlvDebug.ts
M src/goDebugFactory.ts
A test/integration/debugAdapter.test.ts
A test/integration/debugAdapterDlvDap.test.ts
M test/integration/goDebug.test.ts
A test/testdata/helloWorldServer/__debug_bin
A tools/generateDlvDapTest.go
7 files changed, 2,943 insertions(+), 1,397 deletions(-)
diff --git a/src/debugAdapter2/goDlvDebug.ts b/src/debugAdapter2/goDlvDebug.ts
index 0226492..40b9b51 100644
--- a/src/debugAdapter2/goDlvDebug.ts
+++ b/src/debugAdapter2/goDlvDebug.ts
@@ -728,15 +728,11 @@
if (!program) {
throw new Error('The program attribute is missing in the debug configuration in launch.json');
}
- let programIsDirectory = false;
- try {
- programIsDirectory = fs.lstatSync(program).isDirectory();
- } catch (e) {
- throw new Error('The program attribute must point to valid directory, .go file or executable.');
+ let programIsDirectory = true;
+ if (program.endsWith('.go') || launchArgs.mode === 'exec') {
+ programIsDirectory = false;
}
- if (!programIsDirectory && path.extname(program) !== '.go') {
- throw new Error('The program attribute must be a directory or .go file in debug mode');
- }
+
const dirname = programIsDirectory ? program : path.dirname(program);
return {program, dirname, programIsDirectory};
}
diff --git a/src/goDebugFactory.ts b/src/goDebugFactory.ts
index 9ada182..0aa1b2a 100644
--- a/src/goDebugFactory.ts
+++ b/src/goDebugFactory.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
-import { ChildProcess } from 'child_process';
+import { ChildProcess, ChildProcessWithoutNullStreams } from 'child_process';
import getPort = require('get-port');
import { DebugConfiguration } from 'vscode';
import vscode = require('vscode');
@@ -25,7 +25,8 @@
// new one.
await this.terminateDlvDapServerProcess();
- const {port, host} = await this.startDapServer(session.configuration);
+ const {port, host, dlvDapServer} = await startDapServer(session.configuration);
+ this.dlvDapServer = dlvDapServer;
return new vscode.DebugAdapterServer(port, host);
}
@@ -39,26 +40,28 @@
this.dlvDapServer = null;
}
}
+}
- private async startDapServer(configuration: DebugConfiguration): Promise<{ port: number; host: string; }> {
- if (!configuration.host) {
- configuration.host = '127.0.0.1';
- }
-
- if (configuration.port) {
- // If a port has been specified, assume there is an already
- // running dap server to connect to.
- return {port: configuration.port, host: configuration.host};
- } else {
- configuration.port = await getPort();
- }
-
- this.dlvDapServer = spawnDlvDapServerProcess(configuration, logInfo, logError);
- // Wait to give dlv-dap a chance to start before returning.
- return await
- new Promise<{ port: number; host: string; }>((resolve) => setTimeout(() => {
- resolve({port: configuration.port, host: configuration.host});
- }, 500));
+export async function startDapServer(
+ configuration: DebugConfiguration
+): Promise<{ port: number; host: string; dlvDapServer?: ChildProcessWithoutNullStreams}> {
+ if (!configuration.host) {
+ configuration.host = '127.0.0.1';
}
+ if (configuration.port) {
+ // If a port has been specified, assume there is an already
+ // running dap server to connect to.
+ return {port: configuration.port, host: configuration.host};
+ } else {
+ configuration.port = await getPort();
+ }
+
+ const dlvDapServer = spawnDlvDapServerProcess(configuration, logInfo, logError);
+ // Wait to give dlv-dap a chance to start before returning.
+ return await
+ new Promise<{ port: number; host: string; dlvDapServer: ChildProcessWithoutNullStreams}>((resolve) =>
+ setTimeout(() => {
+ resolve({port: configuration.port, host: configuration.host, dlvDapServer});
+ }, 500));
}
diff --git a/test/integration/debugAdapter.test.ts b/test/integration/debugAdapter.test.ts
new file mode 100644
index 0000000..143604d
--- /dev/null
+++ b/test/integration/debugAdapter.test.ts
@@ -0,0 +1,1385 @@
+/*---------------------------------------------------------
+ * Copyright 2021 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+import * as assert from 'assert';
+import * as cp from 'child_process';
+import * as fs from 'fs';
+import getPort = require('get-port');
+import * as http from 'http';
+import { tmpdir } from 'os';
+import * as path from 'path';
+import util = require('util');
+import { DebugConfiguration } from 'vscode';
+import { DebugClient } from 'vscode-debugadapter-testsupport';
+import { ILocation } from 'vscode-debugadapter-testsupport/lib/debugClient';
+import { DebugProtocol } from 'vscode-debugprotocol';
+import { GoDebugConfigurationProvider } from '../../src/goDebugConfiguration';
+// ADD:import { startDapServer } from '../../src/goDebugFactory';
+import { getBinPath, rmdirRecursive } from '../../src/util';
+import { killProcessTree } from '../../src/utils/processUtils';
+
+// Test suite adapted from:
+// https://github.com/microsoft/vscode-mock-debug/blob/master/src/tests/adapter.test.ts
+suite('Go Debug Adapter', function () { // REMOVE
+// ADD:suite('Go Debug Adapter (dlv dap)', function () {
+ this.timeout(60_000);
+
+ const debugConfigProvider = new GoDebugConfigurationProvider();
+ const DEBUG_ADAPTER = path.join('.', 'out', 'src', 'debugAdapter', 'goDebug.js'); // REMOVE
+
+ const PROJECT_ROOT = path.normalize(path.join(__dirname, '..', '..', '..'));
+ const DATA_ROOT = path.join(PROJECT_ROOT, 'test', 'testdata');
+
+ const remoteAttachConfig = {
+ name: 'Attach',
+ type: 'go',
+ request: 'attach',
+ mode: 'remote',
+ host: '127.0.0.1',
+ port: 3456,
+ };
+
+ let dc: DebugClient;
+
+ setup(() => {
+ dc = new DebugClient('node', path.join(PROJECT_ROOT, DEBUG_ADAPTER), 'go', undefined, true); // REMOVE
+ // ADD:dc = new DebugClient('dlv', 'dap', 'go');
+
+ // Launching delve may take longer than the default timeout of 5000.
+ dc.defaultTimeout = 20_000;
+
+ // To connect to a running debug server for debugging the tests, specify PORT.
+ return dc.start(); // REMOVE
+ });
+
+ teardown(() => dc.stop());
+
+ /**
+ * This function sets up a server that returns helloworld on serverPort.
+ * The server will be started as a Delve remote headless instance
+ * that will listen on the specified dlvPort.
+ * We are using a server as opposed to a long-running program
+ * because we can use responses to better test when the program is
+ * running vs stopped/killed.
+ */
+ async function setUpRemoteProgram(
+ dlvPort: number, serverPort: number,
+ acceptMultiClient = true, continueOnStart = true): Promise<cp.ChildProcess> {
+ const serverFolder = path.join(DATA_ROOT, 'helloWorldServer');
+ const toolPath = getBinPath('dlv');
+ const args = ['debug', '--api-version=2', '--headless', `--listen=127.0.0.1:${dlvPort}`];
+ if (acceptMultiClient) {
+ args.push('--accept-multiclient');
+ }
+ if (continueOnStart) {
+ args.push('--continue');
+ }
+ const childProcess = cp.spawn(toolPath, args,
+ { cwd: serverFolder, env: { PORT: `${serverPort}`, ...process.env } });
+
+ // Give dlv a few seconds to start.
+ await new Promise((resolve) => setTimeout(resolve, 10_000));
+ return childProcess;
+ }
+
+ /**
+ * Helper function to set up remote attach configuration.
+ * This will issue an initializeRequest, followed by attachRequest.
+ * It will then wait for an initializedEvent before sending a breakpointRequest
+ * if breakpoints are provided. Lastly the configurationDoneRequest will be sent.
+ * NOTE: For simplicity, this function assumes the breakpoints are in the same file.
+ */
+ async function setUpRemoteAttach(config: DebugConfiguration, breakpoints: ILocation[] = []): Promise<void> {
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ console.log(`Sending initializing request for remote attach setup.`);
+ const initializedResult = await dc.initializeRequest();
+ assert.ok(initializedResult.success);
+
+ // When the attach request is completed successfully, we should get
+ // an initialized event.
+ await Promise.all([
+ new Promise<void>(async (resolve) => {
+ console.log(`Setting up attach request for ${JSON.stringify(debugConfig)}.`);
+ const attachResult = await dc.attachRequest(debugConfig as DebugProtocol.AttachRequestArguments);
+ assert.ok(attachResult.success);
+ resolve();
+ }),
+ dc.waitForEvent('initialized')
+ ]);
+
+ if (breakpoints.length) {
+ console.log(`Sending set breakpoints request for remote attach setup.`);
+ const breakpointsResult = await dc.setBreakpointsRequest({ source: { path: breakpoints[0].path }, breakpoints });
+ assert.ok(breakpointsResult.success && breakpointsResult.body.breakpoints.length === breakpoints.length);
+ // Verify that there are no non-verified breakpoints.
+ breakpointsResult.body.breakpoints.forEach((breakpoint) => {
+ assert.ok(breakpoint.verified);
+ });
+ }
+ console.log(`Sending configuration done request for remote attach setup.`);
+ const configurationDoneResult = await dc.configurationDoneRequest();
+ assert.ok(configurationDoneResult.success);
+ }
+
+ /**
+ * Helper function to retrieve a stopped event for a breakpoint.
+ * This function will keep calling action() until we receive a stoppedEvent.
+ * Will return undefined if the result of repeatedly calling action does not
+ * induce a stoppedEvent.
+ */
+ async function waitForBreakpoint(action: () => void, breakpoint: ILocation): Promise<void> {
+ const assertStoppedLocation = dc.assertStoppedLocation('breakpoint', breakpoint);
+ await new Promise((res) => setTimeout(res, 1_000));
+ action();
+ await assertStoppedLocation;
+ }
+
+ /**
+ * Helper function to assert that a variable has a particular value.
+ * This should be called when the program is stopped.
+ *
+ * The following requests are issued by this function to determine the
+ * value of the variable:
+ * 1. threadsRequest
+ * 2. stackTraceRequest
+ * 3. scopesRequest
+ * 4. variablesRequest
+ */
+ async function assertVariableValue(name: string, val: string): Promise<void> {
+ const threadsResponse = await dc.threadsRequest();
+ assert(threadsResponse.success);
+ const stackTraceResponse = await dc.stackTraceRequest({ threadId: threadsResponse.body.threads[0].id });
+ assert(stackTraceResponse.success);
+ const scopesResponse = await dc.scopesRequest({ frameId: stackTraceResponse.body.stackFrames[0].id });
+ assert(scopesResponse.success);
+ const variablesResponse = await dc.variablesRequest({
+ variablesReference: scopesResponse.body.scopes[0].variablesReference
+ });
+ assert(variablesResponse.success);
+ // Locate the variable with the matching name.
+ const i = variablesResponse.body.variables.findIndex((v) => v.name === name);
+ assert(i >= 0);
+ // Check that the value of name is val.
+ assert.strictEqual(variablesResponse.body.variables[i].value, val);
+ }
+
+ suite('basic', () => { // SKIP
+
+ test('unknown request should produce error', async () => {
+ // ADD:const config = {name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT};
+ // ADD:const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ await dc.send('illegal_request').then(() => {
+ Promise.reject(new Error('does not report error on unknown request'));
+ }).catch(() => {
+ Promise.resolve();
+ });
+ });
+ });
+
+ suite('initialize', () => { // SKIP
+
+ test('should return supported features', async () => {
+ // ADD:const config = {name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT};
+ // ADD:const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ await dc.initializeRequest().then((response) => {
+ response.body = response.body || {};
+ assert.strictEqual(response.body.supportsConditionalBreakpoints, true);
+ assert.strictEqual(response.body.supportsConfigurationDoneRequest, true);
+ assert.strictEqual(response.body.supportsSetVariable, true);
+ });
+ });
+
+ test('should produce error for invalid \'pathFormat\'', async () => { // SKIP
+ // ADD:const config = {name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT};
+ // ADD:const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ await dc.initializeRequest({
+ adapterID: 'mock',
+ linesStartAt1: true,
+ columnsStartAt1: true,
+ pathFormat: 'url'
+ }).then((response) => {
+ Promise.reject(new Error('does not report error on invalid \'pathFormat\' attribute'));
+ }).catch((err) => {
+ // error expected
+ Promise.resolve();
+ });
+ });
+ });
+
+ suite('launch', () => {
+ test('should run program to the end', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should stop on entry', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ // The debug adapter does not support a stack trace request
+ // when there are no goroutines running. Which is true when it is stopped
+ // on entry. Therefore we would need another method from dc.assertStoppedLocation
+ // to check the debugger is stopped on entry.
+ dc.waitForEvent('stopped').then((event) => {
+ const stevent = event as DebugProtocol.StoppedEvent;
+ assert.strictEqual(stevent.body.reason, 'entry');
+ })
+ ]);
+ });
+
+ test('should debug a file', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest', 'test.go');
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should debug a single test', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'test',
+ program: PROGRAM,
+ args: [
+ '-test.run',
+ 'TestMe'
+ ]
+ };
+
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should debug a test package', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'test',
+ program: PROGRAM
+ };
+
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('invalid flags are passed to dlv but should be caught by dlv', async () => { // SKIP
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ dlvFlags: ['--invalid']
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ return Promise.all([
+ dc.assertOutput('stderr', 'Error: unknown flag: --invalid\n', 5000),
+ dc.waitForEvent('terminated'),
+ dc.initializeRequest().then((response) => {
+ // The current debug adapter does not respond to launch request but,
+ // instead, sends error messages and TerminatedEvent as delve is closed.
+ // The promise from dc.launchRequest resolves when the launch response
+ // is received, so the promise will never get resolved.
+ dc.launchRequest(debugConfig as any);
+ })
+ ]);
+ });
+
+ test('should handle threads request after initialization', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.configurationSequence().then(() => {
+ dc.threadsRequest().then((response) => {
+ assert.ok(response.success);
+ });
+ }),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated'),
+ ]);
+ });
+
+ test('should handle delayed initial threads request', async () => {
+ // If the program exits very quickly, the initial threadsRequest
+ // will complete after it has exited.
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+
+ const response = await dc.threadsRequest();
+ assert.ok(response.success);
+ });
+
+ test('user-specified --listen flag should be ignored', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ dlvFlags: ['--listen=127.0.0.1:80'],
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+ });
+
+ suite('set current working directory', () => { // SKIP
+ test('should debug program with cwd set', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest');
+ const FILE = path.join(PROGRAM, 'main.go');
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ cwd: WD,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ await assertVariableValue('strdat', '"Hello, World!"');
+ });
+
+ test('should debug program without cwd set', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest');
+ const FILE = path.join(PROGRAM, 'main.go');
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ await assertVariableValue('strdat', '"Goodbye, World."');
+ });
+
+ test('should debug file program with cwd set', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
+ const FILE = PROGRAM;
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ cwd: WD,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ await assertVariableValue('strdat', '"Hello, World!"');
+ });
+
+ test('should debug file program without cwd set', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
+ const FILE = PROGRAM;
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ await assertVariableValue('strdat', '"Goodbye, World."');
+ });
+
+ test('should run program with cwd set (noDebug)', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ cwd: WD,
+ noDebug: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.launch(debugConfig),
+ dc.waitForEvent('output').then((event) => {
+ assert.strictEqual(event.body.output, 'Hello, World!\n');
+ })
+ ]);
+ });
+
+ test('should run program without cwd set (noDebug)', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ noDebug: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.launch(debugConfig),
+ dc.waitForEvent('output').then((event) => {
+ assert.strictEqual(event.body.output, 'Goodbye, World.\n');
+ })
+ ]);
+ });
+
+ test('should run file program with cwd set (noDebug)', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ cwd: WD,
+ noDebug: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.launch(debugConfig),
+ dc.waitForEvent('output').then((event) => {
+ assert.strictEqual(event.body.output, 'Hello, World!\n');
+ })
+ ]);
+ });
+
+ test('should run file program without cwd set (noDebug)', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ noDebug: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+ dc.launch(debugConfig),
+ dc.waitForEvent('output').then((event) => {
+ assert.strictEqual(event.body.output, 'Goodbye, World.\n');
+ })
+ ]);
+ });
+
+ });
+
+ suite('remote attach', () => { // SKIP
+ let childProcess: cp.ChildProcess;
+ let server: number;
+ let debugConfig: DebugConfiguration;
+ setup(async () => {
+ server = await getPort();
+ remoteAttachConfig.port = await getPort();
+ debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+ });
+
+ teardown(async () => {
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(childProcess);
+ // Wait 2 seconds for the process to be killed.
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=true',
+ async () => {
+ childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, true, true);
+
+ await setUpRemoteAttach(debugConfig);
+ });
+
+ test('can connect and initialize using external dlv --headless --accept-multiclient=false --continue=false',
+ async () => {
+ childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, false, false);
+
+ await setUpRemoteAttach(debugConfig);
+ });
+
+ test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=false',
+ async () => {
+ childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, true, false);
+
+ await setUpRemoteAttach(debugConfig);
+ });
+ });
+
+ // The file paths returned from delve use '/' not the native path
+ // separator, so we can replace any instances of '\' with '/', which
+ // allows the hitBreakpoint check to match.
+ const getBreakpointLocation = (FILE: string, LINE: number, useBackSlash = true) => {
+ return { path: useBackSlash ? FILE.replace(/\\/g, '/') : FILE, line: LINE };
+ };
+
+ suite('setBreakpoints', () => { // SKIP
+ let server: number;
+ let remoteAttachDebugConfig: DebugConfiguration;
+ setup(async () => {
+ server = await getPort();
+ remoteAttachConfig.port = await getPort();
+ remoteAttachDebugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+ });
+
+ test('should stop on a breakpoint', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const FILE = path.join(DATA_ROOT, 'baseTest', 'test.go');
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ });
+
+ test('should stop on a breakpoint in test file', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const FILE = path.join(DATA_ROOT, 'baseTest', 'sample_test.go');
+ const BREAKPOINT_LINE = 15;
+
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'test',
+ program: PROGRAM
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ });
+
+ test('stopped for a breakpoint set during initialization (remote attach)', async () => {
+ const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ // Setup attach with a breakpoint.
+ await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('stopped for a breakpoint set after initialization (remote attach)', async () => {
+ const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ // Setup attach without a breakpoint.
+ await setUpRemoteAttach(remoteAttachDebugConfig);
+
+ // Now sets a breakpoint.
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+ const breakpointsResult = await dc.setBreakpointsRequest(
+ { source: { path: breakpointLocation.path }, breakpoints: [breakpointLocation] });
+ assert.ok(breakpointsResult.success && breakpointsResult.body.breakpoints[0].verified);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('stopped for a breakpoint set during initialization (remote attach)', async () => {
+ const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
+
+ // Setup attach with a breakpoint.
+ await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('should set breakpoints during continue', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'sleep');
+
+ const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
+ const HELLO_LINE = 10;
+ const helloLocation = getBreakpointLocation(FILE, HELLO_LINE);
+
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ ]);
+
+ return Promise.all([
+ dc.setBreakpointsRequest({
+ lines: [helloLocation.line],
+ breakpoints: [{ line: helloLocation.line, column: 0 }],
+ source: { path: helloLocation.path }
+ }),
+ dc.assertStoppedLocation('breakpoint', helloLocation)
+ ]);
+ });
+
+ async function setBreakpointsDuringStep(nextFunc: () => void) {
+ const PROGRAM = path.join(DATA_ROOT, 'sleep');
+
+ const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
+ const SLEEP_LINE = 11;
+ const setupBreakpoint = getBreakpointLocation(FILE, SLEEP_LINE);
+
+ const HELLO_LINE = 10;
+ const onNextBreakpoint = getBreakpointLocation(FILE, HELLO_LINE);
+
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await dc.hitBreakpoint(debugConfig, setupBreakpoint);
+
+ // The program is now stopped at the line containing time.Sleep().
+ // Issue a next request, followed by a setBreakpointsRequest.
+ nextFunc();
+
+ // Note: the current behavior of setting a breakpoint during a next
+ // request will cause the step to be interrupted, so it may not be
+ // stopped on the next line.
+ await Promise.all([
+ dc.setBreakpointsRequest({
+ lines: [onNextBreakpoint.line],
+ breakpoints: [{ line: onNextBreakpoint.line, column: 0 }],
+ source: { path: onNextBreakpoint.path }
+ }),
+ dc.assertStoppedLocation('next cancelled', {})
+ ]);
+
+ // Once the 'step' has completed, continue the program and
+ // make sure the breakpoint set while the program was nexting
+ // is succesfully hit.
+ await Promise.all([
+ dc.continueRequest({ threadId: 1 }),
+ dc.assertStoppedLocation('breakpoint', onNextBreakpoint)
+ ]);
+ }
+
+ test('should set breakpoints during next', async () => {
+ setBreakpointsDuringStep(async () => {
+ const nextResponse = await dc.nextRequest({ threadId: 1 });
+ assert.ok(nextResponse.success);
+ });
+ });
+
+ test('should set breakpoints during step out', async () => {
+ setBreakpointsDuringStep(async () => {
+ const stepOutResponse = await dc.stepOutRequest({ threadId: 1 });
+ assert.ok(stepOutResponse.success);
+ });
+ });
+ });
+
+ suite('conditionalBreakpoints', () => { // SKIP
+ test('should stop on conditional breakpoint', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'condbp');
+ const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+ const BREAKPOINT_LINE = 7;
+ const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ return Promise.all([
+
+ dc.waitForEvent('initialized').then(() => {
+ return dc.setBreakpointsRequest({
+ lines: [location.line],
+ breakpoints: [{ line: location.line, condition: 'i == 2' }],
+ source: { path: location.path }
+ });
+ }).then(() => {
+ return dc.configurationDoneRequest();
+ }),
+
+ dc.launch(debugConfig),
+
+ dc.assertStoppedLocation('breakpoint', location)
+
+ ]).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 1'.
+ assertVariableValue('i', '2')
+ );
+ });
+
+ test('should add breakpoint condition', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'condbp');
+ const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+ const BREAKPOINT_LINE = 7;
+ const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return dc.hitBreakpoint(debugConfig, location).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 0'.
+ assertVariableValue('i', '0')
+ ).then(() =>
+ // Add a condition to the breakpoint, and make sure it runs until 'i == 2'.
+ dc.setBreakpointsRequest({
+ lines: [location.line],
+ breakpoints: [{ line: location.line, condition: 'i == 2' }],
+ source: { path: location.path }
+ }).then(() =>
+ Promise.all([
+ dc.continueRequest({ threadId: 1 }),
+ dc.assertStoppedLocation('breakpoint', location)
+ ]).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 2'.
+ assertVariableValue('i', '2')
+ )
+ )
+ );
+ });
+
+ test('should remove breakpoint condition', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'condbp');
+ const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+ const BREAKPOINT_LINE = 7;
+ const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ return Promise.all([
+
+ dc.waitForEvent('initialized').then(() => {
+ return dc.setBreakpointsRequest({
+ lines: [location.line],
+ breakpoints: [{ line: location.line, condition: 'i == 2' }],
+ source: { path: location.path }
+ });
+ }).then(() => {
+ return dc.configurationDoneRequest();
+ }),
+
+ dc.launch(debugConfig),
+
+ dc.assertStoppedLocation('breakpoint', location)
+
+ ]).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 2'.
+ assertVariableValue('i', '2')
+ ).then(() =>
+ // Remove the breakpoint condition, and make sure the program runs until 'i == 3'.
+ dc.setBreakpointsRequest({
+ lines: [location.line],
+ breakpoints: [{ line: location.line }],
+ source: { path: location.path }
+ }).then(() =>
+ Promise.all([
+ dc.continueRequest({ threadId: 1 }),
+ dc.assertStoppedLocation('breakpoint', location)
+ ]).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 3'.
+ assertVariableValue('i', '3')
+ )
+ )
+ );
+ });
+ });
+
+ suite('panicBreakpoints', () => {
+
+ test('should stop on panic', async () => {
+
+ const PROGRAM_WITH_EXCEPTION = path.join(DATA_ROOT, 'panic');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM_WITH_EXCEPTION,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return Promise.all([
+
+ dc.waitForEvent('initialized').then(() => {
+ return dc.setExceptionBreakpointsRequest({
+ filters: ['all']
+ });
+ }).then(() => {
+ return dc.configurationDoneRequest();
+ }),
+
+ dc.launch(debugConfig),
+
+ dc.assertStoppedLocation('panic', {})
+ ]);
+ });
+ });
+
+ suite('disconnect', () => { // SKIP
+ // The teardown code for the Go Debug Adapter test suite issues a disconnectRequest.
+ // In order for these tests to pass, the debug adapter must not fail if a
+ // disconnectRequest is sent after it has already disconnected.
+
+ test('disconnect should work for remote attach', async () => {
+ const server = await getPort();
+ remoteAttachConfig.port = await getPort();
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+
+ // Setup attach.
+ await setUpRemoteAttach(debugConfig);
+
+ // Calls the helloworld server to get a response.
+ let response = '';
+ await new Promise<void>((resolve) => {
+ http.get(`http://localhost:${server}`, (res) => {
+ res.on('data', (data) => response += data);
+ res.on('end', () => resolve());
+ });
+ });
+
+ await dc.disconnectRequest();
+ // Checks that after the disconnect, the helloworld server still works.
+ let secondResponse = '';
+ await new Promise<void>((resolve) => {
+ http.get(`http://localhost:${server}`, (res) => {
+ res.on('data', (data) => secondResponse += data);
+ res.on('end', () => resolve());
+ });
+ });
+ assert.strictEqual(response, 'Hello, world!');
+ assert.strictEqual(response, secondResponse);
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('should disconnect while continuing on entry', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: false
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect with multiple disconnectRequests', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: false
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ await Promise.all([
+ dc.disconnectRequest({ restart: false }).then(() =>
+ dc.disconnectRequest({ restart: false })
+ ),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect after continue', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ const continueResponse = await dc.continueRequest({ threadId: 1 });
+ assert.ok(continueResponse.success);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect while nexting', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'sleep');
+ const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
+ const BREAKPOINT_LINE = 11;
+ const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: false
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await dc.hitBreakpoint(debugConfig, location);
+
+ const nextResponse = await dc.nextRequest({ threadId: 1 });
+ assert.ok(nextResponse.success);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect while paused on pause', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ const pauseResponse = await dc.pauseRequest({ threadId: 1 });
+ assert.ok(pauseResponse.success);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated'),
+ ]);
+ });
+
+ test('should disconnect while paused on breakpoint', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+ const FILE = path.join(PROGRAM, 'loop.go');
+ const BREAKPOINT_LINE = 5;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect while paused on entry', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect while paused on next', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ const nextResponse = await dc.nextRequest({ threadId: 1 });
+ assert.ok(nextResponse.success);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+ });
+
+ suite('substitute path', () => { // SKIP
+ // TODO(suzmue): add unit tests for substitutePath.
+ let tmpDir: string;
+
+ suiteSetup(() => {
+ tmpDir = fs.mkdtempSync(path.join(DATA_ROOT, 'substitutePathTest'));
+ });
+
+ suiteTeardown(() => {
+ rmdirRecursive(tmpDir);
+ });
+
+ function copyDirectory(name: string) {
+ const from = path.join(DATA_ROOT, name);
+ const to = path.join(tmpDir, name);
+ fs.mkdirSync(to);
+ fs.readdirSync(from).forEach((file) => {
+ fs.copyFileSync(path.join(from, file), path.join(to, file));
+ });
+ return to;
+ }
+
+ async function buildGoProgram(cwd: string, outputFile: string): Promise<string> {
+ const goRuntimePath = getBinPath('go');
+ const execFile = util.promisify(cp.execFile);
+ const child = await execFile(goRuntimePath,
+ ['build', '-o', outputFile, `--gcflags='all=-N -l'`, '.'],
+ { cwd });
+ if (child.stderr.length > 0) {
+ throw Error(child.stderr);
+ }
+ return outputFile;
+ }
+
+ suite('substitutePath with missing files', () => {
+ let goBuildOutput: string;
+ suiteSetup(() => {
+ goBuildOutput = fs.mkdtempSync(path.join(tmpdir(), 'output'));
+ });
+
+ suiteTeardown(() => {
+ rmdirRecursive(goBuildOutput);
+ });
+
+ async function copyBuildDelete(program: string): Promise<{ program: string, output: string }> {
+ const wd = copyDirectory(program);
+ const output = await buildGoProgram(wd, path.join(goBuildOutput, program));
+ rmdirRecursive(wd);
+ return { program: wd, output };
+ }
+
+ test('should stop on a breakpoint set in file with substituted path', async () => {
+ const { program, output } = await copyBuildDelete('baseTest');
+ const FILE = path.join(DATA_ROOT, 'baseTest', 'test.go');
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'exec',
+ program: output,
+ substitutePath: [
+ {
+ from: path.join(DATA_ROOT, 'baseTest'),
+ to: program
+ }
+ ]
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ });
+ });
+
+ suite('substitutePath with remote program', () => { // SKIP
+ let server: number;
+ let remoteAttachDebugConfig: DebugConfiguration;
+ let helloWorldLocal: string;
+ let helloWorldRemote: string;
+ setup(async () => {
+ server = await getPort();
+ remoteAttachConfig.port = await getPort();
+ remoteAttachDebugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+ });
+
+ suiteSetup(() => {
+ helloWorldLocal = copyDirectory('helloWorldServer');
+ helloWorldRemote = path.join(DATA_ROOT, 'helloWorldServer');
+ });
+
+ suiteTeardown(() => {
+ rmdirRecursive(helloWorldLocal);
+ });
+
+ test('stopped for a breakpoint set during initialization using substitutePath (remote attach)', async () => {
+ const FILE = path.join(helloWorldLocal, 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
+ // Setup attach with a breakpoint.
+ remoteAttachDebugConfig.cwd = tmpDir;
+ remoteAttachDebugConfig.remotePath = '';
+ remoteAttachDebugConfig.substitutePath = [
+ { from: helloWorldLocal, to: helloWorldRemote }
+ ];
+ await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ // Skip because it times out in nightly release workflow.
+ // BUG(https://github.com/golang/vscode-go/issues/1043)
+ test.skip('stopped for a breakpoint set during initialization using remotePath (remote attach)', async () => {
+ const FILE = path.join(helloWorldLocal, 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
+ // Setup attach with a breakpoint.
+ remoteAttachDebugConfig.cwd = helloWorldLocal;
+ remoteAttachDebugConfig.remotePath = helloWorldRemote;
+ // This is a bad mapping, make sure that the remotePath config is used first.
+ remoteAttachDebugConfig.substitutePath = [
+ { from: helloWorldLocal, to: helloWorldLocal }
+ ];
+ await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+ });
+ });
+
+});
diff --git a/test/integration/debugAdapterDlvDap.test.ts b/test/integration/debugAdapterDlvDap.test.ts
new file mode 100755
index 0000000..1c03bc4
--- /dev/null
+++ b/test/integration/debugAdapterDlvDap.test.ts
@@ -0,0 +1,1468 @@
+// Everything in this file is generated. DO NOT EDIT.
+// To generate, run 'go run tools/generateDlvDapTest.go'
+/*---------------------------------------------------------
+ * Copyright 2021 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+import * as assert from 'assert';
+import * as cp from 'child_process';
+import * as fs from 'fs';
+import getPort = require('get-port');
+import * as http from 'http';
+import { tmpdir } from 'os';
+import * as path from 'path';
+import util = require('util');
+import { DebugConfiguration } from 'vscode';
+import { DebugClient } from 'vscode-debugadapter-testsupport';
+import { ILocation } from 'vscode-debugadapter-testsupport/lib/debugClient';
+import { DebugProtocol } from 'vscode-debugprotocol';
+import { GoDebugConfigurationProvider } from '../../src/goDebugConfiguration';
+import { startDapServer } from '../../src/goDebugFactory';
+import { getBinPath, rmdirRecursive } from '../../src/util';
+import { killProcessTree } from '../../src/utils/processUtils';
+
+// Test suite adapted from:
+// https://github.com/microsoft/vscode-mock-debug/blob/master/src/tests/adapter.test.ts
+suite('Go Debug Adapter (dlv dap)', function () {
+ this.timeout(60_000);
+
+ const debugConfigProvider = new GoDebugConfigurationProvider();
+
+ const PROJECT_ROOT = path.normalize(path.join(__dirname, '..', '..', '..'));
+ const DATA_ROOT = path.join(PROJECT_ROOT, 'test', 'testdata');
+
+ const remoteAttachConfig = {
+ name: 'Attach',
+ type: 'go',
+ request: 'attach',
+ mode: 'remote',
+ host: '127.0.0.1',
+ port: 3456,
+ };
+
+ let dc: DebugClient;
+
+ setup(() => {
+ dc = new DebugClient('dlv', 'dap', 'go');
+
+ // Launching delve may take longer than the default timeout of 5000.
+ dc.defaultTimeout = 20_000;
+
+ // To connect to a running debug server for debugging the tests, specify PORT.
+ });
+
+ teardown(() => dc.stop());
+
+ /**
+ * This function sets up a server that returns helloworld on serverPort.
+ * The server will be started as a Delve remote headless instance
+ * that will listen on the specified dlvPort.
+ * We are using a server as opposed to a long-running program
+ * because we can use responses to better test when the program is
+ * running vs stopped/killed.
+ */
+ async function setUpRemoteProgram(
+ dlvPort: number, serverPort: number,
+ acceptMultiClient = true, continueOnStart = true): Promise<cp.ChildProcess> {
+ const serverFolder = path.join(DATA_ROOT, 'helloWorldServer');
+ const toolPath = getBinPath('dlv');
+ const args = ['debug', '--api-version=2', '--headless', `--listen=127.0.0.1:${dlvPort}`];
+ if (acceptMultiClient) {
+ args.push('--accept-multiclient');
+ }
+ if (continueOnStart) {
+ args.push('--continue');
+ }
+ const childProcess = cp.spawn(toolPath, args,
+ { cwd: serverFolder, env: { PORT: `${serverPort}`, ...process.env } });
+
+ // Give dlv a few seconds to start.
+ await new Promise((resolve) => setTimeout(resolve, 10_000));
+ return childProcess;
+ }
+
+ /**
+ * Helper function to set up remote attach configuration.
+ * This will issue an initializeRequest, followed by attachRequest.
+ * It will then wait for an initializedEvent before sending a breakpointRequest
+ * if breakpoints are provided. Lastly the configurationDoneRequest will be sent.
+ * NOTE: For simplicity, this function assumes the breakpoints are in the same file.
+ */
+ async function setUpRemoteAttach(config: DebugConfiguration, breakpoints: ILocation[] = []): Promise<void> {
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+ console.log(`Sending initializing request for remote attach setup.`);
+ const initializedResult = await dc.initializeRequest();
+ assert.ok(initializedResult.success);
+
+ // When the attach request is completed successfully, we should get
+ // an initialized event.
+ await Promise.all([
+ new Promise<void>(async (resolve) => {
+ console.log(`Setting up attach request for ${JSON.stringify(debugConfig)}.`);
+ const attachResult = await dc.attachRequest(debugConfig as DebugProtocol.AttachRequestArguments);
+ assert.ok(attachResult.success);
+ resolve();
+ }),
+ dc.waitForEvent('initialized')
+ ]);
+
+ if (breakpoints.length) {
+ console.log(`Sending set breakpoints request for remote attach setup.`);
+ const breakpointsResult = await dc.setBreakpointsRequest({ source: { path: breakpoints[0].path }, breakpoints });
+ assert.ok(breakpointsResult.success && breakpointsResult.body.breakpoints.length === breakpoints.length);
+ // Verify that there are no non-verified breakpoints.
+ breakpointsResult.body.breakpoints.forEach((breakpoint) => {
+ assert.ok(breakpoint.verified);
+ });
+ }
+ console.log(`Sending configuration done request for remote attach setup.`);
+ const configurationDoneResult = await dc.configurationDoneRequest();
+ assert.ok(configurationDoneResult.success);
+ }
+
+ /**
+ * Helper function to retrieve a stopped event for a breakpoint.
+ * This function will keep calling action() until we receive a stoppedEvent.
+ * Will return undefined if the result of repeatedly calling action does not
+ * induce a stoppedEvent.
+ */
+ async function waitForBreakpoint(action: () => void, breakpoint: ILocation): Promise<void> {
+ const assertStoppedLocation = dc.assertStoppedLocation('breakpoint', breakpoint);
+ await new Promise((res) => setTimeout(res, 1_000));
+ action();
+ await assertStoppedLocation;
+ }
+
+ /**
+ * Helper function to assert that a variable has a particular value.
+ * This should be called when the program is stopped.
+ *
+ * The following requests are issued by this function to determine the
+ * value of the variable:
+ * 1. threadsRequest
+ * 2. stackTraceRequest
+ * 3. scopesRequest
+ * 4. variablesRequest
+ */
+ async function assertVariableValue(name: string, val: string): Promise<void> {
+ const threadsResponse = await dc.threadsRequest();
+ assert(threadsResponse.success);
+ const stackTraceResponse = await dc.stackTraceRequest({ threadId: threadsResponse.body.threads[0].id });
+ assert(stackTraceResponse.success);
+ const scopesResponse = await dc.scopesRequest({ frameId: stackTraceResponse.body.stackFrames[0].id });
+ assert(scopesResponse.success);
+ const variablesResponse = await dc.variablesRequest({
+ variablesReference: scopesResponse.body.scopes[0].variablesReference
+ });
+ assert(variablesResponse.success);
+ // Locate the variable with the matching name.
+ const i = variablesResponse.body.variables.findIndex((v) => v.name === name);
+ assert(i >= 0);
+ // Check that the value of name is val.
+ assert.strictEqual(variablesResponse.body.variables[i].value, val);
+ }
+
+ suite.skip('basic', () => { // SKIP
+
+ test('unknown request should produce error', async () => {
+ const config = {name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT};
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+ await dc.send('illegal_request').then(() => {
+ Promise.reject(new Error('does not report error on unknown request'));
+ }).catch(() => {
+ Promise.resolve();
+ });
+ });
+ });
+
+ suite.skip('initialize', () => { // SKIP
+
+ test('should return supported features', async () => {
+ const config = {name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT};
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+ await dc.initializeRequest().then((response) => {
+ response.body = response.body || {};
+ assert.strictEqual(response.body.supportsConditionalBreakpoints, true);
+ assert.strictEqual(response.body.supportsConfigurationDoneRequest, true);
+ assert.strictEqual(response.body.supportsSetVariable, true);
+ });
+ });
+
+ test.skip('should produce error for invalid \'pathFormat\'', async () => { // SKIP
+ const config = {name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT};
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+ await dc.initializeRequest({
+ adapterID: 'mock',
+ linesStartAt1: true,
+ columnsStartAt1: true,
+ pathFormat: 'url'
+ }).then((response) => {
+ Promise.reject(new Error('does not report error on invalid \'pathFormat\' attribute'));
+ }).catch((err) => {
+ // error expected
+ Promise.resolve();
+ });
+ });
+ });
+
+ suite('launch', () => {
+ test('should run program to the end', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should stop on entry', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ // The debug adapter does not support a stack trace request
+ // when there are no goroutines running. Which is true when it is stopped
+ // on entry. Therefore we would need another method from dc.assertStoppedLocation
+ // to check the debugger is stopped on entry.
+ dc.waitForEvent('stopped').then((event) => {
+ const stevent = event as DebugProtocol.StoppedEvent;
+ assert.strictEqual(stevent.body.reason, 'entry');
+ })
+ ]);
+ });
+
+ test('should debug a file', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest', 'test.go');
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should debug a single test', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'test',
+ program: PROGRAM,
+ args: [
+ '-test.run',
+ 'TestMe'
+ ]
+ };
+
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should debug a test package', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'test',
+ program: PROGRAM
+ };
+
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test.skip('invalid flags are passed to dlv but should be caught by dlv', async () => { // SKIP
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ dlvFlags: ['--invalid']
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+ return Promise.all([
+ dc.assertOutput('stderr', 'Error: unknown flag: --invalid\n', 5000),
+ dc.waitForEvent('terminated'),
+ dc.initializeRequest().then((response) => {
+ // The current debug adapter does not respond to launch request but,
+ // instead, sends error messages and TerminatedEvent as delve is closed.
+ // The promise from dc.launchRequest resolves when the launch response
+ // is received, so the promise will never get resolved.
+ dc.launchRequest(debugConfig as any);
+ })
+ ]);
+ });
+
+ test('should handle threads request after initialization', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.configurationSequence().then(() => {
+ dc.threadsRequest().then((response) => {
+ assert.ok(response.success);
+ });
+ }),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated'),
+ ]);
+ });
+
+ test('should handle delayed initial threads request', async () => {
+ // If the program exits very quickly, the initial threadsRequest
+ // will complete after it has exited.
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+
+ const response = await dc.threadsRequest();
+ assert.ok(response.success);
+ });
+
+ test('user-specified --listen flag should be ignored', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ dlvFlags: ['--listen=127.0.0.1:80'],
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+ });
+
+ suite.skip('set current working directory', () => { // SKIP
+ test('should debug program with cwd set', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest');
+ const FILE = path.join(PROGRAM, 'main.go');
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ cwd: WD,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ await assertVariableValue('strdat', '"Hello, World!"');
+ });
+
+ test('should debug program without cwd set', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest');
+ const FILE = path.join(PROGRAM, 'main.go');
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ await assertVariableValue('strdat', '"Goodbye, World."');
+ });
+
+ test('should debug file program with cwd set', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
+ const FILE = PROGRAM;
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ cwd: WD,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ await assertVariableValue('strdat', '"Hello, World!"');
+ });
+
+ test('should debug file program without cwd set', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
+ const FILE = PROGRAM;
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ await assertVariableValue('strdat', '"Goodbye, World."');
+ });
+
+ test('should run program with cwd set (noDebug)', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ cwd: WD,
+ noDebug: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.launch(debugConfig),
+ dc.waitForEvent('output').then((event) => {
+ assert.strictEqual(event.body.output, 'Hello, World!\n');
+ })
+ ]);
+ });
+
+ test('should run program without cwd set (noDebug)', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ noDebug: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.launch(debugConfig),
+ dc.waitForEvent('output').then((event) => {
+ assert.strictEqual(event.body.output, 'Goodbye, World.\n');
+ })
+ ]);
+ });
+
+ test('should run file program with cwd set (noDebug)', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ cwd: WD,
+ noDebug: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.launch(debugConfig),
+ dc.waitForEvent('output').then((event) => {
+ assert.strictEqual(event.body.output, 'Hello, World!\n');
+ })
+ ]);
+ });
+
+ test('should run file program without cwd set (noDebug)', async () => {
+ const WD = path.join(DATA_ROOT, 'cwdTest');
+ const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ noDebug: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+ dc.launch(debugConfig),
+ dc.waitForEvent('output').then((event) => {
+ assert.strictEqual(event.body.output, 'Goodbye, World.\n');
+ })
+ ]);
+ });
+
+ });
+
+ suite.skip('remote attach', () => { // SKIP
+ let childProcess: cp.ChildProcess;
+ let server: number;
+ let debugConfig: DebugConfiguration;
+ setup(async () => {
+ server = await getPort();
+ remoteAttachConfig.port = await getPort();
+ debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+ });
+
+ teardown(async () => {
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(childProcess);
+ // Wait 2 seconds for the process to be killed.
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=true',
+ async () => {
+ childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, true, true);
+
+ await setUpRemoteAttach(debugConfig);
+ });
+
+ test('can connect and initialize using external dlv --headless --accept-multiclient=false --continue=false',
+ async () => {
+ childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, false, false);
+
+ await setUpRemoteAttach(debugConfig);
+ });
+
+ test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=false',
+ async () => {
+ childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, true, false);
+
+ await setUpRemoteAttach(debugConfig);
+ });
+ });
+
+ // The file paths returned from delve use '/' not the native path
+ // separator, so we can replace any instances of '\' with '/', which
+ // allows the hitBreakpoint check to match.
+ const getBreakpointLocation = (FILE: string, LINE: number, useBackSlash = true) => {
+ return { path: useBackSlash ? FILE.replace(/\\/g, '/') : FILE, line: LINE };
+ };
+
+ suite.skip('setBreakpoints', () => { // SKIP
+ let server: number;
+ let remoteAttachDebugConfig: DebugConfiguration;
+ setup(async () => {
+ server = await getPort();
+ remoteAttachConfig.port = await getPort();
+ remoteAttachDebugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+ const {port} = await startDapServer(remoteAttachDebugConfig);
+ await dc.start(port);
+ });
+
+ test('should stop on a breakpoint', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const FILE = path.join(DATA_ROOT, 'baseTest', 'test.go');
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ });
+
+ test('should stop on a breakpoint in test file', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+ const FILE = path.join(DATA_ROOT, 'baseTest', 'sample_test.go');
+ const BREAKPOINT_LINE = 15;
+
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'test',
+ program: PROGRAM
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ });
+
+ test('stopped for a breakpoint set during initialization (remote attach)', async () => {
+ const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ // Setup attach with a breakpoint.
+ await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('stopped for a breakpoint set after initialization (remote attach)', async () => {
+ const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ // Setup attach without a breakpoint.
+ await setUpRemoteAttach(remoteAttachDebugConfig);
+
+ // Now sets a breakpoint.
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+ const breakpointsResult = await dc.setBreakpointsRequest(
+ { source: { path: breakpointLocation.path }, breakpoints: [breakpointLocation] });
+ assert.ok(breakpointsResult.success && breakpointsResult.body.breakpoints[0].verified);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('stopped for a breakpoint set during initialization (remote attach)', async () => {
+ const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
+
+ // Setup attach with a breakpoint.
+ await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('should set breakpoints during continue', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'sleep');
+
+ const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
+ const HELLO_LINE = 10;
+ const helloLocation = getBreakpointLocation(FILE, HELLO_LINE);
+
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig),
+ ]);
+
+ return Promise.all([
+ dc.setBreakpointsRequest({
+ lines: [helloLocation.line],
+ breakpoints: [{ line: helloLocation.line, column: 0 }],
+ source: { path: helloLocation.path }
+ }),
+ dc.assertStoppedLocation('breakpoint', helloLocation)
+ ]);
+ });
+
+ async function setBreakpointsDuringStep(nextFunc: () => void) {
+ const PROGRAM = path.join(DATA_ROOT, 'sleep');
+
+ const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
+ const SLEEP_LINE = 11;
+ const setupBreakpoint = getBreakpointLocation(FILE, SLEEP_LINE);
+
+ const HELLO_LINE = 10;
+ const onNextBreakpoint = getBreakpointLocation(FILE, HELLO_LINE);
+
+ const config = {
+ name: 'Launch file',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await dc.hitBreakpoint(debugConfig, setupBreakpoint);
+
+ // The program is now stopped at the line containing time.Sleep().
+ // Issue a next request, followed by a setBreakpointsRequest.
+ nextFunc();
+
+ // Note: the current behavior of setting a breakpoint during a next
+ // request will cause the step to be interrupted, so it may not be
+ // stopped on the next line.
+ await Promise.all([
+ dc.setBreakpointsRequest({
+ lines: [onNextBreakpoint.line],
+ breakpoints: [{ line: onNextBreakpoint.line, column: 0 }],
+ source: { path: onNextBreakpoint.path }
+ }),
+ dc.assertStoppedLocation('next cancelled', {})
+ ]);
+
+ // Once the 'step' has completed, continue the program and
+ // make sure the breakpoint set while the program was nexting
+ // is succesfully hit.
+ await Promise.all([
+ dc.continueRequest({ threadId: 1 }),
+ dc.assertStoppedLocation('breakpoint', onNextBreakpoint)
+ ]);
+ }
+
+ test('should set breakpoints during next', async () => {
+ setBreakpointsDuringStep(async () => {
+ const nextResponse = await dc.nextRequest({ threadId: 1 });
+ assert.ok(nextResponse.success);
+ });
+ });
+
+ test('should set breakpoints during step out', async () => {
+ setBreakpointsDuringStep(async () => {
+ const stepOutResponse = await dc.stepOutRequest({ threadId: 1 });
+ assert.ok(stepOutResponse.success);
+ });
+ });
+ });
+
+ suite.skip('conditionalBreakpoints', () => { // SKIP
+ test('should stop on conditional breakpoint', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'condbp');
+ const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+ const BREAKPOINT_LINE = 7;
+ const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+ return Promise.all([
+
+ dc.waitForEvent('initialized').then(() => {
+ return dc.setBreakpointsRequest({
+ lines: [location.line],
+ breakpoints: [{ line: location.line, condition: 'i == 2' }],
+ source: { path: location.path }
+ });
+ }).then(() => {
+ return dc.configurationDoneRequest();
+ }),
+
+ dc.launch(debugConfig),
+
+ dc.assertStoppedLocation('breakpoint', location)
+
+ ]).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 1'.
+ assertVariableValue('i', '2')
+ );
+ });
+
+ test('should add breakpoint condition', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'condbp');
+ const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+ const BREAKPOINT_LINE = 7;
+ const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return dc.hitBreakpoint(debugConfig, location).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 0'.
+ assertVariableValue('i', '0')
+ ).then(() =>
+ // Add a condition to the breakpoint, and make sure it runs until 'i == 2'.
+ dc.setBreakpointsRequest({
+ lines: [location.line],
+ breakpoints: [{ line: location.line, condition: 'i == 2' }],
+ source: { path: location.path }
+ }).then(() =>
+ Promise.all([
+ dc.continueRequest({ threadId: 1 }),
+ dc.assertStoppedLocation('breakpoint', location)
+ ]).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 2'.
+ assertVariableValue('i', '2')
+ )
+ )
+ );
+ });
+
+ test('should remove breakpoint condition', async () => {
+
+ const PROGRAM = path.join(DATA_ROOT, 'condbp');
+ const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
+ const BREAKPOINT_LINE = 7;
+ const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+ return Promise.all([
+
+ dc.waitForEvent('initialized').then(() => {
+ return dc.setBreakpointsRequest({
+ lines: [location.line],
+ breakpoints: [{ line: location.line, condition: 'i == 2' }],
+ source: { path: location.path }
+ });
+ }).then(() => {
+ return dc.configurationDoneRequest();
+ }),
+
+ dc.launch(debugConfig),
+
+ dc.assertStoppedLocation('breakpoint', location)
+
+ ]).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 2'.
+ assertVariableValue('i', '2')
+ ).then(() =>
+ // Remove the breakpoint condition, and make sure the program runs until 'i == 3'.
+ dc.setBreakpointsRequest({
+ lines: [location.line],
+ breakpoints: [{ line: location.line }],
+ source: { path: location.path }
+ }).then(() =>
+ Promise.all([
+ dc.continueRequest({ threadId: 1 }),
+ dc.assertStoppedLocation('breakpoint', location)
+ ]).then(() =>
+ // The program is stopped at the breakpoint, check to make sure 'i == 3'.
+ assertVariableValue('i', '3')
+ )
+ )
+ );
+ });
+ });
+
+ suite('panicBreakpoints', () => {
+
+ test('should stop on panic', async () => {
+
+ const PROGRAM_WITH_EXCEPTION = path.join(DATA_ROOT, 'panic');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM_WITH_EXCEPTION,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return Promise.all([
+
+ dc.waitForEvent('initialized').then(() => {
+ return dc.setExceptionBreakpointsRequest({
+ filters: ['all']
+ });
+ }).then(() => {
+ return dc.configurationDoneRequest();
+ }),
+
+ dc.launch(debugConfig),
+
+ dc.assertStoppedLocation('panic', {})
+ ]);
+ });
+ });
+
+ suite.skip('disconnect', () => { // SKIP
+ // The teardown code for the Go Debug Adapter test suite issues a disconnectRequest.
+ // In order for these tests to pass, the debug adapter must not fail if a
+ // disconnectRequest is sent after it has already disconnected.
+
+ test('disconnect should work for remote attach', async () => {
+ const server = await getPort();
+ remoteAttachConfig.port = await getPort();
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ // Setup attach.
+ await setUpRemoteAttach(debugConfig);
+
+ // Calls the helloworld server to get a response.
+ let response = '';
+ await new Promise<void>((resolve) => {
+ http.get(`http://localhost:${server}`, (res) => {
+ res.on('data', (data) => response += data);
+ res.on('end', () => resolve());
+ });
+ });
+
+ await dc.disconnectRequest();
+ // Checks that after the disconnect, the helloworld server still works.
+ let secondResponse = '';
+ await new Promise<void>((resolve) => {
+ http.get(`http://localhost:${server}`, (res) => {
+ res.on('data', (data) => secondResponse += data);
+ res.on('end', () => resolve());
+ });
+ });
+ assert.strictEqual(response, 'Hello, world!');
+ assert.strictEqual(response, secondResponse);
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ test('should disconnect while continuing on entry', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: false
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect with multiple disconnectRequests', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: false
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ await Promise.all([
+ dc.disconnectRequest({ restart: false }).then(() =>
+ dc.disconnectRequest({ restart: false })
+ ),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect after continue', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ const continueResponse = await dc.continueRequest({ threadId: 1 });
+ assert.ok(continueResponse.success);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect while nexting', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'sleep');
+ const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
+ const BREAKPOINT_LINE = 11;
+ const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: false
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await dc.hitBreakpoint(debugConfig, location);
+
+ const nextResponse = await dc.nextRequest({ threadId: 1 });
+ assert.ok(nextResponse.success);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect while paused on pause', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ const pauseResponse = await dc.pauseRequest({ threadId: 1 });
+ assert.ok(pauseResponse.success);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated'),
+ ]);
+ });
+
+ test('should disconnect while paused on breakpoint', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+ const FILE = path.join(PROGRAM, 'loop.go');
+ const BREAKPOINT_LINE = 5;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect while paused on entry', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+
+ test('should disconnect while paused on next', async () => {
+ const PROGRAM = path.join(DATA_ROOT, 'loop');
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: PROGRAM,
+ stopOnEntry: true
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ await Promise.all([
+ dc.configurationSequence(),
+ dc.launch(debugConfig)
+ ]);
+
+ const nextResponse = await dc.nextRequest({ threadId: 1 });
+ assert.ok(nextResponse.success);
+
+ return Promise.all([
+ dc.disconnectRequest({ restart: false }),
+ dc.waitForEvent('terminated')
+ ]);
+ });
+ });
+
+ suite.skip('substitute path', () => { // SKIP
+ // TODO(suzmue): add unit tests for substitutePath.
+ let tmpDir: string;
+
+ suiteSetup(() => {
+ tmpDir = fs.mkdtempSync(path.join(DATA_ROOT, 'substitutePathTest'));
+ });
+
+ suiteTeardown(() => {
+ rmdirRecursive(tmpDir);
+ });
+
+ function copyDirectory(name: string) {
+ const from = path.join(DATA_ROOT, name);
+ const to = path.join(tmpDir, name);
+ fs.mkdirSync(to);
+ fs.readdirSync(from).forEach((file) => {
+ fs.copyFileSync(path.join(from, file), path.join(to, file));
+ });
+ return to;
+ }
+
+ async function buildGoProgram(cwd: string, outputFile: string): Promise<string> {
+ const goRuntimePath = getBinPath('go');
+ const execFile = util.promisify(cp.execFile);
+ const child = await execFile(goRuntimePath,
+ ['build', '-o', outputFile, `--gcflags='all=-N -l'`, '.'],
+ { cwd });
+ if (child.stderr.length > 0) {
+ throw Error(child.stderr);
+ }
+ return outputFile;
+ }
+
+ suite('substitutePath with missing files', () => {
+ let goBuildOutput: string;
+ suiteSetup(() => {
+ goBuildOutput = fs.mkdtempSync(path.join(tmpdir(), 'output'));
+ });
+
+ suiteTeardown(() => {
+ rmdirRecursive(goBuildOutput);
+ });
+
+ async function copyBuildDelete(program: string): Promise<{ program: string, output: string }> {
+ const wd = copyDirectory(program);
+ const output = await buildGoProgram(wd, path.join(goBuildOutput, program));
+ rmdirRecursive(wd);
+ return { program: wd, output };
+ }
+
+ test('should stop on a breakpoint set in file with substituted path', async () => {
+ const { program, output } = await copyBuildDelete('baseTest');
+ const FILE = path.join(DATA_ROOT, 'baseTest', 'test.go');
+ const BREAKPOINT_LINE = 11;
+
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'exec',
+ program: output,
+ substitutePath: [
+ {
+ from: path.join(DATA_ROOT, 'baseTest'),
+ to: program
+ }
+ ]
+ };
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const {port} = await startDapServer(debugConfig);
+ await dc.start(port);
+
+ return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ });
+ });
+
+ suite.skip('substitutePath with remote program', () => { // SKIP
+ let server: number;
+ let remoteAttachDebugConfig: DebugConfiguration;
+ let helloWorldLocal: string;
+ let helloWorldRemote: string;
+ setup(async () => {
+ server = await getPort();
+ remoteAttachConfig.port = await getPort();
+ remoteAttachDebugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+ const {port} = await startDapServer(remoteAttachDebugConfig);
+ await dc.start(port);
+ });
+
+ suiteSetup(() => {
+ helloWorldLocal = copyDirectory('helloWorldServer');
+ helloWorldRemote = path.join(DATA_ROOT, 'helloWorldServer');
+ });
+
+ suiteTeardown(() => {
+ rmdirRecursive(helloWorldLocal);
+ });
+
+ test('stopped for a breakpoint set during initialization using substitutePath (remote attach)', async () => {
+ const FILE = path.join(helloWorldLocal, 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
+ // Setup attach with a breakpoint.
+ remoteAttachDebugConfig.cwd = tmpDir;
+ remoteAttachDebugConfig.remotePath = '';
+ remoteAttachDebugConfig.substitutePath = [
+ { from: helloWorldLocal, to: helloWorldRemote }
+ ];
+ await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+
+ // Skip because it times out in nightly release workflow.
+ // BUG(https://github.com/golang/vscode-go/issues/1043)
+ test.skip('stopped for a breakpoint set during initialization using remotePath (remote attach)', async () => {
+ const FILE = path.join(helloWorldLocal, 'main.go');
+ const BREAKPOINT_LINE = 29;
+ const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
+
+ const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
+ // Setup attach with a breakpoint.
+ remoteAttachDebugConfig.cwd = helloWorldLocal;
+ remoteAttachDebugConfig.remotePath = helloWorldRemote;
+ // This is a bad mapping, make sure that the remotePath config is used first.
+ remoteAttachDebugConfig.substitutePath = [
+ { from: helloWorldLocal, to: helloWorldLocal }
+ ];
+ await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
+
+ // Calls the helloworld server to make the breakpoint hit.
+ await waitForBreakpoint(
+ () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
+ breakpointLocation);
+
+ await dc.disconnectRequest({ restart: false });
+ await killProcessTree(remoteProgram);
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
+ });
+ });
+ });
+
+});
+
diff --git a/test/integration/goDebug.test.ts b/test/integration/goDebug.test.ts
index e3b48ee..09ee29d 100644
--- a/test/integration/goDebug.test.ts
+++ b/test/integration/goDebug.test.ts
@@ -1,16 +1,12 @@
+/*---------------------------------------------------------
+ * Copyright 2021 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
import * as assert from 'assert';
-import * as cp from 'child_process';
import * as fs from 'fs';
-import getPort = require('get-port');
-import * as http from 'http';
-import { tmpdir } from 'os';
import * as path from 'path';
import * as sinon from 'sinon';
-import util = require('util');
-import { DebugConfiguration } from 'vscode';
-import { DebugClient } from 'vscode-debugadapter-testsupport';
-import { ILocation } from 'vscode-debugadapter-testsupport/lib/debugClient';
-import { DebugProtocol } from 'vscode-debugprotocol';
import {
Delve,
escapeGoModPath,
@@ -18,9 +14,6 @@
PackageBuildInfo,
RemoteSourcesAndPackages,
} from '../../src/debugAdapter/goDebug';
-import { GoDebugConfigurationProvider } from '../../src/goDebugConfiguration';
-import { getBinPath, getGoVersion, rmdirRecursive } from '../../src/util';
-import { killProcessTree } from '../../src/utils/processUtils';
suite('Path Manipulation Tests', () => {
test('escapeGoModPath works', () => {
@@ -279,1359 +272,3 @@
assert.deepEqual(remoteSourcesAndPackages.remotePackagesBuildInfo, [helloPackage, testPackage]);
});
});
-
-// Test suite adapted from:
-// https://github.com/microsoft/vscode-mock-debug/blob/master/src/tests/adapter.test.ts
-suite('Go Debug Adapter', function () {
- this.timeout(60_000);
-
- const debugConfigProvider = new GoDebugConfigurationProvider();
- const DEBUG_ADAPTER = path.join('.', 'out', 'src', 'debugAdapter', 'goDebug.js');
-
- const PROJECT_ROOT = path.normalize(path.join(__dirname, '..', '..', '..'));
- const DATA_ROOT = path.join(PROJECT_ROOT, 'test', 'testdata');
-
- const remoteAttachConfig = {
- name: 'Attach',
- type: 'go',
- request: 'attach',
- mode: 'remote',
- host: '127.0.0.1',
- port: 3456,
- };
-
- let dc: DebugClient;
-
- setup(() => {
- dc = new DebugClient('node', path.join(PROJECT_ROOT, DEBUG_ADAPTER), 'go', undefined, true);
-
- // Launching delve may take longer than the default timeout of 5000.
- dc.defaultTimeout = 20_000;
-
- // To connect to a running debug server for debugging the tests, specify PORT.
- return dc.start();
- });
-
- teardown(() => dc.stop());
-
- /**
- * This function sets up a server that returns helloworld on serverPort.
- * The server will be started as a Delve remote headless instance
- * that will listen on the specified dlvPort.
- * We are using a server as opposed to a long-running program
- * because we can use responses to better test when the program is
- * running vs stopped/killed.
- */
- async function setUpRemoteProgram(
- dlvPort: number, serverPort: number,
- acceptMultiClient = true, continueOnStart = true): Promise<cp.ChildProcess> {
- const serverFolder = path.join(DATA_ROOT, 'helloWorldServer');
- const toolPath = getBinPath('dlv');
- const args = ['debug', '--api-version=2', '--headless', `--listen=127.0.0.1:${dlvPort}`];
- if (acceptMultiClient) {
- args.push('--accept-multiclient');
- }
- if (continueOnStart) {
- args.push('--continue');
- }
- const childProcess = cp.spawn(toolPath, args,
- { cwd: serverFolder, env: { PORT: `${serverPort}`, ...process.env } });
-
- // Give dlv a few seconds to start.
- await new Promise((resolve) => setTimeout(resolve, 10_000));
- return childProcess;
- }
-
- /**
- * Helper function to set up remote attach configuration.
- * This will issue an initializeRequest, followed by attachRequest.
- * It will then wait for an initializedEvent before sending a breakpointRequest
- * if breakpoints are provided. Lastly the configurationDoneRequest will be sent.
- * NOTE: For simplicity, this function assumes the breakpoints are in the same file.
- */
- async function setUpRemoteAttach(config: DebugConfiguration, breakpoints: ILocation[] = []): Promise<void> {
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
- console.log(`Sending initializing request for remote attach setup.`);
- const initializedResult = await dc.initializeRequest();
- assert.ok(initializedResult.success);
-
- // When the attach request is completed successfully, we should get
- // an initialized event.
- await Promise.all([
- new Promise<void>(async (resolve) => {
- console.log(`Setting up attach request for ${JSON.stringify(debugConfig)}.`);
- const attachResult = await dc.attachRequest(debugConfig as DebugProtocol.AttachRequestArguments);
- assert.ok(attachResult.success);
- resolve();
- }),
- dc.waitForEvent('initialized')
- ]);
-
- if (breakpoints.length) {
- console.log(`Sending set breakpoints request for remote attach setup.`);
- const breakpointsResult = await dc.setBreakpointsRequest({ source: { path: breakpoints[0].path }, breakpoints });
- assert.ok(breakpointsResult.success && breakpointsResult.body.breakpoints.length === breakpoints.length);
- // Verify that there are no non-verified breakpoints.
- breakpointsResult.body.breakpoints.forEach((breakpoint) => {
- assert.ok(breakpoint.verified);
- });
- }
- console.log(`Sending configuration done request for remote attach setup.`);
- const configurationDoneResult = await dc.configurationDoneRequest();
- assert.ok(configurationDoneResult.success);
- }
-
- /**
- * Helper function to retrieve a stopped event for a breakpoint.
- * This function will keep calling action() until we receive a stoppedEvent.
- * Will return undefined if the result of repeatedly calling action does not
- * induce a stoppedEvent.
- */
- async function waitForBreakpoint(action: () => void, breakpoint: ILocation): Promise<void> {
- const assertStoppedLocation = dc.assertStoppedLocation('breakpoint', breakpoint);
- await new Promise((res) => setTimeout(res, 1_000));
- action();
- await assertStoppedLocation;
- }
-
- /**
- * Helper function to assert that a variable has a particular value.
- * This should be called when the program is stopped.
- *
- * The following requests are issued by this function to determine the
- * value of the variable:
- * 1. threadsRequest
- * 2. stackTraceRequest
- * 3. scopesRequest
- * 4. variablesRequest
- */
- async function assertVariableValue(name: string, val: string): Promise<void> {
- const threadsResponse = await dc.threadsRequest();
- assert(threadsResponse.success);
- const stackTraceResponse = await dc.stackTraceRequest({ threadId: threadsResponse.body.threads[0].id });
- assert(stackTraceResponse.success);
- const scopesResponse = await dc.scopesRequest({ frameId: stackTraceResponse.body.stackFrames[0].id });
- assert(scopesResponse.success);
- const variablesResponse = await dc.variablesRequest({
- variablesReference: scopesResponse.body.scopes[0].variablesReference
- });
- assert(variablesResponse.success);
- // Locate the variable with the matching name.
- const i = variablesResponse.body.variables.findIndex((v) => v.name === name);
- assert(i >= 0);
- // Check that the value of name is val.
- assert.strictEqual(variablesResponse.body.variables[i].value, val);
- }
-
- suite('basic', () => {
-
- test('unknown request should produce error', (done) => {
- dc.send('illegal_request').then(() => {
- done(new Error('does not report error on unknown request'));
- }).catch(() => {
- done();
- });
- });
- });
-
- suite('initialize', () => {
-
- test('should return supported features', () => {
- return dc.initializeRequest().then((response) => {
- response.body = response.body || {};
- assert.strictEqual(response.body.supportsConditionalBreakpoints, true);
- assert.strictEqual(response.body.supportsConfigurationDoneRequest, true);
- assert.strictEqual(response.body.supportsSetVariable, true);
- });
- });
-
- test('should produce error for invalid \'pathFormat\'', (done) => {
- dc.initializeRequest({
- adapterID: 'mock',
- linesStartAt1: true,
- columnsStartAt1: true,
- pathFormat: 'url'
- }).then((response) => {
- done(new Error('does not report error on invalid \'pathFormat\' attribute'));
- }).catch((err) => {
- // error expected
- done();
- });
- });
- });
-
- suite('launch', () => {
- test('should run program to the end', async () => {
-
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should stop on entry', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- stopOnEntry: true
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig),
- // The debug adapter does not support a stack trace request
- // when there are no goroutines running. Which is true when it is stopped
- // on entry. Therefore we would need another method from dc.assertStoppedLocation
- // to check the debugger is stopped on entry.
- dc.waitForEvent('stopped').then((event) => {
- const stevent = event as DebugProtocol.StoppedEvent;
- assert.strictEqual(stevent.body.reason, 'entry');
- })
- ]);
- });
-
- test('should debug a file', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'baseTest', 'test.go');
- const config = {
- name: 'Launch file',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
-
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should debug a single test', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
- const config = {
- name: 'Launch file',
- type: 'go',
- request: 'launch',
- mode: 'test',
- program: PROGRAM,
- args: [
- '-test.run',
- 'TestMe'
- ]
- };
-
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should debug a test package', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
- const config = {
- name: 'Launch file',
- type: 'go',
- request: 'launch',
- mode: 'test',
- program: PROGRAM
- };
-
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('invalid flags are passed to dlv but should be caught by dlv', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- dlvFlags: ['--invalid']
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
- return Promise.all([
- dc.assertOutput('stderr', 'Error: unknown flag: --invalid\n', 5000),
- dc.waitForEvent('terminated'),
- dc.initializeRequest().then((response) => {
- // The current debug adapter does not respond to launch request but,
- // instead, sends error messages and TerminatedEvent as delve is closed.
- // The promise from dc.launchRequest resolves when the launch response
- // is received, so the promise will never get resolved.
- dc.launchRequest(debugConfig as any);
- })
- ]);
- });
-
- test('should handle threads request after initialization', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.configurationSequence().then(() => {
- dc.threadsRequest().then((response) => {
- assert.ok(response.success);
- });
- }),
- dc.launch(debugConfig),
- dc.waitForEvent('terminated'),
- ]);
- });
-
- test('should handle delayed initial threads request', async () => {
- // If the program exits very quickly, the initial threadsRequest
- // will complete after it has exited.
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig),
- dc.waitForEvent('terminated')
- ]);
-
- const response = await dc.threadsRequest();
- assert.ok(response.success);
- });
-
- test('user-specified --listen flag should be ignored', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- dlvFlags: ['--listen=127.0.0.1:80'],
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig),
- dc.waitForEvent('terminated')
- ]);
- });
- });
-
- suite('set current working directory', () => {
- test('should debug program with cwd set', async () => {
- const WD = path.join(DATA_ROOT, 'cwdTest');
- const PROGRAM = path.join(WD, 'cwdTest');
- const FILE = path.join(PROGRAM, 'main.go');
- const BREAKPOINT_LINE = 11;
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- cwd: WD,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
-
- await assertVariableValue('strdat', '"Hello, World!"');
- });
-
- test('should debug program without cwd set', async () => {
- const WD = path.join(DATA_ROOT, 'cwdTest');
- const PROGRAM = path.join(WD, 'cwdTest');
- const FILE = path.join(PROGRAM, 'main.go');
- const BREAKPOINT_LINE = 11;
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
-
- await assertVariableValue('strdat', '"Goodbye, World."');
- });
-
- test('should debug file program with cwd set', async () => {
- const WD = path.join(DATA_ROOT, 'cwdTest');
- const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
- const FILE = PROGRAM;
- const BREAKPOINT_LINE = 11;
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- cwd: WD,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
-
- await assertVariableValue('strdat', '"Hello, World!"');
- });
-
- test('should debug file program without cwd set', async () => {
- const WD = path.join(DATA_ROOT, 'cwdTest');
- const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
- const FILE = PROGRAM;
- const BREAKPOINT_LINE = 11;
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
-
- await assertVariableValue('strdat', '"Goodbye, World."');
- });
-
- test('should run program with cwd set (noDebug)', async () => {
- const WD = path.join(DATA_ROOT, 'cwdTest');
- const PROGRAM = path.join(WD, 'cwdTest');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- cwd: WD,
- noDebug: true
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.launch(debugConfig),
- dc.waitForEvent('output').then((event) => {
- assert.strictEqual(event.body.output, 'Hello, World!\n');
- })
- ]);
- });
-
- test('should run program without cwd set (noDebug)', async () => {
- const WD = path.join(DATA_ROOT, 'cwdTest');
- const PROGRAM = path.join(WD, 'cwdTest');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- noDebug: true
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.launch(debugConfig),
- dc.waitForEvent('output').then((event) => {
- assert.strictEqual(event.body.output, 'Goodbye, World.\n');
- })
- ]);
- });
-
- test('should run file program with cwd set (noDebug)', async () => {
- const WD = path.join(DATA_ROOT, 'cwdTest');
- const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- cwd: WD,
- noDebug: true
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.launch(debugConfig),
- dc.waitForEvent('output').then((event) => {
- assert.strictEqual(event.body.output, 'Hello, World!\n');
- })
- ]);
- });
-
- test('should run file program without cwd set (noDebug)', async () => {
- const WD = path.join(DATA_ROOT, 'cwdTest');
- const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- noDebug: true
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
- dc.launch(debugConfig),
- dc.waitForEvent('output').then((event) => {
- assert.strictEqual(event.body.output, 'Goodbye, World.\n');
- })
- ]);
- });
-
- });
-
- suite('remote attach', () => {
- let childProcess: cp.ChildProcess;
- let server: number;
- let debugConfig: DebugConfiguration;
- setup(async () => {
- server = await getPort();
- remoteAttachConfig.port = await getPort();
- debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
- });
-
- teardown(async () => {
- await dc.disconnectRequest({ restart: false });
- await killProcessTree(childProcess);
- // Wait 2 seconds for the process to be killed.
- await new Promise((resolve) => setTimeout(resolve, 2_000));
- });
-
- test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=true',
- async () => {
- childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, true, true);
-
- await setUpRemoteAttach(debugConfig);
- });
-
- test('can connect and initialize using external dlv --headless --accept-multiclient=false --continue=false',
- async () => {
- childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, false, false);
-
- await setUpRemoteAttach(debugConfig);
- });
-
- test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=false',
- async () => {
- childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, true, false);
-
- await setUpRemoteAttach(debugConfig);
- });
- });
-
- // The file paths returned from delve use '/' not the native path
- // separator, so we can replace any instances of '\' with '/', which
- // allows the hitBreakpoint check to match.
- const getBreakpointLocation = (FILE: string, LINE: number, useBackSlash = true) => {
- return { path: useBackSlash ? FILE.replace(/\\/g, '/') : FILE, line: LINE };
- };
-
- suite('setBreakpoints', () => {
- let server: number;
- let remoteAttachDebugConfig: DebugConfiguration;
- setup(async () => {
- server = await getPort();
- remoteAttachConfig.port = await getPort();
- remoteAttachDebugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
- });
-
- test('should stop on a breakpoint', async () => {
-
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
-
- const FILE = path.join(DATA_ROOT, 'baseTest', 'test.go');
- const BREAKPOINT_LINE = 11;
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
- });
-
- test('should stop on a breakpoint in test file', async () => {
-
- const PROGRAM = path.join(DATA_ROOT, 'baseTest');
-
- const FILE = path.join(DATA_ROOT, 'baseTest', 'sample_test.go');
- const BREAKPOINT_LINE = 15;
-
- const config = {
- name: 'Launch file',
- type: 'go',
- request: 'launch',
- mode: 'test',
- program: PROGRAM
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
- });
-
- test('stopped for a breakpoint set during initialization (remote attach)', async () => {
- const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
- const BREAKPOINT_LINE = 29;
- const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
-
- const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE);
-
- // Setup attach with a breakpoint.
- await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
-
- // Calls the helloworld server to make the breakpoint hit.
- await waitForBreakpoint(
- () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
- breakpointLocation);
-
- await dc.disconnectRequest({ restart: false });
- await killProcessTree(remoteProgram);
- await new Promise((resolve) => setTimeout(resolve, 2_000));
- });
-
- test('stopped for a breakpoint set after initialization (remote attach)', async () => {
- const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
- const BREAKPOINT_LINE = 29;
- const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
-
- // Setup attach without a breakpoint.
- await setUpRemoteAttach(remoteAttachDebugConfig);
-
- // Now sets a breakpoint.
- const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE);
- const breakpointsResult = await dc.setBreakpointsRequest(
- { source: { path: breakpointLocation.path }, breakpoints: [breakpointLocation] });
- assert.ok(breakpointsResult.success && breakpointsResult.body.breakpoints[0].verified);
-
- // Calls the helloworld server to make the breakpoint hit.
- await waitForBreakpoint(
- () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
- breakpointLocation);
-
- await dc.disconnectRequest({ restart: false });
- await killProcessTree(remoteProgram);
- await new Promise((resolve) => setTimeout(resolve, 2_000));
- });
-
- test('stopped for a breakpoint set during initialization (remote attach)', async () => {
- const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
- const BREAKPOINT_LINE = 29;
- const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
-
- const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
-
- // Setup attach with a breakpoint.
- await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
-
- // Calls the helloworld server to make the breakpoint hit.
- await waitForBreakpoint(
- () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
- breakpointLocation);
-
- await dc.disconnectRequest({ restart: false });
- await killProcessTree(remoteProgram);
- await new Promise((resolve) => setTimeout(resolve, 2_000));
- });
-
- test('should set breakpoints during continue', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'sleep');
-
- const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
- const HELLO_LINE = 10;
- const helloLocation = getBreakpointLocation(FILE, HELLO_LINE);
-
- const config = {
- name: 'Launch file',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig),
- ]);
-
- return Promise.all([
- dc.setBreakpointsRequest({
- lines: [helloLocation.line],
- breakpoints: [{ line: helloLocation.line, column: 0 }],
- source: { path: helloLocation.path }
- }),
- dc.assertStoppedLocation('breakpoint', helloLocation)
- ]);
- });
-
- async function setBreakpointsDuringStep(nextFunc: () => void) {
- const PROGRAM = path.join(DATA_ROOT, 'sleep');
-
- const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
- const SLEEP_LINE = 11;
- const setupBreakpoint = getBreakpointLocation(FILE, SLEEP_LINE);
-
- const HELLO_LINE = 10;
- const onNextBreakpoint = getBreakpointLocation(FILE, HELLO_LINE);
-
- const config = {
- name: 'Launch file',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await dc.hitBreakpoint(debugConfig, setupBreakpoint);
-
- // The program is now stopped at the line containing time.Sleep().
- // Issue a next request, followed by a setBreakpointsRequest.
- nextFunc();
-
- // Note: the current behavior of setting a breakpoint during a next
- // request will cause the step to be interrupted, so it may not be
- // stopped on the next line.
- await Promise.all([
- dc.setBreakpointsRequest({
- lines: [onNextBreakpoint.line],
- breakpoints: [{ line: onNextBreakpoint.line, column: 0 }],
- source: { path: onNextBreakpoint.path }
- }),
- dc.assertStoppedLocation('next cancelled', {})
- ]);
-
- // Once the 'step' has completed, continue the program and
- // make sure the breakpoint set while the program was nexting
- // is succesfully hit.
- await Promise.all([
- dc.continueRequest({ threadId: 1 }),
- dc.assertStoppedLocation('breakpoint', onNextBreakpoint)
- ]);
- }
-
- test('should set breakpoints during next', async () => {
- setBreakpointsDuringStep(async () => {
- const nextResponse = await dc.nextRequest({ threadId: 1 });
- assert.ok(nextResponse.success);
- });
- });
-
- test('should set breakpoints during step out', async () => {
- setBreakpointsDuringStep(async () => {
- const stepOutResponse = await dc.stepOutRequest({ threadId: 1 });
- assert.ok(stepOutResponse.success);
- });
- });
- });
-
- suite('conditionalBreakpoints', () => {
- test('should stop on conditional breakpoint', async () => {
-
- const PROGRAM = path.join(DATA_ROOT, 'condbp');
- const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
- const BREAKPOINT_LINE = 7;
- const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
- return Promise.all([
-
- dc.waitForEvent('initialized').then(() => {
- return dc.setBreakpointsRequest({
- lines: [location.line],
- breakpoints: [{ line: location.line, condition: 'i == 2' }],
- source: { path: location.path }
- });
- }).then(() => {
- return dc.configurationDoneRequest();
- }),
-
- dc.launch(debugConfig),
-
- dc.assertStoppedLocation('breakpoint', location)
-
- ]).then(() =>
- // The program is stopped at the breakpoint, check to make sure 'i == 1'.
- assertVariableValue('i', '2')
- );
- });
-
- test('should add breakpoint condition', async () => {
-
- const PROGRAM = path.join(DATA_ROOT, 'condbp');
- const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
- const BREAKPOINT_LINE = 7;
- const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return dc.hitBreakpoint(debugConfig, location).then(() =>
- // The program is stopped at the breakpoint, check to make sure 'i == 0'.
- assertVariableValue('i', '0')
- ).then(() =>
- // Add a condition to the breakpoint, and make sure it runs until 'i == 2'.
- dc.setBreakpointsRequest({
- lines: [location.line],
- breakpoints: [{ line: location.line, condition: 'i == 2' }],
- source: { path: location.path }
- }).then(() =>
- Promise.all([
- dc.continueRequest({ threadId: 1 }),
- dc.assertStoppedLocation('breakpoint', location)
- ]).then(() =>
- // The program is stopped at the breakpoint, check to make sure 'i == 2'.
- assertVariableValue('i', '2')
- )
- )
- );
- });
-
- test('should remove breakpoint condition', async () => {
-
- const PROGRAM = path.join(DATA_ROOT, 'condbp');
- const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
- const BREAKPOINT_LINE = 7;
- const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
- return Promise.all([
-
- dc.waitForEvent('initialized').then(() => {
- return dc.setBreakpointsRequest({
- lines: [location.line],
- breakpoints: [{ line: location.line, condition: 'i == 2' }],
- source: { path: location.path }
- });
- }).then(() => {
- return dc.configurationDoneRequest();
- }),
-
- dc.launch(debugConfig),
-
- dc.assertStoppedLocation('breakpoint', location)
-
- ]).then(() =>
- // The program is stopped at the breakpoint, check to make sure 'i == 2'.
- assertVariableValue('i', '2')
- ).then(() =>
- // Remove the breakpoint condition, and make sure the program runs until 'i == 3'.
- dc.setBreakpointsRequest({
- lines: [location.line],
- breakpoints: [{ line: location.line }],
- source: { path: location.path }
- }).then(() =>
- Promise.all([
- dc.continueRequest({ threadId: 1 }),
- dc.assertStoppedLocation('breakpoint', location)
- ]).then(() =>
- // The program is stopped at the breakpoint, check to make sure 'i == 3'.
- assertVariableValue('i', '3')
- )
- )
- );
- });
- });
-
- suite('panicBreakpoints', () => {
-
- test('should stop on panic', async () => {
-
- const PROGRAM_WITH_EXCEPTION = path.join(DATA_ROOT, 'panic');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM_WITH_EXCEPTION,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
-
- dc.waitForEvent('initialized').then(() => {
- return dc.setExceptionBreakpointsRequest({
- filters: ['all']
- });
- }).then(() => {
- return dc.configurationDoneRequest();
- }),
-
- dc.launch(debugConfig),
-
- dc.assertStoppedLocation('panic', {})
- ]);
- });
- });
-
- suite('disconnect', () => {
- // The teardown code for the Go Debug Adapter test suite issues a disconnectRequest.
- // In order for these tests to pass, the debug adapter must not fail if a
- // disconnectRequest is sent after it has already disconnected.
-
- test('disconnect should work for remote attach', async () => {
- const server = await getPort();
- remoteAttachConfig.port = await getPort();
- const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
-
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
-
- // Setup attach.
- await setUpRemoteAttach(debugConfig);
-
- // Calls the helloworld server to get a response.
- let response = '';
- await new Promise<void>((resolve) => {
- http.get(`http://localhost:${server}`, (res) => {
- res.on('data', (data) => response += data);
- res.on('end', () => resolve());
- });
- });
-
- await dc.disconnectRequest();
- // Checks that after the disconnect, the helloworld server still works.
- let secondResponse = '';
- await new Promise<void>((resolve) => {
- http.get(`http://localhost:${server}`, (res) => {
- res.on('data', (data) => secondResponse += data);
- res.on('end', () => resolve());
- });
- });
- assert.strictEqual(response, 'Hello, world!');
- assert.strictEqual(response, secondResponse);
- await killProcessTree(remoteProgram);
- await new Promise((resolve) => setTimeout(resolve, 2_000));
- });
-
- test('should disconnect while continuing on entry', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'loop');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- stopOnEntry: false
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig)
- ]);
-
- return Promise.all([
- dc.disconnectRequest({ restart: false }),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should disconnect with multiple disconnectRequests', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'loop');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- stopOnEntry: false
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig)
- ]);
-
- await Promise.all([
- dc.disconnectRequest({ restart: false }).then(() =>
- dc.disconnectRequest({ restart: false })
- ),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should disconnect after continue', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'loop');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- stopOnEntry: true
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig)
- ]);
-
- const continueResponse = await dc.continueRequest({ threadId: 1 });
- assert.ok(continueResponse.success);
-
- return Promise.all([
- dc.disconnectRequest({ restart: false }),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should disconnect while nexting', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'sleep');
- const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
- const BREAKPOINT_LINE = 11;
- const location = getBreakpointLocation(FILE, BREAKPOINT_LINE);
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- stopOnEntry: false
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await dc.hitBreakpoint(debugConfig, location);
-
- const nextResponse = await dc.nextRequest({ threadId: 1 });
- assert.ok(nextResponse.success);
-
- return Promise.all([
- dc.disconnectRequest({ restart: false }),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should disconnect while paused on pause', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'loop');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig)
- ]);
-
- const pauseResponse = await dc.pauseRequest({ threadId: 1 });
- assert.ok(pauseResponse.success);
-
- return Promise.all([
- dc.disconnectRequest({ restart: false }),
- dc.waitForEvent('terminated'),
- ]);
- });
-
- test('should disconnect while paused on breakpoint', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'loop');
- const FILE = path.join(PROGRAM, 'loop.go');
- const BREAKPOINT_LINE = 5;
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
-
- return Promise.all([
- dc.disconnectRequest({ restart: false }),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should disconnect while paused on entry', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'loop');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- stopOnEntry: true
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig)
- ]);
-
- return Promise.all([
- dc.disconnectRequest({ restart: false }),
- dc.waitForEvent('terminated')
- ]);
- });
-
- test('should disconnect while paused on next', async () => {
- const PROGRAM = path.join(DATA_ROOT, 'loop');
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'auto',
- program: PROGRAM,
- stopOnEntry: true
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- await Promise.all([
- dc.configurationSequence(),
- dc.launch(debugConfig)
- ]);
-
- const nextResponse = await dc.nextRequest({ threadId: 1 });
- assert.ok(nextResponse.success);
-
- return Promise.all([
- dc.disconnectRequest({ restart: false }),
- dc.waitForEvent('terminated')
- ]);
- });
- });
-
- suite('substitute path', () => {
- // TODO(suzmue): add unit tests for substitutePath.
- let tmpDir: string;
-
- suiteSetup(() => {
- tmpDir = fs.mkdtempSync(path.join(DATA_ROOT, 'substitutePathTest'));
- });
-
- suiteTeardown(() => {
- rmdirRecursive(tmpDir);
- });
-
- function copyDirectory(name: string) {
- const from = path.join(DATA_ROOT, name);
- const to = path.join(tmpDir, name);
- fs.mkdirSync(to);
- fs.readdirSync(from).forEach((file) => {
- fs.copyFileSync(path.join(from, file), path.join(to, file));
- });
- return to;
- }
-
- async function buildGoProgram(cwd: string, outputFile: string): Promise<string> {
- const goRuntimePath = getBinPath('go');
- const execFile = util.promisify(cp.execFile);
- const child = await execFile(goRuntimePath,
- ['build', '-o', outputFile, `--gcflags='all=-N -l'`, '.'],
- { cwd });
- if (child.stderr.length > 0) {
- throw Error(child.stderr);
- }
- return outputFile;
- }
-
- suite('substitutePath with missing files', () => {
- let goBuildOutput: string;
- suiteSetup(() => {
- goBuildOutput = fs.mkdtempSync(path.join(tmpdir(), 'output'));
- });
-
- suiteTeardown(() => {
- rmdirRecursive(goBuildOutput);
- });
-
- async function copyBuildDelete(program: string): Promise<{ program: string, output: string }> {
- const wd = copyDirectory(program);
- const output = await buildGoProgram(wd, path.join(goBuildOutput, program));
- rmdirRecursive(wd);
- return { program: wd, output };
- }
-
- test('should stop on a breakpoint set in file with substituted path', async () => {
- const { program, output } = await copyBuildDelete('baseTest');
- const FILE = path.join(DATA_ROOT, 'baseTest', 'test.go');
- const BREAKPOINT_LINE = 11;
-
- const config = {
- name: 'Launch',
- type: 'go',
- request: 'launch',
- mode: 'exec',
- program: output,
- substitutePath: [
- {
- from: path.join(DATA_ROOT, 'baseTest'),
- to: program
- }
- ]
- };
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
- });
- });
-
- suite('substitutePath with remote program', () => {
- let server: number;
- let remoteAttachDebugConfig: DebugConfiguration;
- let helloWorldLocal: string;
- let helloWorldRemote: string;
- setup(async () => {
- server = await getPort();
- remoteAttachConfig.port = await getPort();
- remoteAttachDebugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
- });
-
- suiteSetup(() => {
- helloWorldLocal = copyDirectory('helloWorldServer');
- helloWorldRemote = path.join(DATA_ROOT, 'helloWorldServer');
- });
-
- suiteTeardown(() => {
- rmdirRecursive(helloWorldLocal);
- });
-
- test('stopped for a breakpoint set during initialization using substitutePath (remote attach)', async () => {
- const FILE = path.join(helloWorldLocal, 'main.go');
- const BREAKPOINT_LINE = 29;
- const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
-
- const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
- // Setup attach with a breakpoint.
- remoteAttachDebugConfig.cwd = tmpDir;
- remoteAttachDebugConfig.remotePath = '';
- remoteAttachDebugConfig.substitutePath = [
- { from: helloWorldLocal, to: helloWorldRemote }
- ];
- await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
-
- // Calls the helloworld server to make the breakpoint hit.
- await waitForBreakpoint(
- () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
- breakpointLocation);
-
- await dc.disconnectRequest({ restart: false });
- await killProcessTree(remoteProgram);
- await new Promise((resolve) => setTimeout(resolve, 2_000));
- });
-
- // Skip because it times out in nightly release workflow.
- // BUG(https://github.com/golang/vscode-go/issues/1043)
- test.skip('stopped for a breakpoint set during initialization using remotePath (remote attach)', async () => {
- const FILE = path.join(helloWorldLocal, 'main.go');
- const BREAKPOINT_LINE = 29;
- const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
-
- const breakpointLocation = getBreakpointLocation(FILE, BREAKPOINT_LINE, false);
- // Setup attach with a breakpoint.
- remoteAttachDebugConfig.cwd = helloWorldLocal;
- remoteAttachDebugConfig.remotePath = helloWorldRemote;
- // This is a bad mapping, make sure that the remotePath config is used first.
- remoteAttachDebugConfig.substitutePath = [
- { from: helloWorldLocal, to: helloWorldLocal }
- ];
- await setUpRemoteAttach(remoteAttachDebugConfig, [breakpointLocation]);
-
- // Calls the helloworld server to make the breakpoint hit.
- await waitForBreakpoint(
- () => http.get(`http://localhost:${server}`).on('error', (data) => console.log(data)),
- breakpointLocation);
-
- await dc.disconnectRequest({ restart: false });
- await killProcessTree(remoteProgram);
- await new Promise((resolve) => setTimeout(resolve, 2_000));
- });
- });
- });
-
-});
diff --git a/test/testdata/helloWorldServer/__debug_bin b/test/testdata/helloWorldServer/__debug_bin
new file mode 100755
index 0000000..8ea5a93
--- /dev/null
+++ b/test/testdata/helloWorldServer/__debug_bin
Binary files differ
diff --git a/tools/generateDlvDapTest.go b/tools/generateDlvDapTest.go
new file mode 100644
index 0000000..c1b308e
--- /dev/null
+++ b/tools/generateDlvDapTest.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+func main() {
+ // Assume this is running from the vscode-go directory.
+ dir, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+ // Find the package.json file.
+ data, err := ioutil.ReadFile(filepath.Join(dir, "test", "integration", "debugAdapter.test.ts"))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ lines := strings.Split(string(data), "\n")
+ output := "// Everything in this file is generated. DO NOT EDIT.\n// To generate, run 'go run tools/generateDlvDapTest.go'\n"
+ for _, line := range lines {
+ if strings.Contains(line, "// REMOVE") {
+ continue
+ }
+ if strings.Contains(line, "// ADD:") {
+ line = line[0:strings.Index(line, "// ADD:")] + line[strings.Index(line, "// ADD:")+len("// ADD:"):]
+ }
+ if strings.Contains(line, "// SKIP") {
+ line = strings.Replace(line, "test(", "test.skip(", 1)
+ line = strings.Replace(line, "suite(", "suite.skip(", 1)
+ }
+
+ output += fmt.Sprintln(line)
+ if strings.Contains(line, "debugConfigProvider.resolveDebugConfiguration") {
+ // dlv-dap needs any dlv flags and the program location on startup.
+ // We start the dlv-dap server after the debug configuration has been
+ // resolved.
+ prefix := ""
+ for c := 0; c < strings.Count(line, string('\t')); c++ {
+ prefix += string('\t')
+ }
+ line = line[0:strings.Index(line, "=")]
+ name := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "const"))
+ output += fmt.Sprintln(prefix + fmt.Sprintf("const {port} = await startDapServer(%s);", name))
+ output += fmt.Sprintln(prefix + "await dc.start(port);")
+ }
+ }
+
+ // Write the data
+ outfile := filepath.Join(dir, "test", "integration", "debugAdapterDlvDap.test.ts")
+ ioutil.WriteFile(outfile, []byte(output), 0777)
+}
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Suzy Mueller.
Kokoro presubmit build finished with status: FAILURE
Logs at: https://source.cloud.google.com/results/invocations/0499a69e-0b51-4a88-b578-229168589f5f
Patch set 1:TryBot-Result -1
Attention is currently required from: Suzy Mueller.
Suzy Mueller uploaded patch set #2 to this change.
tools/generateDlvDapTest.go: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, this change generates a new file with the modifications
required to run dlv dap.
This change includes adding some comments to the original test file
to make generating the tests easy. The goal will be to simply delete
the old file and generating script once the new implementation is
complete.
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
---
M src/goDebugFactory.ts
A test/integration/debugAdapter.test.ts
A test/integration/debugAdapterDlvDap.test.ts
M test/integration/goDebug.test.ts
A tools/generateDlvDapTest.go
5 files changed, 2,942 insertions(+), 1,397 deletions(-)
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Suzy Mueller.
Kokoro presubmit build finished with status: FAILURE
Logs at: https://source.cloud.google.com/results/invocations/945df1a0-2031-4963-acf9-ad854aa288a9
Patch set 2:TryBot-Result -1
Attention is currently required from: Suzy Mueller.
Suzy Mueller uploaded patch set #3 to this change.
tools/generateDlvDapTest.go: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, this change generates a new file with the modifications
required to run dlv dap.
This change includes adding some comments to the original test file
to make generating the tests easy. The goal will be to simply delete
the old file and generating script once the new implementation is
complete.
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
---
M src/goDebugFactory.ts
A test/integration/debugAdapter.test.ts
A test/integration/debugAdapterDlvDap.test.ts
M test/integration/goDebug.test.ts
A tools/generateDlvDapTest.go
5 files changed, 2,940 insertions(+), 1,397 deletions(-)
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Suzy Mueller.
Kokoro presubmit build finished with status: FAILURE
Logs at: https://source.cloud.google.com/results/invocations/218713c0-6331-4f69-b635-acfd1dbd5786
Patch set 3:TryBot-Result -1
Attention is currently required from: Suzy Mueller.
Suzy Mueller uploaded patch set #4 to this change.
tools/generateDlvDapTest.go: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, this change generates a new file with the modifications
required to run dlv dap.
This change includes adding some comments to the original test file
to make generating the tests easy. The goal will be to simply delete
the old file and generating script once the new implementation is
complete.
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
---
M src/goDebugFactory.ts
A test/integration/debugAdapter.test.ts
A test/integration/debugAdapterDlvDap.test.ts
M test/integration/goDebug.test.ts
A tools/generateDlvDapTest.go
5 files changed, 2,937 insertions(+), 1,397 deletions(-)
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Suzy Mueller.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/79dde996-4e6a-462f-990f-6aec514eb985
Patch set 4:TryBot-Result +1
Attention is currently required from: Hyang-Ah Hana Kim, Polina Sokolova.
Patch set 4:Run-TryBot +1Trust +1
Attention is currently required from: Suzy Mueller, Polina Sokolova.
4 comments:
Patchset:
Interesting!
In order to avoid debugAdapterDlvDap.test.ts gets stale, can you run the generator program in the CI program? (Like https://go.googlesource.com/vscode-go/+/refs/heads/master/build/all.bash#54)
Another approach is to refactor the test to operate differently based on the specified mode.
I did similar refactoring work here https://go-review.googlesource.com/c/vscode-go/+/291313/6/test/integration/extension.test.ts#45 - to run the test in both GOPATH and module modes.
And let's open an issue and start investigation on why those skipped tests couldn't work.
File test/integration/debugAdapter.test.ts:
Patch Set #4, Line 19: // ADD:import { startDapServer } from '../../src/goDebugFactory';
I think we need explanation on these rules at the top of this file and mention the test generator program.
When we start to have more feature-rich delve dap, I expect we will eventually have tests that apply only to the delve dap mode. In that case, should we add extra test suite for delve dap specific features, or should we still modify this file?
File tools/generateDlvDapTest.go:
Patch Set #4, Line 1: package main
We need documentation - what it does and how to use.
basic syntax this tool assumes (REMOVE, ADD, SKIP ...)
Patch Set #4, Line 18: // Find the package.json file.
stale comment?
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Suzy Mueller, Hyang-Ah Hana Kim.
5 comments:
Commit Message:
Patch Set #4, Line 14: This change includes adding some comments to the original test file
With the code moving around, it is not clear where these new comments got added. It might be helpful to add an inventory of files to this commit message and header comments to each in the code to make sense of how they all relate. Or maybe a README in the directory.
File src/goDebugFactory.ts:
Patch Set #4, Line 126: throw new Error('The program attribute must point to valid directory, .go file or executable.');
why do we no longer need this error check?
Patch Set #4, Line 31: this.dlvDapServer = dlvDapServer;
Why does this have to be set here now?
File test/integration/debugAdapter.test.ts:
Patch Set #4, Line 25: suite('Go Debug Adapter', function () { // REMOVE
Is this everything that got moved over from the other file as-is? Do I need to review for any diffs?
File tools/generateDlvDapTest.go:
Patch Set #4, Line 1: package main
We need documentation - what it does and how to use. […]
+1
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Suzy Mueller, Hyang-Ah Hana Kim.
Suzy Mueller uploaded patch set #5 to this change.
tools/generateDlvDapTest.go: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, we add the necessary code for dlv-dap guarded by
a boolean to determine which mode to run the tests in.
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
---
M src/goDebugFactory.ts
M test/integration/goDebug.test.ts
2 files changed, 412 insertions(+), 147 deletions(-)
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Suzy Mueller, Hyang-Ah Hana Kim.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/7839cca6-e3f3-48e4-b548-e20312dfbc7a
Patch set 5:TryBot-Result +1
Attention is currently required from: Hyang-Ah Hana Kim, Polina Sokolova.
7 comments:
Commit Message:
Patch Set #4, Line 14: This change includes adding some comments to the original test file
With the code moving around, it is not clear where these new comments got added. […]
I changed to do an approach like the GOPATH vs modules tests so this is fixed.
File src/goDebugFactory.ts:
Patch Set #4, Line 126: throw new Error('The program attribute must point to valid directory, .go file or executable.');
why do we no longer need this error check?
I thought it had something to do with substitutePath but I think I'm wrong about that.
I updated the code to include the 'exec' check without removing the error check.
Patch Set #4, Line 31: this.dlvDapServer = dlvDapServer;
Why does this have to be set here now?
I wanted to export the function not tied to the descriptor factory.
File test/integration/debugAdapter.test.ts:
Patch Set #4, Line 19: // ADD:import { startDapServer } from '../../src/goDebugFactory';
I think we need explanation on these rules at the top of this file and mention the test generator pr […]
Ack.
Patch Set #4, Line 25: suite('Go Debug Adapter', function () { // REMOVE
Is this everything that got moved over from the other file as-is? Do I need to review for any diffs?
I changed to do an approach like the GOPATH vs modules tests so this is fixed.
File tools/generateDlvDapTest.go:
Patch Set #4, Line 1: package main
+1
Ack.
Patch Set #4, Line 18: // Find the package.json file.
stale comment?
Done
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Hyang-Ah Hana Kim, Polina Sokolova.
Suzy Mueller uploaded patch set #6 to this change.
tools/generateDlvDapTest.go: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, we add the necessary code for dlv-dap guarded by
a boolean to determine which mode to run the tests in.
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
---
M src/goDebugFactory.ts
M test/integration/goDebug.test.ts
2 files changed, 410 insertions(+), 140 deletions(-)
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Hyang-Ah Hana Kim, Polina Sokolova.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/b1326d54-1a30-420c-be93-0bd0cce7f18b
Patch set 6:TryBot-Result +1
Attention is currently required from: Suzy Mueller, Polina Sokolova.
Patch set 6:Code-Review +2
1 comment:
Commit Message:
tools/generateDlvDapTest.go: run existing debug tests on dlv dap
update the commit message.
And maybe link the issue 137 and close the issue?
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Suzy Mueller, Polina Sokolova.
Suzy Mueller uploaded patch set #7 to this change.
test/integration/goDebug.test.ts: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, we add the necessary code for dlv-dap guarded by
a boolean to determine which mode to run the tests in.
Updates golang/vscode-go#137
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
---
M src/goDebugFactory.ts
M test/integration/goDebug.test.ts
2 files changed, 410 insertions(+), 140 deletions(-)
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Polina Sokolova.
Patch set 6:Run-TryBot +1Trust +1
1 comment:
Commit Message:
tools/generateDlvDapTest.go: run existing debug tests on dlv dap
update the commit message. […]
Done
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Polina Sokolova.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/ebbf6bf4-da2a-42db-85b3-34b976cca513
Patch set 7:TryBot-Result +1
Attention is currently required from: Suzy Mueller.
7 comments:
Patchset:
This is really starting to come together! So cool!
File src/goDebugFactory.ts:
Patch Set #7, Line 144: throw new Error('The program attribute must be a directory or .go file in debug mode');
debug and test mode?
File test/integration/goDebug.test.ts:
Patch Set #7, Line 452: this.skip(); // not working in dlv-dap.
What happens? A note on the nature of the failure in the comment would be helpful.
Maybe out of scope/too much for this CL, but a TODO with quick next steps (fix in dlv-dap, fix in extension, etc) would be helpful as well.
Patch Set #7, Line 477: await dc.start(port);
This is repeated multiple times. Perhaps add a helper?
Patch Set #7, Line 620: this.skip(); // not working in dlv-dap.
What do you expect the behavior to be? dlvFlags don't make sense in the context of running dlv, do they? If we want to pass these to dlv, then we should change the factory code.
Patch Set #7, Line 1238: this.skip(); // not working in dlv-dap.
That's odd. Definitely curious to find out what the failure looks like!
But maybe instead of adding comments here as there are starting to add up, it makes sense to run the full suite without the skips and just attach the output file to this CL.
Patch Set #7, Line 1860: testAll(true);
Maybe make the dlv-dap-skips controlled by a flag that could be set or unset here?
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Polina Sokolova.
Suzy Mueller uploaded patch set #8 to this change.
test/integration/goDebug.test.ts: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, we add the necessary code for dlv-dap guarded by
a boolean to determine which mode to run the tests in.
Updates golang/vscode-go#137
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
---
M src/goDebugFactory.ts
M test/integration/goDebug.test.ts
2 files changed, 331 insertions(+), 187 deletions(-)
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Polina Sokolova.
6 comments:
File src/goDebugFactory.ts:
Patch Set #7, Line 144: throw new Error('The program attribute must be a directory or .go file in debug mode');
debug and test mode?
Done
File test/integration/goDebug.test.ts:
Patch Set #7, Line 452: this.skip(); // not working in dlv-dap.
What happens? A note on the nature of the failure in the comment would be helpful. […]
I have not investigated any of these failures. That is my planned next step in seperate CLs / issues
Patch Set #7, Line 477: await dc.start(port);
This is repeated multiple times. […]
Sounds good, done.
Patch Set #7, Line 620: this.skip(); // not working in dlv-dap.
What do you expect the behavior to be? dlvFlags don't make sense in the context of running dlv, do t […]
I do not know. I think this may be a case where we need to change the test and update documentation to reflect how these flags are treated differently in the legacy adapter and dlv-dap.
Patch Set #7, Line 1238: this.skip(); // not working in dlv-dap.
That's odd. Definitely curious to find out what the failure looks like! […]
I added a boolean to control whether or not the tests should be skipped, but some of the tests cause the whole suite to crash, so I wasn't able to attach a full output file.
Patch Set #7, Line 1860: testAll(true);
Maybe make the dlv-dap-skips controlled by a flag that could be set or unset here?
good idea! will make this change.
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Polina Sokolova.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/2965cb28-b86b-49c8-90c1-3c39f3ec5eda
Patch set 8:TryBot-Result +1
Attention is currently required from: Suzy Mueller.
Patch set 8:Code-Review +2
Suzy Mueller submitted this change.
test/integration/goDebug.test.ts: run existing debug tests on dlv dap
Since dlv dap communicates over a network connection, we must start
it as a server. In order to have tests for both the old and new
debug adapter, we add the necessary code for dlv-dap guarded by
a boolean to determine which mode to run the tests in.
Updates golang/vscode-go#137
Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/290511
Trust: Suzy Mueller <suz...@golang.org>
Run-TryBot: Suzy Mueller <suz...@golang.org>
TryBot-Result: kokoro <noreply...@google.com>
Reviewed-by: Polina Sokolova <pol...@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hya...@gmail.com>
---
M src/goDebugFactory.ts
M test/integration/goDebug.test.ts
2 files changed, 331 insertions(+), 187 deletions(-)
diff --git a/src/goDebugFactory.ts b/src/goDebugFactory.ts
index 9208655..b2964c0 100644
--- a/src/goDebugFactory.ts
+++ b/src/goDebugFactory.ts
@@ -4,7 +4,7 @@
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
-import { ChildProcess, spawn } from 'child_process';
+import { ChildProcess, ChildProcessWithoutNullStreams, spawn } from 'child_process';
import * as fs from 'fs';
import { DebugConfiguration } from 'vscode';
import { getGoConfig } from './config';
@@ -44,7 +44,8 @@
// new one.
await this.terminateDlvDapServerProcess();
- const { port, host } = await this.startDapServer(configuration);
+ const { port, host, dlvDapServer } = await startDapServer(configuration);
+ this.dlvDapServer = dlvDapServer;
return new vscode.DebugAdapterServer(port, host);
}
@@ -54,28 +55,27 @@
this.dlvDapServer = null;
}
}
+}
- private async startDapServer(configuration: DebugConfiguration): Promise<{ port: number; host: string }> {
- if (!configuration.host) {
- configuration.host = '127.0.0.1';
- }
-
- if (configuration.port) {
- // If a port has been specified, assume there is an already
- // running dap server to connect to.
- return { port: configuration.port, host: configuration.host };
- } else {
- configuration.port = await getPort();
- }
-
- this.dlvDapServer = spawnDlvDapServerProcess(configuration, logInfo, logError);
- // Wait to give dlv-dap a chance to start before returning.
- return await new Promise<{ port: number; host: string }>((resolve) =>
- setTimeout(() => {
- resolve({ port: configuration.port, host: configuration.host });
- }, 500)
- );
+export async function startDapServer(
+ configuration: DebugConfiguration
+): Promise<{ port: number; host: string; dlvDapServer?: ChildProcessWithoutNullStreams }> {
+ if (!configuration.host) {
+ configuration.host = '127.0.0.1';
}
+
+ if (configuration.port) {
+ // If a port has been specified, assume there is an already
+ // running dap server to connect to.
+ return { port: configuration.port, host: configuration.host };
+ } else {
+ configuration.port = await getPort();
+ }
+ const dlvDapServer = spawnDlvDapServerProcess(configuration, logInfo, logError);
+ // Wait to give dlv-dap a chance to start before returning.
+ return await new Promise<{ port: number; host: string; dlvDapServer: ChildProcessWithoutNullStreams }>((resolve) =>
+ setTimeout(() => resolve({ port: configuration.port, host: configuration.host, dlvDapServer }), 500)
+ );
}
function spawnDlvDapServerProcess(
@@ -140,8 +140,8 @@
} catch (e) {
throw new Error('The program attribute must point to valid directory, .go file or executable.');
}
- if (!programIsDirectory && path.extname(program) !== '.go') {
- throw new Error('The program attribute must be a directory or .go file in debug mode');
+ if (!programIsDirectory && (path.extname(program) !== '.go' || launchArgs.mode === 'exec')) {
+ throw new Error('The program attribute must be a directory or .go file in debug and test mode');
}
const dirname = programIsDirectory ? program : path.dirname(program);
return { program, dirname, programIsDirectory };
diff --git a/test/integration/goDebug.test.ts b/test/integration/goDebug.test.ts
index d907739..f28ef4b 100644
--- a/test/integration/goDebug.test.ts
+++ b/test/integration/goDebug.test.ts
@@ -24,6 +24,7 @@
import { GoDebugConfigurationProvider } from '../../src/goDebugConfiguration';
import { getBinPath, rmdirRecursive } from '../../src/util';
import { killProcessTree } from '../../src/utils/processUtils';
+import { startDapServer } from '../../src/goDebugFactory';
import getPort = require('get-port');
import util = require('util');
@@ -288,9 +289,9 @@
// Test suite adapted from:
// https://github.com/microsoft/vscode-mock-debug/blob/master/src/tests/adapter.test.ts
-suite('Go Debug Adapter', function () {
- this.timeout(60_000);
-
+const testAll = (isDlvDap: boolean) => {
+ // To disable skipping of dlvDapTests, set dlvDapSkipsEnabled = false.
+ const dlvDapSkipsEnabled = true;
const debugConfigProvider = new GoDebugConfigurationProvider();
const DEBUG_ADAPTER = path.join('.', 'out', 'src', 'debugAdapter', 'goDebug.js');
@@ -308,17 +309,23 @@
let dc: DebugClient;
- setup(() => {
- dc = new DebugClient('node', path.join(PROJECT_ROOT, DEBUG_ADAPTER), 'go', undefined, true);
+ setup(async () => {
+ if (isDlvDap) {
+ dc = new DebugClient('dlv', 'dap', 'go');
+ // Launching delve may take longer than the default timeout of 5000.
+ dc.defaultTimeout = 20_000;
+ return;
+ }
+
+ dc = new DebugClient('node', path.join(PROJECT_ROOT, DEBUG_ADAPTER), 'go', undefined, true);
// Launching delve may take longer than the default timeout of 5000.
dc.defaultTimeout = 20_000;
-
// To connect to a running debug server for debugging the tests, specify PORT.
- return dc.start();
+ await dc.start();
});
- teardown(() => dc.stop());
+ teardown(async () => await dc.stop());
/**
* This function sets up a server that returns helloworld on serverPort.
@@ -361,7 +368,7 @@
* NOTE: For simplicity, this function assumes the breakpoints are in the same file.
*/
async function setUpRemoteAttach(config: DebugConfiguration, breakpoints: ILocation[] = []): Promise<void> {
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
console.log('Sending initializing request for remote attach setup.');
const initializedResult = await dc.initializeRequest();
assert.ok(initializedResult.success);
@@ -438,41 +445,62 @@
}
suite('basic', () => {
- test('unknown request should produce error', (done) => {
- dc.send('illegal_request')
- .then(() => {
- done(new Error('does not report error on unknown request'));
- })
- .catch(() => {
- done();
- });
+ test('unknown request should produce error', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
+ if (isDlvDap) {
+ const config = { name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT };
+ await initializeDebugConfig(config);
+ }
+
+ try {
+ await dc.send('illegal_request');
+ } catch {
+ return;
+ }
+ throw new Error('does not report error on unknown request');
});
});
suite('initialize', () => {
- test('should return supported features', () => {
- return dc.initializeRequest().then((response) => {
+ test('should return supported features', async () => {
+ if (isDlvDap) {
+ const config = { name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT };
+ await initializeDebugConfig(config);
+ }
+ await dc.initializeRequest().then((response) => {
response.body = response.body || {};
assert.strictEqual(response.body.supportsConditionalBreakpoints, true);
assert.strictEqual(response.body.supportsConfigurationDoneRequest, true);
- assert.strictEqual(response.body.supportsSetVariable, true);
+ if (!isDlvDap) {
+ // not supported in dlv-dap
+ assert.strictEqual(response.body.supportsSetVariable, true);
+ }
});
});
- test("should produce error for invalid 'pathFormat'", (done) => {
- dc.initializeRequest({
- adapterID: 'mock',
- linesStartAt1: true,
- columnsStartAt1: true,
- pathFormat: 'url'
- })
- .then((response) => {
- done(new Error("does not report error on invalid 'pathFormat' attribute"));
- })
- .catch((err) => {
- // error expected
- done();
+ test("should produce error for invalid 'pathFormat'", async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
+ if (isDlvDap) {
+ const config = { name: 'Launch', type: 'go', request: 'launch', program: DATA_ROOT };
+ await initializeDebugConfig(config);
+ }
+ try {
+ await dc.initializeRequest({
+ adapterID: 'mock',
+ linesStartAt1: true,
+ columnsStartAt1: true,
+ pathFormat: 'url'
});
+ } catch (err) {
+ return; // want error
+ }
+ throw new Error("does not report error on invalid 'pathFormat' attribute");
});
});
@@ -487,9 +515,8 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
});
test('should stop on entry', async () => {
@@ -502,9 +529,8 @@
program: PROGRAM,
stopOnEntry: true
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc.configurationSequence(),
dc.launch(debugConfig),
// The debug adapter does not support a stack trace request
@@ -528,9 +554,8 @@
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
});
test('should debug a single test', async () => {
@@ -544,9 +569,8 @@
args: ['-test.run', 'TestMe']
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
});
test('should debug a test package', async () => {
@@ -559,12 +583,15 @@
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
});
- test('invalid flags are passed to dlv but should be caught by dlv', async () => {
+ test('invalid flags are passed to dlv but should be caught by dlv', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'baseTest');
const config = {
name: 'Launch',
@@ -574,8 +601,8 @@
program: PROGRAM,
dlvFlags: ['--invalid']
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc.assertOutput('stderr', 'Error: unknown flag: --invalid\n', 5000),
dc.waitForEvent('terminated'),
dc.initializeRequest().then((response) => {
@@ -598,9 +625,8 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc.configurationSequence().then(() => {
dc.threadsRequest().then((response) => {
assert.ok(response.success);
@@ -623,15 +649,18 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
const response = await dc.threadsRequest();
assert.ok(response.success);
});
- test('user-specified --listen flag should be ignored', async () => {
+ test('user-specified --listen flag should be ignored', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'baseTest');
const config = {
name: 'Launch',
@@ -641,14 +670,18 @@
program: PROGRAM,
dlvFlags: ['--listen=127.0.0.1:80']
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
- return Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
+ await Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
});
});
suite('set current working directory', () => {
- test('should debug program with cwd set', async () => {
+ test('should debug program with cwd set', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const WD = path.join(DATA_ROOT, 'cwdTest');
const PROGRAM = path.join(WD, 'cwdTest');
const FILE = path.join(PROGRAM, 'main.go');
@@ -662,14 +695,17 @@
program: PROGRAM,
cwd: WD
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
await assertVariableValue('strdat', '"Hello, World!"');
});
- test('should debug program without cwd set', async () => {
+ test('should debug program without cwd set', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const WD = path.join(DATA_ROOT, 'cwdTest');
const PROGRAM = path.join(WD, 'cwdTest');
const FILE = path.join(PROGRAM, 'main.go');
@@ -682,14 +718,17 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
await assertVariableValue('strdat', '"Goodbye, World."');
});
- test('should debug file program with cwd set', async () => {
+ test('should debug file program with cwd set', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const WD = path.join(DATA_ROOT, 'cwdTest');
const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
const FILE = PROGRAM;
@@ -703,14 +742,17 @@
program: PROGRAM,
cwd: WD
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
await assertVariableValue('strdat', '"Hello, World!"');
});
- test('should debug file program without cwd set', async () => {
+ test('should debug file program without cwd set', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const WD = path.join(DATA_ROOT, 'cwdTest');
const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
const FILE = PROGRAM;
@@ -723,14 +765,18 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
await assertVariableValue('strdat', '"Goodbye, World."');
});
- test('should run program with cwd set (noDebug)', async () => {
+ test('should run program with cwd set (noDebug)', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const WD = path.join(DATA_ROOT, 'cwdTest');
const PROGRAM = path.join(WD, 'cwdTest');
@@ -743,9 +789,8 @@
cwd: WD,
noDebug: true
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc.launch(debugConfig),
dc.waitForEvent('output').then((event) => {
assert.strictEqual(event.body.output, 'Hello, World!\n');
@@ -753,7 +798,11 @@
]);
});
- test('should run program without cwd set (noDebug)', async () => {
+ test('should run program without cwd set (noDebug)', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const WD = path.join(DATA_ROOT, 'cwdTest');
const PROGRAM = path.join(WD, 'cwdTest');
@@ -765,9 +814,8 @@
program: PROGRAM,
noDebug: true
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc.launch(debugConfig),
dc.waitForEvent('output').then((event) => {
assert.strictEqual(event.body.output, 'Goodbye, World.\n');
@@ -775,7 +823,11 @@
]);
});
- test('should run file program with cwd set (noDebug)', async () => {
+ test('should run file program with cwd set (noDebug)', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const WD = path.join(DATA_ROOT, 'cwdTest');
const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
@@ -788,9 +840,8 @@
cwd: WD,
noDebug: true
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc.launch(debugConfig),
dc.waitForEvent('output').then((event) => {
assert.strictEqual(event.body.output, 'Hello, World!\n');
@@ -798,7 +849,11 @@
]);
});
- test('should run file program without cwd set (noDebug)', async () => {
+ test('should run file program without cwd set (noDebug)', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const WD = path.join(DATA_ROOT, 'cwdTest');
const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
@@ -810,9 +865,8 @@
program: PROGRAM,
noDebug: true
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc.launch(debugConfig),
dc.waitForEvent('output').then((event) => {
assert.strictEqual(event.body.output, 'Goodbye, World.\n');
@@ -828,29 +882,41 @@
setup(async () => {
server = await getPort();
remoteAttachConfig.port = await getPort();
- debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
+ debugConfig = remoteAttachConfig;
});
teardown(async () => {
- await dc.disconnectRequest({ restart: false });
+ await dc.stop();
await killProcessTree(childProcess);
// Wait 2 seconds for the process to be killed.
await new Promise((resolve) => setTimeout(resolve, 2_000));
});
- test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=true', async () => {
+ test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=true', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, true, true);
await setUpRemoteAttach(debugConfig);
});
- test('can connect and initialize using external dlv --headless --accept-multiclient=false --continue=false', async () => {
+ test('can connect and initialize using external dlv --headless --accept-multiclient=false --continue=false', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, false, false);
await setUpRemoteAttach(debugConfig);
});
- test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=false', async () => {
+ test('can connect and initialize using external dlv --headless --accept-multiclient=true --continue=false', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
childProcess = await setUpRemoteProgram(remoteAttachConfig.port, server, true, false);
await setUpRemoteAttach(debugConfig);
@@ -870,10 +936,7 @@
setup(async () => {
server = await getPort();
remoteAttachConfig.port = await getPort();
- remoteAttachDebugConfig = await debugConfigProvider.resolveDebugConfiguration(
- undefined,
- remoteAttachConfig
- );
+ remoteAttachDebugConfig = remoteAttachConfig;
});
test('should stop on a breakpoint', async () => {
@@ -889,9 +952,8 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ const debugConfig = await initializeDebugConfig(config);
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
});
test('should stop on a breakpoint in test file', async () => {
@@ -907,12 +969,15 @@
mode: 'test',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ const debugConfig = await initializeDebugConfig(config);
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
});
- test('stopped for a breakpoint set during initialization (remote attach)', async () => {
+ test('stopped for a breakpoint set during initialization (remote attach)', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
const BREAKPOINT_LINE = 29;
const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
@@ -933,7 +998,11 @@
await new Promise((resolve) => setTimeout(resolve, 2_000));
});
- test('stopped for a breakpoint set after initialization (remote attach)', async () => {
+ test('stopped for a breakpoint set after initialization (remote attach)', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
const BREAKPOINT_LINE = 29;
const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
@@ -960,7 +1029,11 @@
await new Promise((resolve) => setTimeout(resolve, 2_000));
});
- test('stopped for a breakpoint set during initialization (remote attach)', async () => {
+ test('stopped for a breakpoint set during initialization (remote attach)', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const FILE = path.join(DATA_ROOT, 'helloWorldServer', 'main.go');
const BREAKPOINT_LINE = 29;
const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
@@ -981,7 +1054,11 @@
await new Promise((resolve) => setTimeout(resolve, 2_000));
});
- test('should set breakpoints during continue', async () => {
+ test('should set breakpoints during continue', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'sleep');
const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
@@ -995,11 +1072,10 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await Promise.all([dc.configurationSequence(), dc.launch(debugConfig)]);
- return Promise.all([
+ await Promise.all([
dc.setBreakpointsRequest({
lines: [helloLocation.line],
breakpoints: [{ line: helloLocation.line, column: 0 }],
@@ -1026,8 +1102,7 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await dc.hitBreakpoint(debugConfig, setupBreakpoint);
// The program is now stopped at the line containing time.Sleep().
@@ -1071,7 +1146,11 @@
});
suite('conditionalBreakpoints', () => {
- test('should stop on conditional breakpoint', async () => {
+ test('should stop on conditional breakpoint', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'condbp');
const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
const BREAKPOINT_LINE = 7;
@@ -1084,8 +1163,8 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc
.waitForEvent('initialized')
.then(() => {
@@ -1108,7 +1187,11 @@
);
});
- test('should add breakpoint condition', async () => {
+ test('should add breakpoint condition', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'condbp');
const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
const BREAKPOINT_LINE = 7;
@@ -1121,9 +1204,8 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return dc
+ const debugConfig = await initializeDebugConfig(config);
+ await dc
.hitBreakpoint(debugConfig, location)
.then(() =>
// The program is stopped at the breakpoint, check to make sure 'i == 0'.
@@ -1149,7 +1231,11 @@
);
});
- test('should remove breakpoint condition', async () => {
+ test('should remove breakpoint condition', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'condbp');
const FILE = path.join(DATA_ROOT, 'condbp', 'condbp.go');
const BREAKPOINT_LINE = 7;
@@ -1162,11 +1248,11 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc
.waitForEvent('initialized')
- .then(() => {
+ .then(async () => {
return dc.setBreakpointsRequest({
lines: [location.line],
breakpoints: [{ line: location.line, condition: 'i == 2' }],
@@ -1217,9 +1303,8 @@
mode: 'auto',
program: PROGRAM_WITH_EXCEPTION
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
- return Promise.all([
+ const debugConfig = await initializeDebugConfig(config);
+ await Promise.all([
dc
.waitForEvent('initialized')
.then(() => {
@@ -1243,15 +1328,17 @@
// In order for these tests to pass, the debug adapter must not fail if a
// disconnectRequest is sent after it has already disconnected.
- test('disconnect should work for remote attach', async () => {
+ test('disconnect should work for remote attach', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const server = await getPort();
remoteAttachConfig.port = await getPort();
const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig);
-
// Setup attach.
- await setUpRemoteAttach(debugConfig);
+ await setUpRemoteAttach(remoteAttachConfig);
// Calls the helloworld server to get a response.
let response = '';
@@ -1277,7 +1364,11 @@
await new Promise((resolve) => setTimeout(resolve, 2_000));
});
- test('should disconnect while continuing on entry', async () => {
+ test('should disconnect while continuing on entry', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'loop');
const config = {
@@ -1288,14 +1379,17 @@
program: PROGRAM,
stopOnEntry: false
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await Promise.all([dc.configurationSequence(), dc.launch(debugConfig)]);
- return Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
+ await Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
});
- test('should disconnect with multiple disconnectRequests', async () => {
+ test('should disconnect with multiple disconnectRequests', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'loop');
const config = {
@@ -1306,7 +1400,7 @@
program: PROGRAM,
stopOnEntry: false
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
await Promise.all([dc.configurationSequence(), dc.launch(debugConfig)]);
@@ -1316,7 +1410,11 @@
]);
});
- test('should disconnect after continue', async () => {
+ test('should disconnect after continue', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'loop');
const config = {
@@ -1327,17 +1425,20 @@
program: PROGRAM,
stopOnEntry: true
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await Promise.all([dc.configurationSequence(), dc.launch(debugConfig)]);
const continueResponse = await dc.continueRequest({ threadId: 1 });
assert.ok(continueResponse.success);
- return Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
+ await Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
});
- test('should disconnect while nexting', async () => {
+ test('should disconnect while nexting', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'sleep');
const FILE = path.join(DATA_ROOT, 'sleep', 'sleep.go');
const BREAKPOINT_LINE = 11;
@@ -1351,17 +1452,20 @@
program: PROGRAM,
stopOnEntry: false
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
-
+ const debugConfig = await initializeDebugConfig(config);
await dc.hitBreakpoint(debugConfig, location);
const nextResponse = await dc.nextRequest({ threadId: 1 });
assert.ok(nextResponse.success);
- return Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
+ await Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
});
- test('should disconnect while paused on pause', async () => {
+ test('should disconnect while paused on pause', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'loop');
const config = {
@@ -1371,17 +1475,21 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
await Promise.all([dc.configurationSequence(), dc.launch(debugConfig)]);
const pauseResponse = await dc.pauseRequest({ threadId: 1 });
assert.ok(pauseResponse.success);
- return Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
+ await Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
});
- test('should disconnect while paused on breakpoint', async () => {
+ test('should disconnect while paused on breakpoint', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'loop');
const FILE = path.join(PROGRAM, 'loop.go');
const BREAKPOINT_LINE = 5;
@@ -1393,14 +1501,18 @@
mode: 'auto',
program: PROGRAM
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
- return Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
+ await Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
});
- test('should disconnect while paused on entry', async () => {
+ test('should disconnect while paused on entry', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'loop');
const config = {
@@ -1411,14 +1523,18 @@
program: PROGRAM,
stopOnEntry: true
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
await Promise.all([dc.configurationSequence(), dc.launch(debugConfig)]);
- return Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
+ await Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
});
- test('should disconnect while paused on next', async () => {
+ test('should disconnect while paused on next', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const PROGRAM = path.join(DATA_ROOT, 'loop');
const config = {
@@ -1429,14 +1545,14 @@
program: PROGRAM,
stopOnEntry: true
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
await Promise.all([dc.configurationSequence(), dc.launch(debugConfig)]);
const nextResponse = await dc.nextRequest({ threadId: 1 });
assert.ok(nextResponse.success);
- return Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
+ await Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
});
});
@@ -1491,7 +1607,11 @@
return { program: wd, output };
}
- test('should stop on a breakpoint set in file with substituted path', async () => {
+ test('should stop on a breakpoint set in file with substituted path', async function () {
+ if (isDlvDap) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const { program, output } = await copyBuildDelete('baseTest');
const FILE = path.join(DATA_ROOT, 'baseTest', 'test.go');
const BREAKPOINT_LINE = 11;
@@ -1509,9 +1629,9 @@
}
]
};
- const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ const debugConfig = await initializeDebugConfig(config);
- return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
+ await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
});
});
@@ -1523,10 +1643,7 @@
setup(async () => {
server = await getPort();
remoteAttachConfig.port = await getPort();
- remoteAttachDebugConfig = await debugConfigProvider.resolveDebugConfiguration(
- undefined,
- remoteAttachConfig
- );
+ remoteAttachDebugConfig = remoteAttachConfig;
});
suiteSetup(() => {
@@ -1538,7 +1655,11 @@
rmdirRecursive(helloWorldLocal);
});
- test('stopped for a breakpoint set during initialization using substitutePath (remote attach)', async () => {
+ test('stopped for a breakpoint set during initialization using substitutePath (remote attach)', async function () {
+ if (isDlvDap) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const FILE = path.join(helloWorldLocal, 'main.go');
const BREAKPOINT_LINE = 29;
const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
@@ -1563,7 +1684,11 @@
// Skip because it times out in nightly release workflow.
// BUG(https://github.com/golang/vscode-go/issues/1043)
- test.skip('stopped for a breakpoint set during initialization using remotePath (remote attach)', async () => {
+ test.skip('stopped for a breakpoint set during initialization using remotePath (remote attach)', async function () {
+ if (isDlvDap && dlvDapSkipsEnabled) {
+ this.skip(); // not working in dlv-dap.
+ }
+
const FILE = path.join(helloWorldLocal, 'main.go');
const BREAKPOINT_LINE = 29;
const remoteProgram = await setUpRemoteProgram(remoteAttachConfig.port, server);
@@ -1588,4 +1713,23 @@
});
});
});
+
+ async function initializeDebugConfig(config: DebugConfiguration) {
+ const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
+ if (isDlvDap) {
+ const { port } = await startDapServer(debugConfig);
+ await dc.start(port);
+ }
+ return debugConfig;
+ }
+};
+
+suite('Go Debug Adapter Tests (legacy)', function () {
+ this.timeout(60_000);
+ testAll(false);
+});
+
+suite('Go Debug Adapter Tests (dlv-dap)', function () {
+ this.timeout(60_000);
+ testAll(true);
});
To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.