[vscode-go] tools/generateDlvDapTest.go: run existing debug tests on dlv dap

120 views
Skip to first unread message

Suzy Mueller (Gerrit)

unread,
Feb 8, 2021, 5:18:52 PM2/8/21
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Suzy Mueller has uploaded this change for review.

View 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/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.

Gerrit-Project: vscode-go
Gerrit-Branch: master
Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
Gerrit-Change-Number: 290511
Gerrit-PatchSet: 1
Gerrit-Owner: Suzy Mueller <suz...@golang.org>
Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
Gerrit-MessageType: newchange

kokoro (Gerrit)

unread,
Feb 8, 2021, 5:34:22 PM2/8/21
to Suzy Mueller, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

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

View Change

    To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

    Gerrit-Project: vscode-go
    Gerrit-Branch: master
    Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
    Gerrit-Change-Number: 290511
    Gerrit-PatchSet: 1
    Gerrit-Owner: Suzy Mueller <suz...@golang.org>
    Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
    Gerrit-Reviewer: kokoro <noreply...@google.com>
    Gerrit-Attention: Suzy Mueller <suz...@golang.org>
    Gerrit-Comment-Date: Mon, 08 Feb 2021 22:34:16 +0000
    Gerrit-HasComments: No
    Gerrit-Has-Labels: Yes
    Gerrit-MessageType: comment

    Suzy Mueller (Gerrit)

    unread,
    Feb 9, 2021, 5:38:29 PM2/9/21
    to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

    Attention is currently required from: Suzy Mueller.

    Suzy Mueller uploaded patch set #2 to this change.

    View 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.

    Gerrit-Project: vscode-go
    Gerrit-Branch: master
    Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
    Gerrit-Change-Number: 290511
    Gerrit-PatchSet: 2
    Gerrit-Owner: Suzy Mueller <suz...@golang.org>
    Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
    Gerrit-Reviewer: kokoro <noreply...@google.com>
    Gerrit-Attention: Suzy Mueller <suz...@golang.org>
    Gerrit-MessageType: newpatchset

    kokoro (Gerrit)

    unread,
    Feb 9, 2021, 5:51:03 PM2/9/21
    to Suzy Mueller, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

    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

    View Change

      To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

      Gerrit-Project: vscode-go
      Gerrit-Branch: master
      Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
      Gerrit-Change-Number: 290511
      Gerrit-PatchSet: 2
      Gerrit-Owner: Suzy Mueller <suz...@golang.org>
      Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
      Gerrit-Reviewer: kokoro <noreply...@google.com>
      Gerrit-Attention: Suzy Mueller <suz...@golang.org>
      Gerrit-Comment-Date: Tue, 09 Feb 2021 22:50:58 +0000

      Suzy Mueller (Gerrit)

      unread,
      Feb 9, 2021, 5:53:48 PM2/9/21
      to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

      Attention is currently required from: Suzy Mueller.

      Suzy Mueller uploaded patch set #3 to this change.

      View 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.

      Gerrit-Project: vscode-go
      Gerrit-Branch: master
      Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
      Gerrit-Change-Number: 290511
      Gerrit-PatchSet: 3
      Gerrit-Owner: Suzy Mueller <suz...@golang.org>
      Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
      Gerrit-Reviewer: kokoro <noreply...@google.com>
      Gerrit-Attention: Suzy Mueller <suz...@golang.org>
      Gerrit-MessageType: newpatchset

      kokoro (Gerrit)

      unread,
      Feb 9, 2021, 6:07:21 PM2/9/21
      to Suzy Mueller, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

      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

      View Change

        To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

        Gerrit-Project: vscode-go
        Gerrit-Branch: master
        Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
        Gerrit-Change-Number: 290511
        Gerrit-PatchSet: 3
        Gerrit-Owner: Suzy Mueller <suz...@golang.org>
        Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
        Gerrit-Reviewer: kokoro <noreply...@google.com>
        Gerrit-Attention: Suzy Mueller <suz...@golang.org>
        Gerrit-Comment-Date: Tue, 09 Feb 2021 23:07:16 +0000

        Suzy Mueller (Gerrit)

        unread,
        Feb 9, 2021, 6:40:32 PM2/9/21
        to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

        Attention is currently required from: Suzy Mueller.

        Suzy Mueller uploaded patch set #4 to this change.

        View 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.

        Gerrit-Project: vscode-go
        Gerrit-Branch: master
        Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
        Gerrit-Change-Number: 290511
        Gerrit-PatchSet: 4
        Gerrit-Owner: Suzy Mueller <suz...@golang.org>
        Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
        Gerrit-Reviewer: kokoro <noreply...@google.com>
        Gerrit-Attention: Suzy Mueller <suz...@golang.org>
        Gerrit-MessageType: newpatchset

        kokoro (Gerrit)

        unread,
        Feb 9, 2021, 6:54:04 PM2/9/21
        to Suzy Mueller, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

        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

        View Change

          To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

          Gerrit-Project: vscode-go
          Gerrit-Branch: master
          Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
          Gerrit-Change-Number: 290511
          Gerrit-PatchSet: 4
          Gerrit-Owner: Suzy Mueller <suz...@golang.org>
          Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
          Gerrit-Reviewer: kokoro <noreply...@google.com>
          Gerrit-Attention: Suzy Mueller <suz...@golang.org>
          Gerrit-Comment-Date: Tue, 09 Feb 2021 23:53:59 +0000

          Suzy Mueller (Gerrit)

          unread,
          Feb 9, 2021, 8:53:31 PM2/9/21
          to goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, Polina Sokolova, kokoro, golang-co...@googlegroups.com

          Attention is currently required from: Hyang-Ah Hana Kim, Polina Sokolova.

          Patch set 4:Run-TryBot +1Trust +1

          View Change

            To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-Project: vscode-go
            Gerrit-Branch: master
            Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
            Gerrit-Change-Number: 290511
            Gerrit-PatchSet: 4
            Gerrit-Owner: Suzy Mueller <suz...@golang.org>
            Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
            Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
            Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
            Gerrit-Reviewer: kokoro <noreply...@google.com>
            Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
            Gerrit-Attention: Polina Sokolova <pol...@google.com>
            Gerrit-Comment-Date: Wed, 10 Feb 2021 01:53:27 +0000

            Hyang-Ah Hana Kim (Gerrit)

            unread,
            Feb 12, 2021, 10:27:56 AM2/12/21
            to Suzy Mueller, goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, Polina Sokolova, kokoro, golang-co...@googlegroups.com

            Attention is currently required from: Suzy Mueller, Polina Sokolova.

            View Change

            4 comments:

            • Patchset:

            • 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:

            To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-Project: vscode-go
            Gerrit-Branch: master
            Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
            Gerrit-Change-Number: 290511
            Gerrit-PatchSet: 4
            Gerrit-Owner: Suzy Mueller <suz...@golang.org>
            Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
            Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
            Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
            Gerrit-Reviewer: kokoro <noreply...@google.com>
            Gerrit-Attention: Suzy Mueller <suz...@golang.org>
            Gerrit-Attention: Polina Sokolova <pol...@google.com>
            Gerrit-Comment-Date: Fri, 12 Feb 2021 15:27:52 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: No
            Gerrit-MessageType: comment

            Polina Sokolova (Gerrit)

            unread,
            Feb 18, 2021, 2:46:30 AM2/18/21
            to Suzy Mueller, goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, kokoro, golang-co...@googlegroups.com

            Attention is currently required from: Suzy Mueller, Hyang-Ah Hana Kim.

            View Change

            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:

              • 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.

            Gerrit-Project: vscode-go
            Gerrit-Branch: master
            Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
            Gerrit-Change-Number: 290511
            Gerrit-PatchSet: 4
            Gerrit-Owner: Suzy Mueller <suz...@golang.org>
            Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
            Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
            Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
            Gerrit-Reviewer: kokoro <noreply...@google.com>
            Gerrit-Attention: Suzy Mueller <suz...@golang.org>
            Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
            Gerrit-Comment-Date: Thu, 18 Feb 2021 07:46:26 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: No
            Comment-In-Reply-To: Hyang-Ah Hana Kim <hya...@gmail.com>
            Gerrit-MessageType: comment

            Suzy Mueller (Gerrit)

            unread,
            Feb 22, 2021, 2:17:59 PM2/22/21
            to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

            Attention is currently required from: Suzy Mueller, Hyang-Ah Hana Kim.

            Suzy Mueller uploaded patch set #5 to this change.

            View 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.

            Gerrit-Project: vscode-go
            Gerrit-Branch: master
            Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
            Gerrit-Change-Number: 290511
            Gerrit-PatchSet: 5
            Gerrit-Owner: Suzy Mueller <suz...@golang.org>
            Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
            Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
            Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
            Gerrit-Reviewer: kokoro <noreply...@google.com>
            Gerrit-Attention: Suzy Mueller <suz...@golang.org>
            Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
            Gerrit-MessageType: newpatchset

            kokoro (Gerrit)

            unread,
            Feb 22, 2021, 2:28:11 PM2/22/21
            to Suzy Mueller, goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, Polina Sokolova, golang-co...@googlegroups.com

            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

            View Change

              To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-Project: vscode-go
              Gerrit-Branch: master
              Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
              Gerrit-Change-Number: 290511
              Gerrit-PatchSet: 5
              Gerrit-Owner: Suzy Mueller <suz...@golang.org>
              Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
              Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
              Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
              Gerrit-Reviewer: kokoro <noreply...@google.com>
              Gerrit-Attention: Suzy Mueller <suz...@golang.org>
              Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
              Gerrit-Comment-Date: Mon, 22 Feb 2021 19:28:07 +0000

              Suzy Mueller (Gerrit)

              unread,
              Feb 22, 2021, 3:02:41 PM2/22/21
              to goph...@pubsubhelper.golang.org, kokoro, Hyang-Ah Hana Kim, Polina Sokolova, golang-co...@googlegroups.com

              Attention is currently required from: Hyang-Ah Hana Kim, Polina Sokolova.

              View Change

              7 comments:

              • Commit Message:

                • 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.

                • 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:

                • +1

                  Ack.

                • Done

              To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-Project: vscode-go
              Gerrit-Branch: master
              Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
              Gerrit-Change-Number: 290511
              Gerrit-PatchSet: 6
              Gerrit-Owner: Suzy Mueller <suz...@golang.org>
              Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
              Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
              Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
              Gerrit-Reviewer: kokoro <noreply...@google.com>
              Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
              Gerrit-Attention: Polina Sokolova <pol...@google.com>
              Gerrit-Comment-Date: Mon, 22 Feb 2021 20:02:34 +0000
              Gerrit-HasComments: Yes
              Gerrit-Has-Labels: No
              Comment-In-Reply-To: Hyang-Ah Hana Kim <hya...@gmail.com>
              Comment-In-Reply-To: Polina Sokolova <pol...@google.com>
              Gerrit-MessageType: comment

              Suzy Mueller (Gerrit)

              unread,
              Feb 22, 2021, 3:02:45 PM2/22/21
              to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

              Attention is currently required from: Hyang-Ah Hana Kim, Polina Sokolova.

              Suzy Mueller uploaded patch set #6 to this change.

              View 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.

              Gerrit-Project: vscode-go
              Gerrit-Branch: master
              Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
              Gerrit-Change-Number: 290511
              Gerrit-PatchSet: 6
              Gerrit-Owner: Suzy Mueller <suz...@golang.org>
              Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
              Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
              Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
              Gerrit-Reviewer: kokoro <noreply...@google.com>
              Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
              Gerrit-Attention: Polina Sokolova <pol...@google.com>
              Gerrit-MessageType: newpatchset

              kokoro (Gerrit)

              unread,
              Feb 22, 2021, 3:14:16 PM2/22/21
              to Suzy Mueller, goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, Polina Sokolova, golang-co...@googlegroups.com

              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

              View Change

                To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

                Gerrit-Project: vscode-go
                Gerrit-Branch: master
                Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                Gerrit-Change-Number: 290511
                Gerrit-PatchSet: 6
                Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                Gerrit-Reviewer: kokoro <noreply...@google.com>
                Gerrit-Attention: Hyang-Ah Hana Kim <hya...@gmail.com>
                Gerrit-Attention: Polina Sokolova <pol...@google.com>
                Gerrit-Comment-Date: Mon, 22 Feb 2021 20:14:12 +0000

                Hyang-Ah Hana Kim (Gerrit)

                unread,
                Feb 23, 2021, 9:54:59 AM2/23/21
                to Suzy Mueller, goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, kokoro, Polina Sokolova, golang-co...@googlegroups.com

                Attention is currently required from: Suzy Mueller, Polina Sokolova.

                Patch set 6:Code-Review +2

                View Change

                1 comment:


                  • 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.

                Gerrit-Project: vscode-go
                Gerrit-Branch: master
                Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                Gerrit-Change-Number: 290511
                Gerrit-PatchSet: 6
                Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                Gerrit-Reviewer: kokoro <noreply...@google.com>
                Gerrit-Attention: Suzy Mueller <suz...@golang.org>
                Gerrit-Attention: Polina Sokolova <pol...@google.com>
                Gerrit-Comment-Date: Tue, 23 Feb 2021 14:54:53 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: Yes
                Gerrit-MessageType: comment

                Suzy Mueller (Gerrit)

                unread,
                Feb 23, 2021, 12:23:22 PM2/23/21
                to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

                Attention is currently required from: Suzy Mueller, Polina Sokolova.

                Suzy Mueller uploaded patch set #7 to this change.

                View 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.

                Gerrit-Project: vscode-go
                Gerrit-Branch: master
                Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                Gerrit-Change-Number: 290511
                Gerrit-PatchSet: 7
                Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                Gerrit-Reviewer: kokoro <noreply...@google.com>
                Gerrit-Attention: Suzy Mueller <suz...@golang.org>
                Gerrit-Attention: Polina Sokolova <pol...@google.com>
                Gerrit-MessageType: newpatchset

                Suzy Mueller (Gerrit)

                unread,
                Feb 23, 2021, 12:23:51 PM2/23/21
                to goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, kokoro, Polina Sokolova, golang-co...@googlegroups.com

                Attention is currently required from: Polina Sokolova.

                Patch set 6:Run-TryBot +1Trust +1

                View Change

                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.

                Gerrit-Project: vscode-go
                Gerrit-Branch: master
                Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                Gerrit-Change-Number: 290511
                Gerrit-PatchSet: 6
                Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                Gerrit-Reviewer: kokoro <noreply...@google.com>
                Gerrit-Attention: Polina Sokolova <pol...@google.com>
                Gerrit-Comment-Date: Tue, 23 Feb 2021 17:23:46 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: Yes
                Comment-In-Reply-To: Hyang-Ah Hana Kim <hya...@gmail.com>
                Gerrit-MessageType: comment

                kokoro (Gerrit)

                unread,
                Feb 23, 2021, 12:38:07 PM2/23/21
                to Suzy Mueller, goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, Polina Sokolova, golang-co...@googlegroups.com

                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

                View Change

                  To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

                  Gerrit-Project: vscode-go
                  Gerrit-Branch: master
                  Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                  Gerrit-Change-Number: 290511
                  Gerrit-PatchSet: 7
                  Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                  Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                  Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                  Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                  Gerrit-Reviewer: kokoro <noreply...@google.com>
                  Gerrit-Attention: Polina Sokolova <pol...@google.com>
                  Gerrit-Comment-Date: Tue, 23 Feb 2021 17:38:02 +0000

                  Polina Sokolova (Gerrit)

                  unread,
                  Feb 23, 2021, 1:15:36 PM2/23/21
                  to Suzy Mueller, goph...@pubsubhelper.golang.org, kokoro, Hyang-Ah Hana Kim, golang-co...@googlegroups.com

                  Attention is currently required from: Suzy Mueller.

                  View Change

                  7 comments:

                  • Patchset:

                  • 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.

                  Gerrit-Project: vscode-go
                  Gerrit-Branch: master
                  Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                  Gerrit-Change-Number: 290511
                  Gerrit-PatchSet: 7
                  Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                  Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                  Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                  Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                  Gerrit-Reviewer: kokoro <noreply...@google.com>
                  Gerrit-Attention: Suzy Mueller <suz...@golang.org>
                  Gerrit-Comment-Date: Tue, 23 Feb 2021 18:15:30 +0000
                  Gerrit-HasComments: Yes
                  Gerrit-Has-Labels: No
                  Gerrit-MessageType: comment

                  Suzy Mueller (Gerrit)

                  unread,
                  Feb 23, 2021, 4:43:36 PM2/23/21
                  to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

                  Attention is currently required from: Polina Sokolova.

                  Suzy Mueller uploaded patch set #8 to this change.

                  View 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.

                  Gerrit-Project: vscode-go
                  Gerrit-Branch: master
                  Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                  Gerrit-Change-Number: 290511
                  Gerrit-PatchSet: 8
                  Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                  Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                  Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                  Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                  Gerrit-Reviewer: kokoro <noreply...@google.com>

                  Suzy Mueller (Gerrit)

                  unread,
                  Feb 23, 2021, 4:43:37 PM2/23/21
                  to goph...@pubsubhelper.golang.org, kokoro, Hyang-Ah Hana Kim, Polina Sokolova, golang-co...@googlegroups.com

                  Attention is currently required from: Polina Sokolova.

                  View Change

                  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:

                    • 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

                    • This is repeated multiple times. […]

                      Sounds good, done.

                    • 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.

                    • 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.

                  Gerrit-Project: vscode-go
                  Gerrit-Branch: master
                  Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                  Gerrit-Change-Number: 290511
                  Gerrit-PatchSet: 8
                  Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                  Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                  Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                  Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                  Gerrit-Reviewer: kokoro <noreply...@google.com>
                  Gerrit-Attention: Polina Sokolova <pol...@google.com>
                  Gerrit-Comment-Date: Tue, 23 Feb 2021 21:43:31 +0000
                  Gerrit-HasComments: Yes
                  Gerrit-Has-Labels: No

                  kokoro (Gerrit)

                  unread,
                  Feb 23, 2021, 4:55:40 PM2/23/21
                  to Suzy Mueller, goph...@pubsubhelper.golang.org, Hyang-Ah Hana Kim, Polina Sokolova, golang-co...@googlegroups.com

                  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

                  View Change

                    To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

                    Gerrit-Project: vscode-go
                    Gerrit-Branch: master
                    Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                    Gerrit-Change-Number: 290511
                    Gerrit-PatchSet: 8
                    Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                    Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                    Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                    Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                    Gerrit-Reviewer: kokoro <noreply...@google.com>
                    Gerrit-Attention: Polina Sokolova <pol...@google.com>
                    Gerrit-Comment-Date: Tue, 23 Feb 2021 21:55:35 +0000

                    Polina Sokolova (Gerrit)

                    unread,
                    Feb 23, 2021, 5:04:23 PM2/23/21
                    to Suzy Mueller, goph...@pubsubhelper.golang.org, kokoro, Hyang-Ah Hana Kim, golang-co...@googlegroups.com

                    Attention is currently required from: Suzy Mueller.

                    Patch set 8:Code-Review +2

                    View Change

                      To view, visit change 290511. To unsubscribe, or for help writing mail filters, visit settings.

                      Gerrit-Project: vscode-go
                      Gerrit-Branch: master
                      Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                      Gerrit-Change-Number: 290511
                      Gerrit-PatchSet: 8
                      Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                      Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                      Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                      Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                      Gerrit-Reviewer: kokoro <noreply...@google.com>
                      Gerrit-Attention: Suzy Mueller <suz...@golang.org>
                      Gerrit-Comment-Date: Tue, 23 Feb 2021 22:04:17 +0000

                      Suzy Mueller (Gerrit)

                      unread,
                      Feb 23, 2021, 6:13:45 PM2/23/21
                      to goph...@pubsubhelper.golang.org, golang-...@googlegroups.com, Polina Sokolova, kokoro, Hyang-Ah Hana Kim, golang-co...@googlegroups.com

                      Suzy Mueller submitted this change.

                      View Change

                      Approvals: Hyang-Ah Hana Kim: Looks good to me, approved Polina Sokolova: Looks good to me, approved Suzy Mueller: Trusted; Run TryBots kokoro: TryBots succeeded
                      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.

                      Gerrit-Project: vscode-go
                      Gerrit-Branch: master
                      Gerrit-Change-Id: I211addebb1e3a2df512147b1603a538186e2f061
                      Gerrit-Change-Number: 290511
                      Gerrit-PatchSet: 9
                      Gerrit-Owner: Suzy Mueller <suz...@golang.org>
                      Gerrit-Reviewer: Hyang-Ah Hana Kim <hya...@gmail.com>
                      Gerrit-Reviewer: Polina Sokolova <pol...@google.com>
                      Gerrit-Reviewer: Suzy Mueller <suz...@golang.org>
                      Gerrit-Reviewer: kokoro <noreply...@google.com>
                      Gerrit-MessageType: merged
                      Reply all
                      Reply to author
                      Forward
                      0 new messages