mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-27 10:37:38 +01:00
testing: complete test run experience update
Fixes https://github.com/microsoft/vscode/issues/127096
This commit is contained in:
@@ -149,9 +149,8 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
await this.proxy.$runTests({
|
||||
targets: [{
|
||||
testIds: req.tests.map(t => t.id),
|
||||
configGroup: configGroupToBitset[config.group],
|
||||
configLabel: config.label,
|
||||
configId: config.configId,
|
||||
profileGroup: configGroupToBitset[config.group],
|
||||
profileId: config.configId,
|
||||
controllerId: config.controllerId,
|
||||
}],
|
||||
exclude: req.exclude
|
||||
@@ -256,7 +255,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
||||
const tracker = this.runTracker.prepareForMainThreadTestRun(publicReq, TestRunDto.fromInternal(req), token);
|
||||
|
||||
try {
|
||||
configuration.runHandler(publicReq, token);
|
||||
await configuration.runHandler(publicReq, token);
|
||||
} finally {
|
||||
if (tracker.isRunning && !token.isCancellationRequested) {
|
||||
await Event.toPromise(tracker.onEnd);
|
||||
@@ -866,7 +865,7 @@ export class TestRunConfigurationImpl implements vscode.TestRunConfiguration {
|
||||
}
|
||||
|
||||
this.#proxy.$publishTestRunConfig({
|
||||
configId,
|
||||
profileId: configId,
|
||||
controllerId,
|
||||
label: _label,
|
||||
group: groupBitset,
|
||||
|
||||
@@ -48,12 +48,15 @@ const enum ActionOrder {
|
||||
Run = 10,
|
||||
Debug,
|
||||
Coverage,
|
||||
RunUsing,
|
||||
AutoRun,
|
||||
|
||||
// Submenu:
|
||||
Collapse,
|
||||
DisplayMode,
|
||||
Sort,
|
||||
GoToTest,
|
||||
HideTest,
|
||||
}
|
||||
|
||||
const hasAnyTestProvider = ContextKeyGreaterExpr.create(TestingContextKeys.providerCount.key, 0);
|
||||
@@ -90,6 +93,7 @@ export class UnhideTestAction extends Action2 {
|
||||
title: localize('unhideTest', 'Unhide Test'),
|
||||
menu: {
|
||||
id: MenuId.TestItem,
|
||||
order: ActionOrder.HideTest,
|
||||
when: TestingContextKeys.testItemIsHidden.isEqualTo(true)
|
||||
},
|
||||
});
|
||||
@@ -130,6 +134,46 @@ export class DebugAction extends Action2 {
|
||||
}
|
||||
}
|
||||
|
||||
export class RunUsingProfileAction extends Action2 {
|
||||
public static readonly ID = 'testing.runUsing';
|
||||
constructor() {
|
||||
super({
|
||||
id: RunUsingProfileAction.ID,
|
||||
title: localize('testing.runUsing', 'Execute Using Profile...'),
|
||||
icon: icons.testingDebugIcon,
|
||||
menu: {
|
||||
id: MenuId.TestItem,
|
||||
order: ActionOrder.RunUsing,
|
||||
when: TestingContextKeys.hasNonDefaultProfile.isEqualTo(true),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public override async run(acessor: ServicesAccessor, ...elements: IActionableTestTreeElement[]): Promise<any> {
|
||||
const testElements = elements.filter((e): e is TestItemTreeElement => e instanceof TestItemTreeElement);
|
||||
if (testElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commandService = acessor.get(ICommandService);
|
||||
const testService = acessor.get(ITestService);
|
||||
const controllerId = testElements[0].test.controllerId;
|
||||
const profile: ITestRunConfiguration | undefined = await commandService.executeCommand('vscode.pickTestProfile', { onlyControllerId: controllerId });
|
||||
if (!profile) {
|
||||
return;
|
||||
}
|
||||
|
||||
testService.runResolvedTests({
|
||||
targets: [{
|
||||
profileGroup: profile.group,
|
||||
profileId: profile.profileId,
|
||||
controllerId: profile.controllerId,
|
||||
testIds: testElements.filter(t => controllerId === t.test.controllerId).map(t => t.test.item.extId)
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class RunAction extends Action2 {
|
||||
public static readonly ID = 'testing.run';
|
||||
constructor() {
|
||||
@@ -157,12 +201,38 @@ export class RunAction extends Action2 {
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigureTestsAction extends Action2 {
|
||||
public static readonly ID = 'testing.configureTests';
|
||||
export class SelectDefaultTestProfiles extends Action2 {
|
||||
public static readonly ID = 'testing.selectDefaultTestProfiles';
|
||||
constructor() {
|
||||
super({
|
||||
id: ConfigureTestsAction.ID,
|
||||
title: localize('testing.configureTests', 'Configure Tests'),
|
||||
id: SelectDefaultTestProfiles.ID,
|
||||
title: localize('testing.selectDefaultTestProfiles', 'Select Default Profile'),
|
||||
icon: icons.testingUpdateConfiguration,
|
||||
category,
|
||||
});
|
||||
}
|
||||
|
||||
public override async run(acessor: ServicesAccessor, onlyGroup: TestRunConfigurationBitset) {
|
||||
const commands = acessor.get(ICommandService);
|
||||
const testConfigurationService = acessor.get(ITestConfigurationService);
|
||||
const configurations = await commands.executeCommand<ITestRunConfiguration[]>('vscode.pickMultipleTestProfiles', {
|
||||
showConfigureButtons: false,
|
||||
selected: testConfigurationService.getGroupDefaultConfigurations(onlyGroup),
|
||||
onlyGroup,
|
||||
});
|
||||
|
||||
if (configurations?.length) {
|
||||
testConfigurationService.setGroupDefaultConfigurations(onlyGroup, configurations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigureTestProfilesAction extends Action2 {
|
||||
public static readonly ID = 'testing.configureProfile';
|
||||
constructor() {
|
||||
super({
|
||||
id: ConfigureTestProfilesAction.ID,
|
||||
title: localize('testing.configureProfile', 'Configure Test Profiles'),
|
||||
icon: icons.testingUpdateConfiguration,
|
||||
f1: true,
|
||||
category,
|
||||
@@ -173,17 +243,18 @@ export class ConfigureTestsAction extends Action2 {
|
||||
});
|
||||
}
|
||||
|
||||
public override async run(acessor: ServicesAccessor) {
|
||||
public override async run(acessor: ServicesAccessor, onlyGroup?: TestRunConfigurationBitset) {
|
||||
const commands = acessor.get(ICommandService);
|
||||
const testConfigurationService = acessor.get(ITestConfigurationService);
|
||||
const configuration = await commands.executeCommand<ITestRunConfiguration>('vscode.pickTestConfiguration', {
|
||||
placeholder: localize('configureTests', 'Select a configuration to update'),
|
||||
const configuration = await commands.executeCommand<ITestRunConfiguration>('vscode.pickTestProfile', {
|
||||
placeholder: localize('configureProfile', 'Select a profile to update'),
|
||||
showConfigureButtons: false,
|
||||
onlyConfigurable: true,
|
||||
onlyGroup,
|
||||
});
|
||||
|
||||
if (configuration) {
|
||||
testConfigurationService.configure(configuration.controllerId, configuration.configId);
|
||||
testConfigurationService.configure(configuration.controllerId, configuration.profileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,6 +640,7 @@ export class GoToTest extends Action2 {
|
||||
menu: {
|
||||
id: MenuId.TestItem,
|
||||
when: TestingContextKeys.testItemHasUri.isEqualTo(true),
|
||||
order: ActionOrder.GoToTest,
|
||||
},
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorContrib - 10,
|
||||
@@ -1088,7 +1160,7 @@ export const allTestActions = [
|
||||
CancelTestRunAction,
|
||||
ClearTestResultsAction,
|
||||
CollapseAllAction,
|
||||
ConfigureTestsAction,
|
||||
ConfigureTestProfilesAction,
|
||||
DebugAction,
|
||||
DebugAllAction,
|
||||
DebugAtCursor,
|
||||
@@ -1106,7 +1178,9 @@ export const allTestActions = [
|
||||
RunAtCursor,
|
||||
RunCurrentFile,
|
||||
RunSelectedAction,
|
||||
RunUsingProfileAction,
|
||||
SearchForTestExtension,
|
||||
SelectDefaultTestProfiles,
|
||||
ShowMostRecentOutputAction,
|
||||
TestingSortByLocationAction,
|
||||
TestingSortByStatusAction,
|
||||
|
||||
@@ -4,89 +4,150 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { groupBy } from 'vs/base/common/arrays';
|
||||
import { isDefined } from 'vs/base/common/types';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { QuickPickInput, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { QuickPickInput, IQuickPickItem, IQuickInputService, IQuickPickItemButtonEvent } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { testingUpdateConfiguration } from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import { testConfigurationGroupNames } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { ITestRunConfiguration } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestRunConfiguration, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'vscode.pickTestConfiguration',
|
||||
handler: async (accessor: ServicesAccessor, {
|
||||
controllerId,
|
||||
placeholder = localize('testConfigurationUi.pick', 'Pick a test configuration to use'),
|
||||
showConfigureButtons = true,
|
||||
onlyConfigurable = false,
|
||||
}: {
|
||||
controllerId?: string;
|
||||
showConfigureButtons?: boolean;
|
||||
placeholder?: string;
|
||||
onlyConfigurable?: boolean;
|
||||
}) => {
|
||||
const configService = accessor.get(ITestConfigurationService);
|
||||
const items: QuickPickInput<IQuickPickItem & { config: ITestRunConfiguration }>[] = [];
|
||||
const pushItems = (allConfigs: ITestRunConfiguration[], description?: string) => {
|
||||
for (const configs of groupBy(allConfigs, (a, b) => a.group - b.group)) {
|
||||
let added = false;
|
||||
interface IConfigurationPickerOptions {
|
||||
/** Placeholder text */
|
||||
placeholder?: string;
|
||||
/** Show buttons to trigger configuration */
|
||||
showConfigureButtons?: boolean;
|
||||
/** Only show configurations from this controller */
|
||||
onlyControllerId?: string;
|
||||
/** Only show this group */
|
||||
onlyGroup?: TestRunConfigurationBitset;
|
||||
/** Only show items which are configurable */
|
||||
onlyConfigurable?: boolean;
|
||||
}
|
||||
|
||||
for (const config of configs) {
|
||||
if (onlyConfigurable && !config.hasConfigurationHandler) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
items.push({ type: 'separator', label: testConfigurationGroupNames[configs[0].group] });
|
||||
added = true;
|
||||
}
|
||||
|
||||
items.push(({
|
||||
type: 'item',
|
||||
config,
|
||||
label: config.label,
|
||||
description,
|
||||
alwaysShow: true,
|
||||
buttons: config.hasConfigurationHandler && showConfigureButtons
|
||||
? [{
|
||||
iconClass: ThemeIcon.asClassName(testingUpdateConfiguration),
|
||||
tooltip: localize('updateTestConfiguration', 'Update Test Configuration')
|
||||
}] : []
|
||||
}));
|
||||
function buildPicker(accessor: ServicesAccessor, {
|
||||
onlyGroup,
|
||||
showConfigureButtons,
|
||||
onlyControllerId,
|
||||
onlyConfigurable,
|
||||
placeholder = localize('testConfigurationUi.pick', 'Pick a test configuration to use'),
|
||||
}: IConfigurationPickerOptions) {
|
||||
const configService = accessor.get(ITestConfigurationService);
|
||||
const items: QuickPickInput<IQuickPickItem & { config: ITestRunConfiguration }>[] = [];
|
||||
const pushItems = (allConfigs: ITestRunConfiguration[], description?: string) => {
|
||||
for (const configs of groupBy(allConfigs, (a, b) => a.group - b.group)) {
|
||||
let addedHeader = false;
|
||||
if (onlyGroup) {
|
||||
if (configs[0].group !== onlyGroup) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (controllerId !== undefined) {
|
||||
const lookup = configService.getControllerConfigurations(controllerId);
|
||||
if (!lookup) {
|
||||
return;
|
||||
addedHeader = true; // showing one group, no need for label
|
||||
}
|
||||
|
||||
pushItems(lookup.configs);
|
||||
} else {
|
||||
for (const { configs, controller } of configService.all()) {
|
||||
pushItems(configs, controller.label.value);
|
||||
for (const config of configs) {
|
||||
if (onlyConfigurable && !config.hasConfigurationHandler) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!addedHeader) {
|
||||
items.push({ type: 'separator', label: testConfigurationGroupNames[configs[0].group] });
|
||||
addedHeader = true;
|
||||
}
|
||||
|
||||
items.push(({
|
||||
type: 'item',
|
||||
config,
|
||||
label: config.label,
|
||||
description,
|
||||
alwaysShow: true,
|
||||
buttons: config.hasConfigurationHandler && showConfigureButtons
|
||||
? [{
|
||||
iconClass: ThemeIcon.asClassName(testingUpdateConfiguration),
|
||||
tooltip: localize('updateTestConfiguration', 'Update Test Configuration')
|
||||
}] : []
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const quickpick = accessor.get(IQuickInputService).createQuickPick();
|
||||
quickpick.items = items;
|
||||
quickpick.placeholder = placeholder;
|
||||
if (onlyControllerId !== undefined) {
|
||||
const lookup = configService.getControllerConfigurations(onlyControllerId);
|
||||
if (!lookup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pick = await new Promise<ITestRunConfiguration | undefined>(resolve => {
|
||||
quickpick.onDidAccept(() => resolve((quickpick.selectedItems[0] as { config?: ITestRunConfiguration })?.config));
|
||||
quickpick.onDidHide(() => resolve(undefined));
|
||||
quickpick.onDidTriggerItemButton(evt => {
|
||||
const config = (evt.item as { config?: ITestRunConfiguration }).config;
|
||||
if (config) {
|
||||
configService.configure(config.controllerId, config.configId);
|
||||
resolve(undefined);
|
||||
}
|
||||
pushItems(lookup.configs);
|
||||
} else {
|
||||
for (const { configs, controller } of configService.all()) {
|
||||
pushItems(configs, controller.label.value);
|
||||
}
|
||||
}
|
||||
|
||||
const quickpick = accessor.get(IQuickInputService).createQuickPick<IQuickPickItem & { config: ITestRunConfiguration }>();
|
||||
quickpick.items = items;
|
||||
quickpick.placeholder = placeholder;
|
||||
return quickpick;
|
||||
}
|
||||
|
||||
const triggerButtonHandler = (service: ITestConfigurationService, resolve: (arg: undefined) => void) =>
|
||||
(evt: IQuickPickItemButtonEvent<IQuickPickItem>) => {
|
||||
const config = (evt.item as { config?: ITestRunConfiguration }).config;
|
||||
if (config) {
|
||||
service.configure(config.controllerId, config.profileId);
|
||||
resolve(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'vscode.pickMultipleTestProfiles',
|
||||
handler: async (accessor: ServicesAccessor, options: IConfigurationPickerOptions & {
|
||||
selected?: ITestRunConfiguration[],
|
||||
}) => {
|
||||
const configService = accessor.get(ITestConfigurationService);
|
||||
const quickpick = buildPicker(accessor, options);
|
||||
if (!quickpick) {
|
||||
return;
|
||||
}
|
||||
|
||||
quickpick.canSelectMany = true;
|
||||
if (options.selected) {
|
||||
quickpick.selectedItems = quickpick.items
|
||||
.filter((i): i is IQuickPickItem & { config: ITestRunConfiguration } => i.type === 'item')
|
||||
.filter(i => options.selected!.some(s => s.controllerId === i.config.controllerId && s.profileId === i.config.profileId));
|
||||
}
|
||||
|
||||
const pick = await new Promise<ITestRunConfiguration[] | undefined>(resolve => {
|
||||
quickpick.onDidAccept(() => {
|
||||
const selected = quickpick.selectedItems as readonly { config?: ITestRunConfiguration }[];
|
||||
resolve(selected.map(s => s.config).filter(isDefined));
|
||||
});
|
||||
quickpick.onDidHide(() => resolve(undefined));
|
||||
quickpick.onDidTriggerItemButton(triggerButtonHandler(configService, resolve));
|
||||
quickpick.show();
|
||||
});
|
||||
|
||||
quickpick.dispose();
|
||||
return pick;
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'vscode.pickTestProfile',
|
||||
handler: async (accessor: ServicesAccessor, options: IConfigurationPickerOptions) => {
|
||||
const configService = accessor.get(ITestConfigurationService);
|
||||
const quickpick = buildPicker(accessor, options);
|
||||
if (!quickpick) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pick = await new Promise<ITestRunConfiguration | undefined>(resolve => {
|
||||
quickpick.onDidAccept(() => resolve((quickpick.selectedItems[0] as { config?: ITestRunConfiguration })?.config));
|
||||
quickpick.onDidHide(() => resolve(undefined));
|
||||
quickpick.onDidTriggerItemButton(triggerButtonHandler(configService, resolve));
|
||||
quickpick.show();
|
||||
});
|
||||
|
||||
|
||||
@@ -420,17 +420,16 @@ abstract class RunTestDecoration extends Disposable {
|
||||
}
|
||||
|
||||
if (capabilities & TestRunConfigurationBitset.HasNonDefaultConfig) {
|
||||
testActions.push(new Action('testing.gutter.runUsing', localize('run using', 'Run Using...'), undefined, undefined, async () => {
|
||||
const config: ITestRunConfiguration | undefined = await this.commandService.executeCommand('vscode.pickTestConfiguration', test.controllerId);
|
||||
testActions.push(new Action('testing.runUsing', localize('testing.runUsing', 'Execute Using Profile...'), undefined, undefined, async () => {
|
||||
const config: ITestRunConfiguration | undefined = await this.commandService.executeCommand('vscode.pickTestProfile', { onlyControllerId: test.controllerId });
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.testService.runResolvedTests({
|
||||
targets: [{
|
||||
configGroup: config.group,
|
||||
configId: config.configId,
|
||||
configLabel: config.label,
|
||||
profileGroup: config.group,
|
||||
profileId: config.profileId,
|
||||
controllerId: config.controllerId,
|
||||
testIds: [test.item.extId]
|
||||
}]
|
||||
|
||||
@@ -27,6 +27,7 @@ import { localize } from 'vs/nls';
|
||||
import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem';
|
||||
import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -54,7 +55,7 @@ import { ITestExplorerFilterState, TestExplorerFilterState, TestingExplorerFilte
|
||||
import { ITestingProgressUiService } from 'vs/workbench/contrib/testing/browser/testingProgressUiService';
|
||||
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
import { labelForTestInState, TestExplorerStateFilter, TestExplorerViewMode, TestExplorerViewSorting, Testing, testStateNames } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { identifyTest, TestIdPath, TestItemExpandState, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { identifyTest, ITestRunConfiguration, TestIdPath, TestItemExpandState, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { capabilityContextKeys, ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
|
||||
@@ -62,7 +63,7 @@ import { cmpPriority, isFailedState, isStateWithResult } from 'vs/workbench/cont
|
||||
import { getPathForTestInResult, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
|
||||
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { ITestService, testCollectionIsEmpty } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { DebugAllAction, GoToTest, RunAllAction } from './testExplorerActions';
|
||||
import { ConfigureTestProfilesAction, DebugAllAction, GoToTest, RunAllAction, SelectDefaultTestProfiles } from './testExplorerActions';
|
||||
|
||||
export class TestingExplorerView extends ViewPane {
|
||||
public viewModel!: TestingExplorerViewModel;
|
||||
@@ -87,6 +88,7 @@ export class TestingExplorerView extends ViewPane {
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ITestingProgressUiService private readonly testProgressService: ITestingProgressUiService,
|
||||
@ITestConfigurationService private readonly testConfigurationService: ITestConfigurationService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
this.location.set(viewDescriptorService.getViewLocationById(Testing.ExplorerViewId) ?? ViewContainerLocation.Sidebar);
|
||||
@@ -101,6 +103,8 @@ export class TestingExplorerView extends ViewPane {
|
||||
this._register(testService.collection.onBusyProvidersChange(busy => {
|
||||
this.updateDiscoveryProgress(busy);
|
||||
}));
|
||||
|
||||
this._register(testConfigurationService.onDidChange(() => this.updateActions()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,9 +175,11 @@ export class TestingExplorerView extends ViewPane {
|
||||
|
||||
/** @inheritdoc */
|
||||
private getTestConfigGroupActions(group: TestRunConfigurationBitset) {
|
||||
const actions: IAction[] = [];
|
||||
const profileActions: IAction[] = [];
|
||||
|
||||
let participatingGroups = 0;
|
||||
let hasConfigurable = false;
|
||||
const defaults = this.testConfigurationService.getGroupDefaultConfigurations(group);
|
||||
for (const { configs, controller } of this.testConfigurationService.all()) {
|
||||
let hasAdded = false;
|
||||
|
||||
@@ -185,19 +191,19 @@ export class TestingExplorerView extends ViewPane {
|
||||
if (!hasAdded) {
|
||||
hasAdded = true;
|
||||
participatingGroups++;
|
||||
actions.push(new Action(`${controller.id}.$root`, controller.label.value, undefined, false));
|
||||
profileActions.push(new Action(`${controller.id}.$root`, controller.label.value, undefined, false));
|
||||
}
|
||||
|
||||
actions.push(new Action(
|
||||
`${controller.id}.${config.configId}`,
|
||||
config.label,
|
||||
hasConfigurable = hasConfigurable || config.hasConfigurationHandler;
|
||||
profileActions.push(new Action(
|
||||
`${controller.id}.${config.profileId}`,
|
||||
defaults.includes(config) ? localize('defaultTestProfile', '{0} (Default)', config.label) : config.label,
|
||||
undefined,
|
||||
undefined,
|
||||
() => this.testService.runResolvedTests({
|
||||
targets: [{
|
||||
configGroup: config.group,
|
||||
configId: config.configId,
|
||||
configLabel: config.label,
|
||||
profileGroup: config.group,
|
||||
profileId: config.profileId,
|
||||
controllerId: config.controllerId,
|
||||
testIds: this.getSelectedOrVisibleItems()
|
||||
.filter(i => i.controllerId === config.controllerId)
|
||||
@@ -210,10 +216,31 @@ export class TestingExplorerView extends ViewPane {
|
||||
|
||||
// If there's only one group, don't add a heading for it in the dropdown.
|
||||
if (participatingGroups === 1) {
|
||||
actions.shift();
|
||||
profileActions.shift();
|
||||
}
|
||||
|
||||
return actions;
|
||||
let postActions: IAction[] = [];
|
||||
if (profileActions.length > 1) {
|
||||
postActions.push(new Action(
|
||||
'selectDefaultTestConfigurations',
|
||||
localize('selectDefaultConfigs', 'Select Default Profile'),
|
||||
undefined,
|
||||
undefined,
|
||||
() => this.commandService.executeCommand<ITestRunConfiguration>(SelectDefaultTestProfiles.ID, group),
|
||||
));
|
||||
}
|
||||
|
||||
if (hasConfigurable) {
|
||||
postActions.push(new Action(
|
||||
'configureTestProfiles',
|
||||
localize('configureTestProfiles', 'Configure Test Profiles'),
|
||||
undefined,
|
||||
undefined,
|
||||
() => this.commandService.executeCommand<ITestRunConfiguration>(ConfigureTestProfilesAction.ID, group),
|
||||
));
|
||||
}
|
||||
|
||||
return Separator.join(profileActions, postActions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@ export const testRunConfigurationBitsetList = [
|
||||
*/
|
||||
export interface ITestRunConfiguration {
|
||||
controllerId: string;
|
||||
configId: number;
|
||||
profileId: number;
|
||||
label: string;
|
||||
group: TestRunConfigurationBitset;
|
||||
isDefault: boolean;
|
||||
@@ -62,9 +62,8 @@ export interface ResolvedTestRunRequest {
|
||||
targets: {
|
||||
testIds: string[];
|
||||
controllerId: string;
|
||||
configLabel: string;
|
||||
configGroup: TestRunConfigurationBitset;
|
||||
configId: number;
|
||||
profileGroup: TestRunConfigurationBitset;
|
||||
profileId: number;
|
||||
}[]
|
||||
exclude?: ITestIdWithSrc[];
|
||||
isAutoRun?: boolean;
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { isDefined } from 'vs/base/common/types';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
|
||||
import { ITestRunConfiguration, TestRunConfigurationBitset, testRunConfigurationBitsetList } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { IMainThreadTestController } from 'vs/workbench/contrib/testing/common/testService';
|
||||
@@ -51,7 +54,21 @@ export interface ITestConfigurationService {
|
||||
/**
|
||||
* Gets all registered controllers, grouping by controller.
|
||||
*/
|
||||
all(): Iterable<Readonly<{ controller: IMainThreadTestController, configs: ITestRunConfiguration[] }>>;
|
||||
all(): Iterable<Readonly<{
|
||||
capabilities: number,
|
||||
controller: IMainThreadTestController,
|
||||
configs: ITestRunConfiguration[],
|
||||
}>>;
|
||||
|
||||
/**
|
||||
* Gets the default configurations to be run for a given run group.
|
||||
*/
|
||||
getGroupDefaultConfigurations(group: TestRunConfigurationBitset): ITestRunConfiguration[];
|
||||
|
||||
/**
|
||||
* Sets the default configurations to be run for a given run group.
|
||||
*/
|
||||
setGroupDefaultConfigurations(group: TestRunConfigurationBitset, configs: ITestRunConfiguration[]): void;
|
||||
|
||||
/**
|
||||
* Gets the configurations for a controller, in priority order.
|
||||
@@ -88,6 +105,7 @@ export const capabilityContextKeys = (capabilities: number): [key: string, value
|
||||
|
||||
export class TestConfigurationService implements ITestConfigurationService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private readonly preferredDefaults: StoredValue<{ [K in TestRunConfigurationBitset]?: { controllerId: string; configId: number }[] }>;
|
||||
private readonly capabilitiesContexts: { [K in TestRunConfigurationBitset]: IContextKey<boolean> };
|
||||
private readonly changeEmitter = new Emitter<void>();
|
||||
private readonly controllerConfigs = new Map</* controller ID */string, {
|
||||
@@ -101,12 +119,19 @@ export class TestConfigurationService implements ITestConfigurationService {
|
||||
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
) {
|
||||
this.preferredDefaults = new StoredValue({
|
||||
key: 'testingPreferredConfigs',
|
||||
scope: StorageScope.WORKSPACE,
|
||||
target: StorageTarget.USER,
|
||||
}, storageService);
|
||||
|
||||
this.capabilitiesContexts = {
|
||||
[TestRunConfigurationBitset.Run]: TestingContextKeys.hasRunnableTests.bindTo(contextKeyService),
|
||||
[TestRunConfigurationBitset.Debug]: TestingContextKeys.hasDebuggableTests.bindTo(contextKeyService),
|
||||
[TestRunConfigurationBitset.Coverage]: TestingContextKeys.hasCoverableTests.bindTo(contextKeyService),
|
||||
[TestRunConfigurationBitset.HasNonDefaultConfig]: TestingContextKeys.hasNonDefaultConfig.bindTo(contextKeyService),
|
||||
[TestRunConfigurationBitset.HasNonDefaultConfig]: TestingContextKeys.hasNonDefaultProfile.bindTo(contextKeyService),
|
||||
[TestRunConfigurationBitset.HasConfigurable]: TestingContextKeys.hasConfigurableConfig.bindTo(contextKeyService),
|
||||
};
|
||||
|
||||
@@ -144,7 +169,7 @@ export class TestConfigurationService implements ITestConfigurationService {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = ctrl.configs.find(c => c.controllerId === controllerId && c.configId === configId);
|
||||
const config = ctrl.configs.find(c => c.controllerId === controllerId && c.profileId === configId);
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
@@ -172,7 +197,7 @@ export class TestConfigurationService implements ITestConfigurationService {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = ctrl.configs.findIndex(c => c.configId === configId);
|
||||
const index = ctrl.configs.findIndex(c => c.profileId === configId);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
@@ -207,6 +232,43 @@ export class TestConfigurationService implements ITestConfigurationService {
|
||||
return this.controllerConfigs.get(controllerId)?.configs.filter(c => c.group === group) ?? [];
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public getGroupDefaultConfigurations(group: TestRunConfigurationBitset) {
|
||||
const preferred = this.preferredDefaults.get();
|
||||
if (!preferred) {
|
||||
return this.getBaseDefaults(group);
|
||||
}
|
||||
|
||||
const configs = preferred[group]
|
||||
?.map(p => this.controllerConfigs.get(p.controllerId)?.configs.find(
|
||||
c => c.profileId === p.configId && c.group === group))
|
||||
.filter(isDefined);
|
||||
|
||||
return configs?.length ? configs : this.getBaseDefaults(group);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public setGroupDefaultConfigurations(group: TestRunConfigurationBitset, configs: ITestRunConfiguration[]) {
|
||||
this.preferredDefaults.store({
|
||||
...this.preferredDefaults.get(),
|
||||
[group]: configs.map(c => ({ configId: c.profileId, controllerId: c.controllerId })),
|
||||
});
|
||||
|
||||
this.changeEmitter.fire();
|
||||
}
|
||||
|
||||
private getBaseDefaults(group: TestRunConfigurationBitset) {
|
||||
const defaults: ITestRunConfiguration[] = [];
|
||||
for (const { configs } of this.controllerConfigs.values()) {
|
||||
const config = configs.find(c => c.group === group);
|
||||
if (config) {
|
||||
defaults.push(config);
|
||||
}
|
||||
}
|
||||
|
||||
return defaults;
|
||||
}
|
||||
|
||||
private refreshContextKeys() {
|
||||
let allCapabilities = 0;
|
||||
for (const { capabilities } of this.controllerConfigs.values()) {
|
||||
|
||||
@@ -142,7 +142,7 @@ export class TestResultService implements ITestResultService {
|
||||
config = this.testConfiguration.getControllerGroupConfigurations(req.controllerId, TestRunConfigurationBitset.Run)[0];
|
||||
} else {
|
||||
const configs = this.testConfiguration.getControllerGroupConfigurations(req.controllerId, req.config.group);
|
||||
config = configs.find(c => c.configId === req.config!.id) || configs[0];
|
||||
config = configs.find(c => c.profileId === req.config!.id) || configs[0];
|
||||
}
|
||||
|
||||
const resolved: ResolvedTestRunRequest = {
|
||||
@@ -153,9 +153,8 @@ export class TestResultService implements ITestResultService {
|
||||
|
||||
if (config) {
|
||||
resolved.targets.push({
|
||||
configGroup: config.group,
|
||||
configId: config.configId,
|
||||
configLabel: config.label,
|
||||
profileGroup: config.group,
|
||||
profileId: config.profileId,
|
||||
controllerId: req.controllerId,
|
||||
testIds: req.tests,
|
||||
});
|
||||
|
||||
@@ -172,8 +172,6 @@ export interface ITestRootProvider {
|
||||
export interface AmbiguousRunTestsRequest {
|
||||
/** Group to run */
|
||||
group: TestRunConfigurationBitset;
|
||||
/** If there is a configuration in the group with this label, use it */
|
||||
preferGroupLabel?: string;
|
||||
/** Tests to run. Allowed to be from different controllers */
|
||||
tests: ITestIdWithSrc[];
|
||||
/** Tests to exclude. If not given, the current UI excluded tests are used */
|
||||
|
||||
@@ -94,20 +94,39 @@ export class TestService extends Disposable implements ITestService {
|
||||
*/
|
||||
public async runTests(req: AmbiguousRunTestsRequest, token = CancellationToken.None): Promise<ITestResult> {
|
||||
const resolved: ResolvedTestRunRequest = { targets: [], exclude: req.exclude, isAutoRun: req.isAutoRun };
|
||||
for (const byController of groupBy(req.tests, (a, b) => a.controllerId === b.controllerId ? 0 : 1)) {
|
||||
const groups = this.testConfigurationService.getControllerGroupConfigurations(byController[0].controllerId, req.group);
|
||||
const group = req.preferGroupLabel ? (groups.find(g => g.label === req.preferGroupLabel) || groups[0]) : groups[0];
|
||||
if (group) {
|
||||
|
||||
// First, try to run the tests using the default run configurations...
|
||||
const defaultConfigs = this.testConfigurationService.getGroupDefaultConfigurations(req.group);
|
||||
for (const config of defaultConfigs) {
|
||||
const testIds = req.tests.filter(t => t.controllerId === config.controllerId).map(t => t.testId);
|
||||
if (testIds.length) {
|
||||
resolved.targets.push({
|
||||
testIds: byController.map(t => t.testId),
|
||||
configLabel: group.label,
|
||||
configGroup: group.group,
|
||||
configId: group.configId,
|
||||
controllerId: group.controllerId,
|
||||
testIds: testIds,
|
||||
profileGroup: config.group,
|
||||
profileId: config.profileId,
|
||||
controllerId: config.controllerId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If no tests are covered by the defaults, just use whatever the defaults
|
||||
// for their controller are. This can happen if the user chose specific
|
||||
// configs for the run button, but then asked to run a single test from the
|
||||
// explorer or decoration. We shouldn't no-op.
|
||||
if (resolved.targets.length === 0) {
|
||||
for (const byController of groupBy(req.tests, (a, b) => a.controllerId === b.controllerId ? 0 : 1)) {
|
||||
const configs = this.testConfigurationService.getControllerGroupConfigurations(byController[0].controllerId, req.group);
|
||||
if (configs.length) {
|
||||
resolved.targets.push({
|
||||
testIds: byController.map(t => t.testId),
|
||||
profileGroup: req.group,
|
||||
profileId: configs[0].profileId,
|
||||
controllerId: configs[0].controllerId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.runResolvedTests(resolved, token);
|
||||
}
|
||||
|
||||
@@ -138,7 +157,7 @@ export class TestService extends Disposable implements ITestService {
|
||||
{
|
||||
runId: result.id,
|
||||
excludeExtIds: req.exclude!.filter(t => t.controllerId === group.controllerId).map(t => t.testId),
|
||||
configId: group.configId,
|
||||
configId: group.profileId,
|
||||
controllerId: group.controllerId,
|
||||
testIds: group.testIds,
|
||||
},
|
||||
|
||||
@@ -14,14 +14,14 @@ export namespace TestingContextKeys {
|
||||
export const hasDebuggableTests = new RawContextKey('testing.hasDebuggableTests', false, { type: 'boolean', description: localize('testing.hasDebuggableTests', 'Indicates whether any test controller has registered a debug configuration') });
|
||||
export const hasRunnableTests = new RawContextKey('testing.hasRunnableTests', false, { type: 'boolean', description: localize('testing.hasRunnableTests', 'Indicates whether any test controller has registered a run configuration') });
|
||||
export const hasCoverableTests = new RawContextKey('testing.hasCoverableTests', false, { type: 'boolean', description: localize('testing.hasCoverableTests', 'Indicates whether any test controller has registered a coverage configuration') });
|
||||
export const hasNonDefaultConfig = new RawContextKey('testing.hasNonDefaultConfig', false, { type: 'boolean', description: localize('testing.hasNonDefaultConfig', 'Indicates whether any test controller has registered a non-default configuration') });
|
||||
export const hasNonDefaultProfile = new RawContextKey('testing.hasNonDefaultConfig', false, { type: 'boolean', description: localize('testing.hasNonDefaultConfig', 'Indicates whether any test controller has registered a non-default configuration') });
|
||||
export const hasConfigurableConfig = new RawContextKey('testing.hasConfigurableConfig', false, { type: 'boolean', description: localize('testing.hasConfigurableConfig', 'Indicates whether any test configuration can be configured') });
|
||||
|
||||
export const capabilityToContextKey: { [K in TestRunConfigurationBitset]: RawContextKey<boolean> } = {
|
||||
[TestRunConfigurationBitset.Run]: hasRunnableTests,
|
||||
[TestRunConfigurationBitset.Coverage]: hasCoverableTests,
|
||||
[TestRunConfigurationBitset.Debug]: hasDebuggableTests,
|
||||
[TestRunConfigurationBitset.HasNonDefaultConfig]: hasNonDefaultConfig,
|
||||
[TestRunConfigurationBitset.HasNonDefaultConfig]: hasNonDefaultProfile,
|
||||
[TestRunConfigurationBitset.HasConfigurable]: hasConfigurableConfig,
|
||||
};
|
||||
|
||||
|
||||
@@ -35,9 +35,8 @@ suite('Workbench - Test Results Service', () => {
|
||||
|
||||
const defaultOpts = (testIds: string[]): ResolvedTestRunRequest => ({
|
||||
targets: [{
|
||||
configGroup: TestRunConfigurationBitset.Run,
|
||||
configId: 0,
|
||||
configLabel: 'Run Tests',
|
||||
profileGroup: TestRunConfigurationBitset.Run,
|
||||
profileId: 0,
|
||||
controllerId: 'ctrlId',
|
||||
testIds,
|
||||
}]
|
||||
@@ -204,7 +203,7 @@ suite('Workbench - Test Results Service', () => {
|
||||
|
||||
setup(() => {
|
||||
storage = new InMemoryResultStorage(new TestStorageService(), new NullLogService());
|
||||
results = new TestTestResultService(new MockContextKeyService(), storage, new TestConfigurationService(new MockContextKeyService()));
|
||||
results = new TestTestResultService(new MockContextKeyService(), storage, new TestConfigurationService(new MockContextKeyService(), new TestStorageService()));
|
||||
});
|
||||
|
||||
test('pushes new result', () => {
|
||||
@@ -221,7 +220,7 @@ suite('Workbench - Test Results Service', () => {
|
||||
results = new TestResultService(
|
||||
new MockContextKeyService(),
|
||||
storage,
|
||||
new TestConfigurationService(new MockContextKeyService()),
|
||||
new TestConfigurationService(new MockContextKeyService(), new TestStorageService()),
|
||||
);
|
||||
|
||||
assert.strictEqual(0, results.results.length);
|
||||
|
||||
Reference in New Issue
Block a user