Skip to content

Commit a30d41c

Browse files
committed
Respect hidden tests; support testing a single method; support cancel
1 parent 42c870a commit a30d41c

3 files changed

Lines changed: 72 additions & 21 deletions

File tree

src/commonRunTestsHandler.ts

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ export async function commonRunTestsHandler(controller: vscode.TestController, r
1111
// For each authority (i.e. server:namespace) accumulate a map of the class-level Test nodes in the tree.
1212
// We don't yet support running only some TestXXX methods in a testclass
1313
const mapAuthorities = new Map<string, Map<string, vscode.TestItem>>();
14+
const runIndices: number[] =[];
1415
const queue: vscode.TestItem[] = [];
1516

1617
// Loop through all included tests, or all known tests, and add them to our queue
1718
if (request.include) {
1819
request.include.forEach(test => queue.push(test));
1920
} else {
21+
// Run was launched from controller's root level
2022
controller.items.forEach(test => queue.push(test));
2123
}
2224

@@ -25,7 +27,7 @@ export async function commonRunTestsHandler(controller: vscode.TestController, r
2527
const test = queue.pop()!;
2628

2729
// Skip tests the user asked to exclude
28-
if (request.exclude?.includes(test)) {
30+
if (request.exclude && request.exclude.filter((excludedTest) => excludedTest.id === test.id).length > 0) {
2931
continue;
3032
}
3133

@@ -58,15 +60,26 @@ export async function commonRunTestsHandler(controller: vscode.TestController, r
5860
test.children.forEach(test => queue.push(test));
5961
}
6062

63+
// Cancelled while building our structures?
64+
if (cancellation.isCancellationRequested) {
65+
return;
66+
}
67+
6168
if (mapAuthorities.size === 0) {
6269
// Nothing included
6370
vscode.window.showWarningMessage(`Empty test run`);
6471
return;
6572
}
6673

67-
if (cancellation.isCancellationRequested) {
68-
// TODO what?
69-
}
74+
// Stop debugging sessions we started
75+
cancellation.onCancellationRequested(() => {
76+
runIndices.forEach((runIndex) => {
77+
const session = allTestRuns[runIndex]?.debugSession;
78+
if (session) {
79+
vscode.debug.stopDebugging(session);
80+
}
81+
});
82+
});
7083

7184
for await (const mapInstance of mapAuthorities) {
7285

@@ -135,20 +148,43 @@ export async function commonRunTestsHandler(controller: vscode.TestController, r
135148
const runQualifiers = controller.id === `${extensionId}-Local` ? "" : "/noload/nodelete";
136149
// Run tests through the debugger but only stop at breakpoints etc if user chose "Debug Test" instead of "Run Test"
137150
const runIndex = allTestRuns.push(run) - 1;
138-
const configuration: vscode.DebugConfiguration = {
151+
runIndices.push(runIndex);
152+
153+
// Compute the testspec argument for %UnitTest.Manager.RunTest() call.
154+
// Typically it is a testsuite, the subfolder where we copied all the testclasses,
155+
// but if only a single method of a single class is being tested we will also specify testcase and testmethod.
156+
let testSpec = serverSpec.username;
157+
if (request.include?.length === 1) {
158+
const idParts = request.include[0].id.split(":");
159+
if (idParts.length === 4) {
160+
testSpec = `${serverSpec.username}:${idParts[2]}:${idParts[3]}`;
161+
}
162+
}
163+
164+
const configuration = {
139165
"type": "objectscript",
140166
"request": "launch",
141167
"name": `${controller.id.split("-").pop()}Tests:${serverSpec.name}:${namespace}:${serverSpec.username}`,
142-
"program": `##class(%UnitTest.Manager).RunTest("${serverSpec.username}","${runQualifiers}")`,
143-
"testRunIndex": runIndex,
144-
"testIdBase": firstClassTestItem.id.split(":", 2).join(":")
168+
"program": `##class(%UnitTest.Manager).RunTest("${testSpec}","${runQualifiers}")`,
169+
170+
// Extra properties needed by our DebugAdapterTracker
171+
"testingRunIndex": runIndex,
172+
"testingIdBase": firstClassTestItem.id.split(":", 2).join(":")
145173
};
146174
const sessionOptions: vscode.DebugSessionOptions = {
147175
noDebug: request.profile?.kind !== vscode.TestRunProfileKind.Debug,
148176
suppressDebugToolbar: request.profile?.kind !== vscode.TestRunProfileKind.Debug
149177
};
150-
if (!await vscode.debug.startDebugging(folder, configuration, sessionOptions)) {
151-
await vscode.window.showErrorMessage(`Failed to launch testing`, { modal: true });
178+
179+
// ObjectScript debugger's initializeRequest handler needs to identify target server and namespace
180+
// and does this from current active document, so here we make sure there's a suitable one.
181+
vscode.commands.executeCommand("vscode.open", oneUri, { preserveFocus: true });
182+
183+
// Start the debugger unless cancelled
184+
if (cancellation.isCancellationRequested || !await vscode.debug.startDebugging(folder, configuration, sessionOptions)) {
185+
if (!cancellation.isCancellationRequested) {
186+
await vscode.window.showErrorMessage(`Failed to launch testing`, { modal: true });
187+
}
152188
run.end();
153189
allTestRuns[runIndex] = undefined;
154190
return;

src/debugTracker.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as vscode from 'vscode';
2-
import { allTestRuns, loadedTestController, localTestController } from './extension';
2+
import { allTestRuns, loadedTestController, localTestController, TestRun } from './extension';
33
import { refreshHistoryRootItem } from './historyExplorer';
44

55
export class DebugTracker implements vscode.DebugAdapterTracker {
@@ -8,8 +8,8 @@ export class DebugTracker implements vscode.DebugAdapterTracker {
88
private serverName: string;
99
private namespace: string;
1010
private testController: vscode.TestController
11-
private run?: vscode.TestRun;
12-
private testIdBase: string;
11+
private run?: TestRun;
12+
private testingIdBase: string;
1313
private className?: string;
1414
private testMethodName?: string;
1515
private testDuration?: number;
@@ -22,8 +22,11 @@ export class DebugTracker implements vscode.DebugAdapterTracker {
2222
let runType: string;
2323
[ runType, this.serverName, this.namespace ] = this.session.configuration.name.split(':');
2424
this.testController = runType === 'LoadedTests' ? loadedTestController : localTestController;
25-
this.run = allTestRuns[this.session.configuration.testRunIndex];
26-
this.testIdBase = this.session.configuration.testIdBase;
25+
this.run = allTestRuns[this.session.configuration.testingRunIndex];
26+
if (this.run) {
27+
this.run.debugSession = session;
28+
};
29+
this.testingIdBase = this.session.configuration.testingIdBase;
2730
this.methodTestMap = new Map<string, vscode.TestItem>();
2831

2932
const addToMethodTestMap = (testItem?: vscode.TestItem) => {
@@ -39,10 +42,10 @@ export class DebugTracker implements vscode.DebugAdapterTracker {
3942

4043
if (runType === 'LoadedTests') {
4144
// This tree is flat
42-
addToMethodTestMap(this.testController.items.get(this.testIdBase));
45+
addToMethodTestMap(this.testController.items.get(this.testingIdBase));
4346
} else {
4447
// This tree is nested
45-
addToMethodTestMap(this.testController.items.get(this.testIdBase + ':'));
48+
addToMethodTestMap(this.testController.items.get(this.testingIdBase + ':'));
4649
}
4750
}
4851

@@ -70,7 +73,7 @@ export class DebugTracker implements vscode.DebugAdapterTracker {
7073
const methodBegin = line.match(/^ Test([%\dA-Za-z][\dA-Za-z]*).* begins \.\.\.$/);
7174
if (methodBegin) {
7275
this.testMethodName = methodBegin[1];
73-
this.methodTest = this.methodTestMap.get(`${this.testIdBase}:${this.className}:Test${this.testMethodName}`);
76+
this.methodTest = this.methodTestMap.get(`${this.testingIdBase}:${this.className}:Test${this.testMethodName}`);
7477
this.failureMessages = [];
7578
if (this.methodTest) {
7679
this.run.started(this.methodTest)
@@ -143,7 +146,15 @@ export class DebugTracker implements vscode.DebugAdapterTracker {
143146
refreshHistoryRootItem(this.serverName, this.namespace);
144147
}
145148

146-
// Clear reference to run (not known if this is necessary)
147-
allTestRuns[this.session.configuration.testRunIndex] = undefined;
149+
// Clear run record (may not be necessary, but is harmless)
150+
allTestRuns[this.session.configuration.testingRunIndex] = undefined;
151+
}
152+
153+
onError(error: Error): void {
154+
//console.log(`**Erroring session ${this.session.name}: error.message=${error.message}`);
155+
}
156+
157+
onExit(code: number | undefined, signal: string | undefined): void {
158+
//console.log(`**Exiting session ${this.session.name}: code=${code}, signal=${signal}`);
148159
}
149160
}

src/extension.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ export const extensionId = "intersystems-community.testingmanager";
1010
export let localTestController: vscode.TestController;
1111
export let loadedTestController: vscode.TestController;
1212
export let historyBrowserController: vscode.TestController;
13-
export const allTestRuns: (vscode.TestRun | undefined)[] = [];
1413
export let osAPI: any;
1514
export let smAPI: any;
1615

16+
export interface TestRun extends vscode.TestRun {
17+
debugSession?: vscode.DebugSession
18+
}
19+
export const allTestRuns: (TestRun | undefined)[] = [];
20+
1721
export interface IWebServerSpec {
1822
scheme?: string;
1923
host: string;

0 commit comments

Comments
 (0)