| 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, |
| GoDebugSession, |
| 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', () => { |
| assert.strictEqual(escapeGoModPath('BurnSushi/test.go'), '!burn!sushi/test.go'); |
| }); |
| }); |
| |
| suite('GoDebugSession Tests', async () => { |
| const workspaceFolder = '/usr/workspacefolder'; |
| const delve: Delve = {} as Delve; |
| let goDebugSession: GoDebugSession; |
| let remoteSourcesAndPackages: RemoteSourcesAndPackages; |
| let fileSystem: typeof fs; |
| |
| let previousEnv: any; |
| |
| setup(() => { |
| previousEnv = Object.assign({}, process.env); |
| |
| process.env.GOPATH = '/usr/gopath'; |
| process.env.GOROOT = '/usr/goroot'; |
| remoteSourcesAndPackages = new RemoteSourcesAndPackages(); |
| fileSystem = { existsSync: () => false } as unknown as typeof fs; |
| delve.program = workspaceFolder; |
| delve.isApiV1 = false; |
| goDebugSession = new GoDebugSession(true, false, fileSystem); |
| goDebugSession['delve'] = delve; |
| goDebugSession['remoteSourcesAndPackages'] = remoteSourcesAndPackages; |
| }); |
| |
| teardown(() => { |
| process.env = previousEnv; |
| sinon.restore(); |
| }); |
| |
| test('inferRemotePathFromLocalPath works', () => { |
| const sourceFileMapping = new Map<string, string[]>(); |
| sourceFileMapping.set('main.go', ['/app/hello-world/main.go', '/app/main.go']); |
| sourceFileMapping.set('blah.go', ['/app/blah.go']); |
| |
| remoteSourcesAndPackages.remoteSourceFilesNameGrouping = sourceFileMapping; |
| |
| const inferredPath = goDebugSession['inferRemotePathFromLocalPath']( |
| 'C:\\Users\\Documents\\src\\hello-world\\main.go'); |
| assert.strictEqual(inferredPath, '/app/hello-world/main.go'); |
| }); |
| |
| test('inferLocalPathFromRemoteGoPackage works for package in workspaceFolder', () => { |
| const remotePath = '/src/hello-world/morestrings/morestrings.go'; |
| const helloPackage: PackageBuildInfo = { |
| ImportPath: 'hello-world/morestrings', |
| DirectoryPath: '/src/hello-world/morestrings', |
| Files: ['/src/hello-world/morestrings/lessstrings.go', '/src/hello-world/morestrings/morestrings.go'] |
| }; |
| |
| const testPackage: PackageBuildInfo = { |
| ImportPath: 'FooBar/test', |
| DirectoryPath: 'remote/pkg/mod/!foo!bar/test@v1.0.2', |
| Files: ['remote/pkg/mod/!foo!bar/test@v1.0.2/test.go'] |
| }; |
| |
| const localPath = path.join(workspaceFolder, 'hello-world/morestrings/morestrings.go'); |
| const existsSyncStub = sinon.stub(fileSystem, 'existsSync'); |
| existsSyncStub.withArgs(localPath).returns(true); |
| |
| remoteSourcesAndPackages.remotePackagesBuildInfo = [helloPackage, testPackage]; |
| |
| goDebugSession['localPathSeparator'] = '/'; |
| const inferredLocalPath = goDebugSession['inferLocalPathFromRemoteGoPackage'](remotePath); |
| assert.strictEqual(inferredLocalPath, localPath); |
| }); |
| |
| test('inferLocalPathFromRemoteGoPackage works for package in GOPATH/pkg/mod', () => { |
| const remotePath = 'remote/pkg/mod/!foo!bar/test@v1.0.2/test.go'; |
| const helloPackage: PackageBuildInfo = { |
| ImportPath: 'hello-world', |
| DirectoryPath: '/src/hello-world', |
| Files: ['src/hello-world/hello.go', 'src/hello-world/world.go'] |
| }; |
| |
| const testPackage: PackageBuildInfo = { |
| ImportPath: 'FooBar/test', |
| DirectoryPath: 'remote/pkg/mod/!foo!bar/test@v1.0.2', |
| Files: ['remote/pkg/mod/!foo!bar/test@v1.0.2/test.go'] |
| }; |
| |
| const localPath = path.join(process.env.GOPATH, 'pkg/mod/!foo!bar/test@v1.0.2/test.go'); |
| const existsSyncStub = sinon.stub(fileSystem, 'existsSync'); |
| existsSyncStub.withArgs(localPath).returns(true); |
| |
| remoteSourcesAndPackages.remotePackagesBuildInfo = [helloPackage, testPackage]; |
| |
| goDebugSession['localPathSeparator'] = '/'; |
| const inferredLocalPath = goDebugSession['inferLocalPathFromRemoteGoPackage'](remotePath); |
| assert.strictEqual(inferredLocalPath, localPath); |
| }); |
| |
| test('inferLocalPathFromRemoteGoPackage works for package in GOPATH/pkg/mod with relative path', () => { |
| const remotePath = '!foo!bar/test@v1.0.2/test.go'; |
| const helloPackage: PackageBuildInfo = { |
| ImportPath: 'hello-world', |
| DirectoryPath: '/src/hello-world', |
| Files: ['src/hello-world/hello.go', 'src/hello-world/world.go'] |
| }; |
| |
| const testPackage: PackageBuildInfo = { |
| ImportPath: 'FooBar/test', |
| DirectoryPath: '!foo!bar/test@v1.0.2', |
| Files: ['!foo!bar/test@v1.0.2/test.go'] |
| }; |
| |
| const localPath = path.join(process.env.GOPATH, 'pkg/mod/!foo!bar/test@v1.0.2/test.go'); |
| const existsSyncStub = sinon.stub(fileSystem, 'existsSync'); |
| existsSyncStub.withArgs(localPath).returns(true); |
| |
| remoteSourcesAndPackages.remotePackagesBuildInfo = [helloPackage, testPackage]; |
| |
| goDebugSession['localPathSeparator'] = '/'; |
| const inferredLocalPath = goDebugSession['inferLocalPathFromRemoteGoPackage'](remotePath); |
| assert.strictEqual(inferredLocalPath, localPath); |
| }); |
| |
| test('inferLocalPathFromRemoteGoPackage works for package in GOPATH/src', () => { |
| const remotePath = 'remote/gopath/src/foobar/test@v1.0.2-abcde-34/test.go'; |
| const helloPackage: PackageBuildInfo = { |
| ImportPath: 'hello-world', |
| DirectoryPath: '/src/hello-world', |
| Files: ['src/hello-world/hello.go', 'src/hello-world/world.go'] |
| }; |
| |
| const testPackage: PackageBuildInfo = { |
| ImportPath: 'foobar/test', |
| DirectoryPath: 'remote/gopath/src/foobar/test@v1.0.2-abcde-34', |
| Files: ['remote/gopath/src/foobar/test@v1.0.2-abcde-34/test.go'] |
| }; |
| |
| const localPath = path.join(process.env.GOPATH, 'src', 'foobar/test@v1.0.2-abcde-34/test.go'); |
| const existsSyncStub = sinon.stub(fileSystem, 'existsSync'); |
| existsSyncStub.withArgs(localPath).returns(true); |
| |
| remoteSourcesAndPackages.remotePackagesBuildInfo = [helloPackage, testPackage]; |
| |
| goDebugSession['localPathSeparator'] = '/'; |
| const inferredLocalPath = goDebugSession['inferLocalPathFromRemoteGoPackage'](remotePath); |
| assert.strictEqual(inferredLocalPath, localPath); |
| }); |
| |
| test('inferLocalPathFromRemoteGoPackage works for package in GOPATH/src with relative path', () => { |
| const remotePath = 'foobar/test@v1.0.2/test.go'; |
| const helloPackage: PackageBuildInfo = { |
| ImportPath: 'hello-world', |
| DirectoryPath: '/src/hello-world', |
| Files: ['src/hello-world/hello.go', 'src/hello-world/world.go'] |
| }; |
| |
| const testPackage: PackageBuildInfo = { |
| ImportPath: 'foobar/test', |
| DirectoryPath: 'foobar/test@v1.0.2', |
| Files: ['foobar/test@v1.0.2/test.go'] |
| }; |
| |
| const localPath = path.join(process.env.GOPATH, 'src', 'foobar/test@v1.0.2/test.go'); |
| const existsSyncStub = sinon.stub(fileSystem, 'existsSync'); |
| existsSyncStub.withArgs(localPath).returns(true); |
| |
| remoteSourcesAndPackages.remotePackagesBuildInfo = [helloPackage, testPackage]; |
| |
| goDebugSession['localPathSeparator'] = '/'; |
| const inferredLocalPath = goDebugSession['inferLocalPathFromRemoteGoPackage'](remotePath); |
| assert.strictEqual(inferredLocalPath, localPath); |
| }); |
| |
| test('inferLocalPathFromRemoteGoPackage works for package in GOROOT/src', () => { |
| const remotePath = 'remote/goroot/src/foobar/test@v1.0.2/test.go'; |
| const helloPackage: PackageBuildInfo = { |
| ImportPath: 'hello-world', |
| DirectoryPath: '/src/hello-world', |
| Files: ['src/hello-world/hello.go', 'src/hello-world/world.go'] |
| }; |
| |
| const testPackage: PackageBuildInfo = { |
| ImportPath: 'foobar/test', |
| DirectoryPath: 'remote/goroot/src/foobar/test@v1.0.2', |
| Files: ['remote/goroot/src/foobar/test@v1.0.2/test.go'] |
| }; |
| |
| const localPath = path.join(process.env.GOROOT, 'src', 'foobar/test@v1.0.2/test.go'); |
| const existsSyncStub = sinon.stub(fileSystem, 'existsSync'); |
| existsSyncStub.withArgs(localPath).returns(true); |
| |
| remoteSourcesAndPackages.remotePackagesBuildInfo = [helloPackage, testPackage]; |
| |
| goDebugSession['localPathSeparator'] = '/'; |
| const inferredLocalPath = goDebugSession['inferLocalPathFromRemoteGoPackage'](remotePath); |
| assert.strictEqual(inferredLocalPath, localPath); |
| }); |
| |
| test('inferLocalPathFromRemoteGoPackage works for package in GOROOT/src with relative path', () => { |
| const remotePath = 'foobar/test@v1.0.2/test.go'; |
| const helloPackage: PackageBuildInfo = { |
| ImportPath: 'hello-world', |
| DirectoryPath: '/src/hello-world', |
| Files: ['src/hello-world/hello.go', 'src/hello-world/world.go'] |
| }; |
| |
| const testPackage: PackageBuildInfo = { |
| ImportPath: 'foobar/test', |
| DirectoryPath: 'foobar/test@v1.0.2', |
| Files: ['foobar/test@v1.0.2/test.go'] |
| }; |
| |
| const localPath = path.join(process.env.GOROOT, 'src', 'foobar/test@v1.0.2/test.go'); |
| const existsSyncStub = sinon.stub(fileSystem, 'existsSync'); |
| existsSyncStub.withArgs(localPath).returns(true); |
| |
| remoteSourcesAndPackages.remotePackagesBuildInfo = [helloPackage, testPackage]; |
| |
| goDebugSession['localPathSeparator'] = '/'; |
| const inferredLocalPath = goDebugSession['inferLocalPathFromRemoteGoPackage'](remotePath); |
| assert.strictEqual(inferredLocalPath, localPath); |
| }); |
| }); |
| |
| suite('RemoteSourcesAndPackages Tests', () => { |
| const helloPackage: PackageBuildInfo = { |
| ImportPath: 'hello-world', |
| DirectoryPath: '/src/hello-world', |
| Files: ['src/hello-world/hello.go', 'src/hello-world/world.go'] |
| }; |
| const testPackage: PackageBuildInfo = { |
| ImportPath: 'test', |
| DirectoryPath: '/src/test', |
| Files: ['src/test/test.go'] |
| }; |
| const sources = ['src/hello-world/hello.go', 'src/hello-world/world.go', 'src/test/test.go']; |
| let remoteSourcesAndPackages: RemoteSourcesAndPackages; |
| let delve: Delve; |
| setup(() => { |
| delve = { callPromise: () => ({}), isApiV1: false } as unknown as Delve; |
| remoteSourcesAndPackages = new RemoteSourcesAndPackages(); |
| }); |
| |
| teardown(() => { |
| sinon.restore(); |
| }); |
| |
| test('initializeRemotePackagesAndSources retrieves remote packages and sources', async () => { |
| const stub = sinon.stub(delve, 'callPromise'); |
| stub.withArgs('ListPackagesBuildInfo', [{ IncludeFiles: true }]) |
| .returns(Promise.resolve({ List: [helloPackage, testPackage] })); |
| stub.withArgs('ListSources', [{}]) |
| .returns(Promise.resolve({ Sources: sources })); |
| |
| await remoteSourcesAndPackages.initializeRemotePackagesAndSources(delve); |
| assert.deepEqual(remoteSourcesAndPackages.remoteSourceFiles, sources); |
| 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 = 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', () => { |
| |
| const PROGRAM = path.join(DATA_ROOT, 'baseTest'); |
| |
| const config = { |
| name: 'Launch', |
| type: 'go', |
| request: 'launch', |
| mode: 'auto', |
| program: PROGRAM, |
| }; |
| const debugConfig = debugConfigProvider.resolveDebugConfiguration(undefined, config); |
| |
| return Promise.all([ |
| dc.configurationSequence(), |
| dc.launch(debugConfig), |
| dc.waitForEvent('terminated') |
| ]); |
| }); |
| |
| test('should stop on entry', () => { |
| const PROGRAM = path.join(DATA_ROOT, 'baseTest'); |
| const config = { |
| name: 'Launch', |
| type: 'go', |
| request: 'launch', |
| mode: 'auto', |
| program: PROGRAM, |
| stopOnEntry: true |
| }; |
| const debugConfig = 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', () => { |
| const PROGRAM = path.join(DATA_ROOT, 'baseTest', 'test.go'); |
| const config = { |
| name: 'Launch file', |
| type: 'go', |
| request: 'launch', |
| mode: 'auto', |
| program: PROGRAM, |
| }; |
| |
| const debugConfig = debugConfigProvider.resolveDebugConfiguration(undefined, config); |
| |
| return Promise.all([ |
| dc.configurationSequence(), |
| dc.launch(debugConfig), |
| dc.waitForEvent('terminated') |
| ]); |
| }); |
| |
| test('should debug a single test', () => { |
| 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 = debugConfigProvider.resolveDebugConfiguration(undefined, config); |
| |
| return Promise.all([ |
| dc.configurationSequence(), |
| dc.launch(debugConfig), |
| dc.waitForEvent('terminated') |
| ]); |
| }); |
| |
| test('should debug a test package', () => { |
| const PROGRAM = path.join(DATA_ROOT, 'baseTest'); |
| const config = { |
| name: 'Launch file', |
| type: 'go', |
| request: 'launch', |
| mode: 'test', |
| program: PROGRAM |
| }; |
| |
| const debugConfig = 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', () => { |
| const PROGRAM = path.join(DATA_ROOT, 'baseTest'); |
| const config = { |
| name: 'Launch', |
| type: 'go', |
| request: 'launch', |
| mode: 'auto', |
| program: PROGRAM, |
| dlvFlags: ['--invalid'] |
| }; |
| const debugConfig = 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('user-specified --listen flag should be ignored', () => { |
| 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 = 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 = 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 = 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 = 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 = 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)', () => { |
| 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 = 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)', () => { |
| 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 = 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)', () => { |
| 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 = 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)', () => { |
| 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 = 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 = 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 = debugConfigProvider.resolveDebugConfiguration(undefined, remoteAttachConfig); |
| }); |
| |
| test('should stop on a breakpoint', () => { |
| |
| 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 = debugConfigProvider.resolveDebugConfiguration(undefined, config); |
| |
| return dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE)); |
| }); |
| |
| test('should stop on a breakpoint in test file', () => { |
| |
| 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 = 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 = 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 = 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', () => { |
| |
| 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 = 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 = 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', () => { |
| |
| 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 = 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', () => { |
| |
| 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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)); |
| }); |
| }); |
| }); |
| |
| }); |