testService.ts 6.6 KB
Newer Older
C
Connor Peet 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { CancellationToken } from 'vs/base/common/cancellation';
C
Connor Peet 已提交
7
import { Event } from 'vs/base/common/event';
8
import { DisposableStore, IDisposable, IReference } from 'vs/base/common/lifecycle';
C
Connor Peet 已提交
9 10 11
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol';
12
import { ObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
13
import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsRequest, TestIdPath, TestIdWithSrc, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
14
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
C
Connor Peet 已提交
15 16 17 18

export const ITestService = createDecorator<ITestService>('testService');

export interface MainTestController {
19 20
	expandTest(src: TestIdWithSrc, levels: number): Promise<void>;
	lookupTest(test: TestIdWithSrc): Promise<InternalTestItem | undefined>;
21
	runTests(request: RunTestForProviderRequest, token: CancellationToken): Promise<void>;
C
Connor Peet 已提交
22 23 24 25
}

export type TestDiffListener = (diff: TestsDiff) => void;

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
export interface IMainThreadTestCollection extends AbstractIncrementalTestCollection<IncrementalTestCollectionItem> {
	onPendingRootProvidersChange: Event<number>;
	onBusyProvidersChange: Event<number>;

	/**
	 * Number of test root sources who are yet to report.
	 */
	pendingRootProviders: number;

	/**
	 * Number of providers working to discover tests.
	 */
	busyProviders: number;

	/**
	 * Root node IDs.
	 */
	rootIds: ReadonlySet<string>;

45 46 47 48 49
	/**
	 * Iterates over every test in the collection.
	 */
	all: Iterable<IncrementalTestCollectionItem>;

50 51 52 53 54
	/**
	 * Gets a node in the collection by ID.
	 */
	getNodeById(id: string): IncrementalTestCollectionItem | undefined;

55 56 57 58 59 60
	/**
	 * Requests that children be revealed for the given test. "Levels" may
	 * be infinite.
	 */
	expand(testId: string, levels: number): Promise<void>;

61 62 63 64 65 66 67
	/**
	 * Gets a diff that adds all items currently in the tree to a new collection,
	 * allowing it to fully hydrate.
	 */
	getReviverDiff(): TestsDiff;
}

68 69
export const waitForAllRoots = (collection: IMainThreadTestCollection, ct = CancellationToken.None) => {
	if (collection.pendingRootProviders === 0 || ct.isCancellationRequested) {
70 71 72
		return Promise.resolve();
	}

73
	const disposable = new DisposableStore();
74
	return new Promise<void>(resolve => {
75
		disposable.add(collection.onPendingRootProvidersChange(count => {
76 77 78
			if (count === 0) {
				resolve();
			}
79
		}));
80

81 82
		disposable.add(ct.onCancellationRequested(() => resolve()));
	}).finally(() => disposable.dispose());
83 84
};

85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
/**
 * Ensures the test with the given path exists in the collection, if possible.
 * If cancellation is requested, or the test cannot be found, it will return
 * undefined.
 */
export const getTestByPath = async (collection: IMainThreadTestCollection, idPath: TestIdPath, ct = CancellationToken.None) => {
	await waitForAllRoots(collection, ct);

	// Expand all direct children since roots might well have different IDs, but
	// children should start matching.
	await Promise.all([...collection.rootIds].map(r => collection.expand(r, 0)));

	if (ct.isCancellationRequested) {
		return undefined;
	}

	let expandToLevel = 0;
	for (let i = idPath.length - 1; !ct.isCancellationRequested && i >= expandToLevel;) {
		const id = idPath[i];
		const existing = collection.getNodeById(id);
		if (!existing) {
			i--;
			continue;
		}

		if (i === idPath.length - 1) {
			return existing;
		}

		await collection.expand(id, 0);
		expandToLevel = i + 1; // avoid an infinite loop if the test does not exist
		i = idPath.length - 1;
	}
	return undefined;
};

/**
 * Waits for all test in the hierarchy to be fulfilled before returning.
 * If cancellation is requested, it will return early.
 */
export const getAllTestsInHierarchy = async (collection: IMainThreadTestCollection, ct = CancellationToken.None) => {
126 127
	await waitForAllRoots(collection, ct);

128
	if (ct.isCancellationRequested) {
129
		return;
130 131
	}

132
	let l: IDisposable;
133

134 135 136 137
	await Promise.race([
		Promise.all([...collection.rootIds].map(r => collection.expand(r, Infinity))),
		new Promise(r => { l = ct.onCancellationRequested(r); }),
	]).finally(() => l?.dispose());
138 139
};

140 141 142 143 144 145 146 147
/**
 * An instance of the RootProvider should be registered for each extension
 * host.
 */
export interface ITestRootProvider {
	// todo: nothing, yet
}

C
Connor Peet 已提交
148 149
export interface ITestService {
	readonly _serviceBrand: undefined;
C
Connor Peet 已提交
150 151 152
	readonly onShouldSubscribe: Event<{ resource: ExtHostTestingResource, uri: URI; }>;
	readonly onShouldUnsubscribe: Event<{ resource: ExtHostTestingResource, uri: URI; }>;
	readonly onDidChangeProviders: Event<{ delta: number; }>;
C
Connor Peet 已提交
153
	readonly providers: number;
C
Connor Peet 已提交
154
	readonly subscriptions: ReadonlyArray<{ resource: ExtHostTestingResource, uri: URI; }>;
155 156
	readonly testRuns: Iterable<RunTestsRequest>;

157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
	/**
	 * Set of test IDs the user asked to exclude.
	 */
	readonly excludeTests: ObservableValue<ReadonlySet<string>>;

	/**
	 * Sets whether a test is excluded.
	 */
	setTestExcluded(testId: string, exclude?: boolean): void;

	/**
	 * Removes all test exclusions.
	 */
	clearExcludedTests(): void;

172
	/**
173 174 175 176
	 * Updates the number of sources who provide test roots when subscription
	 * is requested. This is equal to the number of extension hosts, and used
	 * with `TestDiffOpType.DeltaRootsComplete` to signal when all roots
	 * are available.
177
	 */
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
	registerRootProvider(provider: ITestRootProvider): IDisposable;

	/**
	 * Registers an interface that runs tests for the given provider ID.
	 */
	registerTestController(providerId: string, controller: MainTestController): IDisposable;

	/**
	 * Requests that tests be executed.
	 */
	runTests(req: RunTestsRequest, token?: CancellationToken): Promise<ITestResult>;

	/**
	 * Cancels an ongoign test run request.
	 */
	cancelTestRun(req: RunTestsRequest): void;

	publishDiff(resource: ExtHostTestingResource, uri: URI, diff: TestsDiff): void;
	subscribeToDiffs(resource: ExtHostTestingResource, uri: URI, acceptDiff?: TestDiffListener): IReference<IMainThreadTestCollection>;

198

C
Connor Peet 已提交
199 200 201
	/**
	 * Looks up a test, by a request to extension hosts.
	 */
202
	lookupTest(test: TestIdWithSrc): Promise<InternalTestItem | undefined>;
C
Connor Peet 已提交
203

204 205 206 207
	/**
	 * Requests to resubscribe to all active subscriptions, discarding old tests.
	 */
	resubscribeToAllTests(): void;
C
Connor Peet 已提交
208
}