@opensumi/ide-testing 2.21.13 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/components/testing.explorer.tree.d.ts.map +1 -1
- package/lib/browser/components/testing.explorer.tree.js +7 -5
- package/lib/browser/components/testing.explorer.tree.js.map +1 -1
- package/lib/browser/components/testing.view.js +1 -1
- package/lib/browser/components/testing.view.js.map +1 -1
- package/lib/browser/icons/icons.d.ts +1 -0
- package/lib/browser/icons/icons.d.ts.map +1 -1
- package/lib/browser/icons/icons.js +15 -14
- package/lib/browser/icons/icons.js.map +1 -1
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/outputPeek/test-output-peek.js +9 -9
- package/lib/browser/outputPeek/test-output-peek.js.map +1 -1
- package/lib/browser/outputPeek/test-peek-message.service.js.map +1 -1
- package/lib/browser/outputPeek/test-peek-opener.service.js +1 -1
- package/lib/browser/outputPeek/test-peek-opener.service.js.map +1 -1
- package/lib/browser/outputPeek/test-peek-widget.js +1 -1
- package/lib/browser/outputPeek/test-peek-widget.js.map +1 -1
- package/lib/browser/outputPeek/test-tree-container.js +2 -2
- package/lib/browser/outputPeek/test-tree-container.js.map +1 -1
- package/lib/browser/test-contextkey.service.js.map +1 -1
- package/lib/browser/test-decorations.js +19 -19
- package/lib/browser/test-decorations.js.map +1 -1
- package/lib/browser/test-profile.service.js.map +1 -1
- package/lib/browser/test-tree-view.model.d.ts +1 -0
- package/lib/browser/test-tree-view.model.d.ts.map +1 -1
- package/lib/browser/test-tree-view.model.js +13 -10
- package/lib/browser/test-tree-view.model.js.map +1 -1
- package/lib/browser/test.result.service.d.ts +1 -1
- package/lib/browser/test.result.service.d.ts.map +1 -1
- package/lib/browser/test.result.service.js +5 -5
- package/lib/browser/test.result.service.js.map +1 -1
- package/lib/browser/test.service.d.ts +4 -0
- package/lib/browser/test.service.d.ts.map +1 -1
- package/lib/browser/test.service.js +31 -4
- package/lib/browser/test.service.js.map +1 -1
- package/lib/browser/testing.contribution.d.ts.map +1 -1
- package/lib/browser/testing.contribution.js +21 -10
- package/lib/browser/testing.contribution.js.map +1 -1
- package/lib/common/commands.d.ts +1 -0
- package/lib/common/commands.d.ts.map +1 -1
- package/lib/common/commands.js +6 -1
- package/lib/common/commands.js.map +1 -1
- package/lib/common/constants.js +12 -12
- package/lib/common/constants.js.map +1 -1
- package/lib/common/getComputedState.d.ts +1 -1
- package/lib/common/getComputedState.d.ts.map +1 -1
- package/lib/common/getComputedState.js +1 -1
- package/lib/common/getComputedState.js.map +1 -1
- package/lib/common/index.d.ts +5 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js.map +1 -1
- package/lib/common/observableValue.d.ts +14 -0
- package/lib/common/observableValue.d.ts.map +1 -0
- package/lib/common/observableValue.js +23 -0
- package/lib/common/observableValue.js.map +1 -0
- package/lib/common/test-result.d.ts +2 -2
- package/lib/common/test-result.d.ts.map +1 -1
- package/lib/common/test-result.js +19 -19
- package/lib/common/test-result.js.map +1 -1
- package/lib/common/testCollection.d.ts +9 -8
- package/lib/common/testCollection.d.ts.map +1 -1
- package/lib/common/testCollection.js +27 -27
- package/lib/common/testCollection.js.map +1 -1
- package/lib/common/testId.js +21 -21
- package/lib/common/testId.js.map +1 -1
- package/lib/common/testingStates.js +11 -11
- package/lib/common/testingStates.js.map +1 -1
- package/lib/common/testingUri.d.ts +1 -1
- package/lib/common/testingUri.d.ts.map +1 -1
- package/lib/common/testingUri.js +16 -16
- package/lib/common/testingUri.js.map +1 -1
- package/package.json +14 -13
- package/src/browser/components/testing.explorer.tree.tsx +129 -0
- package/src/browser/components/testing.module.less +26 -0
- package/src/browser/components/testing.view.tsx +35 -0
- package/src/browser/icons/icons.less +31 -0
- package/src/browser/icons/icons.ts +31 -0
- package/src/browser/index.ts +51 -0
- package/src/browser/outputPeek/test-message-container.tsx +183 -0
- package/src/browser/outputPeek/test-output-peek.ts +282 -0
- package/src/browser/outputPeek/test-peek-message.service.ts +15 -0
- package/src/browser/outputPeek/test-peek-opener.service.ts +131 -0
- package/src/browser/outputPeek/test-peek-widget.less +86 -0
- package/src/browser/outputPeek/test-peek-widget.tsx +127 -0
- package/src/browser/outputPeek/test-tree-container.tsx +171 -0
- package/src/browser/test-contextkey.service.ts +24 -0
- package/src/browser/test-decorations.ts +567 -0
- package/src/browser/test-profile.service.ts +66 -0
- package/src/browser/test-tree-view.model.ts +304 -0
- package/src/browser/test.result.service.ts +193 -0
- package/src/browser/test.service.ts +190 -0
- package/src/browser/testing.contribution.ts +471 -0
- package/src/browser/theme.less +6 -0
- package/src/common/commands.ts +78 -0
- package/src/common/constants.ts +57 -0
- package/src/common/contextKeys.ts +0 -0
- package/src/common/getComputedState.ts +136 -0
- package/src/common/index.ts +55 -0
- package/src/common/observableValue.ts +27 -0
- package/src/common/test-profile.ts +25 -0
- package/src/common/test-result.ts +328 -0
- package/src/common/testCollection.ts +680 -0
- package/src/common/testId.ts +191 -0
- package/src/common/testing-view.ts +28 -0
- package/src/common/testingPeekOpener.ts +31 -0
- package/src/common/testingStates.ts +92 -0
- package/src/common/testingUri.ts +101 -0
- package/src/common/tree-view.model.ts +41 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { Autowired, Injectable } from '@opensumi/di';
|
|
2
|
+
import { CompositeTreeNode, IRecycleTreeHandle, TreeNodeEvent } from '@opensumi/ide-components';
|
|
3
|
+
import { BasicCompositeTreeNode } from '@opensumi/ide-components/lib/recycle-tree/basic/tree-node.define';
|
|
4
|
+
import { Disposable, isDefined, filter, map, Emitter } from '@opensumi/ide-core-browser';
|
|
5
|
+
import { Iterable } from '@opensumi/monaco-editor-core/esm/vs/base/common/iterator';
|
|
6
|
+
|
|
7
|
+
import { ITestService, TestServiceToken } from '../common';
|
|
8
|
+
import { IComputedStateAndDurationAccessor, refreshComputedState } from '../common/getComputedState';
|
|
9
|
+
import { TestResultServiceToken } from '../common/test-result';
|
|
10
|
+
import {
|
|
11
|
+
InternalTestItem,
|
|
12
|
+
TestDiffOpType,
|
|
13
|
+
TestItemExpandState,
|
|
14
|
+
TestResultState,
|
|
15
|
+
TestsDiff,
|
|
16
|
+
} from '../common/testCollection';
|
|
17
|
+
import { ITestTreeItem, ITestTreeViewModel } from '../common/tree-view.model';
|
|
18
|
+
|
|
19
|
+
import { applyTestItemUpdate, IncrementalTestCollectionItem, ITestItemUpdate } from './../common/testCollection';
|
|
20
|
+
import { ITestTreeData } from './../common/tree-view.model';
|
|
21
|
+
import { ResultChangeEvent, TestResultServiceImpl } from './test.result.service';
|
|
22
|
+
|
|
23
|
+
const computedStateAccessor: IComputedStateAndDurationAccessor<ITestTreeItem> = {
|
|
24
|
+
getOwnState: (i) => (i instanceof TestTreeItem ? i.ownState : TestResultState.Unset),
|
|
25
|
+
getCurrentComputedState: (i) => i.state,
|
|
26
|
+
setComputedState: (i, s) => (i.state = s),
|
|
27
|
+
getCurrentComputedDuration: (i) => i.duration,
|
|
28
|
+
getOwnDuration: (i) => (i instanceof TestTreeItem ? i.ownDuration : undefined),
|
|
29
|
+
setComputedDuration: (i, d) => (i.duration = d),
|
|
30
|
+
getChildren: (i) => Iterable.filter(i.children.values(), (t): t is TestTreeItem => t instanceof TestTreeItem),
|
|
31
|
+
*getParents(i) {
|
|
32
|
+
for (let parent = i.parent; parent; parent = parent.parent) {
|
|
33
|
+
yield parent;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class TestTreeItem implements ITestTreeItem {
|
|
39
|
+
constructor(public test: InternalTestItem, public parent: ITestTreeItem | undefined) {}
|
|
40
|
+
|
|
41
|
+
readonly children = new Set<TestTreeItem>();
|
|
42
|
+
|
|
43
|
+
public get label() {
|
|
44
|
+
return this.test.item.label;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public get sortText() {
|
|
48
|
+
return this.test.item.sortText;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public state = TestResultState.Unset;
|
|
52
|
+
|
|
53
|
+
public ownState = TestResultState.Unset;
|
|
54
|
+
|
|
55
|
+
public depth: number = this.parent ? this.parent.depth + 1 : 0;
|
|
56
|
+
|
|
57
|
+
public get tests() {
|
|
58
|
+
return [this.test];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public update = (patch: ITestItemUpdate) => {
|
|
62
|
+
applyTestItemUpdate(this.test, patch);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
public duration: number | undefined;
|
|
66
|
+
|
|
67
|
+
public ownDuration: number | undefined;
|
|
68
|
+
|
|
69
|
+
public retired = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Injectable()
|
|
73
|
+
export class TestTreeViewModelImpl extends Disposable implements ITestTreeViewModel {
|
|
74
|
+
@Autowired(TestServiceToken)
|
|
75
|
+
private readonly testService: ITestService;
|
|
76
|
+
|
|
77
|
+
@Autowired(TestResultServiceToken)
|
|
78
|
+
private readonly testResultService: TestResultServiceImpl;
|
|
79
|
+
|
|
80
|
+
private readonly items = new Map<string, TestTreeItem>();
|
|
81
|
+
|
|
82
|
+
private readonly updateEmitter = new Emitter<void>();
|
|
83
|
+
readonly onUpdate = this.updateEmitter.event;
|
|
84
|
+
|
|
85
|
+
public treeHandlerApi: IRecycleTreeHandle;
|
|
86
|
+
|
|
87
|
+
constructor() {
|
|
88
|
+
super();
|
|
89
|
+
this.addDispose(this.testService.onDidProcessDiff((diff) => this.applyDiff(diff)));
|
|
90
|
+
|
|
91
|
+
for (const test of this.testService.collection.all) {
|
|
92
|
+
this.didUpdateItem(this.createItem(test));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get roots(): Iterable<TestTreeItem> {
|
|
97
|
+
const rootsIt = map(this.testService.collection.rootItems, (r) => this.items.get(r.item.extId));
|
|
98
|
+
return filter(rootsIt, isDefined);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private getRevealDepth(element: TestTreeItem): number | undefined {
|
|
102
|
+
return element.depth === 0 ? 0 : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private createItem(item: InternalTestItem): TestTreeItem {
|
|
106
|
+
const parent = item.parent ? this.items.get(item.parent) : undefined;
|
|
107
|
+
return new TestTreeItem(item, parent);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private findTestTreeItemByExtId(extId: string | undefined): CompositeTreeNode | undefined {
|
|
111
|
+
if (!this.treeHandlerApi || !extId) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const model = this.treeHandlerApi.getModel();
|
|
116
|
+
if (!model) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const findTreeItemByExtId = model.root.flattenedBranch!.find(
|
|
121
|
+
(id) =>
|
|
122
|
+
((model.root.getTreeNodeById(id) as BasicCompositeTreeNode).raw as ITestTreeData).rawItem.test.item.extId ===
|
|
123
|
+
extId,
|
|
124
|
+
);
|
|
125
|
+
const treeItem = model.root.getTreeNodeById(findTreeItemByExtId!) as CompositeTreeNode;
|
|
126
|
+
return treeItem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private didUnStoreItem(items: Map<string, TestTreeItem>, item: TestTreeItem) {
|
|
130
|
+
const parent = item.parent;
|
|
131
|
+
parent?.children.delete(item);
|
|
132
|
+
items.delete(item.test.item.extId);
|
|
133
|
+
if (parent instanceof TestTreeItem) {
|
|
134
|
+
refreshComputedState(computedStateAccessor, parent);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return item.children;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private async didUpdateItem(item: TestTreeItem) {
|
|
141
|
+
item.parent?.children.add(item);
|
|
142
|
+
this.items.set(item.test.item.extId, item);
|
|
143
|
+
const reveal = this.getRevealDepth(item);
|
|
144
|
+
if (reveal !== undefined) {
|
|
145
|
+
await this.expandElement(item, reveal);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const prevState = this.testResultService.getStateById(item.test.item.extId)?.[1];
|
|
149
|
+
if (prevState) {
|
|
150
|
+
item.retired = prevState.retired;
|
|
151
|
+
item.ownState = prevState.computedState;
|
|
152
|
+
item.ownDuration = prevState.ownDuration;
|
|
153
|
+
refreshComputedState(computedStateAccessor, item);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async applyDiff(diff: TestsDiff) {
|
|
158
|
+
for (const op of diff) {
|
|
159
|
+
switch (op[0]) {
|
|
160
|
+
case TestDiffOpType.Add: {
|
|
161
|
+
const item = this.createItem(op[1]);
|
|
162
|
+
await this.didUpdateItem(item);
|
|
163
|
+
|
|
164
|
+
const treeItem = this.findTestTreeItemByExtId(item.parent?.test.item.extId);
|
|
165
|
+
if (treeItem) {
|
|
166
|
+
treeItem.refresh();
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case TestDiffOpType.Update: {
|
|
171
|
+
const patch = op[1];
|
|
172
|
+
const existing = this.items.get(patch.extId);
|
|
173
|
+
if (!existing) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
existing.update(patch);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case TestDiffOpType.Remove: {
|
|
181
|
+
const toRemove = this.items.get(op[1]);
|
|
182
|
+
if (!toRemove) {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const treeItem = this.findTestTreeItemByExtId(toRemove.test.item.extId);
|
|
187
|
+
if (treeItem) {
|
|
188
|
+
(treeItem.parent as CompositeTreeNode).unlinkItem(treeItem);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const queue: Iterable<TestTreeItem>[] = [[toRemove]];
|
|
192
|
+
while (queue.length) {
|
|
193
|
+
for (const item of queue.pop()!) {
|
|
194
|
+
if (item instanceof TestTreeItem) {
|
|
195
|
+
queue.push(this.didUnStoreItem(this.items, item));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (diff.length !== 0) {
|
|
206
|
+
this.updateEmitter.fire();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private listenTreeHandlerEvent(): void {
|
|
211
|
+
if (this.treeHandlerApi) {
|
|
212
|
+
const model = this.treeHandlerApi.getModel();
|
|
213
|
+
this.addDispose(
|
|
214
|
+
model.root.watcher.on(TreeNodeEvent.DidChangeExpansionState, async (node: BasicCompositeTreeNode) => {
|
|
215
|
+
if (node.expanded) {
|
|
216
|
+
const raw = node.raw as ITestTreeData;
|
|
217
|
+
const rawTest = raw.rawItem.test;
|
|
218
|
+
if (rawTest.expand === TestItemExpandState.NotExpandable) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
await this.expandElement(raw.rawItem, raw.rawItem.depth);
|
|
223
|
+
await node.refresh();
|
|
224
|
+
}
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
this.addDispose(
|
|
228
|
+
this.testResultService.onTestChanged(({ item: result }) => {
|
|
229
|
+
if (result.ownComputedState === TestResultState.Unset) {
|
|
230
|
+
const fallback = this.testResultService.getStateById(result.item.extId);
|
|
231
|
+
if (fallback) {
|
|
232
|
+
result = fallback[1];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const item = this.items.get(result.item.extId);
|
|
237
|
+
if (!item) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
item.retired = result.retired;
|
|
242
|
+
item.ownState = result.ownComputedState;
|
|
243
|
+
item.ownDuration = result.ownDuration;
|
|
244
|
+
const explicitComputed = item.children.size ? undefined : result.computedState;
|
|
245
|
+
refreshComputedState(computedStateAccessor, item, explicitComputed);
|
|
246
|
+
model.dispatchChange();
|
|
247
|
+
this.revealTreeById(result.item.extId, false, false);
|
|
248
|
+
this.updateEmitter.fire();
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
this.addDispose(
|
|
252
|
+
this.testResultService.onResultsChanged((evt: ResultChangeEvent) => {
|
|
253
|
+
if (!('removed' in evt)) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const inTree of [...this.items.values()].sort((a, b) => b.depth - a.depth)) {
|
|
258
|
+
const lookup = this.testResultService.getStateById(inTree.test.item.extId)?.[1];
|
|
259
|
+
inTree.ownDuration = lookup?.ownDuration;
|
|
260
|
+
refreshComputedState(computedStateAccessor, inTree, lookup?.ownComputedState ?? TestResultState.Unset);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
model.dispatchChange();
|
|
264
|
+
this.updateEmitter.fire();
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private revealTreeById(id: string | undefined, expand = true, focus = true): void {
|
|
271
|
+
if (!id) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ** 此处要定位到对应 tree 位置 **
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public getTestItem(extId: string): IncrementalTestCollectionItem | undefined {
|
|
279
|
+
return this.testService.collection.getNodeById(extId);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public getTestTreeItem(extId: string): TestTreeItem | undefined {
|
|
283
|
+
return this.items.get(extId);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public expandElement(element: ITestTreeItem, depth: number): Promise<void> {
|
|
287
|
+
if (!(element instanceof TestTreeItem)) {
|
|
288
|
+
return Promise.resolve();
|
|
289
|
+
}
|
|
290
|
+
if (element.test.expand === TestItemExpandState.NotExpandable) {
|
|
291
|
+
return Promise.resolve();
|
|
292
|
+
}
|
|
293
|
+
return this.testService.collection.expand(element.test.item.extId, depth);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
public initTreeModel(): Promise<void> {
|
|
297
|
+
return Promise.resolve();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public setTreeHandlerApi(handle: IRecycleTreeHandle): void {
|
|
301
|
+
this.treeHandlerApi = handle;
|
|
302
|
+
this.listenTreeHandlerEvent();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Injectable, Autowired } from '@opensumi/di';
|
|
2
|
+
import { Emitter, IContextKey, IContextKeyService, URI, uuid, arrays } from '@opensumi/ide-core-browser';
|
|
3
|
+
import { TestingHasAnyResults, TestingIsRunning } from '@opensumi/ide-core-browser/lib/contextkey/testing';
|
|
4
|
+
|
|
5
|
+
import { ITestProfileService, TestProfileServiceToken } from '../common/test-profile';
|
|
6
|
+
import {
|
|
7
|
+
ITestResult,
|
|
8
|
+
ITestResultService,
|
|
9
|
+
TestResultImpl,
|
|
10
|
+
TestResultItemChange,
|
|
11
|
+
TestResultItemChangeReason,
|
|
12
|
+
} from '../common/test-result';
|
|
13
|
+
import {
|
|
14
|
+
ResolvedTestRunRequest,
|
|
15
|
+
ExtensionRunTestsRequest,
|
|
16
|
+
ITestRunProfile,
|
|
17
|
+
TestResultItem,
|
|
18
|
+
TestResultState,
|
|
19
|
+
} from '../common/testCollection';
|
|
20
|
+
import { parseTestUri } from '../common/testingUri';
|
|
21
|
+
|
|
22
|
+
import { TestDto } from './outputPeek/test-output-peek';
|
|
23
|
+
|
|
24
|
+
const { findFirstInSorted } = arrays;
|
|
25
|
+
|
|
26
|
+
export type ResultChangeEvent =
|
|
27
|
+
| { completed: ITestResult }
|
|
28
|
+
| { started: ITestResult }
|
|
29
|
+
| { inserted: ITestResult }
|
|
30
|
+
| { removed: ITestResult[] };
|
|
31
|
+
|
|
32
|
+
export const isRunningTests = (service: ITestResultService) =>
|
|
33
|
+
service.results.length > 0 && service.results[0].completedAt === undefined;
|
|
34
|
+
|
|
35
|
+
@Injectable()
|
|
36
|
+
export class TestResultServiceImpl implements ITestResultService {
|
|
37
|
+
@Autowired(TestProfileServiceToken)
|
|
38
|
+
protected readonly testProfiles: ITestProfileService;
|
|
39
|
+
|
|
40
|
+
@Autowired(IContextKeyService)
|
|
41
|
+
private readonly contextKeyService: IContextKeyService;
|
|
42
|
+
|
|
43
|
+
private changeResultEmitter = new Emitter<ResultChangeEvent>();
|
|
44
|
+
private testChangeEmitter = new Emitter<TestResultItemChange>();
|
|
45
|
+
|
|
46
|
+
private _results: ITestResult[] = [];
|
|
47
|
+
private readonly hasAnyResults: IContextKey<boolean>;
|
|
48
|
+
private readonly isRunning: IContextKey<boolean>;
|
|
49
|
+
|
|
50
|
+
public get results(): ITestResult[] {
|
|
51
|
+
return this._results;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public readonly onResultsChanged = this.changeResultEmitter.event;
|
|
55
|
+
public readonly onTestChanged = this.testChangeEmitter.event;
|
|
56
|
+
|
|
57
|
+
constructor() {
|
|
58
|
+
this.hasAnyResults = TestingHasAnyResults.bind(this.contextKeyService);
|
|
59
|
+
this.isRunning = TestingIsRunning.bind(this.contextKeyService);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private onComplete(result: ITestResult) {
|
|
63
|
+
this.resort();
|
|
64
|
+
this.updateIsRunning();
|
|
65
|
+
this.changeResultEmitter.fire({ completed: result });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private resort() {
|
|
69
|
+
this.results.sort(
|
|
70
|
+
(a, b) => (b.completedAt ?? Number.MAX_SAFE_INTEGER) - (a.completedAt ?? Number.MAX_SAFE_INTEGER),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private updateIsRunning() {
|
|
75
|
+
this.isRunning.set(isRunningTests(this));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public retrieveTest(uri: URI): TestDto | undefined {
|
|
79
|
+
const parts = parseTestUri(uri);
|
|
80
|
+
if (!parts) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { resultId, testExtId, taskIndex, messageIndex } = parts;
|
|
85
|
+
const test = this.getResult(parts.resultId)?.getStateById(testExtId);
|
|
86
|
+
if (!test || !test.tasks[parts.taskIndex]) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return new TestDto(resultId, test, taskIndex, messageIndex);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public createTestResult(req: ResolvedTestRunRequest | ExtensionRunTestsRequest): ITestResult {
|
|
94
|
+
if ('targets' in req) {
|
|
95
|
+
const id = uuid();
|
|
96
|
+
const testResult = new TestResultImpl(id, req);
|
|
97
|
+
this.addTestResult(testResult);
|
|
98
|
+
return testResult;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let profile: ITestRunProfile | undefined;
|
|
102
|
+
if ((req as ExtensionRunTestsRequest).profile) {
|
|
103
|
+
const profiles = this.testProfiles.getControllerProfiles((req as ExtensionRunTestsRequest).controllerId);
|
|
104
|
+
profile = profiles.find((c) => c.profileId === (req as ExtensionRunTestsRequest).profile!.id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const resolved: ResolvedTestRunRequest = {
|
|
108
|
+
targets: [],
|
|
109
|
+
exclude: (req as ExtensionRunTestsRequest).exclude,
|
|
110
|
+
isAutoRun: false,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (profile) {
|
|
114
|
+
resolved.targets.push({
|
|
115
|
+
profileGroup: profile.group,
|
|
116
|
+
profileId: profile.profileId,
|
|
117
|
+
controllerId: (req as ExtensionRunTestsRequest).controllerId,
|
|
118
|
+
testIds: (req as ExtensionRunTestsRequest).include,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const result = new TestResultImpl((req as ExtensionRunTestsRequest).id, resolved);
|
|
123
|
+
this.addTestResult(result);
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public getResult(resultId: string): ITestResult | undefined {
|
|
128
|
+
return this.results.find((r) => r.id === resultId);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public addTestResult(result: ITestResult): ITestResult {
|
|
132
|
+
if (result.completedAt === undefined) {
|
|
133
|
+
this.results.unshift(result);
|
|
134
|
+
} else {
|
|
135
|
+
const index = findFirstInSorted(
|
|
136
|
+
this.results,
|
|
137
|
+
(r) => r.completedAt !== undefined && r.completedAt <= result.completedAt!,
|
|
138
|
+
);
|
|
139
|
+
this.results.splice(index, 0, result);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.hasAnyResults.set(true);
|
|
143
|
+
|
|
144
|
+
if (result instanceof TestResultImpl) {
|
|
145
|
+
result.onComplete(() => this.onComplete(result));
|
|
146
|
+
result.onChange(this.testChangeEmitter.fire, this.testChangeEmitter);
|
|
147
|
+
this.isRunning.set(true);
|
|
148
|
+
this.changeResultEmitter.fire({ started: result });
|
|
149
|
+
} else {
|
|
150
|
+
this.changeResultEmitter.fire({ inserted: result });
|
|
151
|
+
for (const item of result.tests) {
|
|
152
|
+
for (const otherResult of this.results) {
|
|
153
|
+
if (otherResult === result) {
|
|
154
|
+
this.testChangeEmitter.fire({ item, result, reason: TestResultItemChangeReason.ComputedStateChange });
|
|
155
|
+
break;
|
|
156
|
+
} else if (otherResult.getStateById(item.item.extId) !== undefined) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public getStateById(extId: string): [results: ITestResult, item: TestResultItem] | undefined {
|
|
167
|
+
for (const result of this.results) {
|
|
168
|
+
const lookup = result.getStateById(extId);
|
|
169
|
+
if (lookup && lookup.computedState !== TestResultState.Unset) {
|
|
170
|
+
return [result, lookup];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public clear() {
|
|
177
|
+
const keep: ITestResult[] = [];
|
|
178
|
+
const removed: ITestResult[] = [];
|
|
179
|
+
for (const result of this.results) {
|
|
180
|
+
if (result.completedAt !== undefined) {
|
|
181
|
+
removed.push(result);
|
|
182
|
+
} else {
|
|
183
|
+
keep.push(result);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this._results = keep;
|
|
188
|
+
if (keep.length === 0) {
|
|
189
|
+
this.hasAnyResults.set(false);
|
|
190
|
+
}
|
|
191
|
+
this.changeResultEmitter.fire({ removed });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Injectable, Autowired } from '@opensumi/di';
|
|
2
|
+
import {
|
|
3
|
+
CancellationToken,
|
|
4
|
+
CancellationTokenSource,
|
|
5
|
+
Disposable,
|
|
6
|
+
Emitter,
|
|
7
|
+
getIcon,
|
|
8
|
+
IDisposable,
|
|
9
|
+
localize,
|
|
10
|
+
SlotLocation,
|
|
11
|
+
} from '@opensumi/ide-core-browser';
|
|
12
|
+
import { IContextKey, IContextKeyService } from '@opensumi/ide-core-browser/lib/context-key';
|
|
13
|
+
import { TestingCanRefreshTests, TestingServiceProviderCount } from '@opensumi/ide-core-browser/lib/contextkey/testing';
|
|
14
|
+
import { IMainLayoutService } from '@opensumi/ide-main-layout';
|
|
15
|
+
|
|
16
|
+
import { AmbiguousRunTestsRequest, ITestController, ITestService, TestId } from '../common';
|
|
17
|
+
import { Testing } from '../common/constants';
|
|
18
|
+
import { canUseProfileWithTest, ITestProfileService, TestProfileServiceToken } from '../common/test-profile';
|
|
19
|
+
import { ITestResultService, TestResultServiceToken } from '../common/test-result';
|
|
20
|
+
import { MainThreadTestCollection, ResolvedTestRunRequest, TestDiffOpType, TestsDiff } from '../common/testCollection';
|
|
21
|
+
import { TestingContainerId } from '../common/testing-view';
|
|
22
|
+
|
|
23
|
+
import { ITestResult } from './../common/test-result';
|
|
24
|
+
import { TestingView } from './components/testing.view';
|
|
25
|
+
|
|
26
|
+
@Injectable()
|
|
27
|
+
export class TestServiceImpl extends Disposable implements ITestService {
|
|
28
|
+
private controllers = new Map<string, ITestController>();
|
|
29
|
+
private controllerCount: IContextKey<number>;
|
|
30
|
+
private canRefreshTests: IContextKey<boolean>;
|
|
31
|
+
|
|
32
|
+
private readonly processDiffEmitter = new Emitter<TestsDiff>();
|
|
33
|
+
private viewId = '';
|
|
34
|
+
|
|
35
|
+
readonly collection = new MainThreadTestCollection(this.expandTest.bind(this));
|
|
36
|
+
readonly onDidProcessDiff = this.processDiffEmitter.event;
|
|
37
|
+
|
|
38
|
+
@Autowired(IMainLayoutService)
|
|
39
|
+
protected readonly mainlayoutService: IMainLayoutService;
|
|
40
|
+
|
|
41
|
+
@Autowired(TestResultServiceToken)
|
|
42
|
+
protected readonly resultService: ITestResultService;
|
|
43
|
+
|
|
44
|
+
@Autowired(TestProfileServiceToken)
|
|
45
|
+
protected readonly testProfiles: ITestProfileService;
|
|
46
|
+
|
|
47
|
+
@Autowired(IContextKeyService)
|
|
48
|
+
private readonly contextKeyService: IContextKeyService;
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
super();
|
|
52
|
+
this.controllerCount = TestingServiceProviderCount.bind(this.contextKeyService);
|
|
53
|
+
this.canRefreshTests = TestingCanRefreshTests.bind(this.contextKeyService);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public getTestController(controllerId: string): ITestController | undefined {
|
|
57
|
+
return this.controllers.get(controllerId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public async refreshTests(controllerId?: string): Promise<void> {
|
|
61
|
+
const cts = new CancellationTokenSource();
|
|
62
|
+
try {
|
|
63
|
+
if (controllerId) {
|
|
64
|
+
await this.controllers.get(controllerId)?.refreshTests(cts.token);
|
|
65
|
+
} else {
|
|
66
|
+
await Promise.all([...this.controllers.values()].map((c) => c.refreshTests(cts.token)));
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
cts.dispose(true);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private registerTestingExplorerView(): string {
|
|
74
|
+
this.mainlayoutService.collectViewComponent;
|
|
75
|
+
return this.mainlayoutService.collectTabbarComponent(
|
|
76
|
+
[{ id: TestingContainerId }],
|
|
77
|
+
{
|
|
78
|
+
iconClass: getIcon('test'),
|
|
79
|
+
title: localize('test.title'),
|
|
80
|
+
priority: 1,
|
|
81
|
+
containerId: Testing.ExplorerViewId,
|
|
82
|
+
component: TestingView,
|
|
83
|
+
activateKeyBinding: 'ctrlcmd+shift+t',
|
|
84
|
+
},
|
|
85
|
+
SlotLocation.left,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
registerTestController(id: string, testController: ITestController): IDisposable {
|
|
90
|
+
this.controllers.set(id, testController);
|
|
91
|
+
this.controllerCount.set(this.controllers.size);
|
|
92
|
+
this.updateCanRefresh();
|
|
93
|
+
|
|
94
|
+
if (this.controllers.size > 0 && !this.viewId) {
|
|
95
|
+
this.viewId = this.registerTestingExplorerView();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const disposable = new Disposable();
|
|
99
|
+
|
|
100
|
+
disposable.addDispose(
|
|
101
|
+
Disposable.create(() => {
|
|
102
|
+
const diff: TestsDiff = [];
|
|
103
|
+
for (const root of this.collection.rootItems) {
|
|
104
|
+
if (root.controllerId === id) {
|
|
105
|
+
diff.push([TestDiffOpType.Remove, root.item.extId]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
this.publishDiff(id, diff);
|
|
109
|
+
|
|
110
|
+
if (this.controllers.delete(id)) {
|
|
111
|
+
this.controllerCount.set(this.controllers.size);
|
|
112
|
+
this.updateCanRefresh();
|
|
113
|
+
if (this.controllers.size === 0 && this.viewId) {
|
|
114
|
+
this.mainlayoutService.disposeContainer(this.viewId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
disposable.addDispose(testController.canRefresh.onDidChange(this.updateCanRefresh, this));
|
|
121
|
+
|
|
122
|
+
return disposable;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private updateCanRefresh() {
|
|
126
|
+
this.canRefreshTests.set(Array.from(this.controllers.values()).some((c) => c.canRefresh));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public async expandTest(id: string, levels: number) {
|
|
130
|
+
await this.controllers.get(TestId.fromString(id).controllerId)?.expandTest(id, levels);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
publishDiff(_controllerId: string, diff: TestsDiff) {
|
|
134
|
+
this.collection.apply(diff);
|
|
135
|
+
this.processDiffEmitter.fire(diff);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
runTests(req: AmbiguousRunTestsRequest, token = CancellationToken.None): Promise<ITestResult> {
|
|
139
|
+
const resolved: ResolvedTestRunRequest = {
|
|
140
|
+
targets: [],
|
|
141
|
+
exclude: req.exclude?.map((t) => t.item.extId),
|
|
142
|
+
isAutoRun: req.isAutoRun,
|
|
143
|
+
};
|
|
144
|
+
const profiles = this.testProfiles.getBaseDefaultsProfile(req.group);
|
|
145
|
+
for (const profile of profiles) {
|
|
146
|
+
const testIds = req.tests.filter((t) => canUseProfileWithTest(profile, t)).map((t) => t.item.extId);
|
|
147
|
+
resolved.targets.push({
|
|
148
|
+
testIds,
|
|
149
|
+
profileGroup: profile.group,
|
|
150
|
+
profileId: profile.profileId,
|
|
151
|
+
controllerId: profile.controllerId,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return this.runResolvedTests(resolved, token);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async runResolvedTests(req: ResolvedTestRunRequest, token?: CancellationToken): Promise<ITestResult> {
|
|
159
|
+
if (!req.exclude) {
|
|
160
|
+
// default exclude
|
|
161
|
+
req.exclude = [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const result = this.resultService.createTestResult(req);
|
|
165
|
+
try {
|
|
166
|
+
const cancelSource = new CancellationTokenSource(token);
|
|
167
|
+
const requests = req.targets.map((group) =>
|
|
168
|
+
this.controllers
|
|
169
|
+
.get(group.controllerId)
|
|
170
|
+
?.runTests(
|
|
171
|
+
{
|
|
172
|
+
runId: result.id,
|
|
173
|
+
excludeExtIds: req.exclude!.filter((t) => !group.testIds.includes(t)),
|
|
174
|
+
profileId: group.profileId,
|
|
175
|
+
controllerId: group.controllerId,
|
|
176
|
+
testIds: group.testIds,
|
|
177
|
+
},
|
|
178
|
+
cancelSource.token,
|
|
179
|
+
)
|
|
180
|
+
.catch(() => {
|
|
181
|
+
//
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
await Promise.all(requests);
|
|
185
|
+
return result;
|
|
186
|
+
} finally {
|
|
187
|
+
result.markComplete();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|