| /*--------------------------------------------------------- |
| * Copyright 2021 The Go Authors. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------*/ |
| import assert = require('assert'); |
| import path = require('path'); |
| import { |
| DocumentSymbol, |
| FileType, |
| TestItem, |
| Uri, |
| TextDocument, |
| SymbolKind, |
| Range, |
| Position, |
| TestItemCollection, |
| TextDocumentChangeEvent |
| } from 'vscode'; |
| import { packagePathToGoModPathMap as pkg2mod } from '../../src/goModules'; |
| import { TestExplorer, testID } from '../../src/goTestExplorer'; |
| import { MockTestController, MockTestWorkspace } from '../mocks/MockTest'; |
| |
| type Files = Record<string, string | { contents: string; language: string }>; |
| |
| interface TestCase { |
| workspace: string[]; |
| files: Files; |
| } |
| |
| // eslint-disable-next-line @typescript-eslint/no-unused-vars |
| function symbols(doc: TextDocument, token: unknown): Thenable<DocumentSymbol[]> { |
| const syms: DocumentSymbol[] = []; |
| const range = new Range(new Position(0, 0), new Position(0, 0)); |
| doc.getText().replace(/^func (Test|Benchmark|Example)([A-Z]\w+)(\(.*\))/gm, (m, type, name, details) => { |
| syms.push(new DocumentSymbol(type + name, details, SymbolKind.Function, range, range)); |
| return m; |
| }); |
| return Promise.resolve(syms); |
| } |
| |
| function setup(folders: string[], files: Files) { |
| return setupCtor(folders, files, TestExplorer); |
| } |
| |
| function setupCtor<T extends TestExplorer>( |
| folders: string[], |
| files: Files, |
| ctor: new (...args: ConstructorParameters<typeof TestExplorer>) => T |
| ) { |
| const ws = MockTestWorkspace.from(folders, files); |
| const ctrl = new MockTestController(); |
| const expl = new ctor(ctrl, ws, symbols); |
| |
| function walk(dir: Uri, modpath?: string) { |
| const dirs: Uri[] = []; |
| for (const [name, type] of ws.fs.dirs.get(dir.toString())) { |
| const uri = dir.with({ path: path.join(dir.path, name) }); |
| if (type === FileType.Directory) { |
| dirs.push(uri); |
| } else if (name === 'go.mod') { |
| modpath = dir.path; |
| } |
| } |
| pkg2mod[dir.path] = modpath || ''; |
| for (const dir of dirs) { |
| walk(dir, modpath); |
| } |
| } |
| |
| // prevent getModFolderPath from actually doing anything; |
| for (const pkg in pkg2mod) delete pkg2mod[pkg]; |
| walk(Uri.file('/')); |
| |
| return { ctrl, expl, ws }; |
| } |
| |
| function assertTestItems(items: TestItemCollection, expect: string[]) { |
| const actual: string[] = []; |
| function walk(items: TestItemCollection) { |
| items.forEach((item) => { |
| actual.push(item.id); |
| walk(item.children); |
| }); |
| } |
| walk(items); |
| assert.deepStrictEqual(actual, expect); |
| } |
| |
| suite('Test Explorer', () => { |
| suite('Items', () => { |
| interface TC extends TestCase { |
| item?: ([string, string, string] | [string, string, string, string])[]; |
| expect: string[]; |
| } |
| |
| const cases: Record<string, Record<string, TC>> = { |
| Root: { |
| 'Basic module': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/main.go': 'package main' |
| }, |
| expect: ['file:///src/proj?module'] |
| }, |
| 'Basic workspace': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/main.go': 'package main' |
| }, |
| expect: ['file:///src/proj?workspace'] |
| }, |
| 'Module and workspace': { |
| workspace: ['/src/proj1', '/src/proj2'], |
| files: { |
| '/src/proj1/go.mod': 'module test', |
| '/src/proj2/main.go': 'package main' |
| }, |
| expect: ['file:///src/proj1?module', 'file:///src/proj2?workspace'] |
| }, |
| 'Module in workspace': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/mod/go.mod': 'module test', |
| '/src/proj/main.go': 'package main' |
| }, |
| expect: ['file:///src/proj/mod?module', 'file:///src/proj?workspace'] |
| } |
| }, |
| Module: { |
| 'Empty': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/main.go': 'package main' |
| }, |
| item: [['test', '/src/proj', 'module']], |
| expect: [] |
| }, |
| 'Root package': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/main_test.go': 'package main' |
| }, |
| item: [['test', '/src/proj', 'module']], |
| expect: ['file:///src/proj/main_test.go?file'] |
| }, |
| 'Sub packages': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/foo/main_test.go': 'package main', |
| '/src/proj/bar/main_test.go': 'package main' |
| }, |
| item: [['test', '/src/proj', 'module']], |
| expect: ['file:///src/proj/foo?package', 'file:///src/proj/bar?package'] |
| }, |
| 'Nested packages': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/main_test.go': 'package main', |
| '/src/proj/foo/main_test.go': 'package main', |
| '/src/proj/foo/bar/main_test.go': 'package main' |
| }, |
| item: [['test', '/src/proj', 'module']], |
| expect: [ |
| 'file:///src/proj/foo?package', |
| 'file:///src/proj/foo/bar?package', |
| 'file:///src/proj/main_test.go?file' |
| ] |
| } |
| }, |
| Package: { |
| 'Empty': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/pkg/main.go': 'package main' |
| }, |
| item: [ |
| ['test', '/src/proj', 'module'], |
| ['pkg', '/src/proj/pkg', 'package'] |
| ], |
| expect: [] |
| }, |
| 'Flat': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/pkg/main_test.go': 'package main', |
| '/src/proj/pkg/sub/main_test.go': 'package main' |
| }, |
| item: [ |
| ['test', '/src/proj', 'module'], |
| ['pkg', '/src/proj/pkg', 'package'] |
| ], |
| expect: ['file:///src/proj/pkg/main_test.go?file'] |
| }, |
| 'Sub package': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/pkg/sub/main_test.go': 'package main' |
| }, |
| item: [ |
| ['test', '/src/proj', 'module'], |
| ['pkg', '/src/proj/pkg', 'package'] |
| ], |
| expect: [] |
| } |
| }, |
| File: { |
| 'Empty': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/main_test.go': 'package main' |
| }, |
| item: [ |
| ['test', '/src/proj', 'module'], |
| ['main_test.go', '/src/proj/main_test.go', 'file'] |
| ], |
| expect: [] |
| }, |
| 'One of each': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/main_test.go': ` |
| package main |
| |
| func TestMain(*testing.M) {} |
| func TestFoo(*testing.T) {} |
| func BenchmarkBar(*testing.B) {} |
| func ExampleBaz() {} |
| ` |
| }, |
| item: [ |
| ['test', '/src/proj', 'module'], |
| ['main_test.go', '/src/proj/main_test.go', 'file'] |
| ], |
| expect: [ |
| 'file:///src/proj/main_test.go?test#TestFoo', |
| 'file:///src/proj/main_test.go?benchmark#BenchmarkBar', |
| 'file:///src/proj/main_test.go?example#ExampleBaz' |
| ] |
| } |
| } |
| }; |
| |
| for (const n in cases) { |
| suite(n, () => { |
| for (const m in cases[n]) { |
| test(m, async () => { |
| const { workspace, files, expect, item: itemData = [] } = cases[n][m]; |
| const { ctrl } = setup(workspace, files); |
| |
| let item: TestItem | undefined; |
| for (const [label, uri, kind, name] of itemData) { |
| const u = Uri.parse(uri); |
| const child = ctrl.createTestItem(testID(u, kind, name), label, u); |
| (item?.children || ctrl.items).add(child); |
| item = child; |
| } |
| await ctrl.resolveHandler(item); |
| |
| const actual: string[] = []; |
| (item?.children || ctrl.items).forEach((x) => actual.push(x.id)); |
| assert.deepStrictEqual(actual, expect); |
| }); |
| } |
| }); |
| } |
| }); |
| |
| suite('Events', () => { |
| suite('Document opened', () => { |
| class DUT extends TestExplorer { |
| async _didOpen(doc: TextDocument) { |
| await this.didOpenTextDocument(doc); |
| } |
| } |
| |
| interface TC extends TestCase { |
| open: string; |
| expect: string[]; |
| } |
| |
| const cases: Record<string, TC> = { |
| 'In workspace': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/foo_test.go': 'package main\nfunc TestFoo(*testing.T) {}', |
| '/src/proj/bar_test.go': 'package main\nfunc TestBar(*testing.T) {}', |
| '/src/proj/baz/main_test.go': 'package main\nfunc TestBaz(*testing.T) {}' |
| }, |
| open: 'file:///src/proj/foo_test.go', |
| expect: [ |
| 'file:///src/proj?module', |
| 'file:///src/proj/foo_test.go?file', |
| 'file:///src/proj/foo_test.go?test#TestFoo' |
| ] |
| }, |
| 'Outside workspace': { |
| workspace: [], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/foo_test.go': 'package main\nfunc TestFoo(*testing.T) {}' |
| }, |
| open: 'file:///src/proj/foo_test.go', |
| expect: [ |
| 'file:///src/proj?module', |
| 'file:///src/proj/foo_test.go?file', |
| 'file:///src/proj/foo_test.go?test#TestFoo' |
| ] |
| } |
| }; |
| |
| for (const name in cases) { |
| test(name, async () => { |
| const { workspace, files, open, expect } = cases[name]; |
| const { ctrl, expl, ws } = setupCtor(workspace, files, DUT); |
| |
| await expl._didOpen(ws.fs.files.get(open)); |
| |
| assertTestItems(ctrl.items, expect); |
| }); |
| } |
| }); |
| |
| suite('Document edited', async () => { |
| class DUT extends TestExplorer { |
| async _didOpen(doc: TextDocument) { |
| await this.didOpenTextDocument(doc); |
| } |
| |
| async _didChange(e: TextDocumentChangeEvent) { |
| await this.didChangeTextDocument(e); |
| } |
| } |
| |
| interface TC extends TestCase { |
| open: string; |
| changes: [string, string][]; |
| expect: { |
| before: string[]; |
| after: string[]; |
| }; |
| } |
| |
| const cases: Record<string, TC> = { |
| 'Add test': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/foo_test.go': 'package main' |
| }, |
| open: 'file:///src/proj/foo_test.go', |
| changes: [['file:///src/proj/foo_test.go', 'package main\nfunc TestFoo(*testing.T) {}']], |
| expect: { |
| before: ['file:///src/proj?module'], |
| after: [ |
| 'file:///src/proj?module', |
| 'file:///src/proj/foo_test.go?file', |
| 'file:///src/proj/foo_test.go?test#TestFoo' |
| ] |
| } |
| }, |
| 'Remove test': { |
| workspace: ['/src/proj'], |
| files: { |
| '/src/proj/go.mod': 'module test', |
| '/src/proj/foo_test.go': 'package main\nfunc TestFoo(*testing.T) {}' |
| }, |
| open: 'file:///src/proj/foo_test.go', |
| changes: [['file:///src/proj/foo_test.go', 'package main']], |
| expect: { |
| before: [ |
| 'file:///src/proj?module', |
| 'file:///src/proj/foo_test.go?file', |
| 'file:///src/proj/foo_test.go?test#TestFoo' |
| ], |
| after: ['file:///src/proj?module'] |
| } |
| } |
| }; |
| |
| for (const name in cases) { |
| test(name, async () => { |
| const { workspace, files, open, changes, expect } = cases[name]; |
| const { ctrl, expl, ws } = setupCtor(workspace, files, DUT); |
| |
| await expl._didOpen(ws.fs.files.get(open)); |
| |
| assertTestItems(ctrl.items, expect.before); |
| |
| for (const [file, contents] of changes) { |
| const doc = ws.fs.files.get(file); |
| doc.contents = contents; |
| await expl._didChange({ |
| document: doc, |
| contentChanges: [] |
| }); |
| } |
| |
| assertTestItems(ctrl.items, expect.after); |
| }); |
| } |
| }); |
| }); |
| }); |