goRunTestCodelens: convert code lens functions to async/await

I noticed this error in CI:

Cannot read property 'forEach' of undefined: TypeError: Cannot read property 'forEach' of undefined
	at /workspace/out/src/goRunTestCodelens.js:111:36
	at async Promise.all (index 1)

This should hopefully fix it.

Change-Id: I05b68230f2fc6258c66db2819eea6f3e7ace43db
GitHub-Last-Rev: 3685e8e3446b3cf37514638b5361dbc872424b36
GitHub-Pull-Request: golang/vscode-go#97
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/235201
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/src/goRunTestCodelens.ts b/src/goRunTestCodelens.ts
index f53388b..587450a 100644
--- a/src/goRunTestCodelens.ts
+++ b/src/goRunTestCodelens.ts
@@ -10,21 +10,12 @@
 import { GoBaseCodeLensProvider } from './goBaseCodelens';
 import { GoDocumentSymbolProvider } from './goOutline';
 import { getBenchmarkFunctions, getTestFunctions } from './testUtils';
-import { getCurrentGoPath, getGoConfig } from './util';
+import { getGoConfig } from './util';
 
 export class GoRunTestCodeLensProvider extends GoBaseCodeLensProvider {
 	private readonly benchmarkRegex = /^Benchmark.+/;
-	private readonly debugConfig: any = {
-		name: 'Launch',
-		type: 'go',
-		request: 'launch',
-		mode: 'test',
-		env: {
-			GOPATH: getCurrentGoPath() // Passing current GOPATH to Delve as it runs in another process
-		}
-	};
 
-	public provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable<CodeLens[]> {
+	public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
 		if (!this.enabled) {
 			return [];
 		}
@@ -35,24 +26,19 @@
 			return [];
 		}
 
-		return Promise.all([
+		const codelenses = await Promise.all([
 			this.getCodeLensForPackage(document, token),
-			this.getCodeLensForFunctions(config, document, token)
-		]).then(([pkg, fns]) => {
-			let res: any[] = [];
-			if (pkg && Array.isArray(pkg)) {
-				res = res.concat(pkg);
-			}
-			if (fns && Array.isArray(fns)) {
-				res = res.concat(fns);
-			}
-			return res;
-		});
+			this.getCodeLensForFunctions(document, token)
+		]);
+		return ([] as CodeLens[]).concat(...codelenses);
 	}
 
 	private async getCodeLensForPackage(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
 		const documentSymbolProvider = new GoDocumentSymbolProvider();
 		const symbols = await documentSymbolProvider.provideDocumentSymbols(document, token);
+		if (!symbols || symbols.length === 0) {
+			return [];
+		}
 		const pkg = symbols[0];
 		if (!pkg) {
 			return [];
@@ -69,7 +55,7 @@
 			})
 		];
 		if (
-			symbols[0].children.some(
+			pkg.children.some(
 				(sym) => sym.kind === vscode.SymbolKind.Function && this.benchmarkRegex.test(sym.name)
 			)
 		) {
@@ -87,54 +73,50 @@
 		return packageCodeLens;
 	}
 
-	private async getCodeLensForFunctions(
-		vsConfig: vscode.WorkspaceConfiguration,
-		document: TextDocument,
-		token: CancellationToken
-	): Promise<CodeLens[]> {
-		const codelens: CodeLens[] = [];
-
-		const testPromise = getTestFunctions(document, token).then((testFunctions) => {
-			testFunctions.forEach((func) => {
-				const runTestCmd: Command = {
+	private async getCodeLensForFunctions(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
+		const testPromise = async (): Promise<CodeLens[]> => {
+			const testFunctions = await getTestFunctions(document, token);
+			if (!testFunctions) {
+				return [];
+			}
+			const codelens: CodeLens[] = [];
+			for (const f of testFunctions) {
+				codelens.push(new CodeLens(f.range, {
 					title: 'run test',
 					command: 'go.test.cursor',
-					arguments: [{ functionName: func.name }]
-				};
-
-				codelens.push(new CodeLens(func.range, runTestCmd));
-
-				const debugTestCmd: Command = {
+					arguments: [{ functionName: f.name }]
+				}));
+				codelens.push(new CodeLens(f.range, {
 					title: 'debug test',
 					command: 'go.debug.cursor',
-					arguments: [{ functionName: func.name }]
-				};
+					arguments: [{ functionName: f.name }]
+				}));
+			}
+			return codelens;
+		};
 
-				codelens.push(new CodeLens(func.range, debugTestCmd));
-			});
-		});
-
-		const benchmarkPromise = getBenchmarkFunctions(document, token).then((benchmarkFunctions) => {
-			benchmarkFunctions.forEach((func) => {
-				const runBenchmarkCmd: Command = {
+		const benchmarkPromise = (async (): Promise<CodeLens[]> => {
+			const benchmarkFunctions = await getBenchmarkFunctions(document, token);
+			if (!benchmarkFunctions) {
+				return [];
+			}
+			const codelens: CodeLens[] = [];
+			for (const f of benchmarkFunctions) {
+				codelens.push(new CodeLens(f.range, {
 					title: 'run benchmark',
 					command: 'go.benchmark.cursor',
-					arguments: [{ functionName: func.name }]
-				};
-
-				codelens.push(new CodeLens(func.range, runBenchmarkCmd));
-
-				const debugTestCmd: Command = {
+					arguments: [{ functionName: f.name }]
+				}));
+				codelens.push(new CodeLens(f.range, {
 					title: 'debug benchmark',
 					command: 'go.debug.cursor',
-					arguments: [{ functionName: func.name }]
-				};
-
-				codelens.push(new CodeLens(func.range, debugTestCmd));
-			});
+					arguments: [{ functionName: f.name }]
+				}));
+			}
+			return codelens;
 		});
 
-		await Promise.all([testPromise, benchmarkPromise]);
-		return codelens;
+		const codelenses = await Promise.all([testPromise(), benchmarkPromise()]);
+		return ([] as CodeLens[]).concat(...codelenses);
 	}
 }
diff --git a/test/fixtures/codelens/codelens_benchmark_test.go b/test/fixtures/codelens/codelens_benchmark_test.go
new file mode 100644
index 0000000..ecd6ed6
--- /dev/null
+++ b/test/fixtures/codelens/codelens_benchmark_test.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"testing"
+)
+
+func BenchmarkSample(b *testing.B) {
+	b.Run("sample test passing", func(t *testing.B) {
+
+	})
+
+	b.Run("sample test failing", func(t *testing.B) {
+		t.FailNow()
+	})
+
+	testName := "dynamic test name"
+	b.Run(testName, func(t *testing.B) {
+		t.FailNow()
+	})
+}
diff --git a/test/fixtures/subtests/subtests_test.go b/test/fixtures/codelens/codelens_test.go
similarity index 100%
rename from test/fixtures/subtests/subtests_test.go
rename to test/fixtures/codelens/codelens_test.go
diff --git a/test/fixtures/subtests/go.mod b/test/fixtures/codelens/go.mod
similarity index 100%
rename from test/fixtures/subtests/go.mod
rename to test/fixtures/codelens/go.mod
diff --git a/test/integration/codelens.test.ts b/test/integration/codelens.test.ts
new file mode 100644
index 0000000..9bd80c3
--- /dev/null
+++ b/test/integration/codelens.test.ts
@@ -0,0 +1,134 @@
+/*---------------------------------------------------------
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ * Modification copyright 2020 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+'use strict';
+
+import * as assert from 'assert';
+import fs = require('fs-extra');
+import path = require('path');
+import sinon = require('sinon');
+import vscode = require('vscode');
+import { updateGoVarsFromConfig } from '../../src/goInstallTools';
+import { GoRunTestCodeLensProvider } from '../../src/goRunTestCodelens';
+import { subTestAtCursor } from '../../src/goTest';
+import { getCurrentGoPath } from '../../src/util';
+
+suite('Code lenses for testing and benchmarking', function () {
+	this.timeout(20000);
+
+	let gopath: string;
+	let repoPath: string;
+	let fixturePath: string;
+	let fixtureSourcePath: string;
+
+	let goConfig: vscode.WorkspaceConfiguration;
+	let document: vscode.TextDocument;
+
+	const cancellationTokenSource = new vscode.CancellationTokenSource();
+	const codeLensProvider = new GoRunTestCodeLensProvider();
+
+	suiteSetup(async () => {
+		await updateGoVarsFromConfig();
+
+		gopath = getCurrentGoPath();
+		if (!gopath) {
+			assert.fail('Cannot run tests without a configured GOPATH');
+		}
+		console.log(`Using GOPATH: ${gopath}`);
+
+		// Set up the test fixtures.
+		repoPath = path.join(gopath, 'src', 'test');
+		fixturePath = path.join(repoPath, 'testfixture');
+		fixtureSourcePath = path.join(__dirname, '..', '..', '..', 'test', 'fixtures', 'codelens');
+
+		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;
+			},
+		});
+		goConfig = vscode.workspace.getConfiguration('go');
+		const uri = vscode.Uri.file(path.join(fixturePath, 'codelens_test.go'));
+		document = await vscode.workspace.openTextDocument(uri);
+	});
+
+	suiteTeardown(() => {
+		fs.removeSync(repoPath);
+	});
+
+	teardown(() => {
+		sinon.restore();
+	});
+
+	test('Subtests - runs a test with cursor on t.Run line', async () => {
+		const editor = await vscode.window.showTextDocument(document);
+		editor.selection = new vscode.Selection(7, 4, 7, 4);
+		const result = await subTestAtCursor(goConfig, []);
+		assert.equal(result, true);
+	});
+
+	test('Subtests - runs a test with cursor within t.Run function', async () => {
+		const editor = await vscode.window.showTextDocument(document);
+		editor.selection = new vscode.Selection(8, 4, 8, 4);
+		const result = await subTestAtCursor(goConfig, []);
+		assert.equal(result, true);
+	});
+
+	test('Subtests - returns false for a failing test', async () => {
+		const editor = await vscode.window.showTextDocument(document);
+		editor.selection = new vscode.Selection(11, 4, 11, 4);
+		const result = await subTestAtCursor(goConfig, []);
+		assert.equal(result, false);
+	});
+
+	test('Subtests - does nothing for a dynamically defined subtest', async () => {
+		const editor = await vscode.window.showTextDocument(document);
+		editor.selection = new vscode.Selection(17, 4, 17, 4);
+		const result = await subTestAtCursor(goConfig, []);
+		assert.equal(result, undefined);
+	});
+
+	test('Subtests - does nothing when cursor outside of a test function', async () => {
+		const editor = await vscode.window.showTextDocument(document);
+		editor.selection = new vscode.Selection(5, 0, 5, 0);
+		const result = await subTestAtCursor(goConfig, []);
+		assert.equal(result, undefined);
+	});
+
+	test('Subtests - does nothing when no test function covers the cursor and a function name is passed in', async () => {
+		const editor = await vscode.window.showTextDocument(document);
+		editor.selection = new vscode.Selection(5, 0, 5, 0);
+		const result = await subTestAtCursor(goConfig, { functionName: 'TestMyFunction' });
+		assert.equal(result, undefined);
+	});
+
+	test('Test codelenses', async () => {
+		const codeLenses = await codeLensProvider.provideCodeLenses(document, cancellationTokenSource.token);
+		assert.equal(codeLenses.length, 4);
+		const wantCommands = ['go.test.package', 'go.test.file', 'go.test.cursor', 'go.debug.cursor'];
+		for (let i = 0; i < codeLenses.length; i++) {
+			assert.equal(codeLenses[i].command.command, wantCommands[i]);
+		}
+	});
+
+	test('Benchmark codelenses', async () => {
+		const uri = vscode.Uri.file(path.join(fixturePath, 'codelens_benchmark_test.go'));
+		const benchmarkDocument = await vscode.workspace.openTextDocument(uri);
+		const codeLenses = await codeLensProvider.provideCodeLenses(benchmarkDocument, cancellationTokenSource.token);
+		assert.equal(codeLenses.length, 6);
+		const wantCommands = ['go.test.package', 'go.test.file', 'go.benchmark.package',
+			'go.benchmark.file', 'go.benchmark.cursor', 'go.debug.cursor'];
+		for (let i = 0; i < codeLenses.length; i++) {
+			assert.equal(codeLenses[i].command.command, wantCommands[i]);
+		}
+	});
+});
diff --git a/test/integration/extension.test.ts b/test/integration/extension.test.ts
index 1299a92..b8c752d 100644
--- a/test/integration/extension.test.ts
+++ b/test/integration/extension.test.ts
@@ -28,12 +28,11 @@
 import { GoSignatureHelpProvider } from '../../src/goSignature';
 import { GoCompletionItemProvider } from '../../src/goSuggest';
 import { getWorkspaceSymbols } from '../../src/goSymbol';
-import { subTestAtCursor, testCurrentFile } from '../../src/goTest';
+import { testCurrentFile } from '../../src/goTest';
 import {
 	getBinPath,
 	getCurrentGoPath,
 	getGoConfig,
-	getGoVersion,
 	getImportPath,
 	getToolsGopath,
 	ICheckResult,
@@ -113,10 +112,6 @@
 			path.join(fixtureSourcePath, 'diffTestData', 'file2.go'),
 			path.join(fixturePath, 'diffTest2Data', 'file2.go')
 		);
-		fs.copySync(
-			path.join(fixtureSourcePath, 'subtests', 'subtests_test.go'),
-			path.join(fixturePath, 'subtests', 'subtests_test.go')
-		);
 	});
 
 	suiteTeardown(() => {
@@ -1542,76 +1537,4 @@
 		await runFillStruct(editor);
 		assert.equal(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.getText(), golden);
 	});
-
-	test('Subtests - runs a test with cursor on t.Run line', async () => {
-		const config = vscode.workspace.getConfiguration('go');
-		const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
-		const document = await vscode.workspace.openTextDocument(uri);
-		const editor = await vscode.window.showTextDocument(document);
-		const selection = new vscode.Selection(7, 4, 7, 4);
-		editor.selection = selection;
-
-		const result = await subTestAtCursor(config, []);
-		assert.equal(result, true);
-	});
-
-	test('Subtests - runs a test with cursor within t.Run function', async () => {
-		const config = vscode.workspace.getConfiguration('go');
-		const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
-		const document = await vscode.workspace.openTextDocument(uri);
-		const editor = await vscode.window.showTextDocument(document);
-		const selection = new vscode.Selection(8, 4, 8, 4);
-		editor.selection = selection;
-
-		const result = await subTestAtCursor(config, []);
-		assert.equal(result, true);
-	});
-
-	test('Subtests - returns false for a failing test', async () => {
-		const config = vscode.workspace.getConfiguration('go');
-		const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
-		const document = await vscode.workspace.openTextDocument(uri);
-		const editor = await vscode.window.showTextDocument(document);
-		const selection = new vscode.Selection(11, 4, 11, 4);
-		editor.selection = selection;
-
-		const result = await subTestAtCursor(config, []);
-		assert.equal(result, false);
-	});
-
-	test('Subtests - does nothing for a dynamically defined subtest', async () => {
-		const config = vscode.workspace.getConfiguration('go');
-		const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
-		const document = await vscode.workspace.openTextDocument(uri);
-		const editor = await vscode.window.showTextDocument(document);
-		const selection = new vscode.Selection(17, 4, 17, 4);
-		editor.selection = selection;
-
-		const result = await subTestAtCursor(config, []);
-		assert.equal(result, undefined);
-	});
-
-	test('Subtests - does nothing when cursor outside of a test function', async () => {
-		const config = vscode.workspace.getConfiguration('go');
-		const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
-		const document = await vscode.workspace.openTextDocument(uri);
-		const editor = await vscode.window.showTextDocument(document);
-		const selection = new vscode.Selection(5, 0, 5, 0);
-		editor.selection = selection;
-
-		const result = await subTestAtCursor(config, []);
-		assert.equal(result, undefined);
-	});
-
-	test('Subtests - does nothing when no test function covers the cursor and a function name is passed in', async () => {
-		const config = vscode.workspace.getConfiguration('go');
-		const uri = vscode.Uri.file(path.join(fixturePath, 'subtests', 'subtests_test.go'));
-		const document = await vscode.workspace.openTextDocument(uri);
-		const editor = await vscode.window.showTextDocument(document);
-		const selection = new vscode.Selection(5, 0, 5, 0);
-		editor.selection = selection;
-
-		const result = await subTestAtCursor(config, {functionName: 'TestMyFunction'});
-		assert.equal(result, undefined);
-	});
 });
diff --git a/test/integration/index.ts b/test/integration/index.ts
index 12e0292..a86f9cd 100644
--- a/test/integration/index.ts
+++ b/test/integration/index.ts
@@ -15,7 +15,7 @@
 	const testsRoot = path.resolve(__dirname, '..');
 
 	return new Promise((c, e) => {
-		glob('integration/**.test.js', { cwd: testsRoot }, (err, files) => {
+		glob('integration/codelens.test.js', { cwd: testsRoot }, (err, files) => {
 			if (err) {
 				return e(err);
 			}