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: add commands to get current selected tests/profiles #179076

Merged
merged 1 commit into from Apr 4, 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
19 changes: 19 additions & 0 deletions src/vs/workbench/api/common/extHostTesting.ts
Expand Up @@ -23,6 +23,7 @@ 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 { TestCommandId } from 'vs/workbench/contrib/testing/common/constants';
import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId';
import { InvalidTestItemError } from 'vs/workbench/contrib/testing/common/testItemCollection';
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';
Expand Down Expand Up @@ -65,6 +66,24 @@ export class ExtHostTesting implements ExtHostTestingShape {
return controller?.collection.tree.get(targetTest)?.actual ?? toItemFromContext(arg);
}
});

commands.registerCommand(false, 'testing.getExplorerSelection', async (): Promise<any> => {
const inner = await commands.executeCommand<{
include: string[];
exclude: string[];
}>(TestCommandId.GetExplorerSelection);

const lookup = (i: string) => {
const controller = this.controllers.get(TestId.root(i));
if (!controller) { return undefined; }
return TestId.isRoot(i) ? controller.controller : controller.collection.tree.get(i)?.actual;
};

return {
include: inner?.include.map(lookup).filter(isDefined) || [],
exclude: inner?.exclude.map(lookup).filter(isDefined) || [],
};
});
}

/**
Expand Down
47 changes: 45 additions & 2 deletions src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
Expand Up @@ -38,7 +38,7 @@ import { ITestProfileService, canUseProfileWithTest } from 'vs/workbench/contrib
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { IMainThreadTestCollection, ITestService, expandAndGetTestById, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
import { ITestRunProfile, InternalTestItem, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { ExtTestRunProfileKind, ITestRunProfile, InternalTestItem, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestingContinuousRunService } from 'vs/workbench/contrib/testing/common/testingContinuousRunService';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
Expand Down Expand Up @@ -417,11 +417,52 @@ abstract class ExecuteSelectedAction extends ViewAction<TestingExplorerView> {
* @override
*/
public runInView(accessor: ServicesAccessor, view: TestingExplorerView): Promise<ITestResult | undefined> {
const { include, exclude } = view.getSelectedOrVisibleItems();
const { include, exclude } = view.getTreeIncludeExclude();
return accessor.get(ITestService).runTests({ tests: include, exclude, group: this.group });
}
}

export class GetSelectedProfiles extends Action2 {
constructor() {
super({ id: TestCommandId.GetSelectedProfiles, title: localize('getSelectedProfiles', 'Get Selected Profiles') });
}

/**
* @override
*/
public override run(accessor: ServicesAccessor) {
const profiles = accessor.get(ITestProfileService);
return [
...profiles.getGroupDefaultProfiles(TestRunProfileBitset.Run),
...profiles.getGroupDefaultProfiles(TestRunProfileBitset.Debug),
...profiles.getGroupDefaultProfiles(TestRunProfileBitset.Coverage),
].map(p => ({
controllerId: p.controllerId,
label: p.label,
kind: p.group & TestRunProfileBitset.Coverage
? ExtTestRunProfileKind.Coverage
: p.group & TestRunProfileBitset.Debug
? ExtTestRunProfileKind.Debug
: ExtTestRunProfileKind.Run,
}));
}
}

export class GetExplorerSelection extends ViewAction<TestingExplorerView> {
constructor() {
super({ id: TestCommandId.GetExplorerSelection, title: localize('getExplorerSelection', 'Get Explorer Selection'), viewId: Testing.ExplorerViewId });
}

/**
* @override
*/
public override runInView(_accessor: ServicesAccessor, view: TestingExplorerView) {
const { include, exclude } = view.getTreeIncludeExclude(undefined, 'selected');
const mapper = (i: InternalTestItem) => i.item.extId;
return { include: include.map(mapper), exclude: exclude.map(mapper) };
}
}

export class RunSelectedAction extends ExecuteSelectedAction {
constructor() {
super({
Expand Down Expand Up @@ -1334,6 +1375,8 @@ export const allTestActions = [
DebugLastRun,
DebugSelectedAction,
GoToTest,
GetExplorerSelection,
GetSelectedProfiles,
HideTestAction,
OpenOutputPeek,
RefreshTestsAction,
Expand Down
39 changes: 33 additions & 6 deletions src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
Expand Up @@ -135,7 +135,11 @@ export class TestingExplorerView extends ViewPane {
}
}

public getSelectedOrVisibleItems(profile?: ITestRunProfile) {
/**
* Gets include/exclude items in the tree, based either on visible tests
* or a use selection.
*/
public getTreeIncludeExclude(profile?: ITestRunProfile, filterToType: 'visible' | 'selected' = 'visible') {
const projection = this.viewModel.projection.value;
if (!projection) {
return { include: [], exclude: [] };
Expand All @@ -150,7 +154,7 @@ export class TestingExplorerView extends ViewPane {

// To calculate includes and excludes, we include the first children that
// have a majority of their items included too, and then apply exclusions.
const include: InternalTestItem[] = [];
const include = new Set<InternalTestItem>();
const exclude: InternalTestItem[] = [];

const attempt = (element: TestExplorerTreeElement, alreadyIncluded: boolean) => {
Expand Down Expand Up @@ -180,7 +184,7 @@ export class TestingExplorerView extends ViewPane {
// probably fans out later. (Worse case we'll directly include its single child)
&& inTree.visibleChildrenCount !== 1
) {
include.push(element.test);
include.add(element.test);
alreadyIncluded = true;
}

Expand All @@ -190,6 +194,29 @@ export class TestingExplorerView extends ViewPane {
}
};

if (filterToType === 'selected') {
const sel = this.viewModel.tree.getSelection().filter(isDefined);
if (sel.length) {

L:
for (const node of sel) {
if (node instanceof TestItemTreeElement) {
// avoid adding an item if its parent is already included
for (let i: TestItemTreeElement | null = node; i; i = i.parent) {
if (include.has(i.test)) {
continue L;
}
}

include.add(node.test);
node.children.forEach(c => attempt(c, true));
}
}

return { include: [...include], exclude };
}
}

for (const root of this.testService.collection.rootItems) {
const element = projection.getElementByTestId(root.item.extId);
if (!element) {
Expand All @@ -208,7 +235,7 @@ export class TestingExplorerView extends ViewPane {
// note we intentionally check children > 0 here, unlike above, since
// we don't want to bother dispatching to controllers who have no discovered tests
if (element.children.size > 0 && visibleChildren * 2 >= element.children.size) {
include.push(element.test);
include.add(element.test);
element.children.forEach(c => attempt(c, true));
} else {
element.children.forEach(c => attempt(c, false));
Expand All @@ -218,7 +245,7 @@ export class TestingExplorerView extends ViewPane {
}
}

return { include, exclude };
return { include: [...include], exclude };
}

/**
Expand Down Expand Up @@ -294,7 +321,7 @@ export class TestingExplorerView extends ViewPane {
undefined,
undefined,
() => {
const { include, exclude } = this.getSelectedOrVisibleItems(profile);
const { include, exclude } = this.getTreeIncludeExclude(profile);
this.testService.runResolvedTests({
exclude: exclude.map(e => e.item.extId),
targets: [{
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/testing/common/constants.ts
Expand Up @@ -64,6 +64,8 @@ export const enum TestCommandId {
DebugLastRun = 'testing.debugLastRun',
DebugSelectedAction = 'testing.debugSelected',
FilterAction = 'workbench.actions.treeView.testExplorer.filter',
GetExplorerSelection = '_testing.getExplorerSelection',
GetSelectedProfiles = 'testing.getSelectedProfiles',
GoToTest = 'testing.editFocusedTest',
HideTestAction = 'testing.hideTest',
OpenOutputPeek = 'testing.openOutputPeek',
Expand Down
7 changes: 7 additions & 0 deletions src/vs/workbench/contrib/testing/common/testTypes.ts
Expand Up @@ -20,6 +20,13 @@ export const enum TestResultState {
Errored = 6
}

/** note: keep in sync with TestRunProfileKind in vscode.d.ts */
export const enum ExtTestRunProfileKind {
Run = 1,
Debug = 2,
Coverage = 3,
}

export const enum TestRunProfileBitset {
Run = 1 << 1,
Debug = 1 << 2,
Expand Down