| /*--------------------------------------------------------- |
| * Copyright 2021 The Go Authors. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------*/ |
| import * as vscode from 'vscode'; |
| import { GoTestResolver } from './resolve'; |
| |
| // GoTestKind indicates the Go construct represented by a test item. |
| // |
| // - A 'module' is a folder that contains a go.mod file |
| // - A 'workspace' is a VSCode workspace folder that contains .go files outside |
| // of a module |
| // - A 'package' is a folder that contains .go files (and is not a module) |
| // - A 'file' is a file ending with _test.go |
| // - A 'test' is a Go test, e.g. func TestXxx(t *testing.T) |
| // - A 'benchmark' is a Go benchmark, e.g. func BenchmarkXxx(t *testing.B) |
| // - A 'fuzz' is a Fuzz test, e.g., func TestFuzz(f *testing.F) |
| // - An 'example' is a Go example, e.g. func ExampleXxx() |
| // |
| // The top-level test item for a workspace folder is always either a module or a |
| // workspace. If the user opens a file (containing tests) that is not contained |
| // within any workspace folder, a top-level package will be created as a parent |
| // of that file. |
| export type GoTestKind = 'module' | 'workspace' | 'package' | 'file' | 'test' | 'benchmark' | 'fuzz' | 'example'; |
| |
| export class GoTest { |
| // Constructs an ID for an item. The ID of a test item consists of the URI |
| // for the relevant file or folder with the URI query set to the test item |
| // kind (see GoTestKind) and the URI fragment set to the function name, if |
| // the item represents a test, benchmark, or example function. |
| // |
| // - Module: file:///path/to/mod?module |
| // - Workspace: file:///path/to/src?workspace |
| // - Package: file:///path/to/mod/pkg?package |
| // - File: file:///path/to/mod/file.go?file |
| // - Test: file:///path/to/mod/file.go?test#TestXxx |
| // - Benchmark: file:///path/to/mod/file.go?benchmark#BenchmarkXxx |
| // - Fuzz: file:///path/to/mod/file.go?test#FuzzXxx |
| // - Example: file:///path/to/mod/file.go?example#ExampleXxx |
| static id(uri: vscode.Uri, kind: GoTestKind, name?: string): string { |
| uri = uri.with({ query: kind }); |
| if (name) uri = uri.with({ fragment: name }); |
| return uri.toString(); |
| } |
| |
| // Parses the ID as a URI and extracts the kind and name. |
| // |
| // The URI of the relevant file or folder should be retrieved wil |
| // TestItem.uri. |
| static parseId(id: string): { kind: GoTestKind; name?: string } { |
| const u = vscode.Uri.parse(id); |
| const kind = u.query as GoTestKind; |
| const name = u.fragment; |
| return { kind, name }; |
| } |
| } |
| |
| // Check whether the process is running as a test. |
| export function isInTest() { |
| return process.env.VSCODE_GO_IN_TEST === '1'; |
| } |
| |
| // The subset of vscode.FileSystem that is used by the test explorer. |
| export type FileSystem = Pick<vscode.FileSystem, 'readFile' | 'readDirectory'>; |
| |
| // The subset of vscode.workspace that is used by the test explorer. |
| export interface Workspace |
| extends Pick<typeof vscode.workspace, 'workspaceFolders' | 'getWorkspaceFolder' | 'textDocuments'> { |
| // use custom FS type |
| readonly fs: FileSystem; |
| |
| // only include one overload |
| openTextDocument(uri: vscode.Uri): Thenable<vscode.TextDocument>; |
| } |
| |
| export function findItem( |
| items: vscode.TestItemCollection, |
| fn: (item: vscode.TestItem) => vscode.TestItem | undefined |
| ): vscode.TestItem | undefined { |
| let found: vscode.TestItem | undefined; |
| items.forEach((item) => { |
| if (found) return; |
| found = fn(item); |
| }); |
| return found; |
| } |
| |
| export function forEachAsync<T>( |
| items: vscode.TestItemCollection, |
| fn: (item: vscode.TestItem) => Promise<T> |
| ): Promise<T[]> { |
| const promises: Promise<T>[] = []; |
| items.forEach((item) => promises.push(fn(item))); |
| return Promise.all(promises); |
| } |
| |
| export function dispose(resolver: GoTestResolver, item: vscode.TestItem) { |
| resolver.all.delete(item.id); |
| item.parent?.children.delete(item.id); |
| } |
| |
| // Dispose of the item if it has no children, recursively. This facilitates |
| // cleaning up package/file trees that contain no tests. |
| export function disposeIfEmpty(resolver: GoTestResolver, item: vscode.TestItem | undefined) { |
| if (!item) return; |
| // Don't dispose of empty top-level items |
| const { kind } = GoTest.parseId(item.id); |
| if (kind === 'module' || kind === 'workspace' || (kind === 'package' && !item.parent)) { |
| return; |
| } |
| |
| if (item.children.size > 0) { |
| return; |
| } |
| |
| dispose(resolver, item); |
| disposeIfEmpty(resolver, item.parent); |
| } |
| |
| // The 'name' group captures the module name, and the unnamed group ignores any comment that might follow the name. |
| const moduleNameRegex = /^module.(?<name>.*?)(?:\s|\/\/|$)/mu; |
| |
| export function findModuleName(goModContent: string): string { |
| const match = goModContent.toString().match(moduleNameRegex); |
| if (match === null) { |
| throw new Error('failed to find module name in go.mod'); |
| } |
| return match.groups?.name ?? ''; |
| } |