Skip to content

Commit

Permalink
src/goTest: split up test explorer
Browse files Browse the repository at this point in the history
Change-Id: Id2da687d115d5551d70bc8235a6aab5f7ce69ecc
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/343790
Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
Trust: Hyang-Ah Hana Kim <[email protected]>
Trust: Alexander Rakoczy <[email protected]>
Run-TryBot: Hyang-Ah Hana Kim <[email protected]>
TryBot-Result: kokoro <[email protected]>
  • Loading branch information
firelizzard18 authored and hyangah committed Aug 26, 2021
1 parent a1cc5e5 commit 3e12e73
Show file tree
Hide file tree
Showing 12 changed files with 1,836 additions and 1,694 deletions.
6 changes: 3 additions & 3 deletions src/goMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ import { getFormatTool } from './goFormat';
import { resetSurveyConfig, showSurveyConfig, timeMinute } from './goSurvey';
import { ExtensionAPI } from './export';
import extensionAPI from './extensionAPI';
import { isVscodeTestingAPIAvailable, TestExplorer } from './goTestExplorer';
import { GoTestExplorer, isVscodeTestingAPIAvailable } from './goTest/explore';

export let buildDiagnosticCollection: vscode.DiagnosticCollection;
export let lintDiagnosticCollection: vscode.DiagnosticCollection;
Expand Down Expand Up @@ -337,11 +337,11 @@ If you would like additional configuration for diagnostics from gopls, please se
);

if (isVscodeTestingAPIAvailable) {
const testExplorer = TestExplorer.setup(ctx);
const testExplorer = GoTestExplorer.setup(ctx);

ctx.subscriptions.push(
vscode.commands.registerCommand('go.test.refresh', (args) => {
if (args) testExplorer.resolve(args);
if (args) testExplorer.resolver.resolve(args);
})
);
}
Expand Down
247 changes: 247 additions & 0 deletions src/goTest/explore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/*---------------------------------------------------------
* Copyright 2021 The Go Authors. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
import {
ConfigurationChangeEvent,
ExtensionContext,
Range,
TestController,
TestItem,
TestItemCollection,
TestRunProfileKind,
TextDocument,
TextDocumentChangeEvent,
Uri,
workspace,
WorkspaceFoldersChangeEvent
} from 'vscode';
import vscode = require('vscode');
import { GoDocumentSymbolProvider } from '../goOutline';
import { outputChannel } from '../goStatus';
import { dispose, disposeIfEmpty, findItem, GoTest, Workspace } from './utils';
import { GoTestResolver, ProvideSymbols } from './resolve';
import { GoTestRunner } from './run';

// Set true only if the Testing API is available (VSCode version >= 1.59).
export const isVscodeTestingAPIAvailable =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'object' === typeof (vscode as any).tests && 'function' === typeof (vscode as any).tests.createTestController;

// Check whether the process is running as a test.
function isInTest() {
return process.env.VSCODE_GO_IN_TEST === '1';
}

export class GoTestExplorer {
static setup(context: ExtensionContext): GoTestExplorer {
if (!isVscodeTestingAPIAvailable) throw new Error('VSCode Testing API is unavailable');

const ctrl = vscode.tests.createTestController('go', 'Go');
const symProvider = new GoDocumentSymbolProvider(true);
const inst = new this(workspace, ctrl, (doc, token) => symProvider.provideDocumentSymbols(doc, token));

context.subscriptions.push(ctrl);

context.subscriptions.push(
workspace.onDidChangeConfiguration(async (x) => {
try {
await inst.didChangeConfiguration(x);
} catch (error) {
if (isInTest()) throw error;
else outputChannel.appendLine(`Failed while handling 'onDidChangeConfiguration': ${error}`);
}
})
);

context.subscriptions.push(
workspace.onDidOpenTextDocument(async (x) => {
try {
await inst.didOpenTextDocument(x);
} catch (error) {
if (isInTest()) throw error;
else outputChannel.appendLine(`Failed while handling 'onDidOpenTextDocument': ${error}`);
}
})
);

context.subscriptions.push(
workspace.onDidChangeTextDocument(async (x) => {
try {
await inst.didChangeTextDocument(x);
} catch (error) {
if (isInTest()) throw error;
else outputChannel.appendLine(`Failed while handling 'onDidChangeTextDocument': ${error}`);
}
})
);

context.subscriptions.push(
workspace.onDidChangeWorkspaceFolders(async (x) => {
try {
await inst.didChangeWorkspaceFolders(x);
} catch (error) {
if (isInTest()) throw error;
else outputChannel.appendLine(`Failed while handling 'onDidChangeWorkspaceFolders': ${error}`);
}
})
);

const watcher = workspace.createFileSystemWatcher('**/*_test.go', false, true, false);
context.subscriptions.push(watcher);
context.subscriptions.push(
watcher.onDidCreate(async (x) => {
try {
await inst.didCreateFile(x);
} catch (error) {
if (isInTest()) throw error;
else outputChannel.appendLine(`Failed while handling 'FileSystemWatcher.onDidCreate': ${error}`);
}
})
);
context.subscriptions.push(
watcher.onDidDelete(async (x) => {
try {
await inst.didDeleteFile(x);
} catch (error) {
if (isInTest()) throw error;
else outputChannel.appendLine(`Failed while handling 'FileSystemWatcher.onDidDelete': ${error}`);
}
})
);

return inst;
}

public readonly resolver: GoTestResolver;

constructor(
private readonly workspace: Workspace,
private readonly ctrl: TestController,
provideDocumentSymbols: ProvideSymbols
) {
const resolver = new GoTestResolver(workspace, ctrl, provideDocumentSymbols);
const runner = new GoTestRunner(workspace, ctrl, resolver);

this.resolver = resolver;

ctrl.resolveHandler = async (item) => {
try {
await resolver.resolve(item);
} catch (error) {
if (isInTest()) throw error;

const m = 'Failed to resolve tests';
outputChannel.appendLine(`${m}: ${error}`);
await vscode.window.showErrorMessage(m);
}
};

ctrl.createRunProfile(
'go test',
TestRunProfileKind.Run,
async (request, token) => {
try {
await runner.run(request, token);
} catch (error) {
const m = 'Failed to execute tests';
outputChannel.appendLine(`${m}: ${error}`);
await vscode.window.showErrorMessage(m);
}
},
true
);
}

/* ***** Listeners ***** */

protected async didOpenTextDocument(doc: TextDocument) {
await this.documentUpdate(doc);
}

protected async didChangeTextDocument(e: TextDocumentChangeEvent) {
await this.documentUpdate(
e.document,
e.contentChanges.map((x) => x.range)
);
}

protected async didChangeWorkspaceFolders(e: WorkspaceFoldersChangeEvent) {
if (e.added.length > 0) {
await this.resolver.resolve();
}

if (e.removed.length === 0) {
return;
}

this.ctrl.items.forEach((item) => {
const uri = Uri.parse(item.id);
if (uri.query === 'package') {
return;
}

const ws = this.workspace.getWorkspaceFolder(uri);
if (!ws) {
dispose(item);
}
});
}

protected async didCreateFile(file: Uri) {
await this.documentUpdate(await this.workspace.openTextDocument(file));
}

protected async didDeleteFile(file: Uri) {
const id = GoTest.id(file, 'file');
function find(children: TestItemCollection): TestItem {
return findItem(children, (item) => {
if (item.id === id) {
return item;
}

const uri = Uri.parse(item.id);
if (!file.path.startsWith(uri.path)) {
return;
}

return find(item.children);
});
}

const found = find(this.ctrl.items);
if (found) {
dispose(found);
disposeIfEmpty(found.parent);
}
}

protected async didChangeConfiguration(e: ConfigurationChangeEvent) {
let update = false;
this.ctrl.items.forEach((item) => {
if (e.affectsConfiguration('go.testExplorerPackages', item.uri)) {
dispose(item);
update = true;
}
});

if (update) {
this.resolver.resolve();
}
}

// Handle opened documents, document changes, and file creation.
private async documentUpdate(doc: TextDocument, ranges?: Range[]) {
if (doc.uri.scheme === 'git') {
// TODO(firelizzard18): When a workspace is reopened, VSCode passes us git: URIs. Why?
const { path } = JSON.parse(doc.uri.query);
doc = await vscode.workspace.openTextDocument(path);
}

if (!doc.uri.path.endsWith('_test.go')) {
return;
}

await this.resolver.processDocument(doc, ranges);
}
}
Loading

0 comments on commit 3e12e73

Please sign in to comment.