Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'editors/code/src/test_explorer.ts')
-rw-r--r--editors/code/src/test_explorer.ts173
1 files changed, 173 insertions, 0 deletions
diff --git a/editors/code/src/test_explorer.ts b/editors/code/src/test_explorer.ts
new file mode 100644
index 0000000000..2f0b4d5b5c
--- /dev/null
+++ b/editors/code/src/test_explorer.ts
@@ -0,0 +1,173 @@
+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 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 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;
+ }
+ parentList.delete(item.id);
+ }
+ 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 (item.runnable) {
+ idToRunnableMap.set(item.id, item.runnable);
+ }
+ parentList.add(test);
+ };
+
+ const addTestGroup = (testsAndScope: ra.DiscoverTestResults) => {
+ const { tests, scope } = 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 `!scope`). ctx is not a good thing and wastes cpu and memory unnecessarily, so we should remove it.
+ if (!scope) {
+ for (const test of tests) {
+ void testController.resolveHandler!(idToTestMap.get(test.id));
+ }
+ }
+ if (!scope) {
+ return;
+ }
+ const recursivelyRemove = (tests: vscode.TestItemCollection) => {
+ for (const [testId, _] of tests) {
+ if (!testSet.has(testId)) {
+ tests.delete(testId);
+ } else {
+ recursivelyRemove(tests.get(testId)!.children);
+ }
+ }
+ };
+ for (const root of scope) {
+ recursivelyRemove(idToTestMap.get(root)!.children);
+ }
+ };
+
+ ctx.pushClientCleanup(
+ client.onNotification(ra.discoveredTests, (results) => {
+ addTestGroup(results);
+ }),
+ );
+
+ ctx.pushClientCleanup(
+ client.onNotification(ra.endRunTest, () => {
+ currentTestRun!.end();
+ currentTestRun = undefined;
+ }),
+ );
+
+ 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);
+ };
+};