Skip to content

Commit d895eca

Browse files
committed
checkpoint
1 parent 379abe9 commit d895eca

5 files changed

Lines changed: 235 additions & 33 deletions

File tree

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@
7777
],
7878
"menus": {
7979
"commandPalette": [
80+
],
81+
"view/title": [
82+
{
83+
"command": "intersystems-community.servermanager.addServer",
84+
"when": "view == workbench.view.testing",
85+
"group": "navigation"
86+
}
87+
],
88+
"testing/item/context": [
89+
{
90+
"command": "intersystems-community.servermanager.addServer",
91+
"when": "controllerId == 'intersystems-community.testingmanager-History' && testId =~ /:%SYS$/",
92+
"group": "inline"
93+
}
8094
]
8195
}
8296
}

src/extension.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import * as vscode from "vscode";
44
import { setupHistoryExplorerController } from "./historyExplorer";
5-
import { setupWorkspaceTestsController } from "./workspaceTests";
5+
import { setupServerTestsController } from "./serverTests";
6+
import { setupLocalTestsController } from "./localTests";
67

78
export const extensionId = "intersystems-community.testingmanager";
8-
export let historyExplorerController: vscode.TestController;
9-
export let testController: vscode.TestController;
9+
export let localTestController: vscode.TestController;
10+
export let loadedTestController: vscode.TestController;
11+
export let historyBrowserController: vscode.TestController;
1012
export let osAPI;
1113
export let smAPI;
1214

@@ -71,12 +73,16 @@ export async function activate(context: vscode.ExtensionContext) {
7173
// TODO notify user if either of these returned undefined (extensionDependencies setting should prevent that, but better to be safe)
7274

7375
// Other parts of this extension will use the test controllers
74-
testController = vscode.tests.createTestController(`${extensionId}-Controller`, 'Workspace Tests');
75-
context.subscriptions.push(testController);
76-
await setupWorkspaceTestsController();
76+
localTestController = vscode.tests.createTestController(`${extensionId}-Local`, 'LOCAL TESTS');
77+
context.subscriptions.push(localTestController);
78+
await setupLocalTestsController();
7779

78-
historyExplorerController = vscode.tests.createTestController(`${extensionId}-HistoryExplorer`, 'History Explorer');
79-
context.subscriptions.push(historyExplorerController);
80+
loadedTestController = vscode.tests.createTestController(`${extensionId}-Loaded`, 'SERVER TESTS');
81+
context.subscriptions.push(loadedTestController);
82+
await setupServerTestsController();
83+
84+
historyBrowserController = vscode.tests.createTestController(`${extensionId}-History`, 'TESTING HISTORY');
85+
context.subscriptions.push(historyBrowserController);
8086
await setupHistoryExplorerController();
8187

8288
// Register the commands

src/historyExplorer.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as vscode from 'vscode';
2-
import { historyExplorerController, IServerSpec, osAPI, smAPI } from './extension';
2+
import { historyBrowserController, IServerSpec, osAPI, smAPI } from './extension';
33
import logger from './logger';
44
import { makeRESTRequest } from './makeRESTRequest';
55

@@ -13,35 +13,35 @@ const resultMap = new WeakMap<vscode.TestItem, IResult>();
1313
export async function setupHistoryExplorerController() {
1414
logger.info('setupHistoryExplorerController invoked');
1515

16-
historyExplorerController.resolveHandler = async (item) => {
16+
historyBrowserController.resolveHandler = async (item) => {
1717
if (item) {
1818
const idParts = item.id.split(':');
1919
if (idParts.length === 2) {
20-
await addTestInstances(item, historyExplorerController);
20+
await addTestInstances(item, historyBrowserController);
2121
}
2222
else if (idParts.length === 3) {
23-
await addTestSuites(item, historyExplorerController);
23+
await addTestSuites(item, historyBrowserController);
2424
}
2525
else if (idParts.length === 4) {
26-
await addTestCases(item, historyExplorerController);
26+
await addTestCases(item, historyBrowserController);
2727
}
2828
else if (idParts.length === 5) {
29-
await addTestMethods(item, historyExplorerController);
29+
await addTestMethods(item, historyBrowserController);
3030
}
3131
else if (idParts.length === 6) {
32-
await addTestAsserts(item, historyExplorerController);
32+
await addTestAsserts(item, historyBrowserController);
3333
}
3434
}
3535
else {
3636
// Root items
37-
replaceRootItems(historyExplorerController);
37+
replaceRootItems(historyBrowserController);
3838
}
3939
}
40-
historyExplorerController.items.replace([historyExplorerController.createTestItem('-', 'loading...')]);
40+
historyBrowserController.items.replace([historyBrowserController.createTestItem('-', 'loading...')]);
4141

4242
}
4343

44-
async function serverSpec(item: vscode.TestItem): Promise<IServerSpec | undefined> {
44+
export async function serverSpec(item: vscode.TestItem): Promise<IServerSpec | undefined> {
4545
return await smAPI.getServerSpec(item.id.split(':')[0]);
4646
}
4747

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as vscode from 'vscode';
2-
import { testController } from './extension';
3-
import { replaceRootItems } from './historyExplorer';
2+
import { localTestController, osAPI } from './extension';
43
import logger from './logger';
54

65
const isResolvedMap = new WeakMap<vscode.TestItem, boolean>();
@@ -12,45 +11,45 @@ function resolveItemChildren(item: vscode.TestItem) {
1211
const depth = item.id.split('.').length;
1312
const isLeaf = depth > 3;
1413
const pkgSuffix = 'ABC'.charAt(depth -1);
15-
for (let index = 1; index < 6; index++) {
16-
const child = testController.createTestItem(`${item.id}.${index}`, `${isLeaf ? 'Class' : 'Pkg' + pkgSuffix}${index}`);
14+
for (let index = 1; index < (depth + 1); index++) {
15+
const child = localTestController.createTestItem(`${item.id}.${index}`, `${isLeaf ? 'Class' : 'Pkg' + pkgSuffix}${index}`);
1716
child.canResolveChildren = !isLeaf;
1817
item.children.add(child);
1918
}
2019
}
2120
else {
2221
// Root items
23-
replaceRootItems(testController);
22+
replaceLocalRootItems(localTestController);
2423
}
2524
}
2625

27-
export async function setupWorkspaceTestsController() {
28-
logger.info('setupWorkspaceTestsController invoked');
26+
export async function setupLocalTestsController() {
27+
logger.info('setupLocalTestsController invoked');
2928

30-
testController.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runTestsHandler, true);
31-
testController.createRunProfile('Debug Tests', vscode.TestRunProfileKind.Debug, runTestsHandler);
29+
localTestController.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runTestsHandler, true);
30+
localTestController.createRunProfile('Debug Tests', vscode.TestRunProfileKind.Debug, runTestsHandler);
3231
//testController.createRunProfile('Test Coverage', vscode.TestRunProfileKind.Coverage, runTestsHandler);
3332

34-
testController.resolveHandler = resolveItemChildren;
35-
testController.items.replace([testController.createTestItem('-', 'loading...')]);
33+
localTestController.resolveHandler = resolveItemChildren;
34+
localTestController.items.replace([localTestController.createTestItem('-', 'loading...')]);
3635
}
3736

3837
export async function runTestsHandler(request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) {
3938
logger.info('runTestsHandler invoked');
4039

41-
const run = testController.createTestRun(
40+
const run = localTestController.createTestRun(
4241
request,
4342
'Fake Test Results',
4443
true
4544
);
46-
run.appendOutput('Fake output from fake run of fake workspace tests.\r\nTODO');
45+
run.appendOutput('Fake output from fake run of fake local tests.\r\nTODO');
4746
const queue: vscode.TestItem[] = [];
4847

4948
// Loop through all included tests, or all known tests, and add them to our queue
5049
if (request.include) {
5150
request.include.forEach(test => queue.push(test));
5251
} else {
53-
testController.items.forEach(test => queue.push(test));
52+
localTestController.items.forEach(test => queue.push(test));
5453
}
5554

5655
// For every test that was queued, try to run it. Call run.passed() or run.failed().
@@ -101,4 +100,27 @@ export async function runTestsHandler(request: vscode.TestRunRequest, cancellati
101100

102101
await new Promise(resolve => setTimeout(resolve, 5000));
103102
run.end();
104-
}
103+
}
104+
105+
106+
/* Replace root items with one item for each file-type workspace root for which a named server can be identified
107+
*/
108+
function replaceLocalRootItems(controller: vscode.TestController) {
109+
const rootItems: vscode.TestItem[] = [];
110+
const rootMap = new Map<string, vscode.TestItem>();
111+
vscode.workspace.workspaceFolders?.forEach(folder => {
112+
if (folder.uri.scheme === 'file') {
113+
const server = osAPI.serverForUri(folder.uri);
114+
if (server?.serverName && server.namespace) {
115+
const key = folder.index.toString();
116+
if (!rootMap.has(key)) {
117+
const item = controller.createTestItem(key, folder.name);
118+
item.canResolveChildren = true;
119+
rootMap.set(key, item);
120+
}
121+
}
122+
}
123+
});
124+
rootMap.forEach(item => rootItems.push(item));
125+
controller.items.replace(rootItems);
126+
}

src/serverTests.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import * as vscode from 'vscode';
2+
import { loadedTestController } from './extension';
3+
import { replaceRootItems, serverSpec } from './historyExplorer';
4+
import logger from './logger';
5+
import { makeRESTRequest } from './makeRESTRequest';
6+
7+
const isResolvedMap = new WeakMap<vscode.TestItem, boolean>();
8+
9+
async function resolveItemChildren(item: vscode.TestItem) {
10+
if (item) {
11+
item.busy = true;
12+
const spec = await serverSpec(item);
13+
const parts = item.id.split(':');
14+
const namespace = parts[1];
15+
if (spec) {
16+
if (parts.length === 2) {
17+
// Find all TestCase classes
18+
const response = await makeRESTRequest(
19+
"POST",
20+
spec,
21+
{ apiVersion: 1, namespace, path: "/action/query" },
22+
{ query: `CALL %Dictionary.ClassDefinition_SubclassOf('%UnitTest.TestCase', '${(namespace === "%SYS" ? "" : "@")}')` },
23+
);
24+
if (response) {
25+
response?.data?.result?.content?.forEach(async element => {
26+
const fullClassName = element.Name;
27+
28+
const response = await makeRESTRequest(
29+
"POST",
30+
spec,
31+
{ apiVersion: 1, namespace, path: "/action/query" },
32+
{ query: `SELECT Name FROM %Dictionary.MethodDefinition WHERE parent='${fullClassName}' AND Name %STARTSWITH 'Test'` },
33+
);
34+
if (response?.data?.result?.content?.length > 0) {
35+
const tiClass = loadedTestController.createTestItem(
36+
`${item.id}:${fullClassName}`,
37+
fullClassName
38+
);
39+
//tiClass.description = `Class ${fullClassName}`;
40+
response?.data?.result?.content?.forEach(element => {
41+
const testMethodSuffix = element.Name.slice(4);
42+
const tiMethod = loadedTestController.createTestItem(
43+
`${item.id}:${element.Name}`,
44+
testMethodSuffix
45+
);
46+
tiClass.children.add(tiMethod);
47+
});
48+
item.children.add(tiClass);
49+
}
50+
});
51+
}
52+
}
53+
else if (parts.length === 3) {
54+
// Find all Test* methods in a class
55+
const fullClassName = parts[2];
56+
const response = await makeRESTRequest(
57+
"POST",
58+
spec,
59+
{ apiVersion: 1, namespace, path: "/action/query" },
60+
{ query: `SELECT Name FROM %Dictionary.MethodDefinition WHERE parent='${fullClassName}' AND Name %STARTSWITH 'Test'` },
61+
);
62+
if (response) {
63+
response?.data?.result?.content?.forEach(element => {
64+
const testMethodSuffix = element.Name.slice(4);
65+
const child = loadedTestController.createTestItem(
66+
`${item.id}:${element.Name}`,
67+
testMethodSuffix
68+
);
69+
child.canResolveChildren = false;
70+
item.children.add(child);
71+
});
72+
}
73+
}
74+
}
75+
item.busy = false;
76+
}
77+
else {
78+
// Root items
79+
replaceRootItems(loadedTestController);
80+
}
81+
}
82+
83+
export async function setupServerTestsController() {
84+
logger.info('setupServerTestsController invoked');
85+
86+
loadedTestController.createRunProfile('Run Tests', vscode.TestRunProfileKind.Run, runTestsHandler, true);
87+
loadedTestController.createRunProfile('Debug Tests', vscode.TestRunProfileKind.Debug, runTestsHandler);
88+
//testController.createRunProfile('Test Coverage', vscode.TestRunProfileKind.Coverage, runTestsHandler);
89+
90+
loadedTestController.resolveHandler = resolveItemChildren;
91+
loadedTestController.items.replace([loadedTestController.createTestItem('-', 'loading...')]);
92+
}
93+
94+
export async function runTestsHandler(request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) {
95+
logger.info('runTestsHandler invoked');
96+
97+
const run = loadedTestController.createTestRun(
98+
request,
99+
'Fake Test Results',
100+
true
101+
);
102+
run.appendOutput('Fake output from fake run of fake server tests.\r\nTODO');
103+
const queue: vscode.TestItem[] = [];
104+
105+
// Loop through all included tests, or all known tests, and add them to our queue
106+
if (request.include) {
107+
request.include.forEach(test => queue.push(test));
108+
} else {
109+
loadedTestController.items.forEach(test => queue.push(test));
110+
}
111+
112+
// For every test that was queued, try to run it. Call run.passed() or run.failed().
113+
// The `TestMessage` can contain extra information, like a failing location or
114+
// a diff output. But here we'll just give it a textual message.
115+
while (queue.length > 0 && !cancellation.isCancellationRequested) {
116+
const test = queue.pop()!;
117+
118+
// Skip tests the user asked to exclude
119+
if (request.exclude?.includes(test)) {
120+
continue;
121+
}
122+
123+
// Resolve children if not already done
124+
if (test.canResolveChildren && !isResolvedMap.get(test)) {
125+
resolveItemChildren(test);
126+
}
127+
128+
// Return result for leaf items
129+
if (test.children.size === 0) {
130+
const suffix = test.id.split('.').pop()
131+
switch (suffix) {
132+
case '1':
133+
run.skipped(test);
134+
break;
135+
136+
case '2':
137+
run.failed(test, new vscode.TestMessage('fake failure'), 12300);
138+
break;
139+
140+
case '3':
141+
run.errored(test, new vscode.TestMessage('fake error'), 900);
142+
break;
143+
144+
case '4':
145+
run.enqueued(test);
146+
break;
147+
148+
default:
149+
run.passed(test, 45600);
150+
break;
151+
}
152+
}
153+
154+
// Queue any children
155+
test.children.forEach(test => queue.push(test));
156+
}
157+
158+
await new Promise(resolve => setTimeout(resolve, 5000));
159+
run.end();
160+
}

0 commit comments

Comments
 (0)