test/gopls: retry completion query a couple of times

Sometimes gopls returns an empty result for the completion request
until the internal state is stablized (even when it is ready for
other queries).

Try a couple of times before declaring a test failure.

While we are here, make buildLanguageClient non-async.
It doesn't have to be.

Updates golang/vscode-go#363

Change-Id: I29c80b28bb549d7ac239cc3cf5fe6e56966fc151
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/243281
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts
index 1a29751..6d8446f 100644
--- a/src/goLanguageServer.ts
+++ b/src/goLanguageServer.ts
@@ -158,7 +158,7 @@
 		// Track the latest config used to start the language server,
 		// and rebuild the language client.
 		latestConfig = config;
-		languageClient = await buildLanguageClient(config);
+		languageClient = buildLanguageClient(config);
 		crashCount = 0;
 	}
 
@@ -188,7 +188,7 @@
 	return true;
 }
 
-async function buildLanguageClient(config: LanguageServerConfig): Promise<LanguageClient> {
+function buildLanguageClient(config: LanguageServerConfig): LanguageClient {
 	// Reuse the same output channel for each instance of the server.
 	if (config.enabled) {
 		if (!serverOutputChannel) {
diff --git a/test/gopls/extension.test.ts b/test/gopls/extension.test.ts
index 64cddfc..7f5104c 100644
--- a/test/gopls/extension.test.ts
+++ b/test/gopls/extension.test.ts
@@ -46,7 +46,10 @@
 	public async setup() {
 		await this.reset();
 		await this.extension.activate();
-		await sleep(2000);  // allow extension host + gopls to start.
+		await sleep(2000);  // allow the language server to start.
+		// TODO(hyangah): find a better way to check the language server's status.
+		// I thought I'd check the languageClient.onReady(),
+		// but couldn't make it working yet.
 	}
 
 	public async reset(fixtureDirName?: string) {  // name of the fixtures subdirectory to use.
@@ -89,7 +92,9 @@
 	const projectDir = path.join(__dirname, '..', '..', '..');
 	const env = new Env(projectDir);
 
-	suiteSetup(async () => { await env.setup(); });
+	suiteSetup(async () => {
+		await env.setup();
+	});
 	suiteTeardown(async () => { await env.reset(); });
 
 	test('HoverProvider', async () => {
@@ -137,9 +142,23 @@
 			['fmt.<>', new vscode.Position(19, 5), 'Formatter'],
 		];
 		for (const [name, position, wantFilterText] of testCases) {
-			const list = await vscode.commands.executeCommand(
-				'vscode.executeCompletionItemProvider', uri, position) as vscode.CompletionList;
-
+			let list: vscode.CompletionList<vscode.CompletionItem>;
+			// Query completion items. We expect the hard coded filter text hack
+			// has been applied and gopls returns an incomplete list by default
+			// to avoid reordering by vscode. But, if the query is made before
+			// gopls is ready, we observed that gopls returns an empty result
+			// as a complete result, and vscode returns a general completion list instead.
+			// Retry a couple of times if we see a complete result as a workaround.
+			// (github.com/golang/vscode-go/issues/363)
+			for (let i = 0; i < 3; i++) {
+				list = await vscode.commands.executeCommand(
+					'vscode.executeCompletionItemProvider', uri, position) as vscode.CompletionList;
+				if (list.isIncomplete) {
+					break;
+				}
+				await sleep(100);
+				console.log(`${new Date()}: retrying...`);
+			}
 			// Confirm that the hardcoded filter text hack has been applied.
 			if (!list.isIncomplete) {
 				assert.fail(`gopls should provide an incomplete list by default`);
@@ -157,6 +176,5 @@
 				}
 			}
 		}
-
 	});
 });