|
1 | 1 | import * as vscode from 'vscode' |
| 2 | +import path from 'path' |
2 | 3 | import { LSPClient } from '../lsp/lsp' |
| 4 | +import { isErr } from '@bus/result' |
| 5 | +import { sqlmeshExec } from '../utilities/sqlmesh/sqlmesh' |
| 6 | +import { execAsync } from '../utilities/exec' |
3 | 7 |
|
4 | 8 | export const controller = vscode.tests.createTestController( |
5 | 9 | 'sqlmeshTests', |
6 | 10 | 'SQLMesh Tests', |
7 | 11 | ) |
8 | 12 |
|
9 | 13 | export const setupTestController = (lsp: LSPClient) => { |
10 | | - // First, create the `resolveHandler`. This may initially be called with |
11 | | - // "undefined" to ask for all tests in the workspace to be discovered, usually |
12 | | - // when the user opens the Test Explorer for the first time. |
13 | | - controller.resolveHandler = async test => { |
14 | | - if (!test) { |
15 | | - await discoverAllFilesInWorkspace() |
16 | | - } else { |
17 | | - await parseTestsInFileContents(test) |
18 | | - } |
| 14 | + controller.resolveHandler = async () => { |
| 15 | + await discoverWorkspaceTests() |
19 | 16 | } |
20 | 17 |
|
21 | | - // When text documents are open, parse tests in them. |
22 | | - vscode.workspace.onDidOpenTextDocument(parseTestsInDocument) |
23 | | - // We could also listen to document changes to re-parse unsaved changes: |
24 | | - vscode.workspace.onDidChangeTextDocument(e => |
25 | | - parseTestsInDocument(e.document), |
| 18 | + controller.createRunProfile( |
| 19 | + 'Run', |
| 20 | + vscode.TestRunProfileKind.Run, |
| 21 | + (request, token) => runTests(request, token), |
| 22 | + true, |
26 | 23 | ) |
27 | 24 |
|
28 | | - // In this function, we'll get the file TestItem if we've already found it, |
29 | | - // otherwise we'll create it with `canResolveChildren = true` to indicate it |
30 | | - // can be passed to the `controller.resolveHandler` to gets its children. |
31 | | - function getOrCreateFile(uri: vscode.Uri) { |
32 | | - const existing = controller.items.get(uri.toString()) |
33 | | - if (existing) { |
34 | | - return existing |
| 25 | + async function discoverWorkspaceTests() { |
| 26 | + const result = await lsp.call_custom_method('sqlmesh/list_workspace_tests', {}) |
| 27 | + if (isErr(result)) { |
| 28 | + vscode.window.showErrorMessage(`Failed to list SQLMesh tests: ${result.error.message}`) |
| 29 | + return |
| 30 | + } |
| 31 | + controller.items.replace([]) |
| 32 | + const files = new Map<string, vscode.TestItem>() |
| 33 | + for (const entry of result.value.tests) { |
| 34 | + const uri = vscode.Uri.parse(entry.uri) |
| 35 | + let fileItem = files.get(uri.toString()) |
| 36 | + if (!fileItem) { |
| 37 | + fileItem = controller.createTestItem(uri.toString(), path.basename(uri.fsPath), uri) |
| 38 | + fileItem.canResolveChildren = true |
| 39 | + files.set(uri.toString(), fileItem) |
| 40 | + controller.items.add(fileItem) |
| 41 | + } |
| 42 | + const testId = `${uri.toString()}::${entry.name}` |
| 43 | + const testItem = controller.createTestItem(testId, entry.name, uri) |
| 44 | + fileItem.children.add(testItem) |
35 | 45 | } |
36 | | - |
37 | | - const file = controller.createTestItem( |
38 | | - uri.toString(), |
39 | | - uri.path.split('/').pop()!, |
40 | | - uri, |
41 | | - ) |
42 | | - file.canResolveChildren = true |
43 | | - return file |
44 | 46 | } |
45 | 47 |
|
46 | | - function parseTestsInDocument(e: vscode.TextDocument) { |
47 | | - if (e.uri.scheme === 'file' && e.uri.path.endsWith('.md')) { |
48 | | - parseTestsInFileContents(getOrCreateFile(e.uri), e.getText()) |
| 48 | + async function runTests(request: vscode.TestRunRequest, token: vscode.CancellationToken) { |
| 49 | + const run = controller.createTestRun(request) |
| 50 | + const exec = await sqlmeshExec() |
| 51 | + if (isErr(exec)) { |
| 52 | + vscode.window.showErrorMessage(`Unable to run tests: ${JSON.stringify(exec.error)}`) |
| 53 | + run.end() |
| 54 | + return |
49 | 55 | } |
50 | | - } |
51 | 56 |
|
52 | | - async function parseTestsInFileContents( |
53 | | - file: vscode.TestItem, |
54 | | - contents?: string, |
55 | | - ) { |
56 | | - // If a document is open, VS Code already knows its contents. If this is being |
57 | | - // called from the resolveHandler when a document isn't open, we'll need to |
58 | | - // read them from disk ourselves. |
59 | | - if (contents === undefined) { |
60 | | - const rawContent = await vscode.workspace.fs.readFile(file.uri) |
61 | | - contents = new TextDecoder().decode(rawContent) |
| 57 | + const tests: vscode.TestItem[] = [] |
| 58 | + const collect = (item: vscode.TestItem) => { |
| 59 | + if (item.children.size === 0) tests.push(item) |
| 60 | + item.children.forEach(collect) |
62 | 61 | } |
63 | 62 |
|
64 | | - // some custom logic to fill in test.children from the contents... |
| 63 | + if (request.include) request.include.forEach(collect) |
| 64 | + else controller.items.forEach(collect) |
| 65 | + |
| 66 | + for (const t of tests) run.started(t) |
| 67 | + |
| 68 | + const patterns = tests.map(t => { |
| 69 | + const [uriStr, name] = t.id.split('::') |
| 70 | + const uri = vscode.Uri.parse(uriStr) |
| 71 | + return `${uri.fsPath}::${name}` |
| 72 | + }) |
| 73 | + |
| 74 | + const result = await execAsync(exec.value.bin, [...exec.value.args, 'test', ...patterns], { |
| 75 | + cwd: exec.value.workspacePath, |
| 76 | + env: exec.value.env, |
| 77 | + signal: token as unknown as AbortSignal, |
| 78 | + }) |
| 79 | + |
| 80 | + const passed = result.exitCode === 0 |
| 81 | + for (const t of tests) { |
| 82 | + if (passed) run.passed(t) |
| 83 | + else run.failed(t, new vscode.TestMessage(result.stderr || 'Failed')) |
| 84 | + } |
| 85 | + run.end() |
65 | 86 | } |
66 | 87 | } |
0 commit comments