mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-31 21:47:15 +03:00
213 lines
7.6 KiB
TypeScript
213 lines
7.6 KiB
TypeScript
import * as vscode from "vscode";
|
|
import type * as lc from "vscode-languageclient/node";
|
|
import * as ra from "./lsp_ext";
|
|
|
|
import type { Ctx } from "./ctx";
|
|
import { startDebugSession } from "./debug";
|
|
|
|
export const prepareTestExplorer = (
|
|
ctx: Ctx,
|
|
testController: vscode.TestController,
|
|
client: lc.LanguageClient,
|
|
) => {
|
|
let currentTestRun: vscode.TestRun | undefined;
|
|
let idToTestMap: Map<string, vscode.TestItem> = new Map();
|
|
const fileToTestMap: Map<string, vscode.TestItem[]> = new Map();
|
|
const idToRunnableMap: Map<string, ra.Runnable> = new Map();
|
|
|
|
testController.createRunProfile(
|
|
"Run Tests",
|
|
vscode.TestRunProfileKind.Run,
|
|
async (request: vscode.TestRunRequest, cancelToken: vscode.CancellationToken) => {
|
|
if (currentTestRun) {
|
|
await client.sendNotification(ra.abortRunTest);
|
|
while (currentTestRun) {
|
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
}
|
|
}
|
|
|
|
currentTestRun = testController.createTestRun(request);
|
|
cancelToken.onCancellationRequested(async () => {
|
|
await client.sendNotification(ra.abortRunTest);
|
|
});
|
|
const include = request.include?.map((x) => x.id);
|
|
const exclude = request.exclude?.map((x) => x.id);
|
|
await client.sendRequest(ra.runTest, { include, exclude });
|
|
},
|
|
true,
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
testController.createRunProfile(
|
|
"Debug Tests",
|
|
vscode.TestRunProfileKind.Debug,
|
|
async (request: vscode.TestRunRequest) => {
|
|
if (request.include?.length !== 1 || request.exclude?.length !== 0) {
|
|
await vscode.window.showErrorMessage("You can debug only one test at a time");
|
|
return;
|
|
}
|
|
const id = request.include[0]!.id;
|
|
const runnable = idToRunnableMap.get(id);
|
|
if (!runnable) {
|
|
await vscode.window.showErrorMessage("You can debug only one test at a time");
|
|
return;
|
|
}
|
|
await startDebugSession(ctx, runnable);
|
|
},
|
|
true,
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
const deleteTest = (item: vscode.TestItem, parentList: vscode.TestItemCollection) => {
|
|
parentList.delete(item.id);
|
|
idToTestMap.delete(item.id);
|
|
idToRunnableMap.delete(item.id);
|
|
if (item.uri) {
|
|
fileToTestMap.set(
|
|
item.uri.toString(),
|
|
fileToTestMap.get(item.uri.toString())!.filter((t) => t.id !== item.id),
|
|
);
|
|
}
|
|
};
|
|
|
|
const addTest = (item: ra.TestItem) => {
|
|
const parentList = item.parent
|
|
? idToTestMap.get(item.parent)!.children
|
|
: testController.items;
|
|
const oldTest = parentList.get(item.id);
|
|
const uri = item.textDocument?.uri ? vscode.Uri.parse(item.textDocument?.uri) : undefined;
|
|
const range =
|
|
item.range &&
|
|
new vscode.Range(
|
|
new vscode.Position(item.range.start.line, item.range.start.character),
|
|
new vscode.Position(item.range.end.line, item.range.end.character),
|
|
);
|
|
if (oldTest) {
|
|
if (oldTest.uri?.toString() === uri?.toString()) {
|
|
oldTest.range = range;
|
|
return;
|
|
}
|
|
deleteTest(oldTest, parentList);
|
|
}
|
|
const iconToVscodeMap = {
|
|
package: "package",
|
|
module: "symbol-module",
|
|
test: "beaker",
|
|
};
|
|
const test = testController.createTestItem(
|
|
item.id,
|
|
`$(${iconToVscodeMap[item.kind]}) ${item.label}`,
|
|
uri,
|
|
);
|
|
test.range = range;
|
|
test.canResolveChildren = item.canResolveChildren;
|
|
idToTestMap.set(item.id, test);
|
|
if (uri) {
|
|
if (!fileToTestMap.has(uri.toString())) {
|
|
fileToTestMap.set(uri.toString(), []);
|
|
}
|
|
fileToTestMap.get(uri.toString())!.push(test);
|
|
}
|
|
if (item.runnable) {
|
|
idToRunnableMap.set(item.id, item.runnable);
|
|
}
|
|
parentList.add(test);
|
|
};
|
|
|
|
const addTestGroup = (testsAndScope: ra.DiscoverTestResults) => {
|
|
const { tests, scope, scopeFile } = testsAndScope;
|
|
const testSet: Set<string> = new Set();
|
|
for (const test of tests) {
|
|
addTest(test);
|
|
testSet.add(test.id);
|
|
}
|
|
// FIXME(hack_recover_crate_name): We eagerly resolve every test if we got a lazy top level response (detected
|
|
// by checking that `scope` is empty). This is not a good thing and wastes cpu and memory unnecessarily, so we
|
|
// should remove it.
|
|
if (!scope && !scopeFile) {
|
|
for (const test of tests) {
|
|
void testController.resolveHandler!(idToTestMap.get(test.id));
|
|
}
|
|
}
|
|
if (scope) {
|
|
const recursivelyRemove = (tests: vscode.TestItemCollection) => {
|
|
for (const [_, test] of tests) {
|
|
if (!testSet.has(test.id)) {
|
|
deleteTest(test, tests);
|
|
} else {
|
|
recursivelyRemove(test.children);
|
|
}
|
|
}
|
|
};
|
|
for (const root of scope) {
|
|
recursivelyRemove(idToTestMap.get(root)!.children);
|
|
}
|
|
}
|
|
if (scopeFile) {
|
|
const removeByFile = (file: vscode.Uri) => {
|
|
const testsToBeRemoved = (fileToTestMap.get(file.toString()) || []).filter(
|
|
(t) => !testSet.has(t.id),
|
|
);
|
|
for (const test of testsToBeRemoved) {
|
|
const parentList = test.parent?.children || testController.items;
|
|
deleteTest(test, parentList);
|
|
}
|
|
};
|
|
for (const file of scopeFile) {
|
|
removeByFile(vscode.Uri.parse(file.uri));
|
|
}
|
|
}
|
|
};
|
|
|
|
ctx.pushClientCleanup(
|
|
client.onNotification(ra.discoveredTests, (results) => {
|
|
addTestGroup(results);
|
|
}),
|
|
);
|
|
|
|
ctx.pushClientCleanup(
|
|
client.onNotification(ra.endRunTest, () => {
|
|
currentTestRun!.end();
|
|
currentTestRun = undefined;
|
|
}),
|
|
);
|
|
|
|
ctx.pushClientCleanup(
|
|
client.onNotification(ra.appendOutputToRunTest, (output) => {
|
|
currentTestRun!.appendOutput(`${output}\r\n`);
|
|
}),
|
|
);
|
|
|
|
ctx.pushClientCleanup(
|
|
client.onNotification(ra.changeTestState, (results) => {
|
|
const test = idToTestMap.get(results.testId)!;
|
|
if (results.state.tag === "failed") {
|
|
currentTestRun!.failed(test, new vscode.TestMessage(results.state.message));
|
|
} else if (results.state.tag === "passed") {
|
|
currentTestRun!.passed(test);
|
|
} else if (results.state.tag === "started") {
|
|
currentTestRun!.started(test);
|
|
} else if (results.state.tag === "skipped") {
|
|
currentTestRun!.skipped(test);
|
|
} else if (results.state.tag === "enqueued") {
|
|
currentTestRun!.enqueued(test);
|
|
}
|
|
}),
|
|
);
|
|
|
|
testController.resolveHandler = async (item) => {
|
|
const results = await client.sendRequest(ra.discoverTest, { testId: item?.id });
|
|
addTestGroup(results);
|
|
};
|
|
|
|
testController.refreshHandler = async () => {
|
|
testController.items.forEach((t) => {
|
|
testController.items.delete(t.id);
|
|
});
|
|
idToTestMap = new Map();
|
|
await testController.resolveHandler!(undefined);
|
|
};
|
|
};
|