Skip to content

Commit ff2b6a0

Browse files
committed
Improvements
1 parent 489a881 commit ff2b6a0

File tree

9 files changed

+241
-50
lines changed

9 files changed

+241
-50
lines changed

src/commonRunTestsHandler.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from 'vscode';
22
import { IServerSpec } from "@intersystems-community/intersystems-servermanager";
3-
import { allTestRuns, extensionId, osAPI } from './extension';
3+
import { allTestRuns, extensionId, osAPI, OurTestItem } from './extension';
44
import { relativeTestRoot } from './localTests';
55
import logger from './logger';
66
import { makeRESTRequest } from './makeRESTRequest';
@@ -15,14 +15,29 @@ export async function commonRunTestsHandler(controller: vscode.TestController, r
1515
// We don't yet support running only some TestXXX methods in a testclass
1616
const mapAuthorities = new Map<string, Map<string, vscode.TestItem>>();
1717
const runIndices: number[] =[];
18-
const queue: vscode.TestItem[] = [];
18+
const queue: OurTestItem[] = [];
19+
const coverageRequest = request.profile?.kind === vscode.TestRunProfileKind.Coverage;
1920

2021
// Loop through all included tests, or all known tests, and add them to our queue
2122
if (request.include) {
22-
request.include.forEach(test => queue.push(test));
23+
request.include.forEach((test: OurTestItem) => {
24+
if (!coverageRequest || test.supportsCoverage) {
25+
queue.push(test);
26+
}
27+
});
2328
} else {
2429
// Run was launched from controller's root level
25-
controller.items.forEach(test => queue.push(test));
30+
controller.items.forEach((test: OurTestItem) => {
31+
if (!coverageRequest || test.supportsCoverage) {
32+
queue.push(test);
33+
}
34+
});
35+
}
36+
37+
if (coverageRequest && !queue.length) {
38+
// No tests to run, but coverage requested
39+
vscode.window.showErrorMessage("Coverage support not available on target environment(s).", { modal: true });
40+
return;
2641
}
2742

2843
// Process every test that was queued. Recurse down to leaves (testmethods) and build a map of their parents (classes)
@@ -70,7 +85,7 @@ export async function commonRunTestsHandler(controller: vscode.TestController, r
7085

7186
if (mapAuthorities.size === 0) {
7287
// Nothing included
73-
vscode.window.showWarningMessage(`Empty test run`);
88+
vscode.window.showErrorMessage("Empty test run.", { modal: true });
7489
return;
7590
}
7691

@@ -231,7 +246,7 @@ export async function commonRunTestsHandler(controller: vscode.TestController, r
231246
}
232247

233248
let managerClass = "%UnitTest.Manager";
234-
if (request.profile?.kind === vscode.TestRunProfileKind.Coverage) {
249+
if (coverageRequest) {
235250
managerClass = "TestCoverage.Manager";
236251
request.profile.loadDetailedCoverage = async (testRun, fileCoverage, token) => {
237252
return fileCoverage instanceof OurFileCoverage ? fileCoverage.loadDetailedCoverage() : [];

src/coverage.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as vscode from 'vscode';
22
import { makeRESTRequest } from './makeRESTRequest';
33
import logger from './logger';
4-
import { TestRun } from './extension';
4+
import { OurTestRun } from './extension';
55
import { serverSpecForUri } from './historyExplorer';
66
import { OurFileCoverage } from './ourFileCoverage';
77

8-
export async function processCoverage(serverName: string, namespace: string, run: TestRun): Promise<void> {
8+
export async function processCoverage(serverName: string, namespace: string, run: OurTestRun): Promise<void> {
99
const uri = run.debugSession?.workspaceFolder?.uri;
1010
const coverageIndex = run.debugSession?.configuration.coverageIndex;
1111
logger.debug(`processCoverage: serverName=${serverName}, namespace=${namespace}, uri=${uri?.toString()}, coverageIndex=${coverageIndex}`);
@@ -22,7 +22,7 @@ export async function processCoverage(serverName: string, namespace: string, run
2222
}
2323
}
2424

25-
export async function getFileCoverageResults(folderUri: vscode.Uri, namespace: string, run: number): Promise<vscode.FileCoverage[]> {
25+
export async function getFileCoverageResults(folderUri: vscode.Uri, namespace: string, coverageIndex: number): Promise<vscode.FileCoverage[]> {
2626
const serverSpec = serverSpecForUri(folderUri);
2727
const fileCoverageResults: vscode.FileCoverage[] = [];
2828
if (!serverSpec) {
@@ -36,7 +36,7 @@ export async function getFileCoverageResults(folderUri: vscode.Uri, namespace: s
3636
{ apiVersion: 1, namespace, path: "/action/query" },
3737
{
3838
query: "SELECT cu.Hash, cu.Name Name, cu.Type, abcu.ExecutableLines, CoveredLines, ExecutableMethods, CoveredMethods, RtnLine FROM TestCoverage_Data_Aggregate.ByCodeUnit abcu, TestCoverage_Data.CodeUnit cu WHERE abcu.CodeUnit = cu.Hash AND Run = ? ORDER BY Name",
39-
parameters: [run],
39+
parameters: [coverageIndex],
4040
},
4141
);
4242
if (response) {
@@ -56,7 +56,7 @@ export async function getFileCoverageResults(folderUri: vscode.Uri, namespace: s
5656
logger.debug(`getFileCoverageResults element: ${JSON.stringify(element)}`);
5757
logger.debug(`getFileCoverageResults fileUri: ${fileUri.toString()}`);
5858
const fileCoverage = new OurFileCoverage(
59-
run,
59+
coverageIndex,
6060
element.Hash,
6161
fileUri,
6262
new vscode.TestCoverageCount(element.CoveredLines, element.ExecutableLines),

src/debugTracker.ts

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

@@ -9,7 +9,7 @@ export class DebugTracker implements vscode.DebugAdapterTracker {
99
private serverName: string;
1010
private namespace: string;
1111
private testController: vscode.TestController
12-
private run?: TestRun;
12+
private run?: OurTestRun;
1313
private testingIdBase: string;
1414
private className?: string;
1515
private testMethodName?: string;

src/extension.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ export let historyBrowserController: vscode.TestController;
1414
export let osAPI: any;
1515
export let smAPI: serverManager.ServerManagerAPI | undefined;
1616

17-
export interface TestRun extends vscode.TestRun {
17+
export interface OurTestRun extends vscode.TestRun {
1818
debugSession?: vscode.DebugSession
1919
}
20-
export const allTestRuns: (TestRun | undefined)[] = [];
20+
21+
export interface OurTestItem extends vscode.TestItem {
22+
supportsCoverage?: boolean
23+
}
24+
25+
export const allTestRuns: (OurTestRun | undefined)[] = [];
2126

2227
async function getServerManagerAPI(): Promise<serverManager.ServerManagerAPI | undefined> {
2328
const targetExtension = vscode.extensions.getExtension("intersystems-community.servermanager");

src/historyExplorer.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from 'vscode';
22
import { IServerSpec } from "@intersystems-community/intersystems-servermanager";
3-
import { historyBrowserController, osAPI, smAPI } from './extension';
3+
import { historyBrowserController, osAPI, OurTestItem, smAPI } from './extension';
44
import logger from './logger';
55
import { makeRESTRequest } from './makeRESTRequest';
66

@@ -81,7 +81,7 @@ export async function serverSpec(item: vscode.TestItem): Promise<IServerSpec | u
8181
}
8282
}
8383

84-
async function addTestInstances(item: vscode.TestItem, controller: vscode.TestController) {
84+
async function addTestInstances(item: OurTestItem, controller: vscode.TestController) {
8585
item.busy = true;
8686
const spec = await serverSpec(item);
8787
const namespace = item.id.split(':')[1];
@@ -99,22 +99,23 @@ async function addTestInstances(item: vscode.TestItem, controller: vscode.TestCo
9999
path: `${spec.webServer.pathPrefix || ""}/csp/sys/%UnitTest.Portal.Indices.cls`,
100100
});
101101
response?.data?.result?.content?.forEach(element => {
102-
const child = controller.createTestItem(
102+
const child: OurTestItem = controller.createTestItem(
103103
`${item.id}:${element.InstanceIndex}`,
104104
`${element.DateTime}`,
105105
portalUri.with({ query: `Index=${element.InstanceIndex}&$NAMESPACE=${namespace}` })
106106
);
107107
child.sortText = (1e12 - element.InstanceIndex).toString().padStart(12, "0");
108108
child.description = `run ${element.InstanceIndex}`;
109109
child.canResolveChildren = true;
110+
child.supportsCoverage = item.supportsCoverage;
110111
item.children.add(child);
111112
});
112113
}
113114
}
114115
item.busy = false;
115116
}
116117

117-
async function addTestSuites(item: vscode.TestItem, controller: vscode.TestController) {
118+
async function addTestSuites(item: OurTestItem, controller: vscode.TestController) {
118119
const spec = await serverSpec(item);
119120
const parts = item.id.split(':');
120121
const namespace = parts[1];
@@ -132,8 +133,9 @@ async function addTestSuites(item: vscode.TestItem, controller: vscode.TestContr
132133
if (response) {
133134
const run = controller.createTestRun(new vscode.TestRunRequest(), `Item '${item.label}' history`, false);
134135
response?.data?.result?.content?.forEach(element => {
135-
const child = controller.createTestItem(`${item.id}:${element.ID}`, `${element.Name}`);
136+
const child: OurTestItem = controller.createTestItem(`${item.id}:${element.ID}`, `${element.Name}`);
136137
child.canResolveChildren = true;
138+
child.supportsCoverage = item.supportsCoverage;
137139
item.children.add(child);
138140
if (element.Status) {
139141
run.passed(child, element.Duration * 1000);
@@ -147,7 +149,7 @@ async function addTestSuites(item: vscode.TestItem, controller: vscode.TestContr
147149
}
148150
}
149151

150-
async function addTestCases(item: vscode.TestItem, controller: vscode.TestController) {
152+
async function addTestCases(item: OurTestItem, controller: vscode.TestController) {
151153
const spec = await serverSpec(item);
152154
const parts = item.id.split(':');
153155
const namespace = parts[1];
@@ -165,8 +167,9 @@ async function addTestCases(item: vscode.TestItem, controller: vscode.TestContro
165167
if (response) {
166168
const run = controller.createTestRun(new vscode.TestRunRequest(), `Item '${item.label}' history`, false);
167169
response?.data?.result?.content?.forEach(element => {
168-
const child = controller.createTestItem(`${item.id}:${element.ID}`, `${element.Name.split('.').pop()}`);
170+
const child: OurTestItem = controller.createTestItem(`${item.id}:${element.ID}`, `${element.Name.split('.').pop()}`);
169171
child.canResolveChildren = true;
172+
child.supportsCoverage = item.supportsCoverage;
170173
item.children.add(child);
171174
if (element.Status) {
172175
run.passed(child, element.Duration * 1000);
@@ -180,7 +183,7 @@ async function addTestCases(item: vscode.TestItem, controller: vscode.TestContro
180183
}
181184
}
182185

183-
async function addTestMethods(item: vscode.TestItem, controller: vscode.TestController) {
186+
async function addTestMethods(item: OurTestItem, controller: vscode.TestController) {
184187
const spec = await serverSpec(item);
185188
const parts = item.id.split(':');
186189
const namespace = parts[1];
@@ -200,8 +203,9 @@ async function addTestMethods(item: vscode.TestItem, controller: vscode.TestCont
200203
response?.data?.result?.content?.forEach(element => {
201204
const methodName: string = element.Name;
202205
// We drop the first 4 characters of the method name because they should always be "Test"
203-
const child = controller.createTestItem(`${item.id}:${element.ID}`, `${methodName.slice(4)}`);
206+
const child: OurTestItem = controller.createTestItem(`${item.id}:${element.ID}`, `${methodName.slice(4)}`);
204207
child.canResolveChildren = true;
208+
child.supportsCoverage = item.supportsCoverage;
205209
item.children.add(child);
206210

207211
// Remember result fields so they can be reinstated when the descendant Asserts are 'run'
@@ -218,7 +222,7 @@ async function addTestMethods(item: vscode.TestItem, controller: vscode.TestCont
218222
}
219223
}
220224

221-
async function addTestAsserts(item: vscode.TestItem, controller: vscode.TestController) {
225+
async function addTestAsserts(item: OurTestItem, controller: vscode.TestController) {
222226
const spec = await serverSpec(item);
223227
const parts = item.id.split(':');
224228
const namespace = parts[1];
@@ -248,10 +252,11 @@ async function addTestAsserts(item: vscode.TestItem, controller: vscode.TestCont
248252
}
249253

250254
response?.data?.result?.content?.forEach(element => {
251-
const child = controller.createTestItem(`${item.id}:${element.ID}`, `${element.Action}`);
255+
const child: OurTestItem = controller.createTestItem(`${item.id}:${element.ID}`, `${element.Action}`);
252256
child.sortText = `${element.Counter.toString().padStart(element.MaxCounter.toString().length, "0")}`;
253257
child.description = element.Description;
254258
child.canResolveChildren = false;
259+
child.supportsCoverage = item.supportsCoverage;
255260
item.children.add(child);
256261
if (element.Status) {
257262
run.passed(child);
@@ -278,8 +283,9 @@ export function replaceRootItems(controller: vscode.TestController, schemes?: st
278283
if (server.namespace) {
279284
const key = server.serverName + ":" + server.namespace.toUpperCase();
280285
if (!rootMap.has(key)) {
281-
const item = controller.createTestItem(key, key, folder.uri);
286+
const item: OurTestItem = controller.createTestItem(key, key, folder.uri);
282287
item.canResolveChildren = true;
288+
item.supportsCoverage = true; // TODO - check target namespace supports coverage
283289
rootMap.set(key, item);
284290
}
285291
}

src/localTests.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as vscode from 'vscode';
22
import { commonRunTestsHandler } from './commonRunTestsHandler';
3-
import { localTestController, osAPI } from './extension';
3+
import { localTestController, OurTestItem } from './extension';
44
import logger from './logger';
5+
import { resolveServerSpecAndNamespace, supportsCoverage } from './utils';
56

67
const isResolvedMap = new WeakMap<vscode.TestItem, boolean>();
78

8-
async function resolveItemChildren(item: vscode.TestItem) {
9+
async function resolveItemChildren(item: OurTestItem) {
910
if (item) {
1011
isResolvedMap.set(item, true);
1112
const itemUri = item.uri;
@@ -15,15 +16,17 @@ async function resolveItemChildren(item: vscode.TestItem) {
1516
const contents = await vscode.workspace.fs.readDirectory(itemUri);
1617
contents.filter((entry) => entry[1] === vscode.FileType.Directory).forEach((entry) => {
1718
const name = entry[0];
18-
const child = localTestController.createTestItem(`${item.id}${name}.`, name, itemUri.with({path: `${itemUri.path}/${name}`}));
19+
const child: OurTestItem = localTestController.createTestItem(`${item.id}${name}.`, name, itemUri.with({path: `${itemUri.path}/${name}`}));
1920
child.canResolveChildren = true;
21+
child.supportsCoverage = item.supportsCoverage;
2022
item.children.add(child);
2123
});
2224
contents.filter((entry) => entry[1] === vscode.FileType.File).forEach((entry) => {
2325
const name = entry[0];
2426
if (name.endsWith('.cls')) {
25-
const child = localTestController.createTestItem(`${item.id}${name.slice(0, name.length - 4)}`, name, itemUri.with({path: `${itemUri.path}/${name}`}));
27+
const child: OurTestItem = localTestController.createTestItem(`${item.id}${name.slice(0, name.length - 4)}`, name, itemUri.with({path: `${itemUri.path}/${name}`}));
2628
child.canResolveChildren = true;
29+
child.supportsCoverage = item.supportsCoverage;
2730
item.children.add(child);
2831
}
2932
});
@@ -46,9 +49,10 @@ async function resolveItemChildren(item: vscode.TestItem) {
4649
const match = lineText.match(/^Method Test(.+)\(/);
4750
if (match) {
4851
const testName = match[1];
49-
const child = localTestController.createTestItem(`${item.id}:Test${testName}`, testName, itemUri);
52+
const child: OurTestItem = localTestController.createTestItem(`${item.id}:Test${testName}`, testName, itemUri);
5053
child.range = new vscode.Range(new vscode.Position(index, 0), new vscode.Position(index + 1, 0))
5154
child.canResolveChildren = false;
55+
child.supportsCoverage = item.supportsCoverage;
5256
item.children.add(child);
5357
if (!child.parent) {
5458
console.log(`*** BUG - child (id=${child.id}) has no parent after item.children.add(child) where item.id=${item.id}`);
@@ -67,11 +71,19 @@ async function resolveItemChildren(item: vscode.TestItem) {
6771
}
6872
else {
6973
// Root items
70-
replaceLocalRootItems(localTestController);
74+
await replaceLocalRootItems(localTestController);
7175
if (localTestController.items.size > 0) {
7276
localTestController.createRunProfile('Run Local Tests', vscode.TestRunProfileKind.Run, runTestsHandler, true);
7377
localTestController.createRunProfile('Debug Local Tests', vscode.TestRunProfileKind.Debug, runTestsHandler);
74-
localTestController.createRunProfile('Run Local Tests with Coverage', vscode.TestRunProfileKind.Coverage, runTestsHandler);
78+
let supportsCoverage = false;
79+
localTestController.items.forEach((item: OurTestItem) => {
80+
if (item.supportsCoverage) {
81+
supportsCoverage = true;
82+
};
83+
});
84+
if (supportsCoverage) {
85+
localTestController.createRunProfile('Run Local Tests with Coverage', vscode.TestRunProfileKind.Coverage, runTestsHandler);
86+
}
7587
}
7688
}
7789
}
@@ -112,24 +124,25 @@ export function relativeTestRoot(folder: vscode.WorkspaceFolder): string {
112124

113125
/* Replace root items with one item for each file-type workspace root for which a named server can be identified
114126
*/
115-
function replaceLocalRootItems(controller: vscode.TestController) {
127+
async function replaceLocalRootItems(controller: vscode.TestController) {
116128
const rootItems: vscode.TestItem[] = [];
117129
const rootMap = new Map<string, vscode.TestItem>();
118-
vscode.workspace.workspaceFolders?.forEach(folder => {
130+
for await (const folder of vscode.workspace.workspaceFolders || []) {
119131
if (folder.uri.scheme === 'file') {
120-
const server = osAPI.serverForUri(folder.uri);
121-
if (server?.namespace) {
122-
const key = server.serverName + ":" + server.namespace + ":";
132+
const { serverSpec, namespace } = await resolveServerSpecAndNamespace(folder.uri);
133+
if (serverSpec && namespace) {
134+
const key = serverSpec.name + ":" + namespace + ":";
123135
if (!rootMap.has(key)) {
124136
const relativeRoot = relativeTestRoot(folder);
125-
const item = controller.createTestItem(key, folder.name, folder.uri.with({path: `${folder.uri.path}/${relativeRoot}`}));
137+
const item: OurTestItem = controller.createTestItem(key, folder.name, folder.uri.with({path: `${folder.uri.path}/${relativeRoot}`}));
126138
item.description = relativeRoot;
127139
item.canResolveChildren = true;
140+
item.supportsCoverage = await supportsCoverage(folder);
128141
rootMap.set(key, item);
129142
}
130143
}
131144
}
132-
});
145+
}
133146
rootMap.forEach(item => rootItems.push(item));
134147
controller.items.replace(rootItems);
135148
}

0 commit comments

Comments
 (0)