mirror of
https://github.com/Microsoft/vscode
synced 2024-08-24 19:46:05 +00:00
almost complete update for run configurations
This commit is contained in:
parent
9a09d4817d
commit
fa9255c0de
7
src/vs/vscode.proposed.d.ts
vendored
7
src/vs/vscode.proposed.d.ts
vendored
|
@ -1772,7 +1772,7 @@ declare module 'vscode' {
|
|||
*
|
||||
* @param id Identifier for the controller, must be globally unique.
|
||||
*/
|
||||
export function createTestController(id: string): TestController;
|
||||
export function createTestController(id: string, label: string): TestController;
|
||||
|
||||
/**
|
||||
* Requests that tests be run by their controller.
|
||||
|
@ -1931,6 +1931,11 @@ declare module 'vscode' {
|
|||
*/
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* Human-readable label for the test controller.
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* Root test item. Tests in the workspace should be added as children of
|
||||
* the root. The extension controls when to add these, although the
|
||||
|
|
|
@ -17,7 +17,7 @@ import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/t
|
|||
import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
|
||||
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
|
||||
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { IMainThreadTestController, ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { ExtHostContext, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
|
||||
const reviveDiff = (diff: TestsDiff) => {
|
||||
|
@ -38,7 +38,11 @@ const reviveDiff = (diff: TestsDiff) => {
|
|||
export class MainThreadTesting extends Disposable implements MainThreadTestingShape, ITestRootProvider {
|
||||
private readonly proxy: ExtHostTestingShape;
|
||||
private readonly diffListener = this._register(new MutableDisposable());
|
||||
private readonly testProviderRegistrations = new Map<string, IDisposable>();
|
||||
private readonly testProviderRegistrations = new Map<string, {
|
||||
instance: IMainThreadTestController;
|
||||
label: MutableObservableValue<string>;
|
||||
disposable: IDisposable
|
||||
}>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
|
@ -71,7 +75,10 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
|||
* @inheritdoc
|
||||
*/
|
||||
$publishTestRunConfig(config: ITestRunConfiguration): void {
|
||||
this.testConfiguration.addConfiguration(config);
|
||||
const controller = this.testProviderRegistrations.get(config.controllerId);
|
||||
if (controller) {
|
||||
this.testConfiguration.addConfiguration(controller.instance, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,23 +187,43 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $registerTestController(controllerId: string) {
|
||||
public $registerTestController(controllerId: string, labelStr: string) {
|
||||
const disposable = new DisposableStore();
|
||||
|
||||
disposable.add(toDisposable(() => this.testConfiguration.removeConfiguration(controllerId)));
|
||||
disposable.add(this.testService.registerTestController(controllerId, {
|
||||
const label = new MutableObservableValue(labelStr);
|
||||
const controller: IMainThreadTestController = {
|
||||
id: controllerId,
|
||||
label,
|
||||
configureRunConfig: id => this.proxy.$configureRunConfig(controllerId, id),
|
||||
runTests: (req, token) => this.proxy.$runControllerTests(req, token),
|
||||
expandTest: (src, levels) => this.proxy.$expandTest(src, isFinite(levels) ? levels : -1),
|
||||
}));
|
||||
};
|
||||
|
||||
this.testProviderRegistrations.set(controllerId, disposable);
|
||||
|
||||
disposable.add(toDisposable(() => this.testConfiguration.removeConfiguration(controllerId)));
|
||||
disposable.add(this.testService.registerTestController(controllerId, controller));
|
||||
|
||||
this.testProviderRegistrations.set(controllerId, {
|
||||
instance: controller,
|
||||
label,
|
||||
disposable
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $updateControllerLabel(controllerId: string, label: string) {
|
||||
const controller = this.testProviderRegistrations.get(controllerId);
|
||||
if (controller) {
|
||||
controller.label.value = label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $unregisterTestController(controllerId: string) {
|
||||
this.testProviderRegistrations.get(controllerId)?.dispose();
|
||||
this.testProviderRegistrations.get(controllerId)?.disposable.dispose();
|
||||
this.testProviderRegistrations.delete(controllerId);
|
||||
}
|
||||
|
||||
|
@ -231,7 +258,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
|||
public override dispose() {
|
||||
super.dispose();
|
||||
for (const subscription of this.testProviderRegistrations.values()) {
|
||||
subscription.dispose();
|
||||
subscription.disposable.dispose();
|
||||
}
|
||||
this.testProviderRegistrations.clear();
|
||||
}
|
||||
|
|
|
@ -342,9 +342,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
: extHostTypes.ExtensionKind.UI;
|
||||
|
||||
const test: typeof vscode.test = {
|
||||
createTestController(provider) {
|
||||
createTestController(provider, label) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTesting.createTestController(provider);
|
||||
return extHostTesting.createTestController(provider, label);
|
||||
},
|
||||
createTestObserver() {
|
||||
checkProposedApiEnabled(extension);
|
||||
|
|
|
@ -2087,13 +2087,17 @@ export interface ExtHostTestingShape {
|
|||
* Requires file coverage to have been previously requested via $provideFileCoverage.
|
||||
*/
|
||||
$resolveFileCoverage(runId: string, taskId: string, fileIndex: number, token: CancellationToken): Promise<CoverageDetails[]>;
|
||||
/** Configures a test run config. */
|
||||
$configureRunConfig(controllerId: string, configId: number): void;
|
||||
}
|
||||
|
||||
export interface MainThreadTestingShape {
|
||||
// --- test lifecycle:
|
||||
|
||||
/** Registeres that there's a test controller with the given ID */
|
||||
$registerTestController(controllerId: string): void;
|
||||
/** Registers that there's a test controller with the given ID */
|
||||
$registerTestController(controllerId: string, label: string): void;
|
||||
/** Updates the label of an existing test controller. */
|
||||
$updateControllerLabel(controllerId: string, label: string): void;
|
||||
/** Diposes of the test controller with the given ID */
|
||||
$unregisterTestController(controllerId: string): void;
|
||||
/** Requests tests published to VS Code. */
|
||||
|
|
|
@ -53,14 +53,22 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
|||
/**
|
||||
* Implements vscode.test.registerTestProvider
|
||||
*/
|
||||
public createTestController(controllerId: string): vscode.TestController {
|
||||
public createTestController(controllerId: string, label: string): vscode.TestController {
|
||||
const disposable = new DisposableStore();
|
||||
const collection = disposable.add(new SingleUseTestCollection(controllerId));
|
||||
const initialExpand = disposable.add(new RunOnceScheduler(() => collection.expand(collection.root.id, 0), 0));
|
||||
const configurations = new Map<number, vscode.TestRunConfiguration>();
|
||||
const proxy = this.proxy;
|
||||
|
||||
const controller: vscode.TestController = {
|
||||
root: collection.root,
|
||||
get label() {
|
||||
return label;
|
||||
},
|
||||
set label(value: string) {
|
||||
label = value;
|
||||
proxy.$updateControllerLabel(controllerId, label);
|
||||
},
|
||||
get id() {
|
||||
return controllerId;
|
||||
},
|
||||
|
@ -100,13 +108,13 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
|||
},
|
||||
};
|
||||
|
||||
this.proxy.$registerTestController(controllerId);
|
||||
disposable.add(toDisposable(() => this.proxy.$unregisterTestController(controllerId)));
|
||||
proxy.$registerTestController(controllerId, label);
|
||||
disposable.add(toDisposable(() => proxy.$unregisterTestController(controllerId)));
|
||||
|
||||
this.controllers.set(controllerId, { controller, collection, configurations });
|
||||
disposable.add(toDisposable(() => this.controllers.delete(controllerId)));
|
||||
|
||||
disposable.add(collection.onDidGenerateDiff(diff => this.proxy.$publishDiff(controllerId, diff)));
|
||||
disposable.add(collection.onDidGenerateDiff(diff => proxy.$publishDiff(controllerId, diff)));
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
@ -166,6 +174,11 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
|||
return Iterable.find(this.runTracker.trackers, t => t.id === runId)?.getCoverage(taskId)?.resolveFileCoverage(fileIndex, token) ?? Promise.resolve([]);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
$configureRunConfig(controllerId: string, configId: number) {
|
||||
this.controllers.get(controllerId)?.configurations.get(configId)?.configureHandler?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates test results shown to extensions.
|
||||
* @override
|
||||
|
|
|
@ -13,6 +13,8 @@ import { testingColorRunAction, testMessageSeverityColors, testStatesToIconColor
|
|||
export const testingViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.'));
|
||||
export const testingRunIcon = registerIcon('testing-run-icon', Codicon.run, localize('testingRunIcon', 'Icon of the "run test" action.'));
|
||||
export const testingRunAllIcon = registerIcon('testing-run-all-icon', Codicon.runAll, localize('testingRunAllIcon', 'Icon of the "run all tests" action.'));
|
||||
// todo: https://github.com/microsoft/vscode-codicons/issues/72
|
||||
export const testingDebugAllIcon = registerIcon('testing-debug-all-icon', Codicon.debugAlt, localize('testingDebugAllIcon', 'Icon of the "debug all tests" action.'));
|
||||
export const testingDebugIcon = registerIcon('testing-debug-icon', Codicon.debugAlt, localize('testingDebugIcon', 'Icon of the "debug test" action.'));
|
||||
export const testingCancelIcon = registerIcon('testing-cancel-icon', Codicon.debugStop, localize('testingCancelIcon', 'Icon to cancel ongoing test runs.'));
|
||||
export const testingFilterIcon = registerIcon('testing-filter', Codicon.filter, localize('filterIcon', 'Icon for the \'Filter\' action in the testing view.'));
|
||||
|
@ -22,6 +24,8 @@ export const testingHiddenIcon = registerIcon('testing-hidden', Codicon.eyeClose
|
|||
export const testingShowAsList = registerIcon('testing-show-as-list-icon', Codicon.listTree, localize('testingShowAsList', 'Icon shown when the test explorer is disabled as a tree.'));
|
||||
export const testingShowAsTree = registerIcon('testing-show-as-list-icon', Codicon.listFlat, localize('testingShowAsTree', 'Icon shown when the test explorer is disabled as a list.'));
|
||||
|
||||
export const testingUpdateConfiguration = registerIcon('testing-update-configuration', Codicon.gear, localize('testingUpdateConfiguration', 'Icon shown to update test configurations.'));
|
||||
|
||||
export const testingStatesToIcons = new Map<TestResultState, ThemeIcon>([
|
||||
[TestResultState.Errored, registerIcon('testing-error-icon', Codicon.issues, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))],
|
||||
[TestResultState.Failed, registerIcon('testing-failed-icon', Codicon.error, localize('testingFailedIcon', 'Icon shown for tests that failed.'))],
|
||||
|
|
|
@ -26,10 +26,11 @@ import { REVEAL_IN_EXPLORER_COMMAND_ID } from 'vs/workbench/contrib/files/browse
|
|||
import { IActionableTestTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
|
||||
import * as icons from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import { ITestExplorerFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
|
||||
import { TestingExplorerView, TestingExplorerViewModel } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
|
||||
import type { TestingExplorerView, TestingExplorerViewModel } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
|
||||
import { ITestingOutputTerminalService } from 'vs/workbench/contrib/testing/browser/testingOutputTerminalService';
|
||||
import { TestExplorerViewMode, TestExplorerViewSorting, Testing } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { identifyTest, InternalTestItem, ITestIdWithSrc, ITestItem, TestIdPath, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { identifyTest, InternalTestItem, ITestIdWithSrc, ITestItem, ITestRunConfiguration, TestIdPath, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
|
||||
import { ITestingAutoRun } from 'vs/workbench/contrib/testing/common/testingAutoRun';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
|
||||
|
@ -48,12 +49,11 @@ const enum ActionOrder {
|
|||
Debug,
|
||||
Coverage,
|
||||
AutoRun,
|
||||
Collapse,
|
||||
|
||||
// Submenu:
|
||||
Collapse,
|
||||
DisplayMode,
|
||||
Sort,
|
||||
Refresh,
|
||||
}
|
||||
|
||||
const hasAnyTestProvider = ContextKeyGreaterExpr.create(TestingContextKeys.providerCount.key, 0);
|
||||
|
@ -130,7 +130,6 @@ export class DebugAction extends Action2 {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
export class RunAction extends Action2 {
|
||||
public static readonly ID = 'testing.run';
|
||||
constructor() {
|
||||
|
@ -158,6 +157,37 @@ export class RunAction extends Action2 {
|
|||
}
|
||||
}
|
||||
|
||||
export class ConfigureTestsAction extends Action2 {
|
||||
public static readonly ID = 'testing.configureTests';
|
||||
constructor() {
|
||||
super({
|
||||
id: ConfigureTestsAction.ID,
|
||||
title: localize('testing.configureTests', 'Configure Tests'),
|
||||
icon: icons.testingUpdateConfiguration,
|
||||
f1: true,
|
||||
category,
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: TestingContextKeys.hasConfigurableConfig.isEqualTo(true),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public override async run(acessor: ServicesAccessor) {
|
||||
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'),
|
||||
showConfigureButtons: false,
|
||||
onlyConfigurable: true,
|
||||
});
|
||||
|
||||
if (configuration) {
|
||||
testConfigurationService.configure(configuration.controllerId, configuration.configId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ExecuteSelectedAction extends ViewAction<TestingExplorerView> {
|
||||
constructor(id: string, title: string, icon: ThemeIcon, private readonly group: TestRunConfigurationBitset) {
|
||||
super({
|
||||
|
@ -491,7 +521,7 @@ export class CollapseAllAction extends ViewAction<TestingExplorerView> {
|
|||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
order: ActionOrder.Collapse,
|
||||
group: 'navigation',
|
||||
group: 'cikkaose',
|
||||
when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId)
|
||||
}
|
||||
});
|
||||
|
@ -505,33 +535,6 @@ export class CollapseAllAction extends ViewAction<TestingExplorerView> {
|
|||
}
|
||||
}
|
||||
|
||||
export class RefreshTestsAction extends Action2 {
|
||||
public static readonly ID = 'testing.refreshTests';
|
||||
constructor() {
|
||||
super({
|
||||
id: RefreshTestsAction.ID,
|
||||
title: localize('testing.refresh', "Refresh Tests"),
|
||||
category,
|
||||
menu: [{
|
||||
id: MenuId.ViewTitle,
|
||||
order: ActionOrder.Refresh,
|
||||
group: 'refresh',
|
||||
when: ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId)
|
||||
}, {
|
||||
id: MenuId.CommandPalette,
|
||||
when: TestingContextKeys.providerCount.isEqualTo(true),
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public run(accessor: ServicesAccessor) {
|
||||
accessor.get(ITestService).resubscribeToAllTests();
|
||||
}
|
||||
}
|
||||
|
||||
export class ClearTestResultsAction extends Action2 {
|
||||
public static readonly ID = 'testing.clearTestResults';
|
||||
constructor() {
|
||||
|
@ -1085,6 +1088,7 @@ export const allTestActions = [
|
|||
CancelTestRunAction,
|
||||
ClearTestResultsAction,
|
||||
CollapseAllAction,
|
||||
ConfigureTestsAction,
|
||||
DebugAction,
|
||||
DebugAllAction,
|
||||
DebugAtCursor,
|
||||
|
@ -1095,7 +1099,6 @@ export const allTestActions = [
|
|||
GoToTest,
|
||||
HideTestAction,
|
||||
OpenOutputPeek,
|
||||
RefreshTestsAction,
|
||||
ReRunFailedTests,
|
||||
ReRunLastRun,
|
||||
RunAction,
|
||||
|
|
|
@ -26,7 +26,7 @@ import { ITestingProgressUiService, TestingProgressUiService } from 'vs/workbenc
|
|||
import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer';
|
||||
import { testingConfiguation } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { TestIdPath, ITestIdWithSrc, identifyTest, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { identifyTest, ITestIdWithSrc, TestIdPath, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestConfigurationService, TestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
|
||||
import { ITestingAutoRun, TestingAutoRun } from 'vs/workbench/contrib/testing/common/testingAutoRun';
|
||||
import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider';
|
||||
|
@ -38,6 +38,7 @@ import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
|||
import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { allTestActions, runTestsByPath } from './testExplorerActions';
|
||||
import './testingConfigurationUi';
|
||||
|
||||
registerSingleton(ITestService, TestService, true);
|
||||
registerSingleton(ITestResultStorage, TestResultStorage, true);
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { groupBy } from 'vs/base/common/arrays';
|
||||
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 { 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 { 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;
|
||||
|
||||
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')
|
||||
}] : []
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (controllerId !== undefined) {
|
||||
const lookup = configService.getControllerConfigurations(controllerId);
|
||||
if (!lookup) {
|
||||
return;
|
||||
}
|
||||
|
||||
pushItems(lookup.configs);
|
||||
} else {
|
||||
for (const { configs, controller } of configService.all()) {
|
||||
pushItems(configs, controller.label.value);
|
||||
}
|
||||
}
|
||||
|
||||
const quickpick = accessor.get(IQuickInputService).createQuickPick();
|
||||
quickpick.items = items;
|
||||
quickpick.placeholder = placeholder;
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
quickpick.show();
|
||||
});
|
||||
|
||||
quickpick.dispose();
|
||||
return pick;
|
||||
}
|
||||
});
|
||||
|
|
@ -30,7 +30,7 @@ import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browse
|
|||
import { testMessageSeverityColors } from 'vs/workbench/contrib/testing/browser/theme';
|
||||
import { DefaultGutterClickAction, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
import { labelForTestInState } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { identifyTest, IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, TestResultItem, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { identifyTest, IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, ITestRunConfiguration, TestResultItem, TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestConfigurationService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
|
||||
import { maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
|
||||
import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
|
||||
|
@ -419,6 +419,25 @@ 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);
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.testService.runResolvedTests({
|
||||
targets: [{
|
||||
configGroup: config.group,
|
||||
configId: config.configId,
|
||||
configLabel: config.label,
|
||||
controllerId: config.controllerId,
|
||||
testIds: [test.item.extId]
|
||||
}]
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
testActions.push(new Action('testing.gutter.reveal', localize('reveal test', 'Reveal in Test Explorer'), undefined, undefined, async () => {
|
||||
const path = [test];
|
||||
while (true) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import { URI } from 'vs/base/common/uri';
|
|||
import 'vs/css!./media/testing';
|
||||
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
|
||||
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 { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -48,7 +49,7 @@ import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/comm
|
|||
import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation';
|
||||
import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
|
||||
import { ITestTreeProjection, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
|
||||
import { testingHiddenIcon, testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import * as icons from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import { ITestExplorerFilterState, TestExplorerFilterState, TestingExplorerFilter } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
|
||||
import { ITestingProgressUiService } from 'vs/workbench/contrib/testing/browser/testingProgressUiService';
|
||||
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
|
@ -61,7 +62,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 { GoToTest } from './testExplorerActions';
|
||||
import { DebugAllAction, GoToTest, RunAllAction } from './testExplorerActions';
|
||||
|
||||
export class TestingExplorerView extends ViewPane {
|
||||
public viewModel!: TestingExplorerViewModel;
|
||||
|
@ -82,9 +83,10 @@ export class TestingExplorerView extends ViewPane {
|
|||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ITestService testService: ITestService,
|
||||
@ITestService private readonly testService: ITestService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ITestingProgressUiService private readonly testProgressService: ITestingProgressUiService,
|
||||
@ITestConfigurationService private readonly testConfigurationService: ITestConfigurationService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
this.location.set(viewDescriptorService.getViewLocationById(Testing.ExplorerViewId) ?? ViewContainerLocation.Sidebar);
|
||||
|
@ -153,15 +155,65 @@ export class TestingExplorerView extends ViewPane {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
/** @override */
|
||||
public override getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (action.id === Testing.FilterActionId) {
|
||||
return this.instantiationService.createInstance(TestingExplorerFilter, action);
|
||||
switch (action.id) {
|
||||
case Testing.FilterActionId:
|
||||
return this.instantiationService.createInstance(TestingExplorerFilter, action);
|
||||
case RunAllAction.ID:
|
||||
return this.getRunGroupDropdown(TestRunConfigurationBitset.Run, action);
|
||||
case DebugAllAction.ID:
|
||||
return this.getRunGroupDropdown(TestRunConfigurationBitset.Debug, action);
|
||||
default:
|
||||
return super.getActionViewItem(action);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
private getTestConfigGroupActions(group: TestRunConfigurationBitset) {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
let participatingGroups = 0;
|
||||
for (const { configs, controller } of this.testConfigurationService.all()) {
|
||||
let hasAdded = false;
|
||||
|
||||
for (const config of configs) {
|
||||
if (config.group !== group) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasAdded) {
|
||||
hasAdded = true;
|
||||
participatingGroups++;
|
||||
actions.push(new Action(`${controller.id}.$root`, controller.label.value, undefined, false));
|
||||
}
|
||||
|
||||
actions.push(new Action(
|
||||
`${controller.id}.${config.configId}`,
|
||||
config.label,
|
||||
undefined,
|
||||
undefined,
|
||||
() => this.testService.runResolvedTests({
|
||||
targets: [{
|
||||
configGroup: config.group,
|
||||
configId: config.configId,
|
||||
configLabel: config.label,
|
||||
controllerId: config.controllerId,
|
||||
testIds: this.getSelectedOrVisibleItems()
|
||||
.filter(i => i.controllerId === config.controllerId)
|
||||
.map(i => i.item.extId),
|
||||
}]
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return super.getActionViewItem(action);
|
||||
// If there's only one group, don't add a heading for it in the dropdown.
|
||||
if (participatingGroups === 1) {
|
||||
actions.shift();
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,6 +223,38 @@ export class TestingExplorerView extends ViewPane {
|
|||
super.saveState();
|
||||
}
|
||||
|
||||
/**
|
||||
* If items in the tree are selected, returns them. Otherwise, returns
|
||||
* visible tests.
|
||||
*/
|
||||
private getSelectedOrVisibleItems() {
|
||||
return [...this.testService.collection.rootItems]; // todo
|
||||
}
|
||||
|
||||
private getRunGroupDropdown(group: TestRunConfigurationBitset, defaultAction: IAction) {
|
||||
const dropdownActions = this.getTestConfigGroupActions(group);
|
||||
if (dropdownActions.length < 2) {
|
||||
return super.getActionViewItem(defaultAction);
|
||||
}
|
||||
|
||||
const primaryAction = this.instantiationService.createInstance(MenuItemAction, {
|
||||
id: defaultAction.id,
|
||||
title: defaultAction.label,
|
||||
icon: group === TestRunConfigurationBitset.Run
|
||||
? icons.testingRunAllIcon
|
||||
: icons.testingDebugAllIcon,
|
||||
}, undefined, undefined);
|
||||
|
||||
const dropdownAction = new Action('selectRunConfig', 'Select Configuration...', 'codicon-chevron-down', true);
|
||||
|
||||
return this.instantiationService.createInstance(
|
||||
DropdownWithPrimaryActionViewItem,
|
||||
primaryAction, dropdownAction, dropdownActions,
|
||||
'',
|
||||
this.contextMenuService,
|
||||
);
|
||||
}
|
||||
|
||||
private createFilterActionBar() {
|
||||
const bar = new ActionBar(this.treeHeader, {
|
||||
actionViewItemProvider: action => this.getActionViewItem(action),
|
||||
|
@ -911,7 +995,7 @@ abstract class ActionableItemTemplateData<T extends TestItemTreeElement> extends
|
|||
const name = dom.append(wrapper, dom.$('.name'));
|
||||
const label = this.labels.create(name, { supportHighlights: true });
|
||||
|
||||
dom.append(wrapper, dom.$(ThemeIcon.asCSSSelector(testingHiddenIcon)));
|
||||
dom.append(wrapper, dom.$(ThemeIcon.asCSSSelector(icons.testingHiddenIcon)));
|
||||
const actionBar = new ActionBar(wrapper, {
|
||||
actionRunner: this.actionRunner,
|
||||
actionViewItemProvider: action =>
|
||||
|
@ -978,7 +1062,7 @@ class TestItemRenderer extends ActionableItemTemplateData<TestItemTreeElement> {
|
|||
const testHidden = this.testService.excluded.contains(identifyTest(node.element.test));
|
||||
data.wrapper.classList.toggle('test-is-hidden', testHidden);
|
||||
|
||||
const icon = testingStatesToIcons.get(
|
||||
const icon = icons.testingStatesToIcons.get(
|
||||
node.element.test.expand === TestItemExpandState.BusyExpanding || node.element.test.item.busy
|
||||
? TestResultState.Running
|
||||
: node.element.state);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import { localize } from 'vs/nls';
|
||||
import { TestResultState } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { TestRunConfigurationBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
|
||||
export const enum Testing {
|
||||
// marked as "extension" so that any existing test extensions are assigned to it.
|
||||
|
@ -45,3 +46,9 @@ export const labelForTestInState = (label: string, state: TestResultState) => lo
|
|||
key: 'testing.treeElementLabel',
|
||||
comment: ['label then the unit tests state, for example "Addition Tests (Running)"'],
|
||||
}, '{0} ({1})', label, testStateNames[state]);
|
||||
|
||||
export const testConfigurationGroupNames: { [K in TestRunConfigurationBitset]: string } = {
|
||||
[TestRunConfigurationBitset.Debug]: localize('testGroup.debug', 'Debug'),
|
||||
[TestRunConfigurationBitset.Run]: localize('testGroup.run', 'Run'),
|
||||
[TestRunConfigurationBitset.Coverage]: localize('testGroup.coverage', 'Coverage'),
|
||||
};
|
||||
|
|
|
@ -22,6 +22,8 @@ export const enum TestRunConfigurationBitset {
|
|||
Run = 1 << 1,
|
||||
Debug = 1 << 2,
|
||||
Coverage = 1 << 3,
|
||||
HasNonDefaultConfig = 1 << 4,
|
||||
HasConfigurable = 1 << 5,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,6 +33,7 @@ export const testRunConfigurationBitsetList = [
|
|||
TestRunConfigurationBitset.Run,
|
||||
TestRunConfigurationBitset.Debug,
|
||||
TestRunConfigurationBitset.Coverage,
|
||||
TestRunConfigurationBitset.HasNonDefaultConfig,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
|
|||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
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';
|
||||
|
||||
export const ITestConfigurationService = createDecorator<ITestConfigurationService>('testConfigurationService');
|
||||
|
||||
|
@ -22,7 +23,7 @@ export interface ITestConfigurationService {
|
|||
/**
|
||||
* Publishes a new test configuration.
|
||||
*/
|
||||
addConfiguration(config: ITestRunConfiguration): void;
|
||||
addConfiguration(controller: IMainThreadTestController, config: ITestRunConfiguration): void;
|
||||
|
||||
/**
|
||||
* Updates an existing test run configuration
|
||||
|
@ -43,9 +44,27 @@ export interface ITestConfigurationService {
|
|||
controllerCapabilities(controllerId: string): number;
|
||||
|
||||
/**
|
||||
* Gets the configurations for the group, in priorty order.
|
||||
* Configures a test configuration.
|
||||
*/
|
||||
controllerGroupConfigurations(controllerId: string, group: TestRunConfigurationBitset): readonly ITestRunConfiguration[];
|
||||
configure(controllerId: string, configId: number): void;
|
||||
|
||||
/**
|
||||
* Gets all registered controllers, grouping by controller.
|
||||
*/
|
||||
all(): Iterable<Readonly<{ controller: IMainThreadTestController, configs: ITestRunConfiguration[] }>>;
|
||||
|
||||
/**
|
||||
* Gets the configurations for a controller, in priority order.
|
||||
*/
|
||||
getControllerConfigurations(controllerId: string): undefined | {
|
||||
controller: IMainThreadTestController;
|
||||
configs: ITestRunConfiguration[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the configurations for the group in a controller, in priorty order.
|
||||
*/
|
||||
getControllerGroupConfigurations(controllerId: string, group: TestRunConfigurationBitset): readonly ITestRunConfiguration[];
|
||||
}
|
||||
|
||||
const sorter = (a: ITestRunConfiguration, b: ITestRunConfiguration) => {
|
||||
|
@ -66,40 +85,52 @@ export const capabilityContextKeys = (capabilities: number): [key: string, value
|
|||
[TestingContextKeys.hasCoverableTests.key, (capabilities & TestRunConfigurationBitset.Coverage) !== 0],
|
||||
];
|
||||
|
||||
|
||||
export class TestConfigurationService implements ITestConfigurationService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private readonly capabilitiesContexts: { [K in TestRunConfigurationBitset]: IContextKey<boolean> };
|
||||
private readonly changeEmitter = new Emitter<void>();
|
||||
private readonly controllerConfigs = new Map</* controller ID */string, {
|
||||
configs: ITestRunConfiguration[],
|
||||
controller: IMainThreadTestController,
|
||||
capabilities: number,
|
||||
}>();
|
||||
|
||||
/** @inheritdoc */
|
||||
public readonly onDidChange = this.changeEmitter.event;
|
||||
|
||||
constructor(@IContextKeyService contextKeyService: IContextKeyService) {
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
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.HasConfigurable]: TestingContextKeys.hasConfigurableConfig.bindTo(contextKeyService),
|
||||
};
|
||||
|
||||
this.refreshContextKeys();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public addConfiguration(config: ITestRunConfiguration): void {
|
||||
const existing = this.controllerConfigs.get(config.controllerId);
|
||||
if (existing) {
|
||||
existing.configs.push(config);
|
||||
existing.configs.sort(sorter);
|
||||
existing.capabilities |= config.group;
|
||||
public addConfiguration(controller: IMainThreadTestController, config: ITestRunConfiguration): void {
|
||||
let record = this.controllerConfigs.get(config.controllerId);
|
||||
if (record) {
|
||||
record.configs.push(config);
|
||||
record.configs.sort(sorter);
|
||||
record.capabilities |= config.group;
|
||||
} else {
|
||||
this.controllerConfigs.set(config.controllerId, {
|
||||
record = {
|
||||
configs: [config],
|
||||
controller,
|
||||
capabilities: config.group
|
||||
});
|
||||
};
|
||||
this.controllerConfigs.set(config.controllerId, record);
|
||||
}
|
||||
|
||||
if (!config.isDefault) {
|
||||
record.capabilities |= TestRunConfigurationBitset.HasNonDefaultConfig;
|
||||
}
|
||||
|
||||
this.refreshContextKeys();
|
||||
|
@ -123,6 +154,11 @@ export class TestConfigurationService implements ITestConfigurationService {
|
|||
this.changeEmitter.fire();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public configure(controllerId: string, configId: number) {
|
||||
this.controllerConfigs.get(controllerId)?.controller.configureRunConfig(configId);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public removeConfiguration(controllerId: string, configId?: number): void {
|
||||
const ctrl = this.controllerConfigs.get(controllerId);
|
||||
|
@ -157,7 +193,17 @@ export class TestConfigurationService implements ITestConfigurationService {
|
|||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public controllerGroupConfigurations(controllerId: string, group: TestRunConfigurationBitset) {
|
||||
public all() {
|
||||
return this.controllerConfigs.values();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public getControllerConfigurations(controllerId: string) {
|
||||
return this.controllerConfigs.get(controllerId);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public getControllerGroupConfigurations(controllerId: string, group: TestRunConfigurationBitset) {
|
||||
return this.controllerConfigs.get(controllerId)?.configs.filter(c => c.group === group) ?? [];
|
||||
}
|
||||
|
||||
|
|
|
@ -139,9 +139,9 @@ export class TestResultService implements ITestResultService {
|
|||
|
||||
let config: ITestRunConfiguration | undefined;
|
||||
if (!req.config) {
|
||||
config = this.testConfiguration.controllerGroupConfigurations(req.controllerId, TestRunConfigurationBitset.Run)[0];
|
||||
config = this.testConfiguration.getControllerGroupConfigurations(req.controllerId, TestRunConfigurationBitset.Run)[0];
|
||||
} else {
|
||||
const configs = this.testConfiguration.controllerGroupConfigurations(req.controllerId, req.config.group);
|
||||
const configs = this.testConfiguration.getControllerGroupConfigurations(req.controllerId, req.config.group);
|
||||
config = configs.find(c => c.configId === req.config!.id) || configs[0];
|
||||
}
|
||||
|
||||
|
|
|
@ -275,7 +275,7 @@ export class TestResultStorage extends BaseTestResultStorage {
|
|||
await Promise.all(
|
||||
children
|
||||
.filter(child => !stored.has(child.name.replace(/\.[a-z]+$/, '')))
|
||||
.map(child => this.fileService.del(child.resource))
|
||||
.map(child => this.fileService.del(child.resource).catch(() => undefined))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,17 @@ import { Iterable } from 'vs/base/common/iterator';
|
|||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
|
||||
import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, ITestIdWithSrc, ResolvedTestRunRequest, RunTestForControllerRequest, TestIdPath, TestItemExpandState, TestRunConfigurationBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions';
|
||||
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
|
||||
|
||||
export const ITestService = createDecorator<ITestService>('testService');
|
||||
|
||||
export interface MainTestController {
|
||||
export interface IMainThreadTestController {
|
||||
readonly id: string;
|
||||
readonly label: IObservableValue<string>;
|
||||
configureRunConfig(configId: number): void;
|
||||
expandTest(src: ITestIdWithSrc, levels: number): Promise<void>;
|
||||
runTests(request: RunTestForControllerRequest, token: CancellationToken): Promise<void>;
|
||||
}
|
||||
|
@ -204,7 +208,7 @@ export interface ITestService {
|
|||
/**
|
||||
* Registers an interface that runs tests for the given provider ID.
|
||||
*/
|
||||
registerTestController(providerId: string, controller: MainTestController): IDisposable;
|
||||
registerTestController(providerId: string, controller: IMainThreadTestController): IDisposable;
|
||||
|
||||
/**
|
||||
* Requests that tests be executed.
|
||||
|
@ -225,9 +229,4 @@ export interface ITestService {
|
|||
* Publishes a test diff for a controller.
|
||||
*/
|
||||
publishDiff(controllerId: string, diff: TestsDiff): void;
|
||||
|
||||
/**
|
||||
* Requests to resubscribe to all active subscriptions, discarding old tests.
|
||||
*/
|
||||
resubscribeToAllTests(): void;
|
||||
}
|
||||
|
|
|
@ -19,11 +19,11 @@ import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusio
|
|||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
|
||||
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { AmbiguousRunTestsRequest, ITestService, MainTestController } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { AmbiguousRunTestsRequest, IMainThreadTestController, ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
|
||||
export class TestService extends Disposable implements ITestService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private testControllers = new Map<string, MainTestController>();
|
||||
private testControllers = new Map<string, IMainThreadTestController>();
|
||||
|
||||
private readonly cancelExtensionTestRunEmitter = new Emitter<{ runId: string | undefined }>();
|
||||
private readonly processDiffEmitter = new Emitter<TestsDiff>();
|
||||
|
@ -95,7 +95,7 @@ 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.controllerGroupConfigurations(byController[0].controllerId, req.group);
|
||||
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) {
|
||||
resolved.targets.push({
|
||||
|
@ -156,13 +156,6 @@ export class TestService extends Disposable implements ITestService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public resubscribeToAllTests() {
|
||||
// todo
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -174,7 +167,7 @@ export class TestService extends Disposable implements ITestService {
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public registerTestController(id: string, controller: MainTestController): IDisposable {
|
||||
public registerTestController(id: string, controller: IMainThreadTestController): IDisposable {
|
||||
this.testControllers.set(id, controller);
|
||||
this.providerCount.set(this.testControllers.size);
|
||||
|
||||
|
|
|
@ -14,11 +14,15 @@ 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 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.HasConfigurable]: hasConfigurableConfig,
|
||||
};
|
||||
|
||||
export const hasAnyResults = new RawContextKey('testing.hasAnyResults', false);
|
||||
|
|
|
@ -14,6 +14,7 @@ const MochaJUnitReporter = require('mocha-junit-reporter');
|
|||
const url = require('url');
|
||||
const minimatch = require('minimatch');
|
||||
const playwright = require('playwright');
|
||||
const { applyReporter } = require('../reporter');
|
||||
|
||||
// opts
|
||||
const defaultReporterName = process.platform === 'win32' ? 'list' : 'spec';
|
||||
|
@ -21,8 +22,8 @@ const optimist = require('optimist')
|
|||
// .describe('grep', 'only run tests matching <pattern>').alias('grep', 'g').alias('grep', 'f').string('grep')
|
||||
.describe('build', 'run with build output (out-build)').boolean('build')
|
||||
.describe('run', 'only run tests matching <relative_file_path>').string('run')
|
||||
.describe('glob', 'only run tests matching <glob_pattern>').string('glob')
|
||||
.describe('debug', 'do not run browsers headless').boolean('debug')
|
||||
.describe('grep', 'only run tests matching <pattern>').alias('grep', 'g').alias('grep', 'f').string('grep')
|
||||
.describe('debug', 'do not run browsers headless').alias('debug', ['debug-browser']).boolean('debug')
|
||||
.describe('browser', 'browsers in which tests should run').string('browser').default('browser', ['chromium', 'firefox', 'webkit'])
|
||||
.describe('reporter', 'the mocha reporter').string('reporter').default('reporter', defaultReporterName)
|
||||
.describe('reporter-options', 'the mocha reporter options').string('reporter-options').default('reporter-options', '')
|
||||
|
@ -51,30 +52,7 @@ const withReporter = (function () {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', argv.reporter);
|
||||
let ctor;
|
||||
|
||||
try {
|
||||
ctor = require(reporterPath);
|
||||
} catch (err) {
|
||||
try {
|
||||
ctor = require(argv.reporter);
|
||||
} catch (err) {
|
||||
ctor = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec;
|
||||
console.warn(`could not load reporter: ${argv.reporter}, using ${ctor.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseReporterOption(value) {
|
||||
let r = /^([^=]+)=(.*)$/.exec(value);
|
||||
return r ? { [r[1]]: r[2] } : {};
|
||||
}
|
||||
|
||||
let reporterOptions = argv['reporter-options'];
|
||||
reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions;
|
||||
reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {});
|
||||
|
||||
return (_, runner) => new ctor(runner, { reporterOptions })
|
||||
return (_, runner) => applyReporter(runner, argv);
|
||||
}
|
||||
})()
|
||||
|
||||
|
@ -103,7 +81,7 @@ const testModules = (async function () {
|
|||
} else {
|
||||
// glob patterns (--glob)
|
||||
const defaultGlob = '**/*.test.js';
|
||||
const pattern = argv.glob || defaultGlob
|
||||
const pattern = argv.run || defaultGlob
|
||||
isDefaultModules = pattern === defaultGlob;
|
||||
|
||||
promise = new Promise((resolve, reject) => {
|
||||
|
@ -183,7 +161,10 @@ async function runTestsInBrowser(testModules, browserType) {
|
|||
|
||||
try {
|
||||
// @ts-expect-error
|
||||
await page.evaluate(modules => loadAndRun(modules), testModules);
|
||||
await page.evaluate(opts => loadAndRun(opts), {
|
||||
modules: testModules,
|
||||
grep: argv.grep,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
@ -235,7 +216,8 @@ class EchoRunner extends events.EventEmitter {
|
|||
async: runnable.async,
|
||||
slow: () => runnable.slow,
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration
|
||||
duration: runnable.duration,
|
||||
currentRetry: () => runnable.currentRetry,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -85,7 +85,8 @@
|
|||
async: runnable.async,
|
||||
slow: runnable.slow(),
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration
|
||||
duration: runnable.duration,
|
||||
currentRetry: runnable.currentRetry(),
|
||||
};
|
||||
}
|
||||
function serializeError(err) {
|
||||
|
@ -113,7 +114,7 @@
|
|||
runner.on('pending', test => window.mocha_report('pending', serializeRunnable(test)));
|
||||
};
|
||||
|
||||
window.loadAndRun = async function loadAndRun(modules, manual = false) {
|
||||
window.loadAndRun = async function loadAndRun({ modules, grep }, manual = false) {
|
||||
// load
|
||||
await Promise.all(modules.map(module => new Promise((resolve, reject) => {
|
||||
require([module], resolve, err => {
|
||||
|
@ -131,6 +132,10 @@
|
|||
|
||||
// run
|
||||
return new Promise((resolve, reject) => {
|
||||
if (grep) {
|
||||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
if (!manual) {
|
||||
mocha.reporter(PlaywrightReporter);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ const MochaJUnitReporter = require('mocha-junit-reporter');
|
|||
const url = require('url');
|
||||
const net = require('net');
|
||||
const createStatsCollector = require('mocha/lib/stats-collector');
|
||||
const FullJsonStreamReporter = require('../fullJsonStreamReporter');
|
||||
const { applyReporter, importMochaReporter } = require('../reporter');
|
||||
|
||||
// Disable render process reuse, we still have
|
||||
// non-context aware native modules in the renderer.
|
||||
|
@ -76,15 +76,6 @@ function deserializeRunnable(runnable) {
|
|||
};
|
||||
}
|
||||
|
||||
function importMochaReporter(name) {
|
||||
if (name === 'full-json-stream') {
|
||||
return FullJsonStreamReporter;
|
||||
}
|
||||
|
||||
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', name);
|
||||
return require(reporterPath);
|
||||
}
|
||||
|
||||
function deserializeError(err) {
|
||||
const inspect = err.inspect;
|
||||
err.inspect = () => inspect;
|
||||
|
@ -125,11 +116,6 @@ class IPCRunner extends events.EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
function parseReporterOption(value) {
|
||||
let r = /^([^=]+)=(.*)$/.exec(value);
|
||||
return r ? { [r[1]]: r[2] } : {};
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
|
||||
ipcMain.on('error', (_, err) => {
|
||||
|
@ -249,23 +235,7 @@ app.on('ready', () => {
|
|||
});
|
||||
}
|
||||
|
||||
let Reporter;
|
||||
try {
|
||||
Reporter = importMochaReporter(argv.reporter);
|
||||
} catch (err) {
|
||||
try {
|
||||
Reporter = require(argv.reporter);
|
||||
} catch (err) {
|
||||
Reporter = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec;
|
||||
console.warn(`could not load reporter: ${argv.reporter}, using ${Reporter.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
let reporterOptions = argv['reporter-options'];
|
||||
reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions;
|
||||
reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {});
|
||||
|
||||
new Reporter(runner, { reporterOptions });
|
||||
applyReporter(runner, argv);
|
||||
}
|
||||
|
||||
if (!argv.debug) {
|
||||
|
|
42
test/unit/reporter.js
Normal file
42
test/unit/reporter.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const mocha = require('mocha');
|
||||
const FullJsonStreamReporter = require('./fullJsonStreamReporter');
|
||||
const path = require('path');
|
||||
|
||||
function parseReporterOption(value) {
|
||||
let r = /^([^=]+)=(.*)$/.exec(value);
|
||||
return r ? { [r[1]]: r[2] } : {};
|
||||
}
|
||||
|
||||
exports.importMochaReporter = name => {
|
||||
if (name === 'full-json-stream') {
|
||||
return FullJsonStreamReporter;
|
||||
}
|
||||
|
||||
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', name);
|
||||
return require(reporterPath);
|
||||
}
|
||||
|
||||
exports.applyReporter = (runner, argv) => {
|
||||
let Reporter;
|
||||
try {
|
||||
Reporter = exports.importMochaReporter(argv.reporter);
|
||||
} catch (err) {
|
||||
try {
|
||||
Reporter = require(argv.reporter);
|
||||
} catch (err) {
|
||||
Reporter = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec;
|
||||
console.warn(`could not load reporter: ${argv.reporter}, using ${Reporter.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
let reporterOptions = argv['reporter-options'];
|
||||
reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions;
|
||||
reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {});
|
||||
|
||||
return new Reporter(runner, { reporterOptions });
|
||||
}
|
Loading…
Reference in a new issue