From 1724e5fff2d6e24b575642c993cc0243fba61492 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 28 Jun 2022 15:12:13 -0700 Subject: [PATCH 1/2] Clean up test sample - Remove now finalized API - Use `@types/vscode` directly - Removes references to prettier since the sample doesn't use it - Indent with 4 spaces to align with other samples --- test-provider-sample/.eslintignore | 2 - test-provider-sample/.vscode/settings.json | 3 - test-provider-sample/package-lock.json | 2 +- test-provider-sample/package.json | 12 +- test-provider-sample/src/extension.ts | 282 ++++++++++----------- test-provider-sample/src/parser.ts | 36 +-- test-provider-sample/src/testTree.ts | 180 ++++++------- 7 files changed, 253 insertions(+), 264 deletions(-) delete mode 100644 test-provider-sample/.eslintignore delete mode 100644 test-provider-sample/.vscode/settings.json diff --git a/test-provider-sample/.eslintignore b/test-provider-sample/.eslintignore deleted file mode 100644 index fcd9dd7e..00000000 --- a/test-provider-sample/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -vscode.d.ts -vscode.proposed.d.ts \ No newline at end of file diff --git a/test-provider-sample/.vscode/settings.json b/test-provider-sample/.vscode/settings.json deleted file mode 100644 index 6b7f727e..00000000 --- a/test-provider-sample/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "prettier.printWidth": 120 -} \ No newline at end of file diff --git a/test-provider-sample/package-lock.json b/test-provider-sample/package-lock.json index d096c08d..6777d840 100644 --- a/test-provider-sample/package-lock.json +++ b/test-provider-sample/package-lock.json @@ -18,7 +18,7 @@ "vscode-dts": "^0.3.3" }, "engines": { - "vscode": "^1.51.0" + "vscode": "^1.68.0" } }, "node_modules/@eslint/eslintrc": { diff --git a/test-provider-sample/package.json b/test-provider-sample/package.json index 00f72af7..53929a80 100644 --- a/test-provider-sample/package.json +++ b/test-provider-sample/package.json @@ -8,11 +8,10 @@ "license": "MIT", "repository": "https://github.com/Microsoft/vscode-extension-samples", "enabledApiProposals": [ - "testCoverage", - "testRefresh" + "testCoverage" ], "engines": { - "vscode": "^1.51.0" + "vscode": "^1.68.0" }, "categories": [ "Other" @@ -21,18 +20,12 @@ "workspaceContains:*.md" ], "main": "./out/extension.js", - "prettier": { - "printWidth": 120, - "singleQuote": true, - "arrowParens": "avoid" - }, "scripts": { "vscode:prepublish": "npm run compile", "compile": "tsc -p ./", "lint": "eslint . --ext .ts,.tsx", "watch": "tsc -watch -p ./", "download-api": "vscode-dts dev", - "postdownload-api": "vscode-dts main", "postinstall": "npm run download-api" }, "devDependencies": { @@ -44,3 +37,4 @@ "vscode-dts": "^0.3.3" } } + \ No newline at end of file diff --git a/test-provider-sample/src/extension.ts b/test-provider-sample/src/extension.ts index 08222225..9f79d5c9 100644 --- a/test-provider-sample/src/extension.ts +++ b/test-provider-sample/src/extension.ts @@ -2,185 +2,185 @@ import * as vscode from 'vscode'; import { getContentFromFilesystem, MarkdownTestData, TestCase, testData, TestFile } from './testTree'; export async function activate(context: vscode.ExtensionContext) { - const ctrl = vscode.tests.createTestController('mathTestController', 'Markdown Math'); - context.subscriptions.push(ctrl); + const ctrl = vscode.tests.createTestController('mathTestController', 'Markdown Math'); + context.subscriptions.push(ctrl); - const runHandler = (request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) => { - const queue: { test: vscode.TestItem; data: TestCase }[] = []; - const run = ctrl.createTestRun(request); - // map of file uris to statments on each line: - const coveredLines = new Map(); + const runHandler = (request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) => { + const queue: { test: vscode.TestItem; data: TestCase }[] = []; + const run = ctrl.createTestRun(request); + // map of file uris to statments on each line: + const coveredLines = new Map(); - const discoverTests = async (tests: Iterable) => { - for (const test of tests) { - if (request.exclude?.includes(test)) { - continue; - } + const discoverTests = async (tests: Iterable) => { + for (const test of tests) { + if (request.exclude?.includes(test)) { + continue; + } - const data = testData.get(test); - if (data instanceof TestCase) { - run.enqueued(test); - queue.push({ test, data }); - } else { - if (data instanceof TestFile && !data.didResolve) { - await data.updateFromDisk(ctrl, test); - } + const data = testData.get(test); + if (data instanceof TestCase) { + run.enqueued(test); + queue.push({ test, data }); + } else { + if (data instanceof TestFile && !data.didResolve) { + await data.updateFromDisk(ctrl, test); + } - await discoverTests(gatherTestItems(test.children)); - } + await discoverTests(gatherTestItems(test.children)); + } - if (test.uri && !coveredLines.has(test.uri.toString())) { - try { - const lines = (await getContentFromFilesystem(test.uri)).split('\n'); - coveredLines.set( - test.uri.toString(), - lines.map((lineText, lineNo) => - lineText.trim().length ? new vscode.StatementCoverage(0, new vscode.Position(lineNo, 0)) : undefined - ) - ); - } catch { - // ignored - } - } - } + if (test.uri && !coveredLines.has(test.uri.toString())) { + try { + const lines = (await getContentFromFilesystem(test.uri)).split('\n'); + coveredLines.set( + test.uri.toString(), + lines.map((lineText, lineNo) => + lineText.trim().length ? new vscode.StatementCoverage(0, new vscode.Position(lineNo, 0)) : undefined + ) + ); + } catch { + // ignored + } + } + } + }; + + const runTestQueue = async () => { + for (const { test, data } of queue) { + run.appendOutput(`Running ${test.id}\r\n`); + if (cancellation.isCancellationRequested) { + run.skipped(test); + } else { + run.started(test); + await data.run(test, run); + } + + const lineNo = test.range!.start.line; + const fileCoverage = coveredLines.get(test.uri!.toString()); + if (fileCoverage) { + fileCoverage[lineNo]!.executionCount++; + } + + run.appendOutput(`Completed ${test.id}\r\n`); + } + + run.end(); + }; + + run.coverageProvider = { + provideFileCoverage() { + const coverage: vscode.FileCoverage[] = []; + for (const [uri, statements] of coveredLines) { + coverage.push( + vscode.FileCoverage.fromDetails( + vscode.Uri.parse(uri), + statements.filter((s): s is vscode.StatementCoverage => !!s) + ) + ); + } + + return coverage; + }, + }; + + discoverTests(request.include ?? gatherTestItems(ctrl.items)).then(runTestQueue); }; - const runTestQueue = async () => { - for (const { test, data } of queue) { - run.appendOutput(`Running ${test.id}\r\n`); - if (cancellation.isCancellationRequested) { - run.skipped(test); - } else { - run.started(test); - await data.run(test, run); - } - - const lineNo = test.range!.start.line; - const fileCoverage = coveredLines.get(test.uri!.toString()); - if (fileCoverage) { - fileCoverage[lineNo]!.executionCount++; - } - - run.appendOutput(`Completed ${test.id}\r\n`); - } - - run.end(); + ctrl.refreshHandler = async () => { + await Promise.all(getWorkspaceTestPatterns().map(({ pattern }) => findInitialFiles(ctrl, pattern))); }; - run.coverageProvider = { - provideFileCoverage() { - const coverage: vscode.FileCoverage[] = []; - for (const [uri, statements] of coveredLines) { - coverage.push( - vscode.FileCoverage.fromDetails( - vscode.Uri.parse(uri), - statements.filter((s): s is vscode.StatementCoverage => !!s) - ) - ); + ctrl.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runHandler, true); + + ctrl.resolveHandler = async item => { + if (!item) { + context.subscriptions.push(...startWatchingWorkspace(ctrl)); + return; } - return coverage; - }, + const data = testData.get(item); + if (data instanceof TestFile) { + await data.updateFromDisk(ctrl, item); + } }; - discoverTests(request.include ?? gatherTestItems(ctrl.items)).then(runTestQueue); - }; + function updateNodeForDocument(e: vscode.TextDocument) { + if (e.uri.scheme !== 'file') { + return; + } - ctrl.refreshHandler = async () => { - await Promise.all(getWorkspaceTestPatterns().map(({ pattern }) => findInitialFiles(ctrl, pattern))); - }; + if (!e.uri.path.endsWith('.md')) { + return; + } - ctrl.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runHandler, true); - - ctrl.resolveHandler = async item => { - if (!item) { - context.subscriptions.push(...startWatchingWorkspace(ctrl)); - return; + const { file, data } = getOrCreateFile(ctrl, e.uri); + data.updateFromContents(ctrl, e.getText(), file); } - const data = testData.get(item); - if (data instanceof TestFile) { - await data.updateFromDisk(ctrl, item); - } - }; - - function updateNodeForDocument(e: vscode.TextDocument) { - if (e.uri.scheme !== 'file') { - return; - } - - if (!e.uri.path.endsWith('.md')) { - return; + for (const document of vscode.workspace.textDocuments) { + updateNodeForDocument(document); } - const { file, data } = getOrCreateFile(ctrl, e.uri); - data.updateFromContents(ctrl, e.getText(), file); - } - - for (const document of vscode.workspace.textDocuments) { - updateNodeForDocument(document); - } - - context.subscriptions.push( - vscode.workspace.onDidOpenTextDocument(updateNodeForDocument), - vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document)), - ); + context.subscriptions.push( + vscode.workspace.onDidOpenTextDocument(updateNodeForDocument), + vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document)), + ); } function getOrCreateFile(controller: vscode.TestController, uri: vscode.Uri) { - const existing = controller.items.get(uri.toString()); - if (existing) { - return { file: existing, data: testData.get(existing) as TestFile }; - } + const existing = controller.items.get(uri.toString()); + if (existing) { + return { file: existing, data: testData.get(existing) as TestFile }; + } - const file = controller.createTestItem(uri.toString(), uri.path.split('/').pop()!, uri); - controller.items.add(file); + const file = controller.createTestItem(uri.toString(), uri.path.split('/').pop()!, uri); + controller.items.add(file); - const data = new TestFile(); - testData.set(file, data); + const data = new TestFile(); + testData.set(file, data); - file.canResolveChildren = true; - return { file, data }; + file.canResolveChildren = true; + return { file, data }; } function gatherTestItems(collection: vscode.TestItemCollection) { - const items: vscode.TestItem[] = []; - collection.forEach(item => items.push(item)); - return items; + const items: vscode.TestItem[] = []; + collection.forEach(item => items.push(item)); + return items; } function getWorkspaceTestPatterns() { - if (!vscode.workspace.workspaceFolders) { - return []; - } + if (!vscode.workspace.workspaceFolders) { + return []; + } - return vscode.workspace.workspaceFolders.map(workspaceFolder => ({ - workspaceFolder, - pattern: new vscode.RelativePattern(workspaceFolder, '**/*.md'), - })); + return vscode.workspace.workspaceFolders.map(workspaceFolder => ({ + workspaceFolder, + pattern: new vscode.RelativePattern(workspaceFolder, '**/*.md'), + })); } async function findInitialFiles(controller: vscode.TestController, pattern: vscode.GlobPattern) { - for (const file of await vscode.workspace.findFiles(pattern)) { - getOrCreateFile(controller, file); - } + for (const file of await vscode.workspace.findFiles(pattern)) { + getOrCreateFile(controller, file); + } } function startWatchingWorkspace(controller: vscode.TestController) { - return getWorkspaceTestPatterns().map(({ workspaceFolder, pattern }) => { - const watcher = vscode.workspace.createFileSystemWatcher(pattern); + return getWorkspaceTestPatterns().map(({ workspaceFolder, pattern }) => { + const watcher = vscode.workspace.createFileSystemWatcher(pattern); - watcher.onDidCreate(uri => getOrCreateFile(controller, uri)); - watcher.onDidChange(uri => { - const { file, data } = getOrCreateFile(controller, uri); - if (data.didResolve) { - data.updateFromDisk(controller, file); - } + watcher.onDidCreate(uri => getOrCreateFile(controller, uri)); + watcher.onDidChange(uri => { + const { file, data } = getOrCreateFile(controller, uri); + if (data.didResolve) { + data.updateFromDisk(controller, file); + } + }); + watcher.onDidDelete(uri => controller.items.delete(uri.toString())); + + findInitialFiles(controller, pattern); + + return watcher; }); - watcher.onDidDelete(uri => controller.items.delete(uri.toString())); - - findInitialFiles(controller, pattern); - - return watcher; - }); } diff --git a/test-provider-sample/src/parser.ts b/test-provider-sample/src/parser.ts index 03c2139a..e2f486e1 100644 --- a/test-provider-sample/src/parser.ts +++ b/test-provider-sample/src/parser.ts @@ -4,26 +4,26 @@ const testRe = /^([0-9]+)\s*([+*/-])\s*([0-9]+)\s*=\s*([0-9]+)/; const headingRe = /^(#+)\s*(.+)$/; export const parseMarkdown = (text: string, events: { - onTest(range: vscode.Range, a: number, operator: string, b: number, expected: number): void; - onHeading(range: vscode.Range, name: string, depth: number): void; + onTest(range: vscode.Range, a: number, operator: string, b: number, expected: number): void; + onHeading(range: vscode.Range, name: string, depth: number): void; }) => { - const lines = text.split('\n'); + const lines = text.split('\n'); - for (let lineNo = 0; lineNo < lines.length; lineNo++) { - const line = lines[lineNo]; - const test = testRe.exec(line); - if (test) { - const [, a, operator, b, expected] = test; - const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, test[0].length)); - events.onTest(range, Number(a), operator, Number(b), Number(expected)); - continue; - } + for (let lineNo = 0; lineNo < lines.length; lineNo++) { + const line = lines[lineNo]; + const test = testRe.exec(line); + if (test) { + const [, a, operator, b, expected] = test; + const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, test[0].length)); + events.onTest(range, Number(a), operator, Number(b), Number(expected)); + continue; + } - const heading = headingRe.exec(line); - if (heading) { - const [, pounds, name] = heading; - const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, line.length)); - events.onHeading(range, name, pounds.length); + const heading = headingRe.exec(line); + if (heading) { + const [, pounds, name] = heading; + const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, line.length)); + events.onHeading(range, name, pounds.length); + } } - } }; diff --git a/test-provider-sample/src/testTree.ts b/test-provider-sample/src/testTree.ts index 0c816d8e..2f33d33f 100644 --- a/test-provider-sample/src/testTree.ts +++ b/test-provider-sample/src/testTree.ts @@ -11,118 +11,118 @@ export const testData = new WeakMap(); let generationCounter = 0; export const getContentFromFilesystem = async (uri: vscode.Uri) => { - try { - const rawContent = await vscode.workspace.fs.readFile(uri); - return textDecoder.decode(rawContent); - } catch (e) { - console.warn(`Error providing tests for ${uri.fsPath}`, e); - return ''; - } + try { + const rawContent = await vscode.workspace.fs.readFile(uri); + return textDecoder.decode(rawContent); + } catch (e) { + console.warn(`Error providing tests for ${uri.fsPath}`, e); + return ''; + } }; export class TestFile { - public didResolve = false; + public didResolve = false; - public async updateFromDisk(controller: vscode.TestController,item: vscode.TestItem) { - try { - const content = await getContentFromFilesystem(item.uri!); - item.error = undefined; - this.updateFromContents(controller, content, item); - } catch (e) { - item.error = (e as Error).stack; + public async updateFromDisk(controller: vscode.TestController, item: vscode.TestItem) { + try { + const content = await getContentFromFilesystem(item.uri!); + item.error = undefined; + this.updateFromContents(controller, content, item); + } catch (e) { + item.error = (e as Error).stack; + } } - } - /** - * Parses the tests from the input text, and updates the tests contained - * by this file to be those from the text, - */ - public updateFromContents(controller: vscode.TestController, content: string, item: vscode.TestItem) { - const ancestors = [{ item, children: [] as vscode.TestItem[]}]; - const thisGeneration = generationCounter++; - this.didResolve = true; + /** + * Parses the tests from the input text, and updates the tests contained + * by this file to be those from the text, + */ + public updateFromContents(controller: vscode.TestController, content: string, item: vscode.TestItem) { + const ancestors = [{ item, children: [] as vscode.TestItem[] }]; + const thisGeneration = generationCounter++; + this.didResolve = true; - const ascend = (depth: number) => { - while (ancestors.length > depth) { - const finished = ancestors.pop()!; - finished.item.children.replace(finished.children); - } - }; + const ascend = (depth: number) => { + while (ancestors.length > depth) { + const finished = ancestors.pop()!; + finished.item.children.replace(finished.children); + } + }; - parseMarkdown(content, { - onTest: (range, a, operator, b, expected) => { - const parent = ancestors[ancestors.length - 1]; - const data = new TestCase(a, operator as Operator, b, expected, thisGeneration); - const id = `${item.uri}/${data.getLabel()}`; + parseMarkdown(content, { + onTest: (range, a, operator, b, expected) => { + const parent = ancestors[ancestors.length - 1]; + const data = new TestCase(a, operator as Operator, b, expected, thisGeneration); + const id = `${item.uri}/${data.getLabel()}`; - - const tcase = controller.createTestItem(id, data.getLabel(), item.uri); - testData.set(tcase, data); - tcase.range = range; - parent.children.push(tcase); - }, - onHeading: (range, name, depth) => { - ascend(depth); - const parent = ancestors[ancestors.length - 1]; - const id = `${item.uri}/${name}`; + const tcase = controller.createTestItem(id, data.getLabel(), item.uri); + testData.set(tcase, data); + tcase.range = range; + parent.children.push(tcase); + }, - const thead = controller.createTestItem(id, name, item.uri); - thead.range = range; - testData.set(thead, new TestHeading(thisGeneration)); - parent.children.push(thead); - ancestors.push({ item: thead, children: [] }); - }, - }); + onHeading: (range, name, depth) => { + ascend(depth); + const parent = ancestors[ancestors.length - 1]; + const id = `${item.uri}/${name}`; - ascend(0); // finish and assign children for all remaining items - } + const thead = controller.createTestItem(id, name, item.uri); + thead.range = range; + testData.set(thead, new TestHeading(thisGeneration)); + parent.children.push(thead); + ancestors.push({ item: thead, children: [] }); + }, + }); + + ascend(0); // finish and assign children for all remaining items + } } export class TestHeading { - constructor(public generation: number) {} + constructor(public generation: number) { } } type Operator = '+' | '-' | '*' | '/'; export class TestCase { - constructor( - private readonly a: number, - private readonly operator: Operator, - private readonly b: number, - private readonly expected: number, - public generation: number - ) {} + constructor( + private readonly a: number, + private readonly operator: Operator, + private readonly b: number, + private readonly expected: number, + public generation: number + ) { } - getLabel() { - return `${this.a} ${this.operator} ${this.b} = ${this.expected}`; - } - - async run(item: vscode.TestItem, options: vscode.TestRun): Promise { - const start = Date.now(); - await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000)); - const actual = this.evaluate(); - const duration = Date.now() - start; - - if (actual === this.expected) { - options.passed(item, duration); - } else { - const message = vscode.TestMessage.diff(`Expected ${item.label}`, String(this.expected), String(actual)); - message.location = new vscode.Location(item.uri!, item.range!); - options.failed(item, message, duration); + getLabel() { + return `${this.a} ${this.operator} ${this.b} = ${this.expected}`; } - } - private evaluate() { - switch (this.operator) { - case '-': - return this.a - this.b; - case '+': - return this.a + this.b; - case '/': - return Math.floor(this.a / this.b); - case '*': - return this.a * this.b; + async run(item: vscode.TestItem, options: vscode.TestRun): Promise { + const start = Date.now(); + await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000)); + const actual = this.evaluate(); + const duration = Date.now() - start; + + if (actual === this.expected) { + options.passed(item, duration); + } else { + const message = vscode.TestMessage.diff(`Expected ${item.label}`, String(this.expected), String(actual)); + message.location = new vscode.Location(item.uri!, item.range!); + options.failed(item, message, duration); + } + } + + private evaluate() { + switch (this.operator) { + case '-': + return this.a - this.b; + case '+': + return this.a + this.b; + case '/': + return Math.floor(this.a / this.b); + case '*': + return this.a * this.b; + } } - } } From 9abfe1216861f1cb8868d59d7964e815e7be3b1a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 28 Jun 2022 15:15:14 -0700 Subject: [PATCH 2/2] Spaces -> tabs to align with other samples --- test-provider-sample/src/extension.ts | 276 +++++++++++++------------- test-provider-sample/src/parser.ts | 38 ++-- test-provider-sample/src/testTree.ts | 180 ++++++++--------- 3 files changed, 247 insertions(+), 247 deletions(-) diff --git a/test-provider-sample/src/extension.ts b/test-provider-sample/src/extension.ts index 9f79d5c9..0b4abe0c 100644 --- a/test-provider-sample/src/extension.ts +++ b/test-provider-sample/src/extension.ts @@ -2,185 +2,185 @@ import * as vscode from 'vscode'; import { getContentFromFilesystem, MarkdownTestData, TestCase, testData, TestFile } from './testTree'; export async function activate(context: vscode.ExtensionContext) { - const ctrl = vscode.tests.createTestController('mathTestController', 'Markdown Math'); - context.subscriptions.push(ctrl); + const ctrl = vscode.tests.createTestController('mathTestController', 'Markdown Math'); + context.subscriptions.push(ctrl); - const runHandler = (request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) => { - const queue: { test: vscode.TestItem; data: TestCase }[] = []; - const run = ctrl.createTestRun(request); - // map of file uris to statments on each line: - const coveredLines = new Map(); + const runHandler = (request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) => { + const queue: { test: vscode.TestItem; data: TestCase }[] = []; + const run = ctrl.createTestRun(request); + // map of file uris to statments on each line: + const coveredLines = new Map(); - const discoverTests = async (tests: Iterable) => { - for (const test of tests) { - if (request.exclude?.includes(test)) { - continue; - } + const discoverTests = async (tests: Iterable) => { + for (const test of tests) { + if (request.exclude?.includes(test)) { + continue; + } - const data = testData.get(test); - if (data instanceof TestCase) { - run.enqueued(test); - queue.push({ test, data }); - } else { - if (data instanceof TestFile && !data.didResolve) { - await data.updateFromDisk(ctrl, test); - } + const data = testData.get(test); + if (data instanceof TestCase) { + run.enqueued(test); + queue.push({ test, data }); + } else { + if (data instanceof TestFile && !data.didResolve) { + await data.updateFromDisk(ctrl, test); + } - await discoverTests(gatherTestItems(test.children)); - } + await discoverTests(gatherTestItems(test.children)); + } - if (test.uri && !coveredLines.has(test.uri.toString())) { - try { - const lines = (await getContentFromFilesystem(test.uri)).split('\n'); - coveredLines.set( - test.uri.toString(), - lines.map((lineText, lineNo) => - lineText.trim().length ? new vscode.StatementCoverage(0, new vscode.Position(lineNo, 0)) : undefined - ) - ); - } catch { - // ignored - } - } - } - }; + if (test.uri && !coveredLines.has(test.uri.toString())) { + try { + const lines = (await getContentFromFilesystem(test.uri)).split('\n'); + coveredLines.set( + test.uri.toString(), + lines.map((lineText, lineNo) => + lineText.trim().length ? new vscode.StatementCoverage(0, new vscode.Position(lineNo, 0)) : undefined + ) + ); + } catch { + // ignored + } + } + } + }; - const runTestQueue = async () => { - for (const { test, data } of queue) { - run.appendOutput(`Running ${test.id}\r\n`); - if (cancellation.isCancellationRequested) { - run.skipped(test); - } else { - run.started(test); - await data.run(test, run); - } + const runTestQueue = async () => { + for (const { test, data } of queue) { + run.appendOutput(`Running ${test.id}\r\n`); + if (cancellation.isCancellationRequested) { + run.skipped(test); + } else { + run.started(test); + await data.run(test, run); + } - const lineNo = test.range!.start.line; - const fileCoverage = coveredLines.get(test.uri!.toString()); - if (fileCoverage) { - fileCoverage[lineNo]!.executionCount++; - } + const lineNo = test.range!.start.line; + const fileCoverage = coveredLines.get(test.uri!.toString()); + if (fileCoverage) { + fileCoverage[lineNo]!.executionCount++; + } - run.appendOutput(`Completed ${test.id}\r\n`); - } + run.appendOutput(`Completed ${test.id}\r\n`); + } - run.end(); - }; + run.end(); + }; - run.coverageProvider = { - provideFileCoverage() { - const coverage: vscode.FileCoverage[] = []; - for (const [uri, statements] of coveredLines) { - coverage.push( - vscode.FileCoverage.fromDetails( - vscode.Uri.parse(uri), - statements.filter((s): s is vscode.StatementCoverage => !!s) - ) - ); - } + run.coverageProvider = { + provideFileCoverage() { + const coverage: vscode.FileCoverage[] = []; + for (const [uri, statements] of coveredLines) { + coverage.push( + vscode.FileCoverage.fromDetails( + vscode.Uri.parse(uri), + statements.filter((s): s is vscode.StatementCoverage => !!s) + ) + ); + } - return coverage; - }, - }; + return coverage; + }, + }; - discoverTests(request.include ?? gatherTestItems(ctrl.items)).then(runTestQueue); - }; + discoverTests(request.include ?? gatherTestItems(ctrl.items)).then(runTestQueue); + }; - ctrl.refreshHandler = async () => { - await Promise.all(getWorkspaceTestPatterns().map(({ pattern }) => findInitialFiles(ctrl, pattern))); - }; + ctrl.refreshHandler = async () => { + await Promise.all(getWorkspaceTestPatterns().map(({ pattern }) => findInitialFiles(ctrl, pattern))); + }; - ctrl.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runHandler, true); + ctrl.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runHandler, true); - ctrl.resolveHandler = async item => { - if (!item) { - context.subscriptions.push(...startWatchingWorkspace(ctrl)); - return; - } + ctrl.resolveHandler = async item => { + if (!item) { + context.subscriptions.push(...startWatchingWorkspace(ctrl)); + return; + } - const data = testData.get(item); - if (data instanceof TestFile) { - await data.updateFromDisk(ctrl, item); - } - }; + const data = testData.get(item); + if (data instanceof TestFile) { + await data.updateFromDisk(ctrl, item); + } + }; - function updateNodeForDocument(e: vscode.TextDocument) { - if (e.uri.scheme !== 'file') { - return; - } + function updateNodeForDocument(e: vscode.TextDocument) { + if (e.uri.scheme !== 'file') { + return; + } - if (!e.uri.path.endsWith('.md')) { - return; - } + if (!e.uri.path.endsWith('.md')) { + return; + } - const { file, data } = getOrCreateFile(ctrl, e.uri); - data.updateFromContents(ctrl, e.getText(), file); - } + const { file, data } = getOrCreateFile(ctrl, e.uri); + data.updateFromContents(ctrl, e.getText(), file); + } - for (const document of vscode.workspace.textDocuments) { - updateNodeForDocument(document); - } + for (const document of vscode.workspace.textDocuments) { + updateNodeForDocument(document); + } - context.subscriptions.push( - vscode.workspace.onDidOpenTextDocument(updateNodeForDocument), - vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document)), - ); + context.subscriptions.push( + vscode.workspace.onDidOpenTextDocument(updateNodeForDocument), + vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document)), + ); } function getOrCreateFile(controller: vscode.TestController, uri: vscode.Uri) { - const existing = controller.items.get(uri.toString()); - if (existing) { - return { file: existing, data: testData.get(existing) as TestFile }; - } + const existing = controller.items.get(uri.toString()); + if (existing) { + return { file: existing, data: testData.get(existing) as TestFile }; + } - const file = controller.createTestItem(uri.toString(), uri.path.split('/').pop()!, uri); - controller.items.add(file); + const file = controller.createTestItem(uri.toString(), uri.path.split('/').pop()!, uri); + controller.items.add(file); - const data = new TestFile(); - testData.set(file, data); + const data = new TestFile(); + testData.set(file, data); - file.canResolveChildren = true; - return { file, data }; + file.canResolveChildren = true; + return { file, data }; } function gatherTestItems(collection: vscode.TestItemCollection) { - const items: vscode.TestItem[] = []; - collection.forEach(item => items.push(item)); - return items; + const items: vscode.TestItem[] = []; + collection.forEach(item => items.push(item)); + return items; } function getWorkspaceTestPatterns() { - if (!vscode.workspace.workspaceFolders) { - return []; - } + if (!vscode.workspace.workspaceFolders) { + return []; + } - return vscode.workspace.workspaceFolders.map(workspaceFolder => ({ - workspaceFolder, - pattern: new vscode.RelativePattern(workspaceFolder, '**/*.md'), - })); + return vscode.workspace.workspaceFolders.map(workspaceFolder => ({ + workspaceFolder, + pattern: new vscode.RelativePattern(workspaceFolder, '**/*.md'), + })); } async function findInitialFiles(controller: vscode.TestController, pattern: vscode.GlobPattern) { - for (const file of await vscode.workspace.findFiles(pattern)) { - getOrCreateFile(controller, file); - } + for (const file of await vscode.workspace.findFiles(pattern)) { + getOrCreateFile(controller, file); + } } function startWatchingWorkspace(controller: vscode.TestController) { - return getWorkspaceTestPatterns().map(({ workspaceFolder, pattern }) => { - const watcher = vscode.workspace.createFileSystemWatcher(pattern); + return getWorkspaceTestPatterns().map(({ workspaceFolder, pattern }) => { + const watcher = vscode.workspace.createFileSystemWatcher(pattern); - watcher.onDidCreate(uri => getOrCreateFile(controller, uri)); - watcher.onDidChange(uri => { - const { file, data } = getOrCreateFile(controller, uri); - if (data.didResolve) { - data.updateFromDisk(controller, file); - } - }); - watcher.onDidDelete(uri => controller.items.delete(uri.toString())); + watcher.onDidCreate(uri => getOrCreateFile(controller, uri)); + watcher.onDidChange(uri => { + const { file, data } = getOrCreateFile(controller, uri); + if (data.didResolve) { + data.updateFromDisk(controller, file); + } + }); + watcher.onDidDelete(uri => controller.items.delete(uri.toString())); - findInitialFiles(controller, pattern); + findInitialFiles(controller, pattern); - return watcher; - }); + return watcher; + }); } diff --git a/test-provider-sample/src/parser.ts b/test-provider-sample/src/parser.ts index e2f486e1..c25b7820 100644 --- a/test-provider-sample/src/parser.ts +++ b/test-provider-sample/src/parser.ts @@ -4,26 +4,26 @@ const testRe = /^([0-9]+)\s*([+*/-])\s*([0-9]+)\s*=\s*([0-9]+)/; const headingRe = /^(#+)\s*(.+)$/; export const parseMarkdown = (text: string, events: { - onTest(range: vscode.Range, a: number, operator: string, b: number, expected: number): void; - onHeading(range: vscode.Range, name: string, depth: number): void; + onTest(range: vscode.Range, a: number, operator: string, b: number, expected: number): void; + onHeading(range: vscode.Range, name: string, depth: number): void; }) => { - const lines = text.split('\n'); + const lines = text.split('\n'); - for (let lineNo = 0; lineNo < lines.length; lineNo++) { - const line = lines[lineNo]; - const test = testRe.exec(line); - if (test) { - const [, a, operator, b, expected] = test; - const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, test[0].length)); - events.onTest(range, Number(a), operator, Number(b), Number(expected)); - continue; - } + for (let lineNo = 0; lineNo < lines.length; lineNo++) { + const line = lines[lineNo]; + const test = testRe.exec(line); + if (test) { + const [, a, operator, b, expected] = test; + const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, test[0].length)); + events.onTest(range, Number(a), operator, Number(b), Number(expected)); + continue; + } - const heading = headingRe.exec(line); - if (heading) { - const [, pounds, name] = heading; - const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, line.length)); - events.onHeading(range, name, pounds.length); - } - } + const heading = headingRe.exec(line); + if (heading) { + const [, pounds, name] = heading; + const range = new vscode.Range(new vscode.Position(lineNo, 0), new vscode.Position(lineNo, line.length)); + events.onHeading(range, name, pounds.length); + } + } }; diff --git a/test-provider-sample/src/testTree.ts b/test-provider-sample/src/testTree.ts index 2f33d33f..bd99d637 100644 --- a/test-provider-sample/src/testTree.ts +++ b/test-provider-sample/src/testTree.ts @@ -11,118 +11,118 @@ export const testData = new WeakMap(); let generationCounter = 0; export const getContentFromFilesystem = async (uri: vscode.Uri) => { - try { - const rawContent = await vscode.workspace.fs.readFile(uri); - return textDecoder.decode(rawContent); - } catch (e) { - console.warn(`Error providing tests for ${uri.fsPath}`, e); - return ''; - } + try { + const rawContent = await vscode.workspace.fs.readFile(uri); + return textDecoder.decode(rawContent); + } catch (e) { + console.warn(`Error providing tests for ${uri.fsPath}`, e); + return ''; + } }; export class TestFile { - public didResolve = false; + public didResolve = false; - public async updateFromDisk(controller: vscode.TestController, item: vscode.TestItem) { - try { - const content = await getContentFromFilesystem(item.uri!); - item.error = undefined; - this.updateFromContents(controller, content, item); - } catch (e) { - item.error = (e as Error).stack; - } - } + public async updateFromDisk(controller: vscode.TestController, item: vscode.TestItem) { + try { + const content = await getContentFromFilesystem(item.uri!); + item.error = undefined; + this.updateFromContents(controller, content, item); + } catch (e) { + item.error = (e as Error).stack; + } + } - /** - * Parses the tests from the input text, and updates the tests contained - * by this file to be those from the text, - */ - public updateFromContents(controller: vscode.TestController, content: string, item: vscode.TestItem) { - const ancestors = [{ item, children: [] as vscode.TestItem[] }]; - const thisGeneration = generationCounter++; - this.didResolve = true; + /** + * Parses the tests from the input text, and updates the tests contained + * by this file to be those from the text, + */ + public updateFromContents(controller: vscode.TestController, content: string, item: vscode.TestItem) { + const ancestors = [{ item, children: [] as vscode.TestItem[] }]; + const thisGeneration = generationCounter++; + this.didResolve = true; - const ascend = (depth: number) => { - while (ancestors.length > depth) { - const finished = ancestors.pop()!; - finished.item.children.replace(finished.children); - } - }; + const ascend = (depth: number) => { + while (ancestors.length > depth) { + const finished = ancestors.pop()!; + finished.item.children.replace(finished.children); + } + }; - parseMarkdown(content, { - onTest: (range, a, operator, b, expected) => { - const parent = ancestors[ancestors.length - 1]; - const data = new TestCase(a, operator as Operator, b, expected, thisGeneration); - const id = `${item.uri}/${data.getLabel()}`; + parseMarkdown(content, { + onTest: (range, a, operator, b, expected) => { + const parent = ancestors[ancestors.length - 1]; + const data = new TestCase(a, operator as Operator, b, expected, thisGeneration); + const id = `${item.uri}/${data.getLabel()}`; - const tcase = controller.createTestItem(id, data.getLabel(), item.uri); - testData.set(tcase, data); - tcase.range = range; - parent.children.push(tcase); - }, + const tcase = controller.createTestItem(id, data.getLabel(), item.uri); + testData.set(tcase, data); + tcase.range = range; + parent.children.push(tcase); + }, - onHeading: (range, name, depth) => { - ascend(depth); - const parent = ancestors[ancestors.length - 1]; - const id = `${item.uri}/${name}`; + onHeading: (range, name, depth) => { + ascend(depth); + const parent = ancestors[ancestors.length - 1]; + const id = `${item.uri}/${name}`; - const thead = controller.createTestItem(id, name, item.uri); - thead.range = range; - testData.set(thead, new TestHeading(thisGeneration)); - parent.children.push(thead); - ancestors.push({ item: thead, children: [] }); - }, - }); + const thead = controller.createTestItem(id, name, item.uri); + thead.range = range; + testData.set(thead, new TestHeading(thisGeneration)); + parent.children.push(thead); + ancestors.push({ item: thead, children: [] }); + }, + }); - ascend(0); // finish and assign children for all remaining items - } + ascend(0); // finish and assign children for all remaining items + } } export class TestHeading { - constructor(public generation: number) { } + constructor(public generation: number) { } } type Operator = '+' | '-' | '*' | '/'; export class TestCase { - constructor( - private readonly a: number, - private readonly operator: Operator, - private readonly b: number, - private readonly expected: number, - public generation: number - ) { } + constructor( + private readonly a: number, + private readonly operator: Operator, + private readonly b: number, + private readonly expected: number, + public generation: number + ) { } - getLabel() { - return `${this.a} ${this.operator} ${this.b} = ${this.expected}`; - } + getLabel() { + return `${this.a} ${this.operator} ${this.b} = ${this.expected}`; + } - async run(item: vscode.TestItem, options: vscode.TestRun): Promise { - const start = Date.now(); - await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000)); - const actual = this.evaluate(); - const duration = Date.now() - start; + async run(item: vscode.TestItem, options: vscode.TestRun): Promise { + const start = Date.now(); + await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000)); + const actual = this.evaluate(); + const duration = Date.now() - start; - if (actual === this.expected) { - options.passed(item, duration); - } else { - const message = vscode.TestMessage.diff(`Expected ${item.label}`, String(this.expected), String(actual)); - message.location = new vscode.Location(item.uri!, item.range!); - options.failed(item, message, duration); - } - } + if (actual === this.expected) { + options.passed(item, duration); + } else { + const message = vscode.TestMessage.diff(`Expected ${item.label}`, String(this.expected), String(actual)); + message.location = new vscode.Location(item.uri!, item.range!); + options.failed(item, message, duration); + } + } - private evaluate() { - switch (this.operator) { - case '-': - return this.a - this.b; - case '+': - return this.a + this.b; - case '/': - return Math.floor(this.a / this.b); - case '*': - return this.a * this.b; - } - } + private evaluate() { + switch (this.operator) { + case '-': + return this.a - this.b; + case '+': + return this.a + this.b; + case '/': + return Math.floor(this.a / this.b); + case '*': + return this.a * this.b; + } + } }