Skip to content

Commit 808266d

Browse files
authored
Allow tests to be loaded asynchronously (microsoft#119537)
* initial wip in the extension host * wip * wip * wip * continued progress * update api from discussion * update for new proposal * wip * update to lastest testing api, almost everything working Refs microsoft#115089 Design https://gist.github.com/connor4312/73f1883d720654834b7fd40550d3b6e0 * re-wire retirement state * update actions to new async test structure * minor cleanup * remove unused es2018 that failed build
1 parent 6e2cb85 commit 808266d

33 files changed

+1619
-1129
lines changed

.eslintrc.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,7 @@
982982
"allowed": [
983983
"FileSystemProvider",
984984
"TreeDataProvider",
985+
"TestProvider",
985986
"CustomEditorProvider",
986987
"CustomReadonlyEditorProvider",
987988
"TerminalLinkProvider",
@@ -1015,6 +1016,7 @@
10151016
"override",
10161017
"receive",
10171018
"register",
1019+
"remove",
10181020
"rename",
10191021
"save",
10201022
"send",

src/vs/base/browser/ui/tree/asyncDataTree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
616616
return this.tree.isCollapsible(this.getDataNode(element));
617617
}
618618

619-
isCollapsed(element: T): boolean {
619+
isCollapsed(element: TInput | T): boolean {
620620
return this.tree.isCollapsed(this.getDataNode(element));
621621
}
622622

src/vs/base/common/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ export function isNumber(obj: unknown): obj is number {
5050
return (typeof obj === 'number' && !isNaN(obj));
5151
}
5252

53+
/**
54+
* @returns whether the provided parameter is an Iterable, casting to the given generic
55+
*/
56+
export function isIterable<T>(obj: unknown): obj is Iterable<T> {
57+
return !!obj && typeof (obj as any)[Symbol.iterator] === 'function';
58+
}
59+
5360
/**
5461
* @returns whether the provided parameter is a JavaScript Boolean or not.
5562
*/

src/vs/vscode.proposed.d.ts

Lines changed: 122 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,33 +2185,6 @@ declare module 'vscode' {
21852185
* in {@link onDidChangeTest} when a test is added or removed.
21862186
*/
21872187
readonly root: T;
2188-
2189-
/**
2190-
* An event that fires when an existing test `root` changes. This can be
2191-
* a result of a property update, or an update to its children. Changes
2192-
* made to tests will not be visible to {@link TestObserver} instances
2193-
* until this event is fired.
2194-
*
2195-
* When a change is signalled, VS Code will check for any new or removed
2196-
* direct children of the changed ite, For example, firing the event with
2197-
* the {@link testRoot} will detect any new children in `root.children`.
2198-
*/
2199-
readonly onDidChangeTest: Event<T>;
2200-
2201-
/**
2202-
* Promise that should be resolved when all tests that are initially
2203-
* defined have been discovered. The provider should continue to watch for
2204-
* changes and fire `onDidChangeTest` until the hierarchy is disposed.
2205-
*/
2206-
readonly discoveredInitialTests?: Thenable<unknown>;
2207-
2208-
/**
2209-
* An event that fires when a test becomes outdated, as a result of
2210-
* file changes, for example. In "auto run" mode, tests that are outdated
2211-
* will be automatically re-run after a short delay. Firing a test
2212-
* with children will mark the entire subtree as outdated.
2213-
*/
2214-
readonly onDidInvalidateTest?: Event<T>;
22152188
}
22162189

22172190
/**
@@ -2233,8 +2206,9 @@ declare module 'vscode' {
22332206
*
22342207
* @param workspace The workspace in which to observe tests
22352208
* @param cancellationToken Token that signals the used asked to abort the test run.
2209+
* @returns the root test item for the workspace
22362210
*/
2237-
provideWorkspaceTestHierarchy(workspace: WorkspaceFolder, token: CancellationToken): ProviderResult<TestHierarchy<T>>;
2211+
provideWorkspaceTestRoot(workspace: WorkspaceFolder, token: CancellationToken): ProviderResult<T>;
22382212

22392213
/**
22402214
* Requests that tests be provided for the given document. This will be
@@ -2246,18 +2220,21 @@ declare module 'vscode' {
22462220
* saved, if possible.
22472221
*
22482222
* If the test system is not able to provide or estimate for tests on a
2249-
* per-file basis, this method may not be implemented. In that case, VS
2250-
* Code will request and use the information from the workspace hierarchy.
2223+
* per-file basis, this method may not be implemented. In that case, the
2224+
* editor will request and use the information from the workspace tree.
22512225
*
22522226
* @param document The document in which to observe tests
22532227
* @param cancellationToken Token that signals the used asked to abort the test run.
2228+
* @returns the root test item for the workspace
22542229
*/
2255-
provideDocumentTestHierarchy?(document: TextDocument, token: CancellationToken): ProviderResult<TestHierarchy<T>>;
2230+
provideDocumentTestRoot?(document: TextDocument, token: CancellationToken): ProviderResult<T>;
22562231

22572232
/**
2233+
* @todo this will move out of the provider soon
2234+
* @todo this will eventually need to be able to return a summary report, coverage for example.
2235+
*
22582236
* Starts a test run. This should cause {@link onDidChangeTest} to
22592237
* fire with update test states during the run.
2260-
* @todo this will eventually need to be able to return a summary report, coverage for example.
22612238
* @param options Options for this test run
22622239
* @param cancellationToken Token that signals the used asked to abort the test run.
22632240
*/
@@ -2305,18 +2282,55 @@ declare module 'vscode' {
23052282
setState(test: T, state: TestState): void;
23062283
}
23072284

2285+
export interface TestChildrenCollection<T> extends Iterable<T> {
2286+
/**
2287+
* Gets the number of children in the collection.
2288+
*/
2289+
readonly size: number;
2290+
2291+
/**
2292+
* Gets an existing TestItem by its ID, if it exists.
2293+
* @param id ID of the test.
2294+
* @returns the TestItem instance if it exists.
2295+
*/
2296+
get(id: string): T | undefined;
2297+
2298+
/**
2299+
* Adds a new child test item. No-ops if the test was already a child.
2300+
* @param child The test item to add.
2301+
*/
2302+
add(child: T): void;
2303+
2304+
/**
2305+
* Removes the child test item by reference or ID from the collection.
2306+
* @param child Child ID or instance to remove.
2307+
*/
2308+
delete(child: T | string): void;
2309+
2310+
/**
2311+
* Removes all children from the collection.
2312+
*/
2313+
clear(): void;
2314+
}
2315+
23082316
/**
23092317
* A test item is an item shown in the "test explorer" view. It encompasses
23102318
* both a suite and a test, since they have almost or identical capabilities.
23112319
*/
2312-
export class TestItem {
2320+
export class TestItem<TChildren = any> {
23132321
/**
23142322
* Unique identifier for the TestItem. This is used to correlate
23152323
* test results and tests in the document with those in the workspace
23162324
* (test explorer). This must not change for the lifetime of the TestItem.
23172325
*/
23182326
readonly id: string;
23192327

2328+
/**
2329+
* A set of children this item has. You can add new children to it, which
2330+
* will propagate to the editor UI.
2331+
*/
2332+
readonly children: TestChildrenCollection<TChildren>;
2333+
23202334
/**
23212335
* Display name describing the test case.
23222336
*/
@@ -2327,6 +2341,12 @@ declare module 'vscode' {
23272341
*/
23282342
description?: string;
23292343

2344+
/**
2345+
* Location of the test in the workspace. This is used to show line
2346+
* decorations and code lenses for the test.
2347+
*/
2348+
location?: Location;
2349+
23302350
/**
23312351
* Whether this test item can be run individually, defaults to `true`.
23322352
*
@@ -2344,22 +2364,53 @@ declare module 'vscode' {
23442364
debuggable: boolean;
23452365

23462366
/**
2347-
* Location of the test in the workspace. This is used to show line
2348-
* decorations and code lenses for the test.
2349-
*/
2350-
location?: Location;
2351-
2352-
/**
2353-
* Optional list of nested tests for this item.
2367+
* Whether this test item can be expanded in the tree view, implying it
2368+
* has (or may have) children. If this is given, the item may be
2369+
* passed to the {@link TestHierarchy.getChildren} method.
23542370
*/
2355-
children: TestItem[];
2371+
expandable: boolean;
23562372

23572373
/**
23582374
* Creates a new TestItem instance.
23592375
* @param id Value of the "id" property
23602376
* @param label Value of the "label" property.
2377+
* @param parent Parent of this item. This should only be defined for the
2378+
* test root.
2379+
*/
2380+
constructor(id: string, label: string, expandable: boolean);
2381+
2382+
/**
2383+
* Marks the test as outdated. This can happen as a result of file changes,
2384+
* for example. In "auto run" mode, tests that are outdated will be
2385+
* automatically re-run after a short delay. Invoking this on a
2386+
* test with children will mark the entire subtree as outdated.
2387+
*
2388+
* Extensions should generally not override this method.
2389+
*/
2390+
invalidate(): void;
2391+
2392+
/**
2393+
* Requests the children of the test item. Extensions should override this
2394+
* method for any test that can discover children.
2395+
*
2396+
* When called, the item should discover tests and update its's `children`.
2397+
* The provider will be marked as 'busy' when this method is called, and
2398+
* the provider should report `{ busy: false }` to {@link Progress.report}
2399+
* once discovery is complete.
2400+
*
2401+
* The item should continue watching for changes to the children and
2402+
* firing updates until the token is cancelled. The process of watching
2403+
* the tests may involve creating a file watcher, for example.
2404+
*
2405+
* The editor will only call this method when it's interested in refreshing
2406+
* the children of the item, and will not call it again while there's an
2407+
* existing, uncancelled discovery for an item.
2408+
*
2409+
* @param token Cancellation for the request. Cancellation will be
2410+
* requested if the test changes before the previous call completes.
2411+
* @returns a provider result of child test items
23612412
*/
2362-
constructor(id: string, label: string);
2413+
discoverChildren(progress: Progress<{ busy: boolean }>, token: CancellationToken): void;
23632414
}
23642415

23652416
/**
@@ -2483,23 +2534,46 @@ declare module 'vscode' {
24832534
* List of test results. The items in this array are the items that
24842535
* were passed in the {@link test.runTests} method.
24852536
*/
2486-
results: ReadonlyArray<Readonly<TestItemWithResults>>;
2537+
results: ReadonlyArray<Readonly<TestResultSnapshot>>;
24872538
}
24882539

24892540
/**
2490-
* A {@link TestItem} with an associated result, which appear or can be
2491-
* provided in {@link TestResult} interfaces.
2541+
* A {@link TestItem}-like interface with an associated result, which appear
2542+
* or can be provided in {@link TestResult} interfaces.
24922543
*/
2493-
export interface TestItemWithResults extends TestItem {
2544+
export interface TestResultSnapshot {
2545+
/**
2546+
* Unique identifier that matches that of the associated TestItem.
2547+
* This is used to correlate test results and tests in the document with
2548+
* those in the workspace (test explorer).
2549+
*/
2550+
readonly id: string;
2551+
2552+
/**
2553+
* Display name describing the test case.
2554+
*/
2555+
readonly label: string;
2556+
2557+
/**
2558+
* Optional description that appears next to the label.
2559+
*/
2560+
readonly description?: string;
2561+
2562+
/**
2563+
* Location of the test in the workspace. This is used to show line
2564+
* decorations and code lenses for the test.
2565+
*/
2566+
readonly location?: Location;
2567+
24942568
/**
24952569
* Current result of the test.
24962570
*/
2497-
result: TestState;
2571+
readonly result: TestState;
24982572

24992573
/**
25002574
* Optional list of nested tests for this item.
25012575
*/
2502-
children: Readonly<TestItemWithResults>[];
2576+
readonly children: Readonly<TestResultSnapshot>[];
25032577
}
25042578

25052579
//#endregion

src/vs/workbench/api/browser/mainThreadTesting.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import { Range } from 'vs/editor/common/core/range';
1111
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
1212
import { getTestSubscriptionKey, ISerializedTestResults, ITestState, RunTestsRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
1313
import { HydratedTestResult, ITestResultService, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResultService';
14-
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
14+
import { ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService';
1515
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
1616

1717
const reviveDiff = (diff: TestsDiff) => {
1818
for (const entry of diff) {
1919
if (entry[0] === TestDiffOpType.Add || entry[0] === TestDiffOpType.Update) {
2020
const item = entry[1];
21-
if (item.item.location) {
21+
if (item.item?.location) {
2222
item.item.location.uri = URI.revive(item.item.location.uri);
2323
item.item.location.range = Range.lift(item.item.location.range);
2424
}
@@ -27,7 +27,7 @@ const reviveDiff = (diff: TestsDiff) => {
2727
};
2828

2929
@extHostNamedCustomer(MainContext.MainThreadTesting)
30-
export class MainThreadTesting extends Disposable implements MainThreadTestingShape {
30+
export class MainThreadTesting extends Disposable implements MainThreadTestingShape, ITestRootProvider {
3131
private readonly proxy: ExtHostTestingShape;
3232
private readonly testSubscriptions = new Map<string, IDisposable>();
3333
private readonly testProviderRegistrations = new Map<string, IDisposable>();
@@ -56,7 +56,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
5656
}
5757
}));
5858

59-
testService.updateRootProviderCount(1);
59+
this._register(testService.registerRootProvider(this));
6060

6161
for (const { resource, uri } of this.testService.subscriptions) {
6262
this.proxy.$subscribeToTests(resource, uri);
@@ -66,25 +66,14 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
6666
/**
6767
* @inheritdoc
6868
*/
69-
$publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void {
69+
public $publishExtensionProvidedResults(results: ISerializedTestResults, persist: boolean): void {
7070
this.resultService.push(new HydratedTestResult(results, persist));
7171
}
7272

7373
/**
7474
* @inheritdoc
7575
*/
76-
$retireTest(extId: string): void {
77-
for (const result of this.resultService.results) {
78-
if (result instanceof LiveTestResult) {
79-
result.retire(extId);
80-
}
81-
}
82-
}
83-
84-
/**
85-
* @inheritdoc
86-
*/
87-
$updateTestStateInRun(runId: string, testId: string, state: ITestState): void {
76+
public $updateTestStateInRun(runId: string, testId: string, state: ITestState): void {
8877
const r = this.resultService.getResult(runId);
8978
if (r && r instanceof LiveTestResult) {
9079
for (const message of state.messages) {
@@ -105,6 +94,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
10594
const disposable = this.testService.registerTestController(id, {
10695
runTests: (req, token) => this.proxy.$runTestsForProvider(req, token),
10796
lookupTest: test => this.proxy.$lookupTest(test),
97+
expandTest: (src, levels) => this.proxy.$expandTest(src, isFinite(levels) ? levels : -1),
10898
});
10999

110100
this.testProviderRegistrations.set(id, disposable);
@@ -121,7 +111,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
121111
/**
122112
* @inheritdoc
123113
*/
124-
$subscribeToDiffs(resource: ExtHostTestingResource, uriComponents: UriComponents): void {
114+
public $subscribeToDiffs(resource: ExtHostTestingResource, uriComponents: UriComponents): void {
125115
const uri = URI.revive(uriComponents);
126116
const disposable = this.testService.subscribeToDiffs(resource, uri,
127117
diff => this.proxy.$acceptDiff(resource, uriComponents, diff));
@@ -152,7 +142,6 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
152142

153143
public dispose() {
154144
super.dispose();
155-
this.testService.updateRootProviderCount(-1);
156145
for (const subscription of this.testSubscriptions.values()) {
157146
subscription.dispose();
158147
}

0 commit comments

Comments
 (0)