| /*--------------------------------------------------------- |
| * Copyright (C) Microsoft Corporation. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------*/ |
| |
| import * as assert from 'assert'; |
| import cp = require('child_process'); |
| import * as fs from 'fs-extra'; |
| import * as path from 'path'; |
| import * as sinon from 'sinon'; |
| import * as vscode from 'vscode'; |
| import { FilePatch, getEdits, getEditsFromUnifiedDiffStr } from '../../src/diffUtils'; |
| import { check } from '../../src/goCheck'; |
| import { GoDefinitionProvider } from '../../src/goDeclaration'; |
| import { GoHoverProvider } from '../../src/goExtraInfo'; |
| import { runFillStruct } from '../../src/goFillStruct'; |
| import { |
| generateTestCurrentFile, |
| generateTestCurrentFunction, |
| generateTestCurrentPackage |
| } from '../../src/goGenerateTests'; |
| import { getTextEditForAddImport, listPackages } from '../../src/goImport'; |
| import { updateGoVarsFromConfig } from '../../src/goInstallTools'; |
| import { buildLanguageServerConfig } from '../../src/goLanguageServer'; |
| import { goLint } from '../../src/goLint'; |
| import { documentSymbols, GoDocumentSymbolProvider, GoOutlineImportsOptions } from '../../src/goOutline'; |
| import { getAllPackages } from '../../src/goPackages'; |
| import { goPlay } from '../../src/goPlayground'; |
| import { GoSignatureHelpProvider } from '../../src/goSignature'; |
| import { GoCompletionItemProvider } from '../../src/goSuggest'; |
| import { getWorkspaceSymbols } from '../../src/goSymbol'; |
| import { testCurrentFile } from '../../src/goTest'; |
| import { |
| getBinPath, |
| getCurrentGoPath, |
| getGoConfig, |
| getImportPath, |
| getToolsGopath, |
| handleDiagnosticErrors, |
| ICheckResult, |
| isVendorSupported |
| } from '../../src/util'; |
| |
| suite('Go Extension Tests', function () { |
| this.timeout(15000); |
| |
| const dummyCancellationSource = new vscode.CancellationTokenSource(); |
| |
| // suiteSetup will initialize the following vars. |
| let gopath: string; |
| let repoPath: string; |
| let fixturePath: string; |
| let fixtureSourcePath: string; |
| let generateTestsSourcePath: string; |
| let generateFunctionTestSourcePath: string; |
| let generatePackageTestSourcePath: string; |
| let toolsGopath: string; |
| |
| suiteSetup(async () => { |
| await updateGoVarsFromConfig(); |
| |
| gopath = getCurrentGoPath(); |
| if (!gopath) { |
| assert.ok(gopath, 'Cannot run tests if GOPATH is not set as environment variable'); |
| return; |
| } |
| console.log(`Using GOPATH: ${gopath}`); |
| |
| repoPath = path.join(gopath, 'src', 'test'); |
| fixturePath = path.join(repoPath, 'testfixture'); |
| fixtureSourcePath = path.join(__dirname, '..', '..', '..', 'test', 'testdata'); |
| generateTestsSourcePath = path.join(repoPath, 'generatetests'); |
| generateFunctionTestSourcePath = path.join(repoPath, 'generatefunctiontest'); |
| generatePackageTestSourcePath = path.join(repoPath, 'generatePackagetest'); |
| toolsGopath = getToolsGopath() || gopath; |
| |
| fs.removeSync(repoPath); |
| fs.copySync(fixtureSourcePath, fixturePath, { |
| recursive: true, |
| // All of the tests run in GOPATH mode for now. |
| // TODO(rstambler): Run tests in GOPATH and module mode. |
| filter: (src: string): boolean => { |
| if (path.basename(src) === 'go.mod') { |
| return false; |
| } |
| return true; |
| }, |
| }); |
| fs.copySync( |
| path.join(fixtureSourcePath, 'generatetests', 'generatetests.go'), |
| path.join(generateTestsSourcePath, 'generatetests.go') |
| ); |
| fs.copySync( |
| path.join(fixtureSourcePath, 'generatetests', 'generatetests.go'), |
| path.join(generateFunctionTestSourcePath, 'generatetests.go') |
| ); |
| fs.copySync( |
| path.join(fixtureSourcePath, 'generatetests', 'generatetests.go'), |
| path.join(generatePackageTestSourcePath, 'generatetests.go') |
| ); |
| fs.copySync( |
| path.join(fixtureSourcePath, 'diffTestData', 'file1.go'), |
| path.join(fixturePath, 'diffTest1Data', 'file1.go') |
| ); |
| fs.copySync( |
| path.join(fixtureSourcePath, 'diffTestData', 'file2.go'), |
| path.join(fixturePath, 'diffTest1Data', 'file2.go') |
| ); |
| fs.copySync( |
| path.join(fixtureSourcePath, 'diffTestData', 'file1.go'), |
| path.join(fixturePath, 'diffTest2Data', 'file1.go') |
| ); |
| fs.copySync( |
| path.join(fixtureSourcePath, 'diffTestData', 'file2.go'), |
| path.join(fixturePath, 'diffTest2Data', 'file2.go') |
| ); |
| }); |
| |
| suiteTeardown(() => { |
| fs.removeSync(repoPath); |
| }); |
| |
| teardown(() => { |
| sinon.restore(); |
| }); |
| |
| async function testDefinitionProvider(goConfig: vscode.WorkspaceConfiguration): Promise<any> { |
| const provider = new GoDefinitionProvider(goConfig); |
| const uri = vscode.Uri.file(path.join(fixturePath, 'baseTest', 'test.go')); |
| const position = new vscode.Position(10, 3); |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| const definitionInfo = await provider.provideDefinition(textDocument, position, dummyCancellationSource.token); |
| |
| assert.equal( |
| definitionInfo.uri.path.toLowerCase(), |
| uri.path.toLowerCase(), |
| `${definitionInfo.uri.path} is not the same as ${uri.path}` |
| ); |
| assert.equal(definitionInfo.range.start.line, 6); |
| assert.equal(definitionInfo.range.start.character, 5); |
| } |
| |
| async function testSignatureHelpProvider( |
| goConfig: vscode.WorkspaceConfiguration, |
| testCases: [vscode.Position, string, string, string[]][] |
| ): Promise<any> { |
| const provider = new GoSignatureHelpProvider(goConfig); |
| const uri = vscode.Uri.file(path.join(fixturePath, 'gogetdocTestData', 'test.go')); |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| |
| const promises = testCases.map(([position, expected, expectedDoc, expectedParams]) => |
| provider.provideSignatureHelp(textDocument, position, dummyCancellationSource.token).then((sigHelp) => { |
| assert.ok( |
| sigHelp, |
| `No signature for gogetdocTestData/test.go:${position.line + 1}:${position.character + 1}` |
| ); |
| assert.equal(sigHelp.signatures.length, 1, 'unexpected number of overloads'); |
| assert.equal(sigHelp.signatures[0].label, expected); |
| assert.equal(sigHelp.signatures[0].documentation, expectedDoc); |
| assert.equal(sigHelp.signatures[0].parameters.length, expectedParams.length); |
| for (let i = 0; i < expectedParams.length; i++) { |
| assert.equal(sigHelp.signatures[0].parameters[i].label, expectedParams[i]); |
| } |
| }) |
| ); |
| return Promise.all(promises); |
| } |
| |
| async function testHoverProvider( |
| goConfig: vscode.WorkspaceConfiguration, |
| testCases: [vscode.Position, string | null, string | null][] |
| ): Promise<any> { |
| const provider = new GoHoverProvider(goConfig); |
| const uri = vscode.Uri.file(path.join(fixturePath, 'gogetdocTestData', 'test.go')); |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| |
| const promises = testCases.map(([position, expectedSignature, expectedDocumentation]) => |
| provider.provideHover(textDocument, position, dummyCancellationSource.token).then((res) => { |
| if (expectedSignature === null && expectedDocumentation === null) { |
| assert.equal(res, null); |
| return; |
| } |
| let expectedHover = '\n```go\n' + expectedSignature + '\n```\n'; |
| if (expectedDocumentation != null) { |
| expectedHover += expectedDocumentation; |
| } |
| assert.equal(res.contents.length, 1); |
| assert.equal((<vscode.MarkdownString>res.contents[0]).value, expectedHover); |
| }) |
| ); |
| return Promise.all(promises); |
| } |
| |
| test('Test Definition Provider using godoc', async () => { |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| docsTool: { value: 'godoc' } |
| }); |
| await testDefinitionProvider(config); |
| }); |
| |
| test('Test Definition Provider using gogetdoc', async () => { |
| const gogetdocPath = getBinPath('gogetdoc'); |
| if (gogetdocPath === 'gogetdoc') { |
| // gogetdoc is not installed, so skip the test |
| return; |
| } |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| docsTool: { value: 'gogetdoc' } |
| }); |
| await testDefinitionProvider(config); |
| }); |
| |
| test('Test SignatureHelp Provider using godoc', async () => { |
| const printlnDoc = `Println formats using the default formats for its operands and writes to |
| standard output. Spaces are always added between operands and a newline is |
| appended. It returns the number of bytes written and any write error |
| encountered. |
| `; |
| |
| const testCases: [vscode.Position, string, string, string[]][] = [ |
| [ |
| new vscode.Position(19, 13), |
| 'Println(a ...interface{}) (n int, err error)', |
| printlnDoc, |
| ['a ...interface{}'] |
| ], |
| [ |
| new vscode.Position(23, 7), |
| 'print(txt string)', |
| `This is an unexported function so couldn't get this comment on hover :( Not\nanymore!!\n`, |
| ['txt string'] |
| ], |
| [ |
| new vscode.Position(41, 19), |
| 'Hello(s string, exclaim bool) string', |
| 'Hello is a method on the struct ABC. Will signature help understand this\ncorrectly\n', |
| ['s string', 'exclaim bool'] |
| ], |
| [ |
| new vscode.Position(41, 47), |
| 'EmptyLine(s string) string', |
| 'EmptyLine has docs\n\nwith a blank line in the middle\n', |
| ['s string'] |
| ] |
| ]; |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| docsTool: { value: 'godoc' } |
| }); |
| await testSignatureHelpProvider(config, testCases); |
| }); |
| |
| test('Test SignatureHelp Provider using gogetdoc', async () => { |
| const gogetdocPath = getBinPath('gogetdoc'); |
| if (gogetdocPath === 'gogetdoc') { |
| // gogetdoc is not installed, so skip the test |
| return; |
| } |
| |
| const printlnDoc = `Println formats using the default formats for its operands and writes to standard output. |
| Spaces are always added between operands and a newline is appended. |
| It returns the number of bytes written and any write error encountered. |
| `; |
| const testCases: [vscode.Position, string, string, string[]][] = [ |
| [ |
| new vscode.Position(19, 13), |
| 'Println(a ...interface{}) (n int, err error)', |
| printlnDoc, |
| ['a ...interface{}'] |
| ], |
| [ |
| new vscode.Position(23, 7), |
| 'print(txt string)', |
| `This is an unexported function so couldn't get this comment on hover :(\nNot anymore!!\n`, |
| ['txt string'] |
| ], |
| [ |
| new vscode.Position(41, 19), |
| 'Hello(s string, exclaim bool) string', |
| 'Hello is a method on the struct ABC. Will signature help understand this correctly\n', |
| ['s string', 'exclaim bool'] |
| ], |
| [ |
| new vscode.Position(41, 47), |
| 'EmptyLine(s string) string', |
| 'EmptyLine has docs\n\nwith a blank line in the middle\n', |
| ['s string'] |
| ] |
| ]; |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| docsTool: { value: 'gogetdoc' } |
| }); |
| await testSignatureHelpProvider(config, testCases); |
| }); |
| |
| test('Test Hover Provider using godoc', async () => { |
| const printlnDoc = `Println formats using the default formats for its operands and writes to |
| standard output. Spaces are always added between operands and a newline is |
| appended. It returns the number of bytes written and any write error |
| encountered. |
| `; |
| const testCases: [vscode.Position, string | null, string | null][] = [ |
| // [new vscode.Position(3,3), '/usr/local/go/src/fmt'], |
| [new vscode.Position(0, 3), null, null], // keyword |
| [new vscode.Position(23, 14), null, null], // inside a string |
| [new vscode.Position(20, 0), null, null], // just a } |
| [new vscode.Position(28, 16), null, null], // inside a number |
| [new vscode.Position(22, 5), 'main func()', '\n'], |
| [new vscode.Position(40, 23), 'import (math "math")', null], |
| [new vscode.Position(19, 6), 'Println func(a ...interface{}) (n int, err error)', printlnDoc], |
| [ |
| new vscode.Position(23, 4), |
| 'print func(txt string)', |
| `This is an unexported function so couldn't get this comment on hover :( Not\nanymore!!\n` |
| ] |
| ]; |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| docsTool: { value: 'godoc' } |
| }); |
| await testHoverProvider(config, testCases); |
| }); |
| |
| test('Test Hover Provider using gogetdoc', async () => { |
| const gogetdocPath = getBinPath('gogetdoc'); |
| if (gogetdocPath === 'gogetdoc') { |
| // gogetdoc is not installed, so skip the test |
| return; |
| } |
| |
| const printlnDoc = `Println formats using the default formats for its operands and writes to standard output. |
| Spaces are always added between operands and a newline is appended. |
| It returns the number of bytes written and any write error encountered. |
| `; |
| const testCases: [vscode.Position, string | null, string | null][] = [ |
| [new vscode.Position(0, 3), null, null], // keyword |
| [new vscode.Position(23, 11), null, null], // inside a string |
| [new vscode.Position(20, 0), null, null], // just a } |
| [new vscode.Position(28, 16), null, null], // inside a number |
| [new vscode.Position(22, 5), 'func main()', ''], |
| [ |
| new vscode.Position(23, 4), |
| 'func print(txt string)', |
| `This is an unexported function so couldn't get this comment on hover :(\nNot anymore!!\n` |
| ], |
| [ |
| new vscode.Position(40, 23), |
| 'package math', |
| 'Package math provides basic constants and mathematical functions.\n\nThis package does not guarantee bit-identical results across architectures.\n' |
| ], |
| [new vscode.Position(19, 6), 'func Println(a ...interface{}) (n int, err error)', printlnDoc], |
| [ |
| new vscode.Position(27, 14), |
| 'type ABC struct {\n a int\n b int\n c int\n}', |
| `ABC is a struct, you coudn't use Goto Definition or Hover info on this before\nNow you can due to gogetdoc and go doc\n` |
| ], |
| [ |
| new vscode.Position(28, 6), |
| 'func IPv4Mask(a, b, c, d byte) IPMask', |
| 'IPv4Mask returns the IP mask (in 4-byte form) of the\nIPv4 mask a.b.c.d.\n' |
| ] |
| ]; |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| docsTool: { value: 'gogetdoc' } |
| }); |
| await testHoverProvider(config, testCases); |
| }); |
| |
| test('Linting - concurrent process cancelation', async () => { |
| const util = require('../../src/util'); |
| const processutil = require('../../src/utils/processUtils'); |
| sinon.spy(util, 'runTool'); |
| sinon.spy(processutil, 'killProcessTree'); |
| |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| vetOnSave: { value: 'package' }, |
| vetFlags: { value: ['-all'] }, |
| buildOnSave: { value: 'package' }, |
| lintOnSave: { value: 'package' }, |
| // simulate a long running lint process by sleeping for a couple seconds |
| lintTool: { value: process.platform !== 'win32' ? 'sleep' : 'timeout' }, |
| lintFlags: { value: process.platform !== 'win32' ? ['2'] : ['/t', '2'] } |
| }); |
| |
| const results = await Promise.all([ |
| goLint(vscode.Uri.file(path.join(fixturePath, 'linterTest', 'linter_1.go')), config), |
| goLint(vscode.Uri.file(path.join(fixturePath, 'linterTest', 'linter_2.go')), config) |
| ]); |
| assert.equal(util.runTool.callCount, 2, 'should have launched 2 lint jobs'); |
| assert.equal(processutil.killProcessTree.callCount, 1, 'should have killed 1 lint job before launching the next'); |
| }); |
| |
| test('Linting - lint errors with multiple open files', async () => { |
| // handleDiagnosticErrors may adjust the lint errors' ranges to make the error more visible. |
| // This adjustment applies only to the text documents known to vscode. This test checks |
| // the adjustment is made consistently across multiple open text documents. |
| const file1 = await vscode.workspace.openTextDocument(vscode.Uri.file(path.join(fixturePath, 'linterTest', 'linter_1.go'))); |
| const file2 = await vscode.workspace.openTextDocument(vscode.Uri.file(path.join(fixturePath, 'linterTest', 'linter_2.go'))); |
| const warnings = await goLint(file2.uri, Object.create(vscode.workspace.getConfiguration('go'), { |
| lintTool: { value: 'golint' }, |
| lintFlags: { value: [] } |
| }), 'package'); |
| |
| const diagnosticCollection = vscode.languages.createDiagnosticCollection('linttest'); |
| handleDiagnosticErrors(file2, warnings, diagnosticCollection); |
| |
| // The first diagnostic message for each file should be about the use of MixedCaps in package name. |
| // Both files belong to the same package name, and we want them to be identical. |
| const file1Diagnostics = diagnosticCollection.get(file1.uri); |
| const file2Diagnostics = diagnosticCollection.get(file2.uri); |
| assert(file1Diagnostics.length > 0); |
| assert(file2Diagnostics.length > 0); |
| assert.deepStrictEqual(file1Diagnostics[0], file2Diagnostics[0]); |
| }); |
| |
| test('Error checking', async () => { |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| vetOnSave: { value: 'package' }, |
| vetFlags: { value: ['-all'] }, |
| lintOnSave: { value: 'package' }, |
| lintTool: { value: 'golint' }, |
| lintFlags: { value: [] }, |
| buildOnSave: { value: 'package' } |
| }); |
| const expectedLintErrors = [ |
| { |
| line: 7, |
| severity: 'warning', |
| msg: 'exported function Print2 should have comment or be unexported' |
| }, |
| ]; |
| // If a user has enabled diagnostics via a language server, |
| // then we disable running build or vet to avoid duplicate errors and warnings. |
| const lspConfig = buildLanguageServerConfig(getGoConfig()); |
| const expectedBuildVetErrors = lspConfig.enabled ? [] : [{ line: 11, severity: 'error', msg: 'undefined: prin' }]; |
| |
| const expected = [...expectedLintErrors, ...expectedBuildVetErrors]; |
| const diagnostics = await check(vscode.Uri.file(path.join(fixturePath, 'errorsTest', 'errors.go')), config); |
| const sortedDiagnostics = ([] as ICheckResult[]).concat |
| .apply( |
| [], |
| diagnostics.map((x) => x.errors) |
| ) |
| .sort((a: any, b: any) => a.line - b.line); |
| assert.equal(sortedDiagnostics.length > 0, true, `Failed to get linter results`); |
| |
| const matchCount = expected.filter((expectedItem) => { |
| return sortedDiagnostics.some((diag: any) => { |
| return ( |
| expectedItem.line === diag.line && |
| expectedItem.severity === diag.severity && |
| expectedItem.msg === diag.msg |
| ); |
| }); |
| }); |
| assert.equal(matchCount.length >= expected.length, true, `Failed to match expected errors \n${JSON.stringify(sortedDiagnostics)} \n VS\n ${JSON.stringify(expected)}`); |
| }); |
| |
| test('Test Generate unit tests skeleton for file', async () => { |
| const gotestsPath = getBinPath('gotests'); |
| if (gotestsPath === 'gotests') { |
| // gotests is not installed, so skip the test |
| return; |
| } |
| |
| const uri = vscode.Uri.file(path.join(generateTestsSourcePath, 'generatetests.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| await generateTestCurrentFile(); |
| |
| const testFileGenerated = fs.existsSync(path.join(generateTestsSourcePath, 'generatetests_test.go')); |
| assert.equal(testFileGenerated, true, 'Test file not generated.'); |
| }); |
| |
| test('Test Generate unit tests skeleton for a function', async () => { |
| const gotestsPath = getBinPath('gotests'); |
| if (gotestsPath === 'gotests') { |
| // gotests is not installed, so skip the test |
| return; |
| } |
| |
| const uri = vscode.Uri.file(path.join(generateFunctionTestSourcePath, 'generatetests.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| const editor = await vscode.window.showTextDocument(document); |
| editor.selection = new vscode.Selection(5, 0, 6, 0); |
| await generateTestCurrentFunction(); |
| |
| const testFileGenerated = fs.existsSync(path.join(generateTestsSourcePath, 'generatetests_test.go')); |
| assert.equal(testFileGenerated, true, 'Test file not generated.'); |
| }); |
| |
| test('Test Generate unit tests skeleton for package', async () => { |
| const gotestsPath = getBinPath('gotests'); |
| if (gotestsPath === 'gotests') { |
| // gotests is not installed, so skip the test |
| return; |
| } |
| |
| const uri = vscode.Uri.file(path.join(generatePackageTestSourcePath, 'generatetests.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| await generateTestCurrentPackage(); |
| |
| const testFileGenerated = fs.existsSync(path.join(generateTestsSourcePath, 'generatetests_test.go')); |
| assert.equal(testFileGenerated, true, 'Test file not generated.'); |
| }); |
| |
| test('Test diffUtils.getEditsFromUnifiedDiffStr', async function () { |
| if (process.platform === 'win32') { |
| // This test requires diff tool that's not available on windows |
| this.skip(); |
| } |
| |
| const file1path = path.join(fixturePath, 'diffTest1Data', 'file1.go'); |
| const file2path = path.join(fixturePath, 'diffTest1Data', 'file2.go'); |
| const file1uri = vscode.Uri.file(file1path); |
| const file2contents = fs.readFileSync(file2path, 'utf8'); |
| |
| const fileEditPatches: any | FilePatch[] = await new Promise((resolve) => { |
| cp.exec(`diff -u ${file1path} ${file2path}`, (err, stdout, stderr) => { |
| const filePatches: FilePatch[] = getEditsFromUnifiedDiffStr(stdout); |
| |
| if (!filePatches || filePatches.length !== 1) { |
| assert.fail(null, null, 'Failed to get patches for the test file', ''); |
| } |
| |
| if (!filePatches[0].fileName) { |
| assert.fail(null, null, 'Failed to parse the file path from the diff output', ''); |
| } |
| |
| if (!filePatches[0].edits) { |
| assert.fail(null, null, 'Failed to parse edits from the diff output', ''); |
| } |
| resolve(filePatches); |
| }); |
| }); |
| |
| const textDocument = await vscode.workspace.openTextDocument(file1uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| await editor.edit((editBuilder) => { |
| fileEditPatches[0].edits.forEach((edit: any) => { |
| edit.applyUsingTextEditorEdit(editBuilder); |
| }); |
| }); |
| assert.equal(editor.document.getText(), file2contents); |
| }); |
| |
| test('Test diffUtils.getEdits', async () => { |
| const file1path = path.join(fixturePath, 'diffTest2Data', 'file1.go'); |
| const file2path = path.join(fixturePath, 'diffTest2Data', 'file2.go'); |
| const file1uri = vscode.Uri.file(file1path); |
| const file1contents = fs.readFileSync(file1path, 'utf8'); |
| const file2contents = fs.readFileSync(file2path, 'utf8'); |
| |
| const fileEdits = getEdits(file1path, file1contents, file2contents); |
| |
| if (!fileEdits) { |
| assert.fail(null, null, 'Failed to get patches for the test file', ''); |
| } |
| |
| if (!fileEdits.fileName) { |
| assert.fail(null, null, 'Failed to parse the file path from the diff output', ''); |
| } |
| |
| if (!fileEdits.edits) { |
| assert.fail(null, null, 'Failed to parse edits from the diff output', ''); |
| } |
| |
| const textDocument = await vscode.workspace.openTextDocument(file1uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| await editor.edit((editBuilder) => { |
| fileEdits.edits.forEach((edit) => { |
| edit.applyUsingTextEditorEdit(editBuilder); |
| }); |
| }); |
| assert.equal(editor.document.getText(), file2contents); |
| }); |
| |
| test('Test Env Variables are passed to Tests', async () => { |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| testEnvVars: { value: { dummyEnvVar: 'dummyEnvValue', dummyNonString: 1 } } |
| }); |
| const uri = vscode.Uri.file(path.join(fixturePath, 'baseTest', 'sample_test.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| |
| const result = await testCurrentFile(config, false, []); |
| assert.equal(result, true); |
| }); |
| |
| test('Test Outline', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'outlineTest', 'test.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| const options = { |
| document, |
| fileName: document.fileName, |
| importsOption: GoOutlineImportsOptions.Include |
| }; |
| |
| const outlines = await documentSymbols(options, dummyCancellationSource.token); |
| const packageSymbols = outlines.filter((x: any) => x.kind === vscode.SymbolKind.Package); |
| const imports = outlines[0].children.filter((x: any) => x.kind === vscode.SymbolKind.Namespace); |
| const functions = outlines[0].children.filter((x: any) => x.kind === vscode.SymbolKind.Function); |
| |
| assert.equal(packageSymbols.length, 1); |
| assert.equal(packageSymbols[0].name, 'main'); |
| assert.equal(imports.length, 1); |
| assert.equal(imports[0].name, '"fmt"'); |
| assert.equal(functions.length, 2); |
| assert.equal(functions[0].name, 'print'); |
| assert.equal(functions[1].name, 'main'); |
| }); |
| |
| test('Test Outline imports only', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'outlineTest', 'test.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| const options = { |
| document, |
| fileName: document.fileName, |
| importsOption: GoOutlineImportsOptions.Only |
| }; |
| |
| const outlines = await documentSymbols(options, dummyCancellationSource.token); |
| const packageSymbols = outlines.filter((x) => x.kind === vscode.SymbolKind.Package); |
| const imports = outlines[0].children.filter((x: any) => x.kind === vscode.SymbolKind.Namespace); |
| const functions = outlines[0].children.filter((x: any) => x.kind === vscode.SymbolKind.Function); |
| |
| assert.equal(packageSymbols.length, 1); |
| assert.equal(packageSymbols[0].name, 'main'); |
| assert.equal(imports.length, 1); |
| assert.equal(imports[0].name, '"fmt"'); |
| assert.equal(functions.length, 0); |
| }); |
| |
| test('Test Outline document symbols', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'outlineTest', 'test.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| const symbolProvider = new GoDocumentSymbolProvider(); |
| |
| const outlines = await symbolProvider.provideDocumentSymbols(document, dummyCancellationSource.token); |
| const packages = outlines.filter((x) => x.kind === vscode.SymbolKind.Package); |
| const variables = outlines[0].children.filter((x: any) => x.kind === vscode.SymbolKind.Variable); |
| const functions = outlines[0].children.filter((x: any) => x.kind === vscode.SymbolKind.Function); |
| const structs = outlines[0].children.filter((x: any) => x.kind === vscode.SymbolKind.Struct); |
| const interfaces = outlines[0].children.filter((x: any) => x.kind === vscode.SymbolKind.Interface); |
| |
| assert.equal(packages[0].name, 'main'); |
| assert.equal(variables.length, 0); |
| assert.equal(functions[0].name, 'print'); |
| assert.equal(functions[1].name, 'main'); |
| assert.equal(structs.length, 1); |
| assert.equal(structs[0].name, 'foo'); |
| assert.equal(interfaces.length, 1); |
| assert.equal(interfaces[0].name, 'circle'); |
| }); |
| |
| test('Test listPackages', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'baseTest', 'test.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| |
| const includeImportedPkgs = await listPackages(false); |
| const excludeImportedPkgs = await listPackages(true); |
| assert.equal(includeImportedPkgs.indexOf('fmt') > -1, true); |
| assert.equal(excludeImportedPkgs.indexOf('fmt') > -1, false); |
| }); |
| |
| test('Replace vendor packages with relative path', async () => { |
| const vendorSupport = await isVendorSupported(); |
| const filePath = path.join(fixturePath, 'vendoring', 'main.go'); |
| const workDir = path.dirname(filePath); |
| const vendorPkgsFullPath = [ |
| 'test/testfixture/vendoring/vendor/example.com/vendorpls', |
| ]; |
| const vendorPkgsRelativePath = ['example.com/vendorpls']; |
| |
| const gopkgsPromise = getAllPackages(workDir).then((pkgMap) => { |
| const pkgs = Array.from(pkgMap.keys()).filter((p) => { |
| const pkg = pkgMap.get(p); |
| return pkg && pkg.name !== 'main'; |
| }); |
| if (vendorSupport) { |
| vendorPkgsFullPath.forEach((pkg) => { |
| assert.equal(pkgs.indexOf(pkg) > -1, true, `Package not found by goPkgs: ${pkg}`); |
| }); |
| vendorPkgsRelativePath.forEach((pkg) => { |
| assert.equal( |
| pkgs.indexOf(pkg), |
| -1, |
| `Relative path to vendor package ${pkg} should not be returned by gopkgs command` |
| ); |
| }); |
| } |
| return pkgs; |
| }); |
| |
| const listPkgPromise: Thenable<string[]> = vscode.workspace |
| .openTextDocument(vscode.Uri.file(filePath)) |
| .then(async (document) => { |
| await vscode.window.showTextDocument(document); |
| const pkgs = await listPackages(); |
| if (vendorSupport) { |
| vendorPkgsRelativePath.forEach((pkg) => { |
| assert.equal(pkgs.indexOf(pkg) > -1, true, `Relative path for vendor package ${pkg} not found`); |
| }); |
| vendorPkgsFullPath.forEach((pkg) => { |
| assert.equal( |
| pkgs.indexOf(pkg), |
| -1, |
| `Full path for vendor package ${pkg} should be shown by listPackages method` |
| ); |
| }); |
| } |
| return pkgs; |
| }); |
| |
| const values = await Promise.all<string[]>([gopkgsPromise, listPkgPromise]); |
| if (!vendorSupport) { |
| const originalPkgs = values[0].sort(); |
| const updatedPkgs = values[1].sort(); |
| assert.equal(originalPkgs.length, updatedPkgs.length); |
| for (let index = 0; index < originalPkgs.length; index++) { |
| assert.equal(updatedPkgs[index], originalPkgs[index]); |
| } |
| } |
| }); |
| |
| test('Vendor pkgs from other projects should not be allowed to import', async () => { |
| const vendorSupport = await isVendorSupported(); |
| const filePath = path.join(fixturePath, 'baseTest', 'test.go'); |
| const vendorPkgs = [ |
| 'test/testfixture/vendoring/vendor/example.com/vendorpls', |
| ]; |
| |
| const gopkgsPromise = new Promise<void>((resolve, reject) => { |
| const cmd = cp.spawn(getBinPath('gopkgs'), ['-format', '{{.ImportPath}}'], { |
| env: process.env |
| }); |
| const chunks: any[] = []; |
| cmd.stdout.on('data', (d) => chunks.push(d)); |
| cmd.on('close', () => { |
| const pkgs = chunks |
| .join('') |
| .split('\n') |
| .filter((pkg) => pkg) |
| .sort(); |
| if (vendorSupport) { |
| vendorPkgs.forEach((pkg) => { |
| assert.equal(pkgs.indexOf(pkg) > -1, true, `Package not found by goPkgs: ${pkg}`); |
| }); |
| } |
| return resolve(); |
| }); |
| }); |
| |
| const listPkgPromise: Thenable<void> = vscode.workspace |
| .openTextDocument(vscode.Uri.file(filePath)) |
| .then(async (document) => { |
| await vscode.window.showTextDocument(document); |
| const pkgs = await listPackages(); |
| if (vendorSupport) { |
| vendorPkgs.forEach((pkg) => { |
| assert.equal( |
| pkgs.indexOf(pkg), |
| -1, |
| `Vendor package ${pkg} should not be shown by listPackages method` |
| ); |
| }); |
| } |
| }); |
| |
| return Promise.all<void>([gopkgsPromise, listPkgPromise]); |
| }); |
| |
| test('Workspace Symbols', () => { |
| const workspacePath = path.join(fixturePath, 'vendoring'); |
| const configWithoutIgnoringFolders = Object.create(vscode.workspace.getConfiguration('go'), { |
| gotoSymbol: { |
| value: { |
| ignoreFolders: [] |
| } |
| } |
| }); |
| const configWithIgnoringFolders = Object.create(vscode.workspace.getConfiguration('go'), { |
| gotoSymbol: { |
| value: { |
| ignoreFolders: ['vendor'] |
| } |
| } |
| }); |
| const configWithIncludeGoroot = Object.create(vscode.workspace.getConfiguration('go'), { |
| gotoSymbol: { |
| value: { |
| includeGoroot: true |
| } |
| } |
| }); |
| const configWithoutIncludeGoroot = Object.create(vscode.workspace.getConfiguration('go'), { |
| gotoSymbol: { |
| value: { |
| includeGoroot: false |
| } |
| } |
| }); |
| |
| const withoutIgnoringFolders = getWorkspaceSymbols( |
| workspacePath, |
| 'SomethingStr', |
| dummyCancellationSource.token, |
| configWithoutIgnoringFolders |
| ).then((results) => { |
| assert.equal(results[0].name, 'SomethingStrange'); |
| assert.equal(results[0].path, path.join(workspacePath, 'vendor/example.com/vendorpls/lib.go')); |
| }); |
| const withIgnoringFolders = getWorkspaceSymbols( |
| workspacePath, |
| 'SomethingStr', |
| dummyCancellationSource.token, |
| configWithIgnoringFolders |
| ).then((results) => { |
| assert.equal(results.length, 0); |
| }); |
| const withoutIncludingGoroot = getWorkspaceSymbols( |
| workspacePath, |
| 'Mutex', |
| dummyCancellationSource.token, |
| configWithoutIncludeGoroot |
| ).then((results) => { |
| assert.equal(results.length, 0); |
| }); |
| const withIncludingGoroot = getWorkspaceSymbols( |
| workspacePath, |
| 'Mutex', |
| dummyCancellationSource.token, |
| configWithIncludeGoroot |
| ).then((results) => { |
| assert(results.some((result) => result.name === 'Mutex')); |
| }); |
| |
| return Promise.all([withIgnoringFolders, withoutIgnoringFolders, withIncludingGoroot, withoutIncludingGoroot]); |
| }); |
| |
| test('Test Completion', async () => { |
| const printlnDoc = `Println formats using the default formats for its operands and writes to |
| standard output. Spaces are always added between operands and a newline is |
| appended. It returns the number of bytes written and any write error |
| encountered. |
| `; |
| const provider = new GoCompletionItemProvider(); |
| const testCases: [vscode.Position, string, string | null, string | null][] = [ |
| [new vscode.Position(7, 4), 'fmt', 'fmt', null], |
| [new vscode.Position(7, 6), 'Println', 'func(a ...interface{}) (n int, err error)', printlnDoc] |
| ]; |
| const uri = vscode.Uri.file(path.join(fixturePath, 'baseTest', 'test.go')); |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| |
| const promises = testCases.map(([position, expectedLabel, expectedDetail, expectedDoc]) => |
| provider |
| .provideCompletionItems(editor.document, position, dummyCancellationSource.token) |
| .then(async (items) => { |
| const item = items.items.find((x) => x.label === expectedLabel); |
| if (!item) { |
| assert.fail('missing expected item in completion list'); |
| } |
| assert.equal(item.detail, expectedDetail); |
| const resolvedItemResult: vscode.ProviderResult<vscode.CompletionItem> = provider.resolveCompletionItem( |
| item, |
| dummyCancellationSource.token |
| ); |
| if (!resolvedItemResult) { |
| return; |
| } |
| if (resolvedItemResult instanceof vscode.CompletionItem) { |
| if (resolvedItemResult.documentation) { |
| assert.equal((<vscode.MarkdownString>resolvedItemResult.documentation).value, expectedDoc); |
| } |
| return; |
| } |
| const resolvedItem = await resolvedItemResult; |
| if (resolvedItem) { |
| assert.equal((<vscode.MarkdownString>resolvedItem.documentation).value, expectedDoc); |
| } |
| }) |
| ); |
| await Promise.all(promises); |
| }); |
| |
| test('Test Completion Snippets For Functions', async () => { |
| const provider = new GoCompletionItemProvider(); |
| const uri = vscode.Uri.file(path.join(fixturePath, 'completions', 'snippets.go')); |
| const baseConfig = vscode.workspace.getConfiguration('go'); |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| |
| const noFunctionSnippet = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(9, 6), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: false } |
| }) |
| ) |
| .then((items) => { |
| items = items instanceof vscode.CompletionList ? items.items : items; |
| const item = items.find((x) => x.label === 'Print'); |
| if (!item) { |
| assert.fail('Suggestion with label "Print" not found in test case noFunctionSnippet.'); |
| } |
| assert.equal(!item.insertText, true); |
| }); |
| const withFunctionSnippet = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(9, 6), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: true } |
| }) |
| ) |
| .then((items1) => { |
| items1 = items1 instanceof vscode.CompletionList ? items1.items : items1; |
| const item1 = items1.find((x) => x.label === 'Print'); |
| if (!item1) { |
| assert.fail('Suggestion with label "Print" not found in test case withFunctionSnippet.'); |
| } |
| assert.equal((<vscode.SnippetString>item1.insertText).value, 'Print(${1:a ...interface{\\}})'); |
| }); |
| const withFunctionSnippetNotype = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(9, 6), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggestWithoutType: { value: true } |
| }) |
| ) |
| .then((items2) => { |
| items2 = items2 instanceof vscode.CompletionList ? items2.items : items2; |
| const item2 = items2.find((x) => x.label === 'Print'); |
| if (!item2) { |
| assert.fail('Suggestion with label "Print" not found in test case withFunctionSnippetNotype.'); |
| } |
| assert.equal((<vscode.SnippetString>item2.insertText).value, 'Print(${1:a})'); |
| }); |
| const noFunctionAsVarSnippet = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(11, 3), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: false } |
| }) |
| ) |
| .then((items3) => { |
| items3 = items3 instanceof vscode.CompletionList ? items3.items : items3; |
| const item3 = items3.find((x) => x.label === 'funcAsVariable'); |
| if (!item3) { |
| assert.fail('Suggestion with label "Print" not found in test case noFunctionAsVarSnippet.'); |
| } |
| assert.equal(!item3.insertText, true); |
| }); |
| const withFunctionAsVarSnippet = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(11, 3), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: true } |
| }) |
| ) |
| .then((items4) => { |
| items4 = items4 instanceof vscode.CompletionList ? items4.items : items4; |
| const item4 = items4.find((x) => x.label === 'funcAsVariable'); |
| if (!item4) { |
| assert.fail('Suggestion with label "Print" not found in test case withFunctionAsVarSnippet.'); |
| } |
| assert.equal((<vscode.SnippetString>item4.insertText).value, 'funcAsVariable(${1:k string})'); |
| }); |
| const withFunctionAsVarSnippetNoType = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(11, 3), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggestWithoutType: { value: true } |
| }) |
| ) |
| .then((items5) => { |
| items5 = items5 instanceof vscode.CompletionList ? items5.items : items5; |
| const item5 = items5.find((x) => x.label === 'funcAsVariable'); |
| if (!item5) { |
| assert.fail('Suggestion with label "Print" not found in test case withFunctionAsVarSnippetNoType.'); |
| } |
| assert.equal((<vscode.SnippetString>item5.insertText).value, 'funcAsVariable(${1:k})'); |
| }); |
| const noFunctionAsTypeSnippet = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(14, 0), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: false } |
| }) |
| ) |
| .then((items6) => { |
| items6 = items6 instanceof vscode.CompletionList ? items6.items : items6; |
| const item1 = items6.find((x) => x.label === 'HandlerFunc'); |
| const item2 = items6.find((x) => x.label === 'HandlerFuncWithArgNames'); |
| const item3 = items6.find((x) => x.label === 'HandlerFuncNoReturnType'); |
| if (!item1) { |
| assert.fail('Suggestion with label "HandlerFunc" not found in test case noFunctionAsTypeSnippet.'); |
| } |
| assert.equal(!item1.insertText, true); |
| if (!item2) { |
| assert.fail( |
| 'Suggestion with label "HandlerFuncWithArgNames" not found in test case noFunctionAsTypeSnippet.' |
| ); |
| } |
| assert.equal(!item2.insertText, true); |
| if (!item3) { |
| assert.fail( |
| 'Suggestion with label "HandlerFuncNoReturnType" not found in test case noFunctionAsTypeSnippet.' |
| ); |
| } |
| assert.equal(!item3.insertText, true); |
| }); |
| const withFunctionAsTypeSnippet = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(14, 0), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: true } |
| }) |
| ) |
| .then((items7) => { |
| items7 = items7 instanceof vscode.CompletionList ? items7.items : items7; |
| const item11 = items7.find((x) => x.label === 'HandlerFunc'); |
| const item21 = items7.find((x) => x.label === 'HandlerFuncWithArgNames'); |
| const item31 = items7.find((x) => x.label === 'HandlerFuncNoReturnType'); |
| if (!item11) { |
| assert.fail( |
| 'Suggestion with label "HandlerFunc" not found in test case withFunctionAsTypeSnippet.' |
| ); |
| } |
| assert.equal( |
| (<vscode.SnippetString>item11.insertText).value, |
| 'HandlerFunc(func(${1:arg1} string, ${2:arg2} string) {\n\t$3\n}) (string, string)' |
| ); |
| if (!item21) { |
| assert.fail( |
| 'Suggestion with label "HandlerFuncWithArgNames" not found in test case withFunctionAsTypeSnippet.' |
| ); |
| } |
| assert.equal( |
| (<vscode.SnippetString>item21.insertText).value, |
| 'HandlerFuncWithArgNames(func(${1:w} string, ${2:r} string) {\n\t$3\n}) int' |
| ); |
| if (!item31) { |
| assert.fail( |
| 'Suggestion with label "HandlerFuncNoReturnType" not found in test case withFunctionAsTypeSnippet.' |
| ); |
| } |
| assert.equal( |
| (<vscode.SnippetString>item31.insertText).value, |
| 'HandlerFuncNoReturnType(func(${1:arg1} string, ${2:arg2} string) {\n\t$3\n})' |
| ); |
| }); |
| await Promise.all([ |
| noFunctionSnippet, |
| withFunctionSnippet, |
| withFunctionSnippetNotype, |
| noFunctionAsVarSnippet, |
| withFunctionAsVarSnippet, |
| withFunctionAsVarSnippetNoType, |
| noFunctionAsTypeSnippet, |
| withFunctionAsTypeSnippet |
| ]); |
| }); |
| |
| test('Test No Completion Snippets For Functions', async () => { |
| const provider = new GoCompletionItemProvider(); |
| const uri = vscode.Uri.file(path.join(fixturePath, 'completions', 'nosnippets.go')); |
| const baseConfig = vscode.workspace.getConfiguration('go'); |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| |
| const symbolFollowedByBrackets = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(5, 10), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: true } |
| }) |
| ) |
| .then((items) => { |
| items = items instanceof vscode.CompletionList ? items.items : items; |
| const item = items.find((x) => x.label === 'Print'); |
| if (!item) { |
| assert.fail('Suggestion with label "Print" not found in test case symbolFollowedByBrackets.'); |
| } |
| assert.equal(!item.insertText, true, 'Unexpected snippet when symbol is followed by ().'); |
| }); |
| const symbolAsLastParameter = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(7, 13), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: true } |
| }) |
| ) |
| .then((items1) => { |
| items1 = items1 instanceof vscode.CompletionList ? items1.items : items1; |
| const item1 = items1.find((x) => x.label === 'funcAsVariable'); |
| if (!item1) { |
| assert.fail('Suggestion with label "funcAsVariable" not found in test case symbolAsLastParameter.'); |
| } |
| assert.equal(!item1.insertText, true, 'Unexpected snippet when symbol is a parameter inside func call'); |
| }); |
| const symbolsAsNonLastParameter = provider |
| .provideCompletionItemsInternal( |
| editor.document, |
| new vscode.Position(8, 11), |
| dummyCancellationSource.token, |
| Object.create(baseConfig, { |
| useCodeSnippetsOnFunctionSuggest: { value: true } |
| }) |
| ) |
| .then((items2) => { |
| items2 = items2 instanceof vscode.CompletionList ? items2.items : items2; |
| const item2 = items2.find((x) => x.label === 'funcAsVariable'); |
| if (!item2) { |
| assert.fail( |
| 'Suggestion with label "funcAsVariable" not found in test case symbolsAsNonLastParameter.' |
| ); |
| } |
| assert.equal( |
| !item2.insertText, |
| true, |
| 'Unexpected snippet when symbol is one of the parameters inside func call.' |
| ); |
| }); |
| await Promise.all([symbolFollowedByBrackets, symbolAsLastParameter, symbolsAsNonLastParameter]); |
| }); |
| |
| test('Test Completion on unimported packages', async () => { |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| autocompleteUnimportedPackages: { value: true } |
| }); |
| const provider = new GoCompletionItemProvider(); |
| const testCases: [vscode.Position, string[]][] = [ |
| [new vscode.Position(10, 3), ['bytes']], |
| [new vscode.Position(11, 6), ['Abs', 'Acos', 'Asin']] |
| ]; |
| const uri = vscode.Uri.file(path.join(fixturePath, 'completions', 'unimportedPkgs.go')); |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| |
| const promises = testCases.map(([position, expected]) => |
| provider |
| .provideCompletionItemsInternal(editor.document, position, dummyCancellationSource.token, config) |
| .then((items) => { |
| items = items instanceof vscode.CompletionList ? items.items : items; |
| const labels = items.map((x) => x.label); |
| for (const entry of expected) { |
| assert.equal( |
| labels.indexOf(entry) > -1, |
| true, |
| `missing expected item in completion list: ${entry} Actual: ${labels}` |
| ); |
| } |
| }) |
| ); |
| await Promise.all(promises); |
| }); |
| |
| test('Test Completion on unimported packages (multiple)', async () => { |
| const config = Object.create(vscode.workspace.getConfiguration('go'), { |
| gocodeFlags: { value: ['-builtin'] } |
| }); |
| const provider = new GoCompletionItemProvider(); |
| const position = new vscode.Position(3, 14); |
| const expectedItems = [ |
| { |
| label: 'template (html/template)', |
| import: '\nimport (\n\t"html/template"\n)\n' |
| }, |
| { |
| label: 'template (text/template)', |
| import: '\nimport (\n\t"text/template"\n)\n' |
| } |
| ]; |
| const uri = vscode.Uri.file(path.join(fixturePath, 'completions', 'unimportedMultiplePkgs.go')); |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| |
| const completionResult = await provider.provideCompletionItemsInternal( |
| editor.document, |
| position, |
| dummyCancellationSource.token, |
| config |
| ); |
| const items = completionResult instanceof vscode.CompletionList ? completionResult.items : completionResult; |
| const labels = items.map((x) => x.label); |
| expectedItems.forEach((expectedItem) => { |
| const actualItem: vscode.CompletionItem = items.filter((item) => item.label === expectedItem.label)[0]; |
| if (!actualItem) { |
| assert.fail( |
| actualItem, |
| expectedItem, |
| `Missing expected item in completion list: ${expectedItem.label} Actual: ${labels}` |
| ); |
| } |
| if (!actualItem.additionalTextEdits) { |
| assert.fail(`Missing additionalTextEdits on suggestion for ${actualItem}`); |
| } |
| assert.equal(actualItem.additionalTextEdits.length, 1); |
| assert.equal(actualItem.additionalTextEdits[0].newText, expectedItem.import); |
| }); |
| }); |
| |
| test('Test Completion on Comments for Exported Members', async () => { |
| const provider = new GoCompletionItemProvider(); |
| const testCases: [vscode.Position, string[]][] = [ |
| [new vscode.Position(6, 4), ['Language']], |
| [new vscode.Position(9, 4), ['GreetingText']], |
| // checking for comment completions with begining of comment without space |
| [new vscode.Position(12, 2), []], |
| // cursor between /$/ this should not trigger any completion |
| [new vscode.Position(12, 1), []], |
| [new vscode.Position(12, 4), ['SayHello']], |
| [new vscode.Position(17, 5), ['HelloParams']], |
| [new vscode.Position(26, 5), ['Abs']] |
| ]; |
| const uri = vscode.Uri.file(path.join(fixturePath, 'completions', 'exportedMemberDocs.go')); |
| |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| |
| const promises = testCases.map(([position, expected]) => |
| provider.provideCompletionItems(editor.document, position, dummyCancellationSource.token).then((items) => { |
| const labels = items.items.map((x) => x.label); |
| assert.equal( |
| expected.length, |
| labels.length, |
| `expected number of completions: ${expected.length} Actual: ${labels.length} at position(${position.line + 1 |
| },${position.character + 1}) ${labels}` |
| ); |
| expected.forEach((entry, index) => { |
| assert.equal( |
| entry, |
| labels[index], |
| `mismatch in comment completion list Expected: ${entry} Actual: ${labels[index]}` |
| ); |
| }); |
| }) |
| ); |
| await Promise.all(promises); |
| }); |
| |
| test('getImportPath()', () => { |
| const testCases: [string, string][] = [ |
| ['import "github.com/sirupsen/logrus"', 'github.com/sirupsen/logrus'], |
| ['import "net/http"', 'net/http'], |
| ['"github.com/sirupsen/logrus"', 'github.com/sirupsen/logrus'], |
| ['', ''], |
| ['func foo(bar int) (int, error) {', ''], |
| ['// This is a comment, complete with punctuation.', ''] |
| ]; |
| |
| testCases.forEach((run) => { |
| assert.equal(run[1], getImportPath(run[0])); |
| }); |
| }); |
| |
| test('goPlay - success run', async () => { |
| const goplayPath = getBinPath('goplay'); |
| if (goplayPath === 'goplay') { |
| // goplay is not installed, so skip the test |
| return; |
| } |
| |
| const validCode = ` |
| package main |
| import ( |
| "fmt" |
| ) |
| func main() { |
| for i := 1; i < 4; i++ { |
| fmt.Printf("%v ", i) |
| } |
| fmt.Print("Go!") |
| }`; |
| const goConfig = Object.create(vscode.workspace.getConfiguration('go'), { |
| playground: { value: { run: true, openbrowser: false, share: false } } |
| }); |
| |
| await goPlay(validCode, goConfig['playground']).then( |
| (result) => { |
| assert(result.includes('1 2 3 Go!')); |
| }, |
| (e) => { |
| assert.ifError(e); |
| } |
| ); |
| }); |
| |
| test('goPlay - success run & share', async () => { |
| const goplayPath = getBinPath('goplay'); |
| if (goplayPath === 'goplay') { |
| // goplay is not installed, so skip the test |
| return; |
| } |
| |
| const validCode = ` |
| package main |
| import ( |
| "fmt" |
| ) |
| func main() { |
| for i := 1; i < 4; i++ { |
| fmt.Printf("%v ", i) |
| } |
| fmt.Print("Go!") |
| }`; |
| const goConfig = Object.create(vscode.workspace.getConfiguration('go'), { |
| playground: { value: { run: true, openbrowser: false, share: true } } |
| }); |
| |
| await goPlay(validCode, goConfig['playground']).then( |
| (result) => { |
| assert(result.includes('1 2 3 Go!')); |
| assert(result.includes('https://play.golang.org/')); |
| }, |
| (e) => { |
| assert.ifError(e); |
| } |
| ); |
| }); |
| |
| test('goPlay - fail', async () => { |
| const goplayPath = getBinPath('goplay'); |
| if (goplayPath === 'goplay') { |
| // goplay is not installed, so skip the test |
| return; |
| } |
| |
| const invalidCode = ` |
| package main |
| import ( |
| "fmt" |
| ) |
| func fantasy() { |
| fmt.Print("not a main package, sorry") |
| }`; |
| const goConfig = Object.create(vscode.workspace.getConfiguration('go'), { |
| playground: { value: { run: true, openbrowser: false, share: false } } |
| }); |
| |
| await goPlay(invalidCode, goConfig['playground']).then( |
| (result) => { |
| assert.ifError(result); |
| }, |
| (e) => { |
| assert.ok(e); |
| } |
| ); |
| }); |
| |
| test('Build Tags checking', async () => { |
| const goplsConfig = buildLanguageServerConfig(getGoConfig()); |
| if (goplsConfig.enabled) { |
| // Skip this test if gopls is enabled. Build/Vet checks this test depend on are |
| // disabled when the language server is enabled, and gopls is not handling tags yet. |
| return; |
| } |
| // Note: The following checks can't be parallelized because the underlying go build command |
| // runner (goBuild) will cancel any outstanding go build commands. |
| |
| const checkWithTags = async (tags: string) => { |
| const fileUri = vscode.Uri.file(path.join(fixturePath, 'buildTags', 'hello.go')); |
| const defaultGoCfg = getGoConfig(fileUri); |
| const cfg = Object.create(defaultGoCfg, { |
| vetOnSave: { value: 'off' }, |
| lintOnSave: { value: 'off' }, |
| buildOnSave: { value: 'package' }, |
| buildTags: { value: tags } |
| }) as vscode.WorkspaceConfiguration; |
| |
| const diagnostics = await check(fileUri, cfg); |
| return ([] as string[]).concat(...diagnostics.map<string[]>((d) => { |
| return d.errors.map((e) => e.msg) as string[]; |
| })); |
| }; |
| |
| const errors1 = await checkWithTags('randomtag'); |
| assert.deepEqual(errors1, ['undefined: fmt.Prinln'], 'check with buildtag "randomtag" failed. Unexpected errors found.'); |
| |
| // TODO(hyangah): after go1.13, -tags expects a comma-separated tag list. |
| // For backwards compatibility, space-separated tag lists are still recognized, |
| // but change to a space-separated list once we stop testing with go1.12. |
| const errors2 = await checkWithTags('randomtag other'); |
| assert.deepEqual(errors2, ['undefined: fmt.Prinln'], |
| 'check with multiple buildtags "randomtag,other" failed. Unexpected errors found.'); |
| |
| const errors3 = await checkWithTags(''); |
| assert.equal(errors3.length, 1, |
| 'check without buildtag failed. Unexpected number of errors found' + JSON.stringify(errors3)); |
| const errMsg = errors3[0]; |
| assert.ok( |
| errMsg.includes(`can't load package: package test/testfixture/buildTags`) || |
| errMsg.includes(`build constraints exclude all Go files`), |
| `check without buildtags failed. Go files not excluded. ${errMsg}` |
| ); |
| }); |
| |
| test('Test Tags checking', async () => { |
| const config1 = Object.create(vscode.workspace.getConfiguration('go'), { |
| vetOnSave: { value: 'off' }, |
| lintOnSave: { value: 'off' }, |
| buildOnSave: { value: 'package' }, |
| testTags: { value: null }, |
| buildTags: { value: 'randomtag' } |
| }); |
| |
| const config2 = Object.create(vscode.workspace.getConfiguration('go'), { |
| vetOnSave: { value: 'off' }, |
| lintOnSave: { value: 'off' }, |
| buildOnSave: { value: 'package' }, |
| testTags: { value: 'randomtag' } |
| }); |
| |
| const config3 = Object.create(vscode.workspace.getConfiguration('go'), { |
| vetOnSave: { value: 'off' }, |
| lintOnSave: { value: 'off' }, |
| buildOnSave: { value: 'package' }, |
| testTags: { value: 'randomtag othertag' } |
| }); |
| |
| const config4 = Object.create(vscode.workspace.getConfiguration('go'), { |
| vetOnSave: { value: 'off' }, |
| lintOnSave: { value: 'off' }, |
| buildOnSave: { value: 'package' }, |
| testTags: { value: '' } |
| }); |
| |
| const uri = vscode.Uri.file(path.join(fixturePath, 'testTags', 'hello_test.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| |
| const result1 = await testCurrentFile(config1, false, []); |
| assert.equal(result1, true); |
| |
| const result2 = await testCurrentFile(config2, false, []); |
| assert.equal(result2, true); |
| |
| const result3 = await testCurrentFile(config3, false, []); |
| assert.equal(result3, true); |
| |
| const result4 = await testCurrentFile(config4, false, []); |
| assert.equal(result4, false); |
| }); |
| |
| function fixEOL(eol: vscode.EndOfLine, strWithLF: string): string { |
| if (eol === vscode.EndOfLine.LF) { |
| return strWithLF; |
| } |
| return strWithLF.split('\n').join('\r\n'); // replaceAll. |
| } |
| |
| test('Add imports when no imports', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'importTest', 'noimports.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| |
| const expectedText = document.getText() + fixEOL(document.eol, '\n' + 'import (\n\t"bytes"\n)\n'); |
| const edits = getTextEditForAddImport('bytes'); |
| const edit = new vscode.WorkspaceEdit(); |
| edit.set(document.uri, edits); |
| return vscode.workspace.applyEdit(edit).then(() => { |
| assert.equal( |
| vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), |
| expectedText |
| ); |
| return Promise.resolve(); |
| }); |
| }); |
| |
| test('Add imports to an import block', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'importTest', 'groupImports.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| const eol = document.eol; |
| |
| const expectedText = document.getText().replace( |
| fixEOL(eol, '\t"fmt"\n\t"math"'), |
| fixEOL(eol, '\t"bytes"\n\t"fmt"\n\t"math"')); |
| const edits = getTextEditForAddImport('bytes'); |
| const edit = new vscode.WorkspaceEdit(); |
| edit.set(document.uri, edits); |
| await vscode.workspace.applyEdit(edit); |
| assert.equal(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), expectedText); |
| }); |
| |
| test('Add imports and collapse single imports to an import block', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'importTest', 'singleImports.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| const eol = document.eol; |
| |
| const expectedText = document |
| .getText() |
| .replace( |
| fixEOL(eol, 'import "fmt"\nimport . "math" // comment'), |
| fixEOL(eol, 'import (\n\t"bytes"\n\t"fmt"\n\t. "math" // comment\n)') |
| ); |
| const edits = getTextEditForAddImport('bytes'); |
| const edit = new vscode.WorkspaceEdit(); |
| edit.set(document.uri, edits); |
| await vscode.workspace.applyEdit(edit); |
| assert.equal(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), expectedText); |
| }); |
| |
| test('Add imports and avoid pseudo package imports for cgo', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'importTest', 'cgoImports.go')); |
| const document = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(document); |
| const eol = document.eol; |
| |
| const expectedText = document |
| .getText() |
| .replace( |
| fixEOL(eol, 'import "math"'), |
| fixEOL(eol, 'import (\n\t"bytes"\n\t"math"\n)') |
| ); |
| const edits = getTextEditForAddImport('bytes'); |
| const edit = new vscode.WorkspaceEdit(); |
| edit.set(document.uri, edits); |
| await vscode.workspace.applyEdit(edit); |
| assert.equal(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), expectedText); |
| }); |
| |
| test('Fill struct', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'fillStruct', 'input_1.go')); |
| const golden = fs.readFileSync(path.join(fixturePath, 'fillStruct', 'golden_1.go'), 'utf-8'); |
| |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| await vscode.window.showTextDocument(textDocument); |
| |
| const editor = await vscode.window.showTextDocument(textDocument); |
| const selection = new vscode.Selection(12, 15, 12, 15); |
| editor.selection = selection; |
| await runFillStruct(editor); |
| assert.equal(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), golden); |
| }); |
| |
| test('Fill struct - select line', async () => { |
| const uri = vscode.Uri.file(path.join(fixturePath, 'fillStruct', 'input_2.go')); |
| const golden = fs.readFileSync(path.join(fixturePath, 'fillStruct', 'golden_2.go'), 'utf-8'); |
| |
| const textDocument = await vscode.workspace.openTextDocument(uri); |
| const editor = await vscode.window.showTextDocument(textDocument); |
| |
| const selection = new vscode.Selection(7, 0, 7, 10); |
| editor.selection = selection; |
| await runFillStruct(editor); |
| assert.equal(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), golden); |
| }); |
| }); |