Skip to content

Commit

Permalink
testing: add commands for toggling continuous run state of test items (
Browse files Browse the repository at this point in the history
  • Loading branch information
connor4312 authored and mengjiechen committed Nov 26, 2024
1 parent b0b2d5f commit 0f7bdb7
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 31 deletions.
10 changes: 10 additions & 0 deletions src/vs/workbench/api/common/extHostApiCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,16 @@ const newCommands: ApiCommand[] = [
[ApiCommandArgument.TestItem],
ApiCommandResult.Void
),
new ApiCommand(
'vscode.startContinuousTestRun', 'testing.startContinuousRunFromExtension', 'Starts running the given tests with continuous run mode.',
[ApiCommandArgument.TestProfile, ApiCommandArgument.Arr(ApiCommandArgument.TestItem)],
ApiCommandResult.Void
),
new ApiCommand(
'vscode.stopContinuousTestRun', 'testing.stopContinuousRunFromExtension', 'Stops running the given tests with continuous run mode.',
[ApiCommandArgument.Arr(ApiCommandArgument.TestItem)],
ApiCommandResult.Void
),
// --- continue edit session
new ApiCommand(
'vscode.experimental.editSession.continue', '_workbench.editSessions.actions.continueEditSession', 'Continue the current edit session in a different workspace',
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHostCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ export class ApiCommandArgument<V, O = V> {
static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof extHostTypes.CallHierarchyItem, extHostTypeConverter.CallHierarchyItem.from);
static readonly TypeHierarchyItem = new ApiCommandArgument('item', 'A type hierarchy item', v => v instanceof extHostTypes.TypeHierarchyItem, extHostTypeConverter.TypeHierarchyItem.from);
static readonly TestItem = new ApiCommandArgument('testItem', 'A VS Code TestItem', v => v instanceof TestItemImpl, extHostTypeConverter.TestItem.from);
static readonly TestProfile = new ApiCommandArgument('testProfile', 'A VS Code test profile', v => v instanceof extHostTypes.TestRunProfileBase, extHostTypeConverter.TestRunProfile.from);

constructor(
readonly name: string,
Expand Down
9 changes: 5 additions & 4 deletions src/vs/workbench/api/common/extHostTestItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type * as vscode from 'vscode';
import { URI } from '../../../base/common/uri.js';
import * as editorRange from '../../../editor/common/core/range.js';
import { createPrivateApiFor, getPrivateApiFor, IExtHostTestItemApi } from './extHostTestingPrivateApi.js';
import { TestId, TestIdPathParts } from '../../contrib/testing/common/testId.js';
import { createTestItemChildren, ExtHostTestItemEvent, ITestChildrenLike, ITestItemApi, ITestItemChildren, TestItemCollection, TestItemEventOp } from '../../contrib/testing/common/testItemCollection.js';
import { denamespaceTestTag, ITestItem, ITestItemContext } from '../../contrib/testing/common/testTypes.js';
import type * as vscode from 'vscode';
import * as Convert from './extHostTypeConverters.js';
import { URI } from '../../../base/common/uri.js';
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js';
import { createPrivateApiFor, getPrivateApiFor, IExtHostTestItemApi } from './extHostTestingPrivateApi.js';
import * as Convert from './extHostTypeConverters.js';

const testItemPropAccessor = <K extends keyof vscode.TestItem>(
api: IExtHostTestItemApi,
Expand Down
40 changes: 16 additions & 24 deletions src/vs/workbench/api/common/extHostTesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

/* eslint-disable local/code-no-native-private */

import type * as vscode from 'vscode';
import { RunOnceScheduler } from '../../../base/common/async.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
Expand All @@ -20,19 +21,18 @@ import { IPosition } from '../../../editor/common/core/position.js';
import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../platform/log/common/log.js';
import { TestCommandId } from '../../contrib/testing/common/constants.js';
import { TestId, TestPosition } from '../../contrib/testing/common/testId.js';
import { InvalidTestItemError } from '../../contrib/testing/common/testItemCollection.js';
import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, ITestRunProfile, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestControllerCapability, TestMessageFollowupRequest, TestMessageFollowupResponse, TestResultState, TestsDiff, TestsDiffOp, isStartControllerTests } from '../../contrib/testing/common/testTypes.js';
import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';
import { ExtHostTestingShape, ILocationDto, MainContext, MainThreadTestingShape } from './extHost.protocol.js';
import { IExtHostCommands } from './extHostCommands.js';
import { IExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js';
import { IExtHostRpcService } from './extHostRpcService.js';
import { ExtHostTestItemCollection, TestItemImpl, TestItemRootImpl, toItemFromContext } from './extHostTestItem.js';
import * as Convert from './extHostTypeConverters.js';
import { FileCoverage, TestRunProfileKind, TestRunRequest } from './extHostTypes.js';
import { TestCommandId } from '../../contrib/testing/common/constants.js';
import { TestId, TestPosition } from '../../contrib/testing/common/testId.js';
import { InvalidTestItemError } from '../../contrib/testing/common/testItemCollection.js';
import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, ITestRunProfile, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestControllerCapability, TestMessageFollowupRequest, TestMessageFollowupResponse, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from '../../contrib/testing/common/testTypes.js';
import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';
import type * as vscode from 'vscode';
import { FileCoverage, TestRunProfileBase, TestRunRequest } from './extHostTypes.js';

interface ControllerInfo {
controller: vscode.TestController;
Expand Down Expand Up @@ -256,7 +256,7 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {

await this.proxy.$runTests({
preserveFocus: req.preserveFocus ?? true,
group: profileGroupToBitset[profile.kind],
group: Convert.TestRunProfileKind.from(profile.kind),
targets: [{
testIds: req.include?.map(t => TestId.fromExtHostTestItem(t, controller.collection.root.id).toString()) ?? [controller.collection.root.id],
profileId: profile.profileId,
Expand Down Expand Up @@ -965,7 +965,7 @@ export class TestRunCoordinator {
this.proxy.$startedExtensionTestRun({
controllerId,
continuous: !!request.continuous,
profile: profile && { group: profileGroupToBitset[profile.kind], id: profile.profileId },
profile: profile && { group: Convert.TestRunProfileKind.from(profile.kind), id: profile.profileId },
exclude: request.exclude?.map(t => TestId.fromExtHostTestItem(t, collection.root.id).toString()) ?? [],
id: dto.id,
include: request.include?.map(t => TestId.fromExtHostTestItem(t, collection.root.id).toString()) ?? [collection.root.id],
Expand Down Expand Up @@ -1222,7 +1222,7 @@ const updateProfile = (impl: TestRunProfileImpl, proxy: MainThreadTestingShape,
}
};

export class TestRunProfileImpl implements vscode.TestRunProfile {
export class TestRunProfileImpl extends TestRunProfileBase implements vscode.TestRunProfile {
readonly #proxy: MainThreadTestingShape;
readonly #activeProfiles: Set<number>;
readonly #onDidChangeDefaultProfiles: Event<DefaultProfileChangeEvent>;
Expand Down Expand Up @@ -1306,26 +1306,24 @@ export class TestRunProfileImpl implements vscode.TestRunProfile {
profiles: Map<number, vscode.TestRunProfile>,
activeProfiles: Set<number>,
onDidChangeActiveProfiles: Event<DefaultProfileChangeEvent>,
public readonly controllerId: string,
public readonly profileId: number,
controllerId: string,
profileId: number,
private _label: string,
public readonly kind: vscode.TestRunProfileKind,
kind: vscode.TestRunProfileKind,
public runHandler: (request: vscode.TestRunRequest, token: vscode.CancellationToken) => Thenable<void> | void,
_isDefault = false,
public _tag: vscode.TestTag | undefined = undefined,
private _supportsContinuousRun = false,
) {
super(controllerId, profileId, kind);

this.#proxy = proxy;
this.#profiles = profiles;
this.#activeProfiles = activeProfiles;
this.#onDidChangeDefaultProfiles = onDidChangeActiveProfiles;
profiles.set(profileId, this);

const groupBitset = profileGroupToBitset[kind];
if (typeof groupBitset !== 'number') {
throw new Error(`Unknown TestRunProfile.group ${kind}`);
}

const groupBitset = Convert.TestRunProfileKind.from(kind);
if (_isDefault) {
activeProfiles.add(profileId);
}
Expand Down Expand Up @@ -1360,12 +1358,6 @@ export class TestRunProfileImpl implements vscode.TestRunProfile {
}
}

const profileGroupToBitset: { [K in TestRunProfileKind]: TestRunProfileBitset } = {
[TestRunProfileKind.Coverage]: TestRunProfileBitset.Coverage,
[TestRunProfileKind.Debug]: TestRunProfileBitset.Debug,
[TestRunProfileKind.Run]: TestRunProfileBitset.Run,
};

function findTestInResultSnapshot(extId: TestId, snapshot: readonly Readonly<vscode.TestResultSnapshot>[]) {
for (let i = 0; i < extId.path.length; i++) {
const item = snapshot.find(s => s.id === extId.path[i]);
Expand Down
24 changes: 23 additions & 1 deletion src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import * as notebooks from '../../contrib/notebook/common/notebookCommon.js';
import { ICellRange } from '../../contrib/notebook/common/notebookRange.js';
import * as search from '../../contrib/search/common/search.js';
import { TestId } from '../../contrib/testing/common/testId.js';
import { CoverageDetails, DetailType, ICoverageCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestTag, TestMessageType, TestResultItem, denamespaceTestTag, namespaceTestTag } from '../../contrib/testing/common/testTypes.js';
import { CoverageDetails, DetailType, ICoverageCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestRunProfileReference, ITestTag, TestMessageType, TestResultItem, TestRunProfileBitset, denamespaceTestTag, namespaceTestTag } from '../../contrib/testing/common/testTypes.js';
import { EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js';
import { ACTIVE_GROUP, SIDE_GROUP } from '../../services/editor/common/editorService.js';
import { Dto } from '../../services/extensions/common/proxyIdentifier.js';
Expand Down Expand Up @@ -1953,6 +1953,28 @@ export namespace TestTag {
export const denamespace = denamespaceTestTag;
}

export namespace TestRunProfile {
export function from(item: types.TestRunProfileBase): ITestRunProfileReference {
return {
controllerId: item.controllerId,
profileId: item.profileId,
group: TestRunProfileKind.from(item.kind),
};
}
}

export namespace TestRunProfileKind {
const profileGroupToBitset: { [K in vscode.TestRunProfileKind]: TestRunProfileBitset } = {
[types.TestRunProfileKind.Coverage]: TestRunProfileBitset.Coverage,
[types.TestRunProfileKind.Debug]: TestRunProfileBitset.Debug,
[types.TestRunProfileKind.Run]: TestRunProfileBitset.Run,
};

export function from(kind: types.TestRunProfileKind): TestRunProfileBitset {
return profileGroupToBitset.hasOwnProperty(kind) ? profileGroupToBitset[kind] : TestRunProfileBitset.Run;
}
}

export namespace TestItem {
export type Raw = vscode.TestItem;

Expand Down
8 changes: 8 additions & 0 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4150,6 +4150,14 @@ export enum TestRunProfileKind {
Coverage = 3,
}

export class TestRunProfileBase {
constructor(
public readonly controllerId: string,
public readonly profileId: number,
public readonly kind: vscode.TestRunProfileKind,
) { }
}

@es5ClassCompat
export class TestRunRequest implements vscode.TestRunRequest {
constructor(
Expand Down
32 changes: 30 additions & 2 deletions src/vs/workbench/contrib/testing/browser/testing.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ import { TestCommandId, Testing } from '../common/constants.js';
import { ITestCoverageService, TestCoverageService } from '../common/testCoverageService.js';
import { ITestExplorerFilterState, TestExplorerFilterState } from '../common/testExplorerFilterState.js';
import { TestId, TestPosition } from '../common/testId.js';
import { ITestProfileService, TestProfileService } from '../common/testProfileService.js';
import { canUseProfileWithTest, ITestProfileService, TestProfileService } from '../common/testProfileService.js';
import { ITestResultService, TestResultService } from '../common/testResultService.js';
import { ITestResultStorage, TestResultStorage } from '../common/testResultStorage.js';
import { ITestService } from '../common/testService.js';
import { TestService } from '../common/testServiceImpl.js';
import { ITestItem, TestRunProfileBitset } from '../common/testTypes.js';
import { ITestItem, ITestRunProfileReference, TestRunProfileBitset } from '../common/testTypes.js';
import { TestingContentProvider } from '../common/testingContentProvider.js';
import { TestingContextKeys } from '../common/testingContextKeys.js';
import { ITestingContinuousRunService, TestingContinuousRunService } from '../common/testingContinuousRunService.js';
Expand Down Expand Up @@ -153,6 +153,34 @@ CommandsRegistry.registerCommand({
accessor.get(IViewsService).openView(Testing.ExplorerViewId, focus);
}
});
CommandsRegistry.registerCommand({
id: TestCommandId.StartContinousRunFromExtension,
handler: async (accessor: ServicesAccessor, profileRef: ITestRunProfileReference, tests: readonly ITestItem[]) => {
const profiles = accessor.get(ITestProfileService);
const collection = accessor.get(ITestService).collection;
const profile = profiles.getControllerProfiles(profileRef.controllerId).find(p => p.profileId === profileRef.profileId);
if (!profile?.supportsContinuousRun) {
return;
}

const crService = accessor.get(ITestingContinuousRunService);
for (const test of tests) {
const found = collection.getNodeById(test.extId);
if (found && canUseProfileWithTest(profile, found)) {
crService.start([profile], found.item.extId);
}
}
}
});
CommandsRegistry.registerCommand({
id: TestCommandId.StopContinousRunFromExtension,
handler: async (accessor: ServicesAccessor, tests: readonly ITestItem[]) => {
const crService = accessor.get(ITestingContinuousRunService);
for (const test of tests) {
crService.stop(test.extId);
}
}
});

CommandsRegistry.registerCommand({
id: 'vscode.peekTestError',
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/testing/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ export const enum TestCommandId {
SelectDefaultTestProfiles = 'testing.selectDefaultTestProfiles',
ShowMostRecentOutputAction = 'testing.showMostRecentOutput',
StartContinousRun = 'testing.startContinuousRun',
StartContinousRunFromExtension = 'testing.startContinuousRunFromExtension',
StopContinousRunFromExtension = 'testing.stopContinuousRunFromExtension',
StopContinousRun = 'testing.stopContinuousRun',
TestingSortByDurationAction = 'testing.sortByDuration',
TestingSortByLocationAction = 'testing.sortByLocation',
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/contrib/testing/common/testTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ export interface ITestRunProfile {
supportsContinuousRun: boolean;
}

export interface ITestRunProfileReference {
controllerId: string;
profileId: number;
group: TestRunProfileBitset;
}

/**
* A fully-resolved request to run tests, passsed between the main thread
* and extension host.
Expand Down

0 comments on commit 0f7bdb7

Please sign in to comment.