test/integration: refactor installation tests, add slow tests

Refactored the tool installation tests to follow the Mocha framework a
bit better, with setup, teardown, etc. I also added a test that should
only run in CI, when a special environment variable is set. This test
will be slow, as it download tools from the Internet. However, I think
it's worth it to have this so that we get notified earlier on about
breakages upstream.

Updates golang/vscode-go#42

Change-Id: I65edb83bf7e5b1f9374d8a9edbc8af93c6a9c660
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/236058
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index db14834..1a8a449 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -76,6 +76,7 @@
           run: npm run test
         env:
           CODE_VERSION: 'insiders'
+          VSCODEGO_BEFORE_RELEASE_TESTS: true
 
       - name: Publish
         if: github.ref == 'refs/heads/master' && github.repository == 'golang/vscode-go'
diff --git a/src/goTools.ts b/src/goTools.ts
index 99ed0ac..eaa2b87 100644
--- a/src/goTools.ts
+++ b/src/goTools.ts
@@ -176,7 +176,7 @@
 	return tools;
 }
 
-const allToolsInformation: { [key: string]: Tool } = {
+export const allToolsInformation: { [key: string]: Tool } = {
 	'gocode': {
 		name: 'gocode',
 		importPath: 'github.com/mdempsky/gocode',
diff --git a/test/integration/index.ts b/test/integration/index.ts
index 12e0292..2d964a1 100644
--- a/test/integration/index.ts
+++ b/test/integration/index.ts
@@ -6,7 +6,6 @@
 import * as Mocha from 'mocha';
 import * as path from 'path';
 export function run(): Promise<void> {
-	// Create the mocha test
 	const mocha = new Mocha({
 		ui: 'tdd',
 	});
diff --git a/test/integration/install.test.ts b/test/integration/install.test.ts
index f2de7f7..2c663f5 100644
--- a/test/integration/install.test.ts
+++ b/test/integration/install.test.ts
@@ -12,31 +12,55 @@
 import sinon = require('sinon');
 import util = require('util');
 import vscode = require('vscode');
+import { toolInstallationEnvironment } from '../../src/goEnv';
 import { installTools } from '../../src/goInstallTools';
-import { getTool, getToolAtVersion } from '../../src/goTools';
+import { allToolsInformation, getTool, getToolAtVersion } from '../../src/goTools';
 import { getBinPath, getGoVersion, rmdirRecursive } from '../../src/util';
 
 suite('Installation Tests', function () {
-	this.timeout(10000);
+	// Disable timeout when we are running slow tests.
+	let timeout = 10000;
+	if (shouldRunSlowTests()) {
+		timeout = 0;
+	}
+	this.timeout(timeout);
 
-	test('install tools', async () => {
-		const goVersion = await getGoVersion();
-		const testCases: string[][] = [
-			['gopls'],
-			['gopls', 'guru'],
-		];
-		const proxyDir = buildFakeProxy([].concat(...testCases));
+	let tmpToolsGopath: string;
+	let sandbox: sinon.SinonSandbox;
+	let toolsGopathStub: sinon.SinonStub;
 
-		for (const missing of testCases) {
-			// Create a temporary directory in which to install tools.
-			const tmpToolsGopath = fs.mkdtempSync(path.join(os.tmpdir(), 'install-test'));
-			fs.mkdirSync(path.join(tmpToolsGopath, 'bin'));
-			fs.mkdirSync(path.join(tmpToolsGopath, 'src'));
+	setup(() => {
+		// Create a temporary directory in which to install tools.
+		tmpToolsGopath = fs.mkdtempSync(path.join(os.tmpdir(), 'install-test'));
+		fs.mkdirSync(path.join(tmpToolsGopath, 'bin'));
+		fs.mkdirSync(path.join(tmpToolsGopath, 'src'));
 
-			const sandbox = sinon.createSandbox();
-			const utils = require('../../src/util');
+		sandbox = sinon.createSandbox();
+		const utils = require('../../src/util');
+		toolsGopathStub = sandbox.stub(utils, 'getToolsGopath').returns(tmpToolsGopath);
+	});
 
-			const toolsGopathStub = sandbox.stub(utils, 'getToolsGopath').returns(tmpToolsGopath);
+	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);
+		envForTest['GOPATH'] = tmpToolsGopath;
+		const execFile = util.promisify(cp.execFile);
+		await execFile(goRuntimePath, ['clean', '-modcache'], {
+			env: envForTest,
+		});
+		rmdirRecursive(tmpToolsGopath);
+	});
+
+	// 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: {
@@ -45,34 +69,57 @@
 					}
 				},
 			});
-			const configStub = sandbox.stub(vscode.workspace, 'getConfiguration').returns(goConfig);
-			// TODO(rstambler): Test with versions as well.
-			const missingTools = missing.map((tool) => getToolAtVersion(tool));
-			await installTools(missingTools, goVersion);
-
-			sinon.assert.calledWith(toolsGopathStub);
-			sinon.assert.calledWith(configStub);
-			sandbox.restore();
-
-			// Read the $GOPATH/bin to confirm that the expected tools were
-			// installed.
-			const readdir = util.promisify(fs.readdir);
-			const files = await readdir(path.join(tmpToolsGopath, 'bin'));
-			assert.deepEqual(files, missing, `tool installation failed for ${missing}`);
-
-			// Clean up the temporary GOPATH. To delete the module cache, run `go clean -modcache`.
-			const goRuntimePath = getBinPath('go');
-			const envForTest = Object.assign({}, process.env);
-			envForTest['GOPATH'] = tmpToolsGopath;
-			const execFile = util.promisify(cp.execFile);
-			await execFile(goRuntimePath, ['clean', '-modcache'], {
-				env: envForTest,
-			});
-			rmdirRecursive(tmpToolsGopath);
+			configStub = sandbox.stub(vscode.workspace, 'getConfiguration').returns(goConfig);
+		} else {
+			const env = toolInstallationEnvironment();
+			console.log(`Installing tools using GOPROXY=${env['GOPROXY']}`);
 		}
 
-		rmdirRecursive(proxyDir);
+		// 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', 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
@@ -103,3 +150,10 @@
 	}
 	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'];
+}