blob: c33dca2acbb657c386b3e08db4278a65a091f721 [file] [log] [blame]
/*---------------------------------------------------------
* 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 { getGoConfig } from '../../src/config';
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,
getImportPath,
getToolsGopath,
handleDiagnosticErrors,
ICheckResult,
isVendorSupported
} from '../../src/util';
suite('Go Extension Tests', function () {
this.timeout(20000);
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
gotoSymbol: {
value: {
ignoreFolders: []
}
}
});
const configWithIgnoringFolders = Object.create(getGoConfig(), {
gotoSymbol: {
value: {
ignoreFolders: ['vendor']
}
}
});
const configWithIncludeGoroot = Object.create(getGoConfig(), {
gotoSymbol: {
value: {
includeGoroot: true
}
}
});
const configWithoutIncludeGoroot = Object.create(getGoConfig(), {
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 = getGoConfig();
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 = getGoConfig();
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
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(getGoConfig(), {
vetOnSave: { value: 'off' },
lintOnSave: { value: 'off' },
buildOnSave: { value: 'package' },
testTags: { value: null },
buildTags: { value: 'randomtag' }
});
const config2 = Object.create(getGoConfig(), {
vetOnSave: { value: 'off' },
lintOnSave: { value: 'off' },
buildOnSave: { value: 'package' },
testTags: { value: 'randomtag' }
});
const config3 = Object.create(getGoConfig(), {
vetOnSave: { value: 'off' },
lintOnSave: { value: 'off' },
buildOnSave: { value: 'package' },
testTags: { value: 'randomtag othertag' }
});
const config4 = Object.create(getGoConfig(), {
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);
});
});