Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testing: initial implementation of continuous run #170770

Merged
merged 1 commit into from Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/vs/workbench/api/browser/mainThreadTesting.ts
Expand Up @@ -175,6 +175,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
refreshTests: token => this.proxy.$refreshTests(controllerId, token),
configureRunProfile: id => this.proxy.$configureRunProfile(controllerId, id),
runTests: (reqs, token) => this.proxy.$runControllerTests(reqs, token),
startContinuousRun: (reqs, token) => this.proxy.$startContinuousRun(reqs, token),
expandTest: (testId, levels) => this.proxy.$expandTest(testId, isFinite(levels) ? levels : -1),
};

Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHost.api.impl.ts
Expand Up @@ -384,7 +384,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
// namespace: tests
const tests: typeof vscode.tests = {
createTestController(provider, label, refreshHandler?: (token: vscode.CancellationToken) => Thenable<void> | void) {
return extHostTesting.createTestController(provider, label, refreshHandler);
return extHostTesting.createTestController(extension, provider, label, refreshHandler);
},
createTestObserver() {
checkProposedApiEnabled(extension, 'testObserver');
Expand Down Expand Up @@ -1370,6 +1370,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
LinkedEditingRanges: extHostTypes.LinkedEditingRanges,
TestResultState: extHostTypes.TestResultState,
TestRunRequest: extHostTypes.TestRunRequest,
TestRunRequest2: extHostTypes.TestRunRequest2,
TestMessage: extHostTypes.TestMessage,
TestTag: extHostTypes.TestTag,
TestRunProfileKind: extHostTypes.TestRunProfileKind,
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/api/common/extHost.protocol.ts
Expand Up @@ -56,7 +56,7 @@ import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/out
import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm';
import { IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { CoverageDetails, ExtensionRunTestsRequest, IFileCoverage, ISerializedTestResults, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, RunTestForControllerRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes';
import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, IStartControllerTests, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/workbench/services/authentication/common/authentication';
Expand Down Expand Up @@ -2209,7 +2209,8 @@ export const enum ExtHostTestingResource {
}

export interface ExtHostTestingShape {
$runControllerTests(req: RunTestForControllerRequest[], token: CancellationToken): Promise<{ error?: string }[]>;
$runControllerTests(req: IStartControllerTests[], token: CancellationToken): Promise<{ error?: string }[]>;
$startContinuousRun(req: ICallProfileRunHandler[], token: CancellationToken): Promise<{ error?: string }[]>;
$cancelExtensionTestRun(runId: string | undefined): void;
/** Handles a diff of tests, as a result of a subscribeToDiffs() call */
$acceptDiff(diff: TestsDiffOp.Serialized[]): void;
Expand Down
75 changes: 58 additions & 17 deletions src/vs/workbench/api/common/extHostTesting.ts
Expand Up @@ -15,16 +15,18 @@ import { MarshalledId } from 'vs/base/common/marshallingIds';
import { deepFreeze } from 'vs/base/common/objects';
import { isDefined } from 'vs/base/common/types';
import { generateUuid } from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtHostTestingShape, ILocationDto, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostTestItemCollection, TestItemImpl, TestItemRootImpl, toItemFromContext } from 'vs/workbench/api/common/extHostTestItem';
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
import { TestRunProfileKind, TestRunRequest } from 'vs/workbench/api/common/extHostTypes';
import { TestRunProfileKind, TestRunRequest2 } from 'vs/workbench/api/common/extHostTypes';
import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId';
import { InvalidTestItemError } from 'vs/workbench/contrib/testing/common/testItemCollection';
import { AbstractIncrementalTestCollection, CoverageDetails, IFileCoverage, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, ITestItem, ITestItemContext, RunTestForControllerRequest, RunTestForControllerResult, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes';
import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestItem, ITestItemContext, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import type * as vscode from 'vscode';

interface ControllerInfo {
Expand Down Expand Up @@ -69,7 +71,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
/**
* Implements vscode.test.registerTestProvider
*/
public createTestController(controllerId: string, label: string, refreshHandler?: (token: CancellationToken) => Thenable<void> | void): vscode.TestController {
public createTestController(extension: IExtensionDescription, controllerId: string, label: string, refreshHandler?: (token: CancellationToken) => Thenable<void> | void): vscode.TestController {
if (this.controllers.has(controllerId)) {
throw new Error(`Attempt to insert a duplicate controller with ID "${controllerId}"`);
}
Expand Down Expand Up @@ -101,15 +103,19 @@ export class ExtHostTesting implements ExtHostTestingShape {
get id() {
return controllerId;
},
createRunProfile: (label, group, runHandler, isDefault, tag?: vscode.TestTag | undefined) => {
createRunProfile: (label, group, runHandler, isDefault, tag?: vscode.TestTag | undefined, supportsContinuousRun?: boolean) => {
// Derive the profile ID from a hash so that the same profile will tend
// to have the same hashes, allowing re-run requests to work across reloads.
let profileId = hash(label);
while (profiles.has(profileId)) {
profileId++;
}

return new TestRunProfileImpl(this.proxy, profiles, controllerId, profileId, label, group, runHandler, isDefault, tag);
if (supportsContinuousRun !== undefined) {
checkProposedApiEnabled(extension, 'testContinuousRun');
}

return new TestRunProfileImpl(this.proxy, profiles, controllerId, profileId, label, group, runHandler, isDefault, tag, supportsContinuousRun);
},
createTestItem(id, label, uri) {
return new TestItemImpl(controllerId, id, label, uri);
Expand Down Expand Up @@ -250,13 +256,31 @@ export class ExtHostTesting implements ExtHostTestingShape {
/**
* Runs tests with the given set of IDs. Allows for test from multiple
* providers to be run.
* @override
* @inheritdoc
*/
public async $runControllerTests(reqs: RunTestForControllerRequest[], token: CancellationToken): Promise<RunTestForControllerResult[]> {
return Promise.all(reqs.map(req => this.runControllerTestRequest(req, token)));
public async $runControllerTests(reqs: IStartControllerTests[], token: CancellationToken): Promise<IStartControllerTestsResult[]> {
return Promise.all(reqs.map(req => this.runControllerTestRequest(req, false, token)));
}

public async runControllerTestRequest(req: RunTestForControllerRequest, token: CancellationToken): Promise<RunTestForControllerResult> {
/**
* Starts continuous test runs with the given set of IDs. Allows for test from
* multiple providers to be run.
* @inheritdoc
*/
public async $startContinuousRun(reqs: IStartControllerTests[], token: CancellationToken): Promise<IStartControllerTestsResult[]> {
const cts = new CancellationTokenSource(token);
const res = await Promise.all(reqs.map(req => this.runControllerTestRequest(req, true, cts.token)));

// avoid returning until cancellation is requested, otherwise ipc disposes of the token
if (!token.isCancellationRequested && !res.some(r => r.error)) {
await new Promise(r => token.onCancellationRequested(r));
}

cts.dispose(true);
return res;
}

private async runControllerTestRequest(req: ICallProfileRunHandler | ICallProfileRunHandler, isContinuous: boolean, token: CancellationToken): Promise<IStartControllerTestsResult> {
const lookup = this.controllers.get(req.controllerId);
if (!lookup) {
return {};
Expand All @@ -283,13 +307,14 @@ export class ExtHostTesting implements ExtHostTestingShape {
return {};
}

const publicReq = new TestRunRequest(
const publicReq = new TestRunRequest2(
includeTests.some(i => i.actual instanceof TestItemRootImpl) ? undefined : includeTests.map(t => t.actual),
excludeTests.map(t => t.actual),
profile,
isContinuous,
);

const tracker = this.runTracker.prepareForMainThreadTestRun(
const tracker = isStartControllerTests(req) && this.runTracker.prepareForMainThreadTestRun(
publicReq,
TestRunDto.fromInternal(req, lookup.collection),
token,
Expand All @@ -301,11 +326,13 @@ export class ExtHostTesting implements ExtHostTestingShape {
} catch (e) {
return { error: String(e) };
} finally {
if (tracker.hasRunningTasks && !token.isCancellationRequested) {
await Event.toPromise(tracker.onEnd);
}
if (tracker) {
if (tracker.hasRunningTasks && !token.isCancellationRequested) {
await Event.toPromise(tracker.onEnd);
}

tracker.dispose();
tracker.dispose();
}
}
}

Expand Down Expand Up @@ -584,7 +611,7 @@ export class TestRunCoordinator {
/**
* Implements the public `createTestRun` API.
*/
public createTestRun(controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest, name: string | undefined, persist: boolean): vscode.TestRun {
public createTestRun(controllerId: string, collection: ExtHostTestItemCollection, request: vscode.TestRunRequest2, name: string | undefined, persist: boolean): vscode.TestRun {
const existing = this.tracked.get(request);
if (existing) {
return existing.createRun(name);
Expand All @@ -596,6 +623,7 @@ export class TestRunCoordinator {
const profile = tryGetProfileFromTestRunReq(request);
this.proxy.$startedExtensionTestRun({
controllerId,
continuous: !!request.continuous,
profile: profile && { group: profileGroupToBitset[profile.kind], id: profile.profileId },
exclude: request.exclude?.map(t => TestId.fromExtHostTestItem(t, collection.root.id).toString()) ?? [],
id: dto.id,
Expand Down Expand Up @@ -647,7 +675,7 @@ export class TestRunDto {
);
}

public static fromInternal(request: RunTestForControllerRequest, collection: ExtHostTestItemCollection) {
public static fromInternal(request: IStartControllerTests, collection: ExtHostTestItemCollection) {
return new TestRunDto(
request.controllerId,
request.runId,
Expand Down Expand Up @@ -948,6 +976,17 @@ export class TestRunProfileImpl implements vscode.TestRunProfile {
}
}

public get supportsContinuousRun() {
return this._supportsContinuousRun;
}

public set supportsContinuousRun(supports: boolean) {
if (supports !== this._supportsContinuousRun) {
this._supportsContinuousRun = supports;
this.#proxy.$updateTestRunConfig(this.controllerId, this.profileId, { supportsContinuousRun: supports });
}
}

public get isDefault() {
return this._isDefault;
}
Expand Down Expand Up @@ -993,6 +1032,7 @@ export class TestRunProfileImpl implements vscode.TestRunProfile {
public runHandler: (request: vscode.TestRunRequest, token: vscode.CancellationToken) => Thenable<void> | void,
private _isDefault = false,
public _tag: vscode.TestTag | undefined = undefined,
private _supportsContinuousRun = false,
) {
this.#proxy = proxy;
this.#profiles = profiles;
Expand All @@ -1011,6 +1051,7 @@ export class TestRunProfileImpl implements vscode.TestRunProfile {
group: groupBitset,
isDefault: _isDefault,
hasConfigurationHandler: false,
supportsContinuousRun: _supportsContinuousRun,
});
}

Expand Down
12 changes: 12 additions & 0 deletions src/vs/workbench/api/common/extHostTypes.ts
Expand Up @@ -3766,6 +3766,18 @@ export class TestRunRequest implements vscode.TestRunRequest {
) { }
}

@es5ClassCompat
export class TestRunRequest2 extends TestRunRequest implements vscode.TestRunRequest2 {
constructor(
include: vscode.TestItem[] | undefined = undefined,
exclude: vscode.TestItem[] | undefined = undefined,
profile: vscode.TestRunProfile | undefined = undefined,
public readonly continuous = false,
) {
super(include, exclude, profile);
}
}

@es5ClassCompat
export class TestMessage implements vscode.TestMessage {
public expectedOutput?: string;
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/test/browser/extHostTesting.test.ts
Expand Up @@ -701,6 +701,7 @@ suite('ExtHost Testing', () => {
include: [single.root.id],
exclude: [new TestId(['ctrlId', 'id-b']).toString()],
persist: false,
continuous: false,
}]
]);

Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/testing/browser/icons.ts
Expand Up @@ -25,6 +25,8 @@ export const testingShowAsTree = registerIcon('testing-show-as-list-icon', Codic

export const testingUpdateProfiles = registerIcon('testing-update-profiles', Codicon.gear, localize('testingUpdateProfiles', 'Icon shown to update test profiles.'));
export const testingRefreshTests = registerIcon('testing-refresh-tests', Codicon.refresh, localize('testingRefreshTests', 'Icon on the button to refresh tests.'));
export const testingTurnContinuousRunOn = registerIcon('testing-turn-continuous-run-on', Codicon.eye, localize('testingTurnContinuousRunOn', 'Icon to turn continuous test runs on.'));
export const testingTurnContinuousRunOff = registerIcon('testing-turn-continuous-run-pff', Codicon.eyeClosed, localize('testingTurnContinuousRunOff', 'Icon to turn continuous test runs off.'));
export const testingCancelRefreshTests = registerIcon('testing-cancel-refresh-tests', Codicon.stop, localize('testingCancelRefreshTests', 'Icon on the button to cancel refreshing tests.'));

export const testingStatesToIcons = new Map<TestResultState, ThemeIcon>([
Expand Down