mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-04-27 16:55:44 +08:00
test-provider-sample: update api
This commit is contained in:
@ -5,6 +5,7 @@
|
||||
"version": "0.0.1",
|
||||
"publisher": "vscode-samples",
|
||||
"repository": "https://github.com/Microsoft/vscode-extension-samples",
|
||||
"enableProposedApi": true,
|
||||
"engines": {
|
||||
"vscode": "^1.51.0"
|
||||
},
|
||||
|
||||
@ -1,13 +1,125 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { MathTestController } from './testController';
|
||||
import { MarkdownTestData, TestCase, TestFile } from './testTree';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.test.registerTestController(new MathTestController()),
|
||||
const ctrl = vscode.test.createTestController<MarkdownTestData>('mathTestController');
|
||||
context.subscriptions.push(ctrl);
|
||||
|
||||
vscode.commands.registerCommand('test-provider-sample.runTests', async tests => {
|
||||
await vscode.test.runTests({ tests: tests instanceof Array ? tests : [tests], debug: false });
|
||||
vscode.window.showInformationMessage('Test run complete');
|
||||
}),
|
||||
// All VS Code tests are in a tree, starting at the automatically created "root".
|
||||
// We'll give it a label, and set its status so that VS Code will call
|
||||
// `resolveChildrenHandler` when the test explorer is opened.
|
||||
ctrl.root.label = 'Markdown Math';
|
||||
ctrl.root.status = vscode.TestItemStatus.Pending;
|
||||
|
||||
ctrl.runHandler = (request: vscode.TestRunRequest<MarkdownTestData>, cancellation: vscode.CancellationToken) => {
|
||||
const queue: vscode.TestItem<TestCase>[] = [];
|
||||
const run = ctrl.createTestRun(request);
|
||||
const discoverTests = async (tests: Iterable<vscode.TestItem<MarkdownTestData>>) => {
|
||||
for (const test of tests) {
|
||||
if (request.exclude?.includes(test)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (test.data instanceof TestCase) {
|
||||
run.setState(test, vscode.TestResultState.Queued);
|
||||
queue.push(test as vscode.TestItem<TestCase>);
|
||||
} else {
|
||||
if (test.data instanceof TestFile && test.status === vscode.TestItemStatus.Pending) {
|
||||
await test.data.updateFromDisk(ctrl, test);
|
||||
}
|
||||
|
||||
await discoverTests(test.children.values());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const runTestQueue = async () => {
|
||||
for (const test of queue) {
|
||||
run.appendOutput(`Running ${test.id}\r\n`);
|
||||
if (cancellation.isCancellationRequested) {
|
||||
run.setState(test, vscode.TestResultState.Skipped);
|
||||
} else {
|
||||
run.setState(test, vscode.TestResultState.Running);
|
||||
await test.data.run(test, run);
|
||||
}
|
||||
run.appendOutput(`Completed ${test.id}\r\n`);
|
||||
}
|
||||
|
||||
run.end();
|
||||
};
|
||||
|
||||
discoverTests(request.tests).then(runTestQueue);
|
||||
};
|
||||
|
||||
ctrl.resolveChildrenHandler = (item, token) => {
|
||||
if (item === ctrl.root) {
|
||||
startWatchingWorkspace(ctrl, token);
|
||||
} else if (item.data instanceof TestFile) {
|
||||
item.data.updateFromDisk(ctrl, item);
|
||||
}
|
||||
};
|
||||
|
||||
function updateNodeForDocument(e: vscode.TextDocument) {
|
||||
if (!e.uri.path.endsWith('.md')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = getOrCreateFile(ctrl, e.uri);
|
||||
node.data.updateFromContents(ctrl, e.getText(), node);
|
||||
}
|
||||
|
||||
for (const document of vscode.workspace.textDocuments) {
|
||||
updateNodeForDocument(document);
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidOpenTextDocument(updateNodeForDocument),
|
||||
vscode.workspace.onDidChangeTextDocument(e => updateNodeForDocument(e.document))
|
||||
);
|
||||
}
|
||||
|
||||
function getOrCreateFile(controller: vscode.TestController, uri: vscode.Uri): vscode.TestItem<TestFile> {
|
||||
const existing = controller.root.children.get(uri.toString());
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const file = controller.createTestItem(
|
||||
uri.toString(),
|
||||
uri.path.split('/').pop()!,
|
||||
controller.root,
|
||||
uri,
|
||||
new TestFile()
|
||||
);
|
||||
|
||||
file.status = vscode.TestItemStatus.Pending;
|
||||
return file;
|
||||
}
|
||||
|
||||
function startWatchingWorkspace(controller: vscode.TestController, token: vscode.CancellationToken) {
|
||||
if (!vscode.workspace.workspaceFolders) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const workspaceFolder of vscode.workspace.workspaceFolders) {
|
||||
const pattern = new vscode.RelativePattern(workspaceFolder, '**/*.md');
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
const contentChange = new vscode.EventEmitter<vscode.Uri>();
|
||||
|
||||
watcher.onDidCreate(uri => getOrCreateFile(controller, uri));
|
||||
watcher.onDidChange(uri => contentChange.fire(uri));
|
||||
watcher.onDidDelete(uri => controller.root.children.get(uri.toString())?.dispose());
|
||||
token.onCancellationRequested(() => {
|
||||
controller.root.status = vscode.TestItemStatus.Pending;
|
||||
watcher.dispose();
|
||||
});
|
||||
|
||||
vscode.workspace.findFiles(pattern).then(files => {
|
||||
for (const file of files) {
|
||||
getOrCreateFile(controller, file);
|
||||
}
|
||||
|
||||
controller.root.status = vscode.TestItemStatus.Resolved;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,337 +0,0 @@
|
||||
import { TextDecoder } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import { parseMarkdown } from './parser';
|
||||
|
||||
const textDecoder = new TextDecoder('utf-8');
|
||||
|
||||
type MarkdownTestData = WorkspaceTestRoot | DocumentTestRoot | TestFile | TestHeading | TestCase;
|
||||
|
||||
export class MathTestController implements vscode.TestController<MarkdownTestData> {
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public createWorkspaceTestRoot(workspaceFolder: vscode.WorkspaceFolder) {
|
||||
return WorkspaceTestRoot.create(workspaceFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public createDocumentTestRoot(document: vscode.TextDocument) {
|
||||
if (!document.uri.path.endsWith('.md')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return DocumentTestRoot.create(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public runTests(request: vscode.TestRunRequest<MarkdownTestData>, cancellation: vscode.CancellationToken) {
|
||||
const run = vscode.test.createTestRun(request);
|
||||
const queue: vscode.TestItem<TestCase>[] = [];
|
||||
const discoverTests = async (tests: Iterable<vscode.TestItem<MarkdownTestData>>) => {
|
||||
for (const test of tests) {
|
||||
if (request.exclude?.includes(test)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (test.data instanceof TestCase) {
|
||||
run.setState(test, vscode.TestResultState.Queued);
|
||||
queue.push(test as vscode.TestItem<TestCase, any>);
|
||||
} else {
|
||||
if (test.data instanceof TestFile && test.children.size === 0) {
|
||||
await test.data.refresh();
|
||||
}
|
||||
|
||||
await discoverTests(test.children.values());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const runTestQueue = async () => {
|
||||
for (const test of queue) {
|
||||
run.appendOutput(`Running ${test.id}\r\n`);
|
||||
if (cancellation.isCancellationRequested) {
|
||||
run.setState(test, vscode.TestResultState.Skipped);
|
||||
} else {
|
||||
run.setState(test, vscode.TestResultState.Running);
|
||||
await test.data.run(run);
|
||||
}
|
||||
run.appendOutput(`Completed ${test.id}\r\n`);
|
||||
}
|
||||
|
||||
run.end();
|
||||
};
|
||||
|
||||
discoverTests(request.tests).then(runTestQueue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class WorkspaceTestRoot {
|
||||
public static create(workspaceFolder: vscode.WorkspaceFolder) {
|
||||
const item = vscode.test.createTestItem<WorkspaceTestRoot, TestFile>(
|
||||
{ id: `mdtests ${workspaceFolder.uri}`, label: 'Markdown Tests', uri: workspaceFolder.uri },
|
||||
new WorkspaceTestRoot(workspaceFolder)
|
||||
);
|
||||
|
||||
item.status = vscode.TestItemStatus.Pending;
|
||||
item.resolveHandler = token => {
|
||||
const pattern = new vscode.RelativePattern(workspaceFolder, '**/*.md');
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(pattern);
|
||||
const contentChange = new vscode.EventEmitter<vscode.Uri>();
|
||||
|
||||
watcher.onDidCreate(uri =>
|
||||
item.addChild(TestFile.create(uri, getContentFromFilesystem, contentChange.event))
|
||||
);
|
||||
watcher.onDidChange(uri => contentChange.fire(uri));
|
||||
watcher.onDidDelete(uri => item.children.get(uri.toString())?.dispose());
|
||||
token.onCancellationRequested(() => {
|
||||
item.status = vscode.TestItemStatus.Pending;
|
||||
watcher.dispose();
|
||||
});
|
||||
|
||||
vscode.workspace.findFiles(pattern).then(files => {
|
||||
for (const file of files) {
|
||||
item.addChild(TestFile.create(file, getContentFromFilesystem, contentChange.event));
|
||||
}
|
||||
|
||||
item.status = vscode.TestItemStatus.Resolved;
|
||||
});
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
constructor(public readonly workspaceFolder: vscode.WorkspaceFolder) { }
|
||||
}
|
||||
|
||||
class DocumentTestRoot {
|
||||
public static create(document: vscode.TextDocument) {
|
||||
const item = vscode.test.createTestItem<DocumentTestRoot, TestFile>(
|
||||
{ id: `mdtests ${document.uri}`, label: 'Markdown Tests', uri: document.uri },
|
||||
new DocumentTestRoot()
|
||||
);
|
||||
|
||||
item.status = vscode.TestItemStatus.Pending;
|
||||
item.resolveHandler = token => {
|
||||
const contentChange = new vscode.EventEmitter<vscode.Uri>();
|
||||
const changeListener = vscode.workspace.onDidChangeTextDocument(e => {
|
||||
contentChange.fire(e.document.uri);
|
||||
});
|
||||
|
||||
const file = TestFile.create(document.uri, () => Promise.resolve(document.getText()), contentChange.event);
|
||||
item.addChild(file);
|
||||
|
||||
token.onCancellationRequested(() => {
|
||||
changeListener.dispose();
|
||||
item.status = vscode.TestItemStatus.Pending;
|
||||
});
|
||||
|
||||
item.status = vscode.TestItemStatus.Resolved;
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
let generationCounter = 0;
|
||||
|
||||
type ContentGetter = (uri: vscode.Uri) => Promise<string>;
|
||||
|
||||
const getContentFromFilesystem: ContentGetter = async 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 '';
|
||||
}
|
||||
};
|
||||
|
||||
class TestFile {
|
||||
public static create(uri: vscode.Uri, getContent: ContentGetter, onContentChange: vscode.Event<vscode.Uri>) {
|
||||
const item = vscode.test.createTestItem<TestFile>({
|
||||
id: uri.toString(),
|
||||
label: uri.path.split('/').pop()!,
|
||||
uri,
|
||||
});
|
||||
|
||||
item.data = new TestFile(uri, getContent, item);
|
||||
item.status = vscode.TestItemStatus.Pending;
|
||||
item.resolveHandler = token => {
|
||||
const doRefresh = () => {
|
||||
item.data.refresh().then(() => {
|
||||
if (!token.isCancellationRequested) {
|
||||
item.status = vscode.TestItemStatus.Resolved;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const listener = onContentChange(uri => {
|
||||
if (uri.toString() === uri.toString()) {
|
||||
doRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
token.onCancellationRequested(() => {
|
||||
item.status = vscode.TestItemStatus.Pending;
|
||||
listener.dispose();
|
||||
});
|
||||
|
||||
doRefresh();
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly uri: vscode.Uri,
|
||||
private readonly getContent: ContentGetter,
|
||||
private readonly item: vscode.TestItem<TestFile>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the tests from the input text, and updates the tests contained
|
||||
* by this file to be those from the text,
|
||||
*/
|
||||
public async refresh() {
|
||||
const ancestors: (vscode.TestItem<TestFile> | vscode.TestItem<TestHeading>)[] = [this.item];
|
||||
const thisGeneration = generationCounter++;
|
||||
|
||||
parseMarkdown(await this.getContent(this.uri), {
|
||||
onTest: (range, a, operator, b, expected) => {
|
||||
const parent = ancestors[ancestors.length - 1];
|
||||
const tcase = TestCase.create(Number(a), operator as Operator, Number(b), Number(expected), range, thisGeneration, parent);
|
||||
|
||||
const existing = parent.children.get(tcase.id);
|
||||
if (existing) {
|
||||
existing.data.generation = thisGeneration;
|
||||
} else {
|
||||
parent.addChild(tcase);
|
||||
}
|
||||
},
|
||||
onHeading: (range, name, depth) => {
|
||||
while (ancestors.length > depth) {
|
||||
ancestors.pop();
|
||||
}
|
||||
|
||||
const parent = ancestors[ancestors.length - 1];
|
||||
const thead = TestHeading.create(name, range, thisGeneration, parent);
|
||||
const existing = parent.children.get(thead.id);
|
||||
if (existing && existing.data instanceof TestHeading) {
|
||||
ancestors.push(existing);
|
||||
existing.data.generation = thisGeneration;
|
||||
} else {
|
||||
existing?.dispose();
|
||||
parent.addChild(thead);
|
||||
ancestors.push(thead);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.prune(thisGeneration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes tests that were deleted from the source. Each test suite and case
|
||||
* has a 'generation' counter which is updated each time we discover it. This
|
||||
* is called after discovery is finished to remove any children who are no
|
||||
* longer in this generation.
|
||||
*/
|
||||
private prune(thisGeneration: number) {
|
||||
const queue: (vscode.TestItem<TestHeading | TestFile, TestHeading | TestCase>)[] = [this.item];
|
||||
for (const parent of queue) {
|
||||
for (const child of parent.children.values()) {
|
||||
if (child.data.generation < thisGeneration) {
|
||||
child.dispose();
|
||||
} else if (child.data instanceof TestHeading) {
|
||||
queue.push(child as vscode.TestItem<TestHeading>);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestHeading {
|
||||
public static create(label: string, range: vscode.Range, generation: number, parent: vscode.TestItem<TestFile | TestHeading>) {
|
||||
const item = vscode.test.createTestItem<TestHeading, TestHeading | TestCase>({
|
||||
id: `mktests/${parent.uri!.toString()}/${label}`,
|
||||
label,
|
||||
uri: parent.uri,
|
||||
}, new TestHeading(generation));
|
||||
|
||||
item.range = range;
|
||||
return item;
|
||||
}
|
||||
|
||||
protected constructor(public generation: number) { }
|
||||
}
|
||||
|
||||
type Operator = '+' | '-' | '*' | '/';
|
||||
|
||||
class TestCase {
|
||||
public static create(
|
||||
a: number,
|
||||
operator: Operator,
|
||||
b: number,
|
||||
expected: number,
|
||||
range: vscode.Range,
|
||||
generation: number,
|
||||
parent: vscode.TestItem<TestHeading | TestFile>,
|
||||
) {
|
||||
const label = `${a} ${operator} ${b} = ${expected}`;
|
||||
const item = vscode.test.createTestItem<TestCase, never>({
|
||||
id: `mktests/${parent.uri!.toString()}/${label}`,
|
||||
label,
|
||||
uri: parent.uri,
|
||||
});
|
||||
|
||||
item.data = new TestCase(a, operator, b, expected, item, generation);
|
||||
item.range = range;
|
||||
return item;
|
||||
}
|
||||
|
||||
protected constructor(
|
||||
private readonly a: number,
|
||||
private readonly operator: Operator,
|
||||
private readonly b: number,
|
||||
private readonly expected: number,
|
||||
private readonly item: vscode.TestItem<TestCase>,
|
||||
public generation: number,
|
||||
) { }
|
||||
|
||||
async run(options: vscode.TestRun<MarkdownTestData>): Promise<void> {
|
||||
const start = Date.now();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000));
|
||||
const actual = this.evaluate();
|
||||
const duration = Date.now() - start;
|
||||
|
||||
console.log('run', this.item.label);
|
||||
|
||||
if (actual === this.expected) {
|
||||
options.setState(this.item, vscode.TestResultState.Passed, duration);
|
||||
} else {
|
||||
const message = vscode.TestMessage.diff(`Expected ${this.item.label}`, String(this.expected), String(actual));
|
||||
message.location = new vscode.Location(this.item.uri!, this.item.range!);
|
||||
options.appendMessage(this.item, message);
|
||||
options.setState(this.item, vscode.TestResultState.Failed, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
149
test-provider-sample/src/testTree.ts
Normal file
149
test-provider-sample/src/testTree.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { TextDecoder } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import { parseMarkdown } from './parser';
|
||||
|
||||
const textDecoder = new TextDecoder('utf-8');
|
||||
|
||||
export type MarkdownTestData = TestFile | TestHeading | TestCase;
|
||||
|
||||
let generationCounter = 0;
|
||||
|
||||
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 '';
|
||||
}
|
||||
};
|
||||
|
||||
export class TestFile {
|
||||
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.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: (vscode.TestItem<TestFile> | vscode.TestItem<TestHeading>)[] = [item];
|
||||
const thisGeneration = generationCounter++;
|
||||
|
||||
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 existing = parent.children.get(id);
|
||||
if (existing) {
|
||||
existing.data.generation = thisGeneration;
|
||||
existing.range = range;
|
||||
} else {
|
||||
const tcase = controller.createTestItem(id, data.getLabel(), parent, item.uri, data);
|
||||
tcase.range = range;
|
||||
}
|
||||
},
|
||||
|
||||
onHeading: (range, name, depth) => {
|
||||
while (ancestors.length > depth) {
|
||||
ancestors.pop();
|
||||
}
|
||||
|
||||
const parent = ancestors[ancestors.length - 1];
|
||||
const id = `${item.uri}/${name}`;
|
||||
const existing = parent.children.get(id);
|
||||
|
||||
if (existing && existing.data instanceof TestHeading) {
|
||||
ancestors.push(existing);
|
||||
existing.data.generation = thisGeneration;
|
||||
existing.range = range;
|
||||
} else {
|
||||
existing?.dispose();
|
||||
const thead = controller.createTestItem(id, name, parent, item.uri, new TestHeading(thisGeneration));
|
||||
thead.range = range;
|
||||
ancestors.push(thead);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.prune(item, thisGeneration);
|
||||
item.status = vscode.TestItemStatus.Resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes tests that were deleted from the source. Each test suite and case
|
||||
* has a 'generation' counter which is updated each time we discover it. This
|
||||
* is called after discovery is finished to remove any children who are no
|
||||
* longer in this generation.
|
||||
*/
|
||||
private prune(item: vscode.TestItem, thisGeneration: number) {
|
||||
const queue: vscode.TestItem<TestHeading | TestCase | TestFile>[] = [item];
|
||||
for (const parent of queue) {
|
||||
for (const child of parent.children.values()) {
|
||||
if (child.data.generation < thisGeneration) {
|
||||
child.dispose();
|
||||
} else if (child.data instanceof TestHeading) {
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TestHeading {
|
||||
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
|
||||
) {}
|
||||
|
||||
getLabel() {
|
||||
return `${this.a} ${this.operator} ${this.b} = ${this.expected}`;
|
||||
}
|
||||
|
||||
async run(item: vscode.TestItem, options: vscode.TestRun<MarkdownTestData>): Promise<void> {
|
||||
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.setState(item, vscode.TestResultState.Passed, 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.appendMessage(item, message);
|
||||
options.setState(item, vscode.TestResultState.Failed, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user