blob: d73350d4b2c3b79f25c3c670f59d48c515623cde [file] [log] [blame]
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------*/
import AdmZip = require('adm-zip');
import * as assert from 'assert';
import cp = require('child_process');
import fs = require('fs');
import os = require('os');
import path = require('path');
import sinon = require('sinon');
import url = require('url');
import util = require('util');
import vscode = require('vscode');
import { toolInstallationEnvironment } from '../../src/goEnv';
import { installTools } from '../../src/goInstallTools';
import { allToolsInformation, getConfiguredTools, getTool, getToolAtVersion } from '../../src/goTools';
import { getBinPath, getGoVersion, GoVersion, rmdirRecursive } from '../../src/util';
import { correctBinname } from '../../src/utils/pathUtils';
suite('Installation Tests', function () {
// Disable timeout when we are running slow tests.
let timeout = 10000;
if (shouldRunSlowTests()) {
timeout = 0;
}
this.timeout(timeout);
let tmpToolsGopath: string;
let tmpToolsGopath2: string;
let sandbox: sinon.SinonSandbox;
let toolsGopathStub: sinon.SinonStub;
setup(() => {
// Create a temporary directory in which to install tools.
tmpToolsGopath = fs.mkdtempSync(path.join(os.tmpdir(), 'install-test'));
// a temporary directory to be used as the second GOPATH element.
tmpToolsGopath2 = fs.mkdtempSync(path.join(os.tmpdir(), 'install-test2'));
const toolsGopath = tmpToolsGopath + path.delimiter + tmpToolsGopath2;
sandbox = sinon.createSandbox();
const utils = require('../../src/util');
toolsGopathStub = sandbox.stub(utils, 'getToolsGopath').returns(toolsGopath);
});
teardown(async () => {
sandbox.restore();
// Clean up the temporary GOPATH. To delete the module cache, run `go clean -modcache`.
const goRuntimePath = getBinPath('go');
const envForTest = Object.assign({}, process.env);
for (const p of [tmpToolsGopath, tmpToolsGopath2]) {
envForTest['GOPATH'] = p;
const execFile = util.promisify(cp.execFile);
await execFile(goRuntimePath, ['clean', '-modcache'], {
env: envForTest,
});
rmdirRecursive(p);
}
});
// runTest actually executes the logic of the test.
// If withLocalProxy is true, the test does not require internet.
async function runTest(testCases: string[], withLocalProxy?: boolean) {
let proxyDir: string;
let configStub: sinon.SinonStub;
if (withLocalProxy) {
proxyDir = buildFakeProxy([].concat(...testCases));
const goConfig = Object.create(vscode.workspace.getConfiguration('go'), {
toolsEnvVars: {
value: {
GOPROXY: url.pathToFileURL(proxyDir),
GOSUMDB: 'off',
}
},
});
configStub = sandbox.stub(vscode.workspace, 'getConfiguration').returns(goConfig);
} else {
const env = toolInstallationEnvironment();
console.log(`Installing tools using GOPROXY=${env['GOPROXY']}`);
}
// TODO(rstambler): Test with versions as well.
const missingTools = testCases.map((tool) => getToolAtVersion(tool));
const goVersion = await getGoVersion();
await installTools(missingTools, goVersion);
// Confirm that each expected tool has been installed.
const checks: Promise<void>[] = [];
const exists = util.promisify(fs.exists);
for (const tool of testCases) {
checks.push(new Promise<void>(async (resolve) => {
// Check that the expect tool has been installed to $GOPATH/bin.
const ok = await exists(path.join(tmpToolsGopath, 'bin', correctBinname(tool)));
if (!ok) {
assert.fail(`expected ${tmpToolsGopath}/bin/${tool}, not found`);
}
return resolve();
}));
}
await Promise.all(checks);
sandbox.assert.calledWith(toolsGopathStub);
if (withLocalProxy) {
sandbox.assert.calledWith(configStub);
rmdirRecursive(proxyDir);
}
}
test('Install one tool with a local proxy', async () => {
await runTest(['gopls'], true);
});
test('Install multiple tools with a local proxy', async () => {
await runTest(['gopls', 'guru'], true);
});
test('Install all tools via GOPROXY', async () => {
// Only run this test if we are in CI before a Nightly release.
if (!shouldRunSlowTests()) {
return;
}
const tools = Object.keys(allToolsInformation);
await runTest(tools);
});
});
// buildFakeProxy creates a fake file-based proxy used for testing. The code is
// mostly adapted from golang.org/x/tools/internal/proxydir/proxydir.go.
function buildFakeProxy(tools: string[]) {
const proxyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'proxydir'));
for (const toolName of tools) {
const tool = getTool(toolName);
const module = tool.importPath;
const version = `v1.0.0`; // hardcoded for now
const dir = path.join(proxyDir, module, '@v');
fs.mkdirSync(dir, { recursive: true });
// Write the list file.
fs.writeFileSync(path.join(dir, 'list'), `${version}\n`);
// Write the go.mod file.
fs.writeFileSync(path.join(dir, `${version}.mod`), `module ${module}\n`);
// Write the info file.
fs.writeFileSync(path.join(dir, `${version}.info`), `{ "Version": "${version}", "Time": "2020-04-07T14:45:07Z" } `);
// Write the zip file.
const zip = new AdmZip();
const content = `package main; func main() {};`;
zip.addFile(`${module}@${version}/main.go`, Buffer.alloc(content.length, content));
zip.writeZip(path.join(dir, `${version}.zip`));
}
return proxyDir;
}
// Check if VSCODEGO_BEFORE_RELEASE_TESTS is set to true. This environment
// variable is set by the CI system that releases the Nightly extension,
// allowing us to opt-in to more rigorous testing only before releases.
function shouldRunSlowTests(): boolean {
return !!process.env['VSCODEGO_BEFORE_RELEASE_TESTS'];
}
suite('getConfiguredTools', () => {
test('do not require legacy tools when using language server', async () => {
const configured = getConfiguredTools(fakeGoVersion('1.15.6'), { useLanguageServer: true });
const got = configured.map((tool) => tool.name) ?? [];
assert(got.includes('gopls'), `omitted 'gopls': ${JSON.stringify(got)}`);
assert(!got.includes('guru') && !got.includes('gocode'), `suggested legacy tools: ${JSON.stringify(got)}`);
});
test('do not require gopls when not using language server', async () => {
const configured = getConfiguredTools(fakeGoVersion('1.15.6'), { useLanguageServer: false });
const got = configured.map((tool) => tool.name) ?? [];
assert(!got.includes('gopls'), `suggested 'gopls': ${JSON.stringify(got)}`);
assert(got.includes('guru') && got.includes('gocode'), `omitted legacy tools: ${JSON.stringify(got)}`);
});
test('do not require gopls when the go version is old', async () => {
const configured = getConfiguredTools(fakeGoVersion('1.9'), { useLanguageServer: true });
const got = configured.map((tool) => tool.name) ?? [];
assert(!got.includes('gopls'), `suggested 'gopls' for old go: ${JSON.stringify(got)}`);
assert(got.includes('guru') && got.includes('gocode'), `omitted legacy tools: ${JSON.stringify(got)}`);
});
});
function fakeGoVersion(version: string) {
return new GoVersion('/path/to/go', `go version go${version} windows/amd64`);
}