testing: make test ids locally unique, instead of globally unique

This commit is contained in:
Connor Peet 2021-07-16 12:51:53 -07:00
parent 42c268a626
commit da7d92b8b1
No known key found for this signature in database
GPG key ID: CF8FD2EA0DBC61BD
21 changed files with 430 additions and 390 deletions

View file

@ -2190,11 +2190,11 @@ declare module 'vscode' {
*/
export interface TestItem {
/**
* Unique identifier for the TestItem. This is used to correlate
* Identifier for the TestItem. This is used to correlate
* test results and tests in the document with those in the workspace
* (test explorer). This must not change for the lifetime of the TestItem.
* (test explorer). This cannot change for the lifetime of the TestItem,
* and must be unique among its parent's direct children.
*/
// todo@API globally vs extension vs controller unique. I would strongly recommend non-global
readonly id: string;
/**
@ -2380,6 +2380,11 @@ declare module 'vscode' {
*/
readonly id: string;
/**
* Parent of this item.
*/
readonly parent?: TestResultSnapshot;
/**
* URI this TestItem is associated with. May be a file or file.
*/

View file

@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RunOnceScheduler } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
@ -18,11 +17,12 @@ import { generateUuid } from 'vs/base/common/uuid';
import { ExtHostTestingShape, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
import { TestItemImpl, TestItemRootImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
import { TestRunProfileGroup, TestRunRequest } from 'vs/workbench/api/common/extHostTypes';
import { SingleUseTestCollection, TestPosition } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
import { SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
import { AbstractIncrementalTestCollection, CoverageDetails, IFileCoverage, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, ITestIdWithSrc, ITestItem, RunTestForControllerRequest, TestRunProfileBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId';
import type * as vscode from 'vscode';
interface ControllerInfo {
@ -58,7 +58,6 @@ export class ExtHostTesting implements ExtHostTestingShape {
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 profiles = new Map<number, vscode.TestRunProfile>();
const proxy = this.proxy;
@ -92,9 +91,6 @@ export class ExtHostTesting implements ExtHostTestingShape {
},
set resolveChildrenHandler(fn) {
collection.resolveHandler = fn;
if (fn) {
initialExpand.schedule();
}
},
get resolveChildrenHandler() {
return collection.resolveHandler;
@ -239,7 +235,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
.map(id => lookup.collection.tree.get(id))
.filter(isDefined)
.filter(exclude => includeTests.some(
include => collection.tree.comparePositions(include, exclude) === TestPosition.IsChild,
include => include.fullId.compare(exclude.fullId) === TestPosition.IsChild,
));
if (!includeTests.length) {
@ -247,7 +243,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
}
const publicReq = new TestRunRequest(
includeTests.map(t => t.actual),
includeTests.some(i => i.actual instanceof TestItemRootImpl) ? undefined : includeTests.map(t => t.actual),
excludeTests.map(t => t.actual),
profile,
);
@ -437,12 +433,15 @@ const tryGetProfileFromTestRunReq = (request: vscode.TestRunRequest) => {
};
export class TestRunDto {
private readonly includePrefix: string[];
private readonly excludePrefix: string[];
public static fromPublic(controllerId: string, collection: SingleUseTestCollection, request: vscode.TestRunRequest) {
return new TestRunDto(
controllerId,
generateUuid(),
request.include && new Set(request.include.map(t => t.id)),
new Set(request.exclude?.map(t => t.id) ?? Iterable.empty()),
request.include?.map(t => TestId.fromExtHostTestItem(t, controllerId).toString()) ?? [controllerId],
request.exclude?.map(t => TestId.fromExtHostTestItem(t, controllerId).toString()) ?? [],
collection,
);
}
@ -451,8 +450,8 @@ export class TestRunDto {
return new TestRunDto(
request.controllerId,
request.runId,
request.testIds.includes(collection.root.id) ? undefined : new Set(request.testIds),
new Set(request.excludeExtIds),
request.testIds,
request.excludeExtIds,
collection,
);
}
@ -460,21 +459,29 @@ export class TestRunDto {
constructor(
public readonly controllerId: string,
public readonly id: string,
private readonly include: ReadonlySet<string> | undefined,
private readonly exclude: ReadonlySet<string>,
include: string[],
exclude: string[],
public readonly colllection: SingleUseTestCollection,
) { }
) {
this.includePrefix = include.map(id => id + TestIdPathParts.Delimiter);
this.excludePrefix = exclude.map(id => id + TestIdPathParts.Delimiter);
}
public isIncluded(test: vscode.TestItem) {
for (let t: vscode.TestItem | undefined = test; t; t = t.parent) {
if (this.include?.has(t.id)) {
return true;
} else if (this.exclude.has(t.id)) {
const id = TestId.fromExtHostTestItem(test, this.controllerId).toString() + TestIdPathParts.Delimiter;
for (const prefix of this.excludePrefix) {
if (id === prefix || id.startsWith(prefix)) {
return false;
}
}
return this.include === undefined; // default to true if running all tests with include=undefined
for (const prefix of this.includePrefix) {
if (id === prefix || id.startsWith(prefix)) {
return true;
}
}
return false;
}
}
@ -574,16 +581,18 @@ class TestRunImpl implements vscode.TestRun {
}
setState(test: vscode.TestItem, state: vscode.TestResultState, duration?: number): void {
if (!this.#ended && this.#req.isIncluded(test)) {
const req = this.#req;
if (!this.#ended && req.isIncluded(test)) {
this.ensureTestIsKnown(test);
this.#proxy.$updateTestStateInRun(this.#req.id, this.taskId, test.id, state, duration);
this.#proxy.$updateTestStateInRun(req.id, this.taskId, TestId.fromExtHostTestItem(test, req.controllerId).toString(), state, duration);
}
}
appendMessage(test: vscode.TestItem, message: vscode.TestMessage): void {
if (!this.#ended && this.#req.isIncluded(test)) {
const req = this.#req;
if (!this.#ended && req.isIncluded(test)) {
this.ensureTestIsKnown(test);
this.#proxy.$appendTestMessageInRun(this.#req.id, this.taskId, test.id, Convert.TestMessage.from(message));
this.#proxy.$appendTestMessageInRun(req.id, this.taskId, TestId.fromExtHostTestItem(test, req.controllerId).toString(), Convert.TestMessage.from(message));
}
}
@ -608,8 +617,9 @@ class TestRunImpl implements vscode.TestRun {
}
const chain: ITestItem[] = [];
const root = this.#req.colllection.root;
while (true) {
chain.unshift(Convert.TestItem.from(test));
chain.unshift(Convert.TestItem.from(test, root.id));
if (sent.has(test.id)) {
break;
@ -623,10 +633,9 @@ class TestRunImpl implements vscode.TestRun {
test = test.parent;
}
const root = this.#req.colllection.root;
if (!sent.has(root.id)) {
sent.add(root.id);
chain.unshift(Convert.TestItem.from(root));
chain.unshift(Convert.TestItem.from(root, root.id));
}
this.#proxy.$addTestsToRun(this.#req.controllerId, this.#req.id, chain);

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TestIdPathParts } from 'vs/workbench/contrib/testing/common/testId';
import * as vscode from 'vscode';
export const enum ExtHostTestItemEventOp {
@ -242,6 +243,9 @@ export class TestItemImpl implements vscode.TestItem {
*/
constructor(id: string, label: string, uri: vscode.Uri | undefined) {
const api = getPrivateApiFor(this);
if (id.includes(TestIdPathParts.Delimiter)) {
throw new Error(`Test IDs may not include the ${JSON.stringify(id)} symbol`);
}
Object.defineProperties(this, {
id: {
@ -256,7 +260,9 @@ export class TestItemImpl implements vscode.TestItem {
},
parent: {
enumerable: false,
get() { return api.parent; },
get() {
return api.parent instanceof TestItemRootImpl ? undefined : api.parent;
},
},
children: {
value: createTestItemCollection(this),
@ -272,3 +278,9 @@ export class TestItemImpl implements vscode.TestItem {
getPrivateApiFor(this).listener?.({ op: ExtHostTestItemEventOp.Invalidated });
}
}
export class TestItemRootImpl extends TestItemImpl {
constructor(controllerId: string, label: string) {
super(controllerId, label, undefined);
}
}

View file

@ -32,6 +32,7 @@ import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import * as search from 'vs/workbench/contrib/search/common/search';
import { CoverageDetails, DetailType, ICoveredCount, IFileCoverage, ISerializedTestResults, ITestItem, ITestItemContext, ITestMessage, SerializedTestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import type * as vscode from 'vscode';
@ -1658,11 +1659,11 @@ export namespace TestMessage {
}
export namespace TestItem {
export type Raw<T = unknown> = vscode.TestItem;
export type Raw = vscode.TestItem;
export function from(item: vscode.TestItem): ITestItem {
export function from(item: vscode.TestItem, controllerId: string): ITestItem {
return {
extId: item.id,
extId: TestId.fromExtHostTestItem(item, controllerId).toString(),
label: item.label,
uri: item.uri,
range: Range.from(item.range) || null,
@ -1671,20 +1672,9 @@ export namespace TestItem {
};
}
export function fromResultSnapshot(item: vscode.TestResultSnapshot): ITestItem {
return {
extId: item.id,
label: item.label,
uri: item.uri,
range: Range.from(item.range) || null,
description: item.description || null,
error: null,
};
}
export function toPlain(item: ITestItem): Omit<vscode.TestItem, 'children' | 'invalidate' | 'discoverChildren'> {
return {
id: item.extId,
id: TestId.fromString(item.extId).localId,
label: item.label,
uri: URI.revive(item.uri),
range: Range.to(item.range || undefined),
@ -1695,8 +1685,8 @@ export namespace TestItem {
};
}
export function to(item: ITestItem): TestItemImpl {
const testItem = new TestItemImpl(item.extId, item.label, URI.revive(item.uri));
function to(item: ITestItem): TestItemImpl {
const testItem = new TestItemImpl(TestId.fromString(item.extId).localId, item.label, URI.revive(item.uri));
testItem.range = Range.to(item.range || undefined);
testItem.description = item.description || undefined;
return testItem;
@ -1715,18 +1705,27 @@ export namespace TestItem {
}
export namespace TestResults {
const convertTestResultItem = (item: SerializedTestResultItem, byInternalId: Map<string, SerializedTestResultItem>): vscode.TestResultSnapshot => ({
...TestItem.toPlain(item.item),
taskStates: item.tasks.map(t => ({
state: t.state,
duration: t.duration,
messages: t.messages.map(TestMessage.to),
})),
children: item.children
.map(c => byInternalId.get(c))
.filter(isDefined)
.map(c => convertTestResultItem(c, byInternalId)),
});
const convertTestResultItem = (item: SerializedTestResultItem, byInternalId: Map<string, SerializedTestResultItem>): vscode.TestResultSnapshot => {
const snapshot: vscode.TestResultSnapshot = ({
...TestItem.toPlain(item.item),
parent: undefined,
taskStates: item.tasks.map(t => ({
state: t.state,
duration: t.duration,
messages: t.messages.map(TestMessage.to),
})),
children: item.children
.map(c => byInternalId.get(c))
.filter(isDefined)
.map(c => convertTestResultItem(c, byInternalId))
});
for (const child of snapshot.children) {
(child as any).parent = snapshot;
}
return snapshot;
};
export function to(serialized: ISerializedTestResults): vscode.TestRunResult {
const roots: SerializedTestResultItem[] = [];

View file

@ -29,15 +29,15 @@ import { ITestExplorerFilterState } from 'vs/workbench/contrib/testing/browser/t
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, ITestRunProfile, TestIdPath, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { identifyTest, InternalTestItem, ITestIdWithSrc, ITestItem, ITestRunProfile, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestProfileService } 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';
import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
import { getPathForTestInResult, ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { getTestByPath, IMainThreadTestCollection, ITestService, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
import { expandAndGetTestById, IMainThreadTestCollection, ITestService, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@ -660,7 +660,7 @@ export class GoToTest extends Action2 {
const editorService = accessor.get(IEditorService);
const { range, uri, extId } = element.test.item;
accessor.get(ITestExplorerFilterState).reveal.value = [extId];
accessor.get(ITestExplorerFilterState).reveal.value = extId;
accessor.get(ITestingPeekOpener).closeAllPeeks();
let isFile = true;
@ -710,7 +710,7 @@ export class GoToTest extends Action2 {
const fileService = accessor.get(IFileService);
const editorService = accessor.get(IEditorService);
accessor.get(ITestExplorerFilterState).reveal.value = [test.extId];
accessor.get(ITestExplorerFilterState).reveal.value = test.extId;
accessor.get(ITestingPeekOpener).closeAllPeeks();
let isFile = true;
@ -928,13 +928,13 @@ export class DebugCurrentFile extends ExecuteTestsInCurrentFile {
}
}
export const runTestsByPath = async (
export const discoverAndRunTests = async (
collection: IMainThreadTestCollection,
progress: IProgressService,
paths: ReadonlyArray<TestIdPath>,
ids: ReadonlyArray<string>,
runTests: (tests: ReadonlyArray<InternalTestItem>) => Promise<ITestResult>,
): Promise<ITestResult | undefined> => {
const todo = Promise.all(paths.map(p => getTestByPath(collection, p)));
const todo = Promise.all(ids.map(p => expandAndGetTestById(collection, p)));
const tests = (await showDiscoveringWhile(progress, todo)).filter(isDefined);
return tests.length ? await runTests(tests) : undefined;
};
@ -945,7 +945,7 @@ abstract class RunOrDebugExtsByPath extends Action2 {
*/
public async run(accessor: ServicesAccessor, ...args: unknown[]) {
const testService = accessor.get(ITestService);
await runTestsByPath(
await discoverAndRunTests(
accessor.get(ITestService).collection,
accessor.get(IProgressService),
[...this.getTestExtIdsToRun(accessor, ...args)],
@ -953,7 +953,7 @@ abstract class RunOrDebugExtsByPath extends Action2 {
);
}
protected abstract getTestExtIdsToRun(accessor: ServicesAccessor, ...args: unknown[]): Iterable<TestIdPath>;
protected abstract getTestExtIdsToRun(accessor: ServicesAccessor, ...args: unknown[]): Iterable<string>;
protected abstract runTest(service: ITestService, node: readonly InternalTestItem[]): Promise<ITestResult>;
}
@ -971,23 +971,21 @@ abstract class RunOrDebugFailedTests extends RunOrDebugExtsByPath {
/**
* @inheritdoc
*/
protected getTestExtIdsToRun(accessor: ServicesAccessor): Iterable<TestIdPath> {
protected getTestExtIdsToRun(accessor: ServicesAccessor) {
const { results } = accessor.get(ITestResultService);
const paths = new Map<string /* id */, string /* path */>();
const sep = '$$TEST SEP$$';
const ids = new Set<string>();
for (let i = results.length - 1; i >= 0; i--) {
const resultSet = results[i];
for (const test of resultSet.tests) {
const path = getPathForTestInResult(test, resultSet).join(sep);
if (isFailedState(test.ownComputedState)) {
paths.set(test.item.extId, path);
ids.add(test.item.extId);
} else {
paths.delete(test.item.extId);
ids.delete(test.item.extId);
}
}
}
return Iterable.map(paths.values(), p => p.split(sep));
return ids;
}
}
@ -1008,7 +1006,7 @@ abstract class RunOrDebugLastRun extends RunOrDebugExtsByPath {
/**
* @inheritdoc
*/
protected *getTestExtIdsToRun(accessor: ServicesAccessor, runId?: string): Iterable<TestIdPath> {
protected *getTestExtIdsToRun(accessor: ServicesAccessor, runId?: string): Iterable<string> {
const resultService = accessor.get(ITestResultService);
const lastResult = runId ? resultService.results.find(r => r.id === runId) : resultService.results[0];
if (!lastResult) {
@ -1017,10 +1015,7 @@ abstract class RunOrDebugLastRun extends RunOrDebugExtsByPath {
for (const test of lastResult.request.targets) {
for (const testId of test.testIds) {
const test = lastResult.getStateById(testId);
if (test) {
yield getPathForTestInResult(test, lastResult);
}
yield testId;
}
}
}
@ -1086,7 +1081,7 @@ export class ReRunLastRun extends RunOrDebugLastRun {
protected runTest(service: ITestService, internalTests: InternalTestItem[]): Promise<ITestResult> {
return service.runTests({
group: TestRunProfileBitset.Debug,
group: TestRunProfileBitset.Run,
tests: internalTests.map(identifyTest),
});
}

View file

@ -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 { identifyTest, ITestIdWithSrc, TestIdPath, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { identifyTest, ITestIdWithSrc, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestProfileService, TestProfileService } 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';
@ -37,7 +37,7 @@ import { ITestResultStorage, TestResultStorage } from 'vs/workbench/contrib/test
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 { allTestActions, discoverAndRunTests } from './testExplorerActions';
import './testingConfigurationUi';
registerSingleton(ITestService, TestService, true);
@ -127,8 +127,8 @@ CommandsRegistry.registerCommand({
CommandsRegistry.registerCommand({
id: 'vscode.revealTestInExplorer',
handler: async (accessor: ServicesAccessor, pathToTest: TestIdPath) => {
accessor.get(ITestExplorerFilterState).reveal.value = pathToTest;
handler: async (accessor: ServicesAccessor, testId: string) => {
accessor.get(ITestExplorerFilterState).reveal.value = testId;
accessor.get(IViewsService).openView(Testing.ExplorerViewId);
}
});
@ -144,13 +144,13 @@ CommandsRegistry.registerCommand({
});
CommandsRegistry.registerCommand({
id: 'vscode.runTestsByPath',
handler: async (accessor: ServicesAccessor, group: TestRunProfileBitset, ...pathToTests: TestIdPath[]) => {
id: 'vscode.runTestsById',
handler: async (accessor: ServicesAccessor, group: TestRunProfileBitset, ...testIds: string[]) => {
const testService = accessor.get(ITestService);
await runTestsByPath(
await discoverAndRunTests(
accessor.get(ITestService).collection,
accessor.get(IProgressService),
pathToTests,
testIds,
tests => testService.runTests({ group, tests: tests.map(identifyTest) }),
);
}

View file

@ -437,20 +437,8 @@ abstract class RunTestDecoration extends Disposable {
}));
}
testActions.push(new Action('testing.gutter.reveal', localize('reveal test', 'Reveal in Test Explorer'), undefined, undefined, async () => {
const path = [test];
while (true) {
const parentId = path[0].parent;
const parent = parentId && collection.getNodeById(parentId);
if (!parent) {
break;
}
path.unshift(parent);
}
await this.commandService.executeCommand('vscode.revealTestInExplorer', path.map(t => t.item.extId));
}));
testActions.push(new Action('testing.gutter.reveal', localize('reveal test', 'Reveal in Test Explorer'), undefined, undefined,
() => this.commandService.executeCommand('vscode.revealTestInExplorer', test.item.extId)));
return testActions;
}

View file

@ -27,19 +27,14 @@ import { testingFilterIcon } from 'vs/workbench/contrib/testing/browser/icons';
import { TestExplorerStateFilter, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
import { TestIdPath } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
export interface ITestExplorerFilterState {
_serviceBrand: undefined;
readonly text: MutableObservableValue<string>;
/**
* Reveal request: the path to the test to reveal. The last element of the
* array is the test the user wanted to reveal, and the previous
* items are its parents.
*/
readonly reveal: MutableObservableValue<TestIdPath | undefined>;
/** Test ID the user wants to reveal in the explorer */
readonly reveal: MutableObservableValue<string | undefined>;
readonly stateFilter: MutableObservableValue<TestExplorerStateFilter>;
readonly currentDocumentOnly: MutableObservableValue<boolean>;
/** Whether excluded test should be shown in the view */
@ -67,7 +62,7 @@ export class TestExplorerFilterState implements ITestExplorerFilterState {
}, this.storage), false);
public readonly showExcludedTests = new MutableObservableValue(false);
public readonly reveal = new MutableObservableValue<TestIdPath | undefined>(undefined);
public readonly reveal = new MutableObservableValue</* test ID */string | undefined>(undefined);
public readonly onDidRequestInputFocus = this.focusEmitter.event;

View file

@ -55,12 +55,13 @@ 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, ITestRunProfile, TestIdPath, TestItemExpandState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { identifyTest, ITestRunProfile, TestItemExpandState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { capabilityContextKeys, ITestProfileService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { cmpPriority, isFailedState, isStateWithResult } from 'vs/workbench/contrib/testing/common/testingStates';
import { getPathForTestInResult, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { 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 { ConfigureTestProfilesAction, DebugAllAction, GoToTest, RunAllAction, SelectDefaultTestProfiles } from './testExplorerActions';
@ -451,7 +452,7 @@ export class TestingExplorerViewModel extends Disposable {
}
}));
this._register(filterState.reveal.onDidChange(this.revealByIdPath, this));
this._register(filterState.reveal.onDidChange(this.revealById, this));
this._register(onDidChangeVisibility(visible => {
if (visible) {
@ -495,7 +496,7 @@ export class TestingExplorerViewModel extends Disposable {
return;
}
this.revealByIdPath(getPathForTestInResult(evt.item, evt.result), false, false);
this.revealById(evt.item.item.extId, false, false);
}));
this._register(testResults.onResultsChanged(evt => {
@ -525,8 +526,8 @@ export class TestingExplorerViewModel extends Disposable {
* Tries to reveal by extension ID. Queues the request if the extension
* ID is not currently available.
*/
private revealByIdPath(idPath: TestIdPath | undefined, expand = true, focus = true) {
if (!idPath) {
private revealById(id: string | undefined, expand = true, focus = true) {
if (!id) {
this.hasPendingReveal = false;
return;
}
@ -538,6 +539,7 @@ export class TestingExplorerViewModel extends Disposable {
// If the item itself is visible in the tree, show it. Otherwise, expand
// its closest parent.
let expandToLevel = 0;
const idPath = [...TestId.fromString(id).idsFromRoot()];
for (let i = idPath.length - 1; i >= expandToLevel; i--) {
const element = this.projection.value.getElementByTestId(idPath[i]);
// Skip all elements that aren't in the tree.
@ -687,7 +689,7 @@ export class TestingExplorerViewModel extends Disposable {
this.projection.value?.applyTo(this.tree);
if (this.hasPendingReveal) {
this.revealByIdPath(this.filterState.reveal.value);
this.revealById(this.filterState.reveal.value);
}
}

View file

@ -64,7 +64,7 @@ import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingC
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
import { buildTestUri, ParsedTestUri, parseTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
import { getPathForTestInResult, ITestResult, maxCountPriority, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResult, maxCountPriority, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService, ResultChangeEvent } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
@ -900,10 +900,6 @@ export class TestCaseElement implements ITreeElement {
return icons.testingStatesToIcons.get(this.test.computedState);
}
public get path() {
return getPathForTestInResult(this.test, this.results);
}
constructor(
private readonly results: ITestResult,
public readonly test: TestResultItem,
@ -926,11 +922,7 @@ class TestTaskElement implements ITreeElement {
public readonly label: string;
public readonly icon = undefined;
public get path() {
return getPathForTestInResult(this.test, this.results);
}
constructor(private readonly results: ITestResult, public readonly test: TestResultItem, index: number) {
constructor(results: ITestResult, public readonly test: TestResultItem, index: number) {
this.id = `${results.id}/${test.item.extId}/${index}`;
this.task = results.tasks[index];
this.context = String(index);
@ -1318,12 +1310,13 @@ class TreeActionsProvider {
}
if (element instanceof TestCaseElement || element instanceof TestTaskElement) {
const extId = element.test.item.extId;
primary.push(new Action(
'testing.outputPeek.revealInExplorer',
localize('testing.revealInExplorer', "Reveal in Test Explorer"),
Codicon.listTree.classNames,
undefined,
() => this.commandService.executeCommand('vscode.revealTestInExplorer', element.path),
() => this.commandService.executeCommand('vscode.revealTestInExplorer', extId),
));
if (capabilities & TestRunProfileBitset.Run) {
@ -1332,17 +1325,17 @@ class TreeActionsProvider {
localize('run test', 'Run Test'),
ThemeIcon.asClassName(icons.testingRunIcon),
undefined,
() => this.commandService.executeCommand('vscode.runTestsByPath', false, element.path),
() => this.commandService.executeCommand('vscode.runTestsById', TestRunProfileBitset.Run, extId),
));
}
if (capabilities & TestRunProfileBitset.Coverage) {
if (capabilities & TestRunProfileBitset.Debug) {
primary.push(new Action(
'testing.outputPeek.debugTest',
localize('debug test', 'Debug Test'),
ThemeIcon.asClassName(icons.testingDebugIcon),
undefined,
() => this.commandService.executeCommand('vscode.runTestsByPath', true, element.path),
() => this.commandService.executeCommand('vscode.runTestsById', TestRunProfileBitset.Debug, extId),
));
}
}

View file

@ -8,9 +8,10 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { assertNever } from 'vs/base/common/types';
import { diffTestItems, ExtHostTestItemEvent, ExtHostTestItemEventOp, getPrivateApiFor, TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
import { diffTestItems, ExtHostTestItemEvent, ExtHostTestItemEventOp, getPrivateApiFor, TestItemImpl, TestItemRootImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
import { applyTestItemUpdate, TestDiffOpType, TestItemExpandState, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
type TestItemRaw = Convert.TestItem.Raw;
@ -22,9 +23,10 @@ export interface IHierarchyProvider {
* @private
*/
export interface OwnedCollectionTestItem {
expand: TestItemExpandState;
parent: string | null;
readonly fullId: TestId;
readonly parent: TestId | null;
actual: TestItemImpl;
expand: TestItemExpandState;
/**
* Number of levels of items below this one that are expanded. May be infinite.
*/
@ -32,118 +34,6 @@ export interface OwnedCollectionTestItem {
resolveBarrier?: Barrier;
}
/**
* Enum for describing relative positions of tests. Similar to
* `node.compareDocumentPosition` in the DOM.
*/
export const enum TestPosition {
/** Neither a nor b are a child of one another. They may share a common parent, though. */
Disconnected,
/** b is a child of a */
IsChild,
/** b is a parent of a */
IsParent,
/** a === b */
IsSame,
}
/**
* Test tree is (or will be after debt week 2020-03) the standard collection
* for test trees. Internally it indexes tests by their extension ID in
* a map.
*/
export class TestTree<T extends OwnedCollectionTestItem> {
private readonly map = new Map<string, T>();
private readonly _roots = new Set<T>();
public readonly roots: ReadonlySet<T> = this._roots;
/**
* Gets the size of the tree.
*/
public get size() {
return this.map.size;
}
/**
* Adds a new test to the tree if it doesn't exist.
* @throws if a duplicate item is inserted
*/
public add(test: T) {
if (this.map.has(test.actual.id)) {
throw new Error(`Attempted to insert a duplicate test item ID ${test.actual.id}`);
}
this.map.set(test.actual.id, test);
if (!test.parent) {
this._roots.add(test);
}
}
/**
* Gets whether the test exists in the tree.
*/
public has(testId: string) {
return this.map.has(testId);
}
/**
* Removes a test ID from the tree. This is NOT recursive.
*/
public delete(testId: string) {
const existing = this.map.get(testId);
if (!existing) {
return false;
}
this.map.delete(testId);
this._roots.delete(existing);
return true;
}
/**
* Gets a test item by ID from the tree.
*/
public get(testId: string) {
return this.map.get(testId);
}
/**
* Compares the positions of the two items in the test tree.
*/
public comparePositions(aOrId: T | string, bOrId: T | string) {
const a = typeof aOrId === 'string' ? this.map.get(aOrId) : aOrId;
const b = typeof bOrId === 'string' ? this.map.get(bOrId) : bOrId;
if (!a || !b) {
return TestPosition.Disconnected;
}
if (a === b) {
return TestPosition.IsSame;
}
for (let p = this.map.get(b.parent!); p; p = this.map.get(p.parent!)) {
if (p === a) {
return TestPosition.IsChild;
}
}
for (let p = this.map.get(a.parent!); p; p = this.map.get(p.parent!)) {
if (p === b) {
return TestPosition.IsParent;
}
}
return TestPosition.Disconnected;
}
/**
* Iterates over all test in the tree.
*/
[Symbol.iterator]() {
return this.map.values();
}
}
/**
* Maintains tests created and registered for a single set of hierarchies
* for a workspace or document.
@ -154,15 +44,13 @@ export class SingleUseTestCollection extends Disposable {
private readonly diffOpEmitter = this._register(new Emitter<TestsDiff>());
private _resolveHandler?: (item: TestItemRaw) => Promise<void> | void;
public readonly root = new TestItemImpl(`${this.controllerId}Root`, this.controllerId, undefined);
public readonly tree = new TestTree<OwnedCollectionTestItem>();
public readonly root = new TestItemRootImpl(this.controllerId, this.controllerId);
public readonly tree = new Map</* full test id */string, OwnedCollectionTestItem>();
protected diff: TestsDiff = [];
constructor(
private readonly controllerId: string,
) {
constructor(private readonly controllerId: string) {
super();
this.upsertItem(this.root, null);
this.upsertItem(this.root, undefined);
}
/**
@ -170,7 +58,7 @@ export class SingleUseTestCollection extends Disposable {
*/
public set resolveHandler(handler: undefined | ((item: TestItemRaw) => void)) {
this._resolveHandler = handler;
for (const test of this.tree) {
for (const test of this.tree.values()) {
this.updateExpandability(test);
}
}
@ -244,24 +132,23 @@ export class SingleUseTestCollection extends Disposable {
}
public override dispose() {
for (const item of this.tree) {
for (const item of this.tree.values()) {
getPrivateApiFor(item.actual).listener = undefined;
}
this.tree.clear();
this.diff = [];
super.dispose();
}
private onTestItemEvent(internal: OwnedCollectionTestItem, evt: ExtHostTestItemEvent) {
const extId = internal?.actual.id;
switch (evt.op) {
case ExtHostTestItemEventOp.Invalidated:
this.pushDiff([TestDiffOpType.Retire, extId]);
this.pushDiff([TestDiffOpType.Retire, internal.fullId.toString()]);
break;
case ExtHostTestItemEventOp.RemoveChild:
this.removeItem(evt.id);
this.removeItem(TestId.joinToString(internal.fullId, evt.id));
break;
case ExtHostTestItemEventOp.Upsert:
@ -276,6 +163,7 @@ export class SingleUseTestCollection extends Disposable {
case ExtHostTestItemEventOp.SetProp:
const { key, value } = evt;
const extId = internal.fullId.toString();
switch (key) {
case 'canResolveChildren':
this.updateExpandability(internal);
@ -296,35 +184,43 @@ export class SingleUseTestCollection extends Disposable {
}
}
private upsertItem(actual: TestItemRaw, parent: OwnedCollectionTestItem | null) {
private upsertItem(actual: TestItemRaw, parent: OwnedCollectionTestItem | undefined) {
if (!(actual instanceof TestItemImpl)) {
throw new Error(`TestItems provided to the VS Code API must extend \`vscode.TestItem\`, but ${actual.id} did not`);
}
const fullId = TestId.fromExtHostTestItem(actual, this.root.id, parent?.actual);
// If the item already exists under a different parent, remove it.
let internal = this.tree.get(actual.id);
if (internal && internal.parent !== parent?.actual.id) {
(internal.actual.parent ?? this.root).children.delete(actual.id);
internal = undefined;
// If this test item exists elsewhere in the tree already (exists at an
// old ID with an existing parent), remove that old item.
const privateApi = getPrivateApiFor(actual);
if (privateApi.parent && privateApi.parent !== parent?.actual) {
privateApi.parent.children.delete(actual.id);
}
let internal = this.tree.get(fullId.toString());
// Case 1: a brand new item
if (!internal) {
const parentId = parent ? parent.actual.id : null;
// always expand root node to know if there are tests (and whether to show the welcome view)
const pExpandLvls = parent ? parent.expandLevels : 1;
internal = {
fullId,
actual,
parent: parentId,
parent: parent ? fullId.parentId : null,
expandLevels: pExpandLvls /* intentionally undefined or 0 */ ? pExpandLvls - 1 : undefined,
expand: TestItemExpandState.NotExpandable, // updated by `connectItemAndChildren`
};
this.tree.add(internal);
this.tree.set(internal.fullId.toString(), internal);
this.setItemParent(actual, parent);
this.pushDiff([
TestDiffOpType.Add,
{ parent: parentId, controllerId: this.controllerId, expand: internal.expand, item: Convert.TestItem.from(actual) },
{
parent: internal.parent && internal.parent.toString(),
controllerId: this.controllerId,
expand: internal.expand,
item: Convert.TestItem.from(actual, this.root.id),
},
]);
this.connectItemAndChildren(actual, internal, parent);
@ -351,24 +247,27 @@ export class SingleUseTestCollection extends Disposable {
this.connectItemAndChildren(actual, internal, parent);
// Remove any children still referencing the old parent that aren't
// included in the new one. Note that children might have moved to a new
// parent, so the parent ID check is done.
// Remove any orphaned children.
for (const child of oldChildren) {
if (!actual.children.get(child.id) && this.tree.get(child.id)?.parent === actual.id) {
this.removeItem(child.id);
if (!actual.children.get(child.id)) {
this.removeItem(TestId.joinToString(fullId, child.id));
}
}
}
private connectItem(actual: TestItemImpl, internal: OwnedCollectionTestItem, parent: OwnedCollectionTestItem | null) {
private setItemParent(actual: TestItemImpl, parent: OwnedCollectionTestItem | undefined) {
getPrivateApiFor(actual).parent = parent && parent.actual !== this.root ? parent.actual : undefined;
}
private connectItem(actual: TestItemImpl, internal: OwnedCollectionTestItem, parent: OwnedCollectionTestItem | undefined) {
this.setItemParent(actual, parent);
const api = getPrivateApiFor(actual);
api.parent = parent && parent.actual !== this.root ? parent.actual : undefined;
api.parent = parent?.actual;
api.listener = evt => this.onTestItemEvent(internal, evt);
this.updateExpandability(internal);
}
private connectItemAndChildren(actual: TestItemImpl, internal: OwnedCollectionTestItem, parent: OwnedCollectionTestItem | null) {
private connectItemAndChildren(actual: TestItemImpl, internal: OwnedCollectionTestItem, parent: OwnedCollectionTestItem | undefined) {
this.connectItem(actual, internal, parent);
// Discover any existing children that might have already been added
@ -401,7 +300,7 @@ export class SingleUseTestCollection extends Disposable {
}
internal.expand = newState;
this.pushDiff([TestDiffOpType.Update, { extId: internal.actual.id, expand: newState }]);
this.pushDiff([TestDiffOpType.Update, { extId: internal.fullId.toString(), expand: newState }]);
if (newState === TestItemExpandState.Expandable && internal.expandLevels !== undefined) {
this.resolveChildren(internal);
@ -419,7 +318,7 @@ export class SingleUseTestCollection extends Disposable {
}
const asyncChildren = internal.actual.children.all
.map(c => this.expand(c.id, levels))
.map(c => this.expand(TestId.joinToString(internal.fullId, c.id), levels))
.filter(isThenable);
if (asyncChildren.length) {
@ -467,7 +366,7 @@ export class SingleUseTestCollection extends Disposable {
}
private pushExpandStateUpdate(internal: OwnedCollectionTestItem) {
this.pushDiff([TestDiffOpType.Update, { extId: internal.actual.id, expand: internal.expand }]);
this.pushDiff([TestDiffOpType.Update, { extId: internal.fullId.toString(), expand: internal.expand }]);
}
private removeItem(childId: string) {
@ -486,9 +385,9 @@ export class SingleUseTestCollection extends Disposable {
}
getPrivateApiFor(item.actual).listener = undefined;
this.tree.delete(item.actual.id);
this.tree.delete(item.fullId.toString());
for (const child of item.actual.children.all) {
queue.push(this.tree.get(child.id));
queue.push(this.tree.get(TestId.joinToString(item.fullId, child.id)));
}
}
}

View file

@ -50,12 +50,6 @@ export interface ITestRunProfile {
hasConfigurationHandler: boolean;
}
/**
* Defines the path to a test, as a list of test IDs. The last element of the
* array is the test ID, and the predecessors are its parents, in order.
*/
export type TestIdPath = string[];
/**
* A fully-resolved request to run tests, passsed between the main thread
* and extension host.

View file

@ -0,0 +1,159 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const enum TestIdPathParts {
/** Delimiter for path parts in test IDs */
Delimiter = '\0',
}
/**
* Enum for describing relative positions of tests. Similar to
* `node.compareDocumentPosition` in the DOM.
*/
export const enum TestPosition {
/** a === b */
IsSame,
/** Neither a nor b are a child of one another. They may share a common parent, though. */
Disconnected,
/** b is a child of a */
IsChild,
/** b is a parent of a */
IsParent,
}
type TestItemLike = { id: string; parent?: TestItemLike };
/**
* The test ID is a stringifiable client that
*/
export class TestId {
private stringifed?: string;
/**
* Creates a test ID from an ext host test item.
*/
public static fromExtHostTestItem(item: TestItemLike, rootId: string, parent = item.parent) {
if (item.id === rootId) {
return new TestId([rootId]);
}
let path = [item.id];
for (let i = parent; i && i.id !== rootId; i = i.parent) {
path.push(i.id);
}
path.push(rootId);
return new TestId(path.reverse());
}
/**
* Creates a test ID from a serialized TestId instance.
*/
public static fromString(idString: string) {
return new TestId(idString.split(TestIdPathParts.Delimiter));
}
/**
* Gets the ID resulting from adding b to the base ID.
*/
public static join(base: TestId, b: string) {
return new TestId([...base.path, b]);
}
/**
* Gets the string ID resulting from adding b to the base ID.
*/
public static joinToString(base: string | TestId, b: string) {
return base.toString() + TestIdPathParts.Delimiter + b;
}
constructor(
public readonly path: readonly string[],
private readonly viewEnd = path.length,
) {
if (path.length === 0 || viewEnd < 1) {
throw new Error('cannot create test with empty path');
}
}
/**
* Gets the ID of the parent test.
*/
public get parentId(): TestId {
return this.viewEnd > 1 ? new TestId(this.path, this.viewEnd - 1) : this;
}
/**
* Gets the local ID of the current full test ID.
*/
public get localId() {
return this.path[this.viewEnd - 1];
}
/**
* Gets whether this ID refers to the root.
*/
public get isRoot() {
return this.viewEnd === 1;
}
/**
* Returns an iterable that yields IDs of all parent items down to and
* including the current item.
*/
public *idsFromRoot() {
let built = this.path[0];
yield built;
for (let i = 1; i < this.viewEnd; i++) {
built += TestIdPathParts.Delimiter;
built += this.path[i];
yield built;
}
}
/**
* Compares the other test ID with this one.
*/
public compare(other: TestId) {
for (let i = 0; i < other.viewEnd && i < this.viewEnd; i++) {
if (other.path[i] !== this.path[i]) {
return TestPosition.Disconnected;
}
}
if (other.viewEnd > this.viewEnd) {
return TestPosition.IsChild;
}
if (other.viewEnd < this.viewEnd) {
return TestPosition.IsParent;
}
return TestPosition.IsSame;
}
/**
* Serializes the ID.
*/
public toJSON() {
return this.toString();
}
/**
* Serializes the ID to a string.
*/
public toString() {
if (!this.stringifed) {
this.stringifed = this.path[0];
for (let i = 1; i < this.viewEnd; i++) {
this.stringifed += TestIdPathParts.Delimiter;
this.stringifed += this.path[i];
}
}
return this.stringifed;
}
}

View file

@ -13,7 +13,7 @@ import { localize } from 'vs/nls';
import { TestResultState } from 'vs/workbench/api/common/extHostTypes';
import { IComputedStateAccessor, refreshComputedState } from 'vs/workbench/contrib/testing/common/getComputedState';
import { IObservableValue, MutableObservableValue, staticObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestIdPath, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
import { maxPriority, statesInOrder } from 'vs/workbench/contrib/testing/common/testingStates';
@ -86,15 +86,6 @@ export const resultItemParents = function* (results: ITestResult, item: TestResu
}
};
export const getPathForTestInResult = (test: TestResultItem, results: ITestResult): TestIdPath => {
const path: TestIdPath = [];
for (const node of resultItemParents(results, test)) {
path.unshift(node.item.extId);
}
return path;
};
/**
* Count of the number of tests in each run state.
*/

View file

@ -11,8 +11,9 @@ 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, TestRunProfileBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, ITestIdWithSrc, ResolvedTestRunRequest, RunTestForControllerRequest, TestItemExpandState, TestRunProfileBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
export const ITestService = createDecorator<ITestService>('testService');
@ -82,18 +83,12 @@ export const testCollectionIsEmpty = (collection: IMainThreadTestCollection) =>
!Iterable.some(collection.rootItems, r => r.children.size > 0);
/**
* Ensures the test with the given path exists in the collection, if possible.
* Ensures the test with the given ID exists in the collection, if possible.
* If cancellation is requested, or the test cannot be found, it will return
* undefined.
*/
export const getTestByPath = async (collection: IMainThreadTestCollection, idPath: TestIdPath, ct = CancellationToken.None) => {
// Expand all direct children since roots might well have different IDs, but
// children should start matching.
await expandFirstLevel(collection);
if (ct.isCancellationRequested) {
return undefined;
}
export const expandAndGetTestById = async (collection: IMainThreadTestCollection, id: string, ct = CancellationToken.None) => {
const idPath = [...TestId.fromString(id).idsFromRoot()];
let expandToLevel = 0;
for (let i = idPath.length - 1; !ct.isCancellationRequested && i >= expandToLevel;) {

View file

@ -155,7 +155,9 @@ export class TestService extends Disposable implements ITestService {
group => this.testControllers.get(group.controllerId)?.runTests(
{
runId: result.id,
excludeExtIds: req.exclude!.filter(t => t.controllerId === group.controllerId).map(t => t.testId),
excludeExtIds: req.exclude!
.filter(t => t.controllerId === group.controllerId && !group.testIds.includes(t.testId))
.map(t => t.testId),
profileId: group.profileId,
controllerId: group.controllerId,
testIds: group.testIds,

View file

@ -7,6 +7,7 @@ import * as assert from 'assert';
import { Emitter } from 'vs/base/common/event';
import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation';
import { TestDiffOpType, TestItemExpandState, TestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestResultState } from 'vs/workbench/contrib/testing/common/testingStates';
import { TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { Convert, TestItemImpl } from 'vs/workbench/contrib/testing/common/testStubs';
@ -44,7 +45,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
test('expands children', async () => {
harness.flush();
harness.tree.expand(harness.projection.getElementByTestId('id-a')!);
harness.tree.expand(harness.projection.getElementByTestId(new TestId(['ctrlId', 'id-a']).toString())!);
assert.deepStrictEqual(harness.flush(), [
{ e: 'a', children: [{ e: 'aa' }, { e: 'ab' }] }, { e: 'b' }
]);
@ -54,10 +55,10 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
harness.flush();
harness.pushDiff([
TestDiffOpType.Add,
{ controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('c', 'c', undefined)) },
{ controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('c', 'c', undefined), 'ctrl2') },
], [
TestDiffOpType.Add,
{ controllerId: 'ctrl2', parent: 'c', expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('c-a', 'ca', undefined)) },
{ controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('c-a', 'ca', undefined), 'ctrl2') },
]);
assert.deepStrictEqual(harness.flush(), [
@ -68,7 +69,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
test('updates nodes if they add children', async () => {
harness.flush();
harness.tree.expand(harness.projection.getElementByTestId('id-a')!);
harness.tree.expand(harness.projection.getElementByTestId(new TestId(['ctrlId', 'id-a']).toString())!);
assert.deepStrictEqual(harness.flush(), [
{ e: 'a', children: [{ e: 'aa' }, { e: 'ab' }] },
@ -85,7 +86,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
test('updates nodes if they remove children', async () => {
harness.flush();
harness.tree.expand(harness.projection.getElementByTestId('id-a')!);
harness.tree.expand(harness.projection.getElementByTestId(new TestId(['ctrlId', 'id-a']).toString())!);
assert.deepStrictEqual(harness.flush(), [
{ e: 'a', children: [{ e: 'aa' }, { e: 'ab' }] },
@ -105,7 +106,7 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
resultsService.getStateById = () => [undefined, resultInState(TestResultState.Failed)];
const resultInState = (state: TestResultState): TestResultItem => ({
item: Convert.TestItem.from(harness.c.tree.get('id-a')!.actual),
item: Convert.TestItem.from(harness.c.tree.get(new TestId(['ctrlId', 'id-a']).toString())!.actual, 'ctrlId'),
parent: 'id-root',
tasks: [],
retired: false,

View file

@ -7,6 +7,7 @@ import * as assert from 'assert';
import { Emitter } from 'vs/base/common/event';
import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
import { TestDiffOpType, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestResultItemChange } from 'vs/workbench/contrib/testing/common/testResult';
import { Convert, TestItemImpl } from 'vs/workbench/contrib/testing/common/testStubs';
import { TestTreeTestHarness } from 'vs/workbench/contrib/testing/test/browser/testObjectTree';
@ -42,10 +43,10 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => {
harness.flush();
harness.pushDiff([
TestDiffOpType.Add,
{ controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('c', 'root2', undefined)) },
{ controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('c', 'root2', undefined), 'ctrl2') },
], [
TestDiffOpType.Add,
{ controllerId: 'ctrl2', parent: 'c', expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('c-a', 'c', undefined)) },
{ controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('c-a', 'c', undefined), 'ctrl2') },
]);
assert.deepStrictEqual(harness.flush(), [

View file

@ -12,8 +12,9 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
import { ITestTaskState, ResolvedTestRunRequest, TestResultItem, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestProfileService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestResultState } from 'vs/workbench/contrib/testing/common/testingStates';
import { getPathForTestInResult, HydratedTestResult, LiveOutputController, LiveTestResult, makeEmptyCounts, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { HydratedTestResult, LiveOutputController, LiveTestResult, makeEmptyCounts, resultItemParents, TestResultItemChange, TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
import { TestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { InMemoryResultStorage, ITestResultStorage } from 'vs/workbench/contrib/testing/common/testResultStorage';
import { Convert, getInitializedMainTestCollection, testStubs } from 'vs/workbench/contrib/testing/common/testStubs';
@ -63,15 +64,15 @@ suite('Workbench - Test Results Service', () => {
tests = testStubs.nested();
await tests.expand(tests.root.id, Infinity);
r.addTestChainToRun('ctrl', [
Convert.TestItem.from(tests.root),
Convert.TestItem.from(tests.root.children.get('id-a')!),
Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-aa')!),
r.addTestChainToRun('ctrlId', [
Convert.TestItem.from(tests.root, 'ctrlId'),
Convert.TestItem.from(tests.root.children.get('id-a')!, 'ctrlId'),
Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-aa')!, 'ctrlId'),
]);
r.addTestChainToRun('ctrl', [
Convert.TestItem.from(tests.root.children.get('id-a')!),
Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-ab')!),
r.addTestChainToRun('ctrlId', [
Convert.TestItem.from(tests.root.children.get('id-a')!, 'ctrlId'),
Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-ab')!, 'ctrlId'),
]);
});
@ -122,8 +123,8 @@ suite('Workbench - Test Results Service', () => {
[TestResultState.Failed]: 3,
});
assert.deepStrictEqual(r.getStateById('id-a')?.ownComputedState, TestResultState.Failed);
assert.deepStrictEqual(r.getStateById('id-a')?.tasks[0].state, TestResultState.Failed);
assert.deepStrictEqual(r.getStateById(new TestId(['ctrlId', 'id-a']).toString())?.ownComputedState, TestResultState.Failed);
assert.deepStrictEqual(r.getStateById(new TestId(['ctrlId', 'id-a']).toString())?.tasks[0].state, TestResultState.Failed);
assert.deepStrictEqual(getChangeSummary(), [
{ label: 'a', reason: TestResultItemChangeReason.OwnStateChange },
{ label: 'aa', reason: TestResultItemChangeReason.OwnStateChange },
@ -134,14 +135,14 @@ suite('Workbench - Test Results Service', () => {
test('updateState', () => {
changed.clear();
r.updateState('id-aa', 't', TestResultState.Running);
r.updateState(new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), 't', TestResultState.Running);
assert.deepStrictEqual(r.counts, {
...makeEmptyCounts(),
[TestResultState.Unset]: 2,
[TestResultState.Running]: 1,
[TestResultState.Queued]: 1,
});
assert.deepStrictEqual(r.getStateById('id-aa')?.ownComputedState, TestResultState.Running);
assert.deepStrictEqual(r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString())?.ownComputedState, TestResultState.Running);
// update computed state:
assert.deepStrictEqual(r.getStateById(tests.root.id)?.computedState, TestResultState.Running);
assert.deepStrictEqual(getChangeSummary(), [
@ -153,7 +154,7 @@ suite('Workbench - Test Results Service', () => {
test('retire', () => {
changed.clear();
r.retire('id-a');
r.retire(new TestId(['ctrlId', 'id-a']).toString());
assert.deepStrictEqual(getChangeSummary(), [
{ label: 'a', reason: TestResultItemChangeReason.Retired },
{ label: 'aa', reason: TestResultItemChangeReason.ParentRetired },
@ -161,24 +162,24 @@ suite('Workbench - Test Results Service', () => {
]);
changed.clear();
r.retire('id-a');
r.retire(new TestId(['ctrlId', 'id-a']).toString());
assert.strictEqual(changed.size, 0);
});
test('ignores outside run', () => {
changed.clear();
r.updateState('id-b', 't', TestResultState.Running);
r.updateState(new TestId(['ctrlId', 'id-b']).toString(), 't', TestResultState.Running);
assert.deepStrictEqual(r.counts, {
...makeEmptyCounts(),
[TestResultState.Queued]: 2,
[TestResultState.Unset]: 2,
});
assert.deepStrictEqual(r.getStateById('id-b'), undefined);
assert.deepStrictEqual(r.getStateById(new TestId(['ctrlId', 'id-b']).toString()), undefined);
});
test('markComplete', () => {
r.setAllToState(TestResultState.Queued, 't', () => true);
r.updateState('id-aa', 't', TestResultState.Passed);
r.updateState(new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), 't', TestResultState.Passed);
changed.clear();
r.markComplete();
@ -190,7 +191,7 @@ suite('Workbench - Test Results Service', () => {
});
assert.deepStrictEqual(r.getStateById(tests.root.id)?.ownComputedState, TestResultState.Unset);
assert.deepStrictEqual(r.getStateById('id-aa')?.ownComputedState, TestResultState.Passed);
assert.deepStrictEqual(r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString())?.ownComputedState, TestResultState.Passed);
});
});
@ -214,7 +215,7 @@ suite('Workbench - Test Results Service', () => {
test('serializes and re-hydrates', async () => {
results.push(r);
r.updateState('id-aa', 't', TestResultState.Passed);
r.updateState(new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), 't', TestResultState.Passed);
r.markComplete();
await timeout(10); // allow persistImmediately async to happen
@ -235,7 +236,7 @@ suite('Workbench - Test Results Service', () => {
delete expected.item.description;
expected.item.uri = actual.item.uri;
assert.deepStrictEqual(actual, { ...expected, src: undefined, retired: true, children: ['id-a'] });
assert.deepStrictEqual(actual, { ...expected, src: undefined, retired: true, children: [new TestId(['ctrlId', 'id-a']).toString()] });
assert.deepStrictEqual(rehydrated.counts, r.counts);
assert.strictEqual(typeof rehydrated.completedAt, 'number');
});
@ -278,7 +279,7 @@ suite('Workbench - Test Results Service', () => {
name: 'hello world',
request: defaultOpts([]),
items: [{
...(await getInitializedMainTestCollection()).getNodeById('id-a')!,
...(await getInitializedMainTestCollection()).getNodeById(new TestId(['ctrlId', 'id-a']).toString())!,
tasks: [{ state, duration: 0, messages: [] }],
computedState: state,
ownComputedState: state,
@ -312,22 +313,14 @@ suite('Workbench - Test Results Service', () => {
});
test('resultItemParents', () => {
assert.deepStrictEqual([...resultItemParents(r, r.getStateById('id-aa')!)], [
r.getStateById('id-aa'),
r.getStateById('id-a'),
r.getStateById(tests.root.id),
assert.deepStrictEqual([...resultItemParents(r, r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString())!)], [
r.getStateById(new TestId(['ctrlId', 'id-a', 'id-aa']).toString()),
r.getStateById(new TestId(['ctrlId', 'id-a']).toString()),
r.getStateById(new TestId(['ctrlId']).toString()),
]);
assert.deepStrictEqual([...resultItemParents(r, r.getStateById(tests.root.id)!)], [
r.getStateById(tests.root.id),
]);
});
test('getPathForTestInResult', () => {
assert.deepStrictEqual([...getPathForTestInResult(r.getStateById('id-aa')!, r)], [
tests.root.id,
'id-a',
'id-aa',
]);
});
});

View file

@ -6,6 +6,7 @@
import * as assert from 'assert';
import { range } from 'vs/base/common/arrays';
import { NullLogService } from 'vs/platform/log/common/log';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { ITestResult, LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { InMemoryResultStorage, RETAIN_MAX_RESULTS } from 'vs/workbench/contrib/testing/common/testResultStorage';
import { Convert, testStubs } from 'vs/workbench/contrib/testing/common/testStubs';
@ -26,14 +27,14 @@ suite('Workbench - Test Result Storage', () => {
t.addTask({ id: 't', name: undefined, running: true });
const tests = testStubs.nested();
tests.expand(tests.root.id, Infinity);
t.addTestChainToRun('ctrl', [
Convert.TestItem.from(tests.root),
Convert.TestItem.from(tests.root.children.get('id-a')!),
Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-aa')!),
t.addTestChainToRun('ctrlId', [
Convert.TestItem.from(tests.root, 'ctrlId'),
Convert.TestItem.from(tests.root.children.get('id-a')!, 'ctrlId'),
Convert.TestItem.from(tests.root.children.get('id-a')!.children.get('id-aa')!, 'ctrlId'),
]);
if (addMessage) {
t.appendMessage('id-a', 't', {
t.appendMessage(new TestId(['ctrlId', 'id-a']).toString(), 't', {
message: addMessage,
actualOutput: undefined,
expectedOutput: undefined,
@ -75,7 +76,8 @@ suite('Workbench - Test Result Storage', () => {
test('limits stored result by budget', async () => {
const r = range(100).map(() => makeResult('a'.repeat(2048)));
await storage.persist(r);
assert.strictEqual(true, (await storage.read()).length < 50);
const length = (await storage.read()).length;
assert.strictEqual(true, length < 50);
});
test('always stores the min number of results', async () => {

View file

@ -13,6 +13,7 @@ import { TestRunProfileImpl, TestRunCoordinator, TestRunDto } from 'vs/workbench
import * as convert from 'vs/workbench/api/common/extHostTypeConverters';
import { TestMessage, TestResultState, TestRunProfileGroup } from 'vs/workbench/api/common/extHostTypes';
import { TestDiffOpType, TestItemExpandState } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestItemImpl, testStubs } from 'vs/workbench/contrib/testing/common/testStubs';
import { TestSingleUseCollection } from 'vs/workbench/contrib/testing/test/common/ownedTestCollection';
import type { TestItem, TestRunRequest } from 'vscode';
@ -73,18 +74,20 @@ suite('ExtHost Testing', () => {
suite('OwnedTestCollection', () => {
test('adds a root recursively', async () => {
await single.expand(single.root.id, Infinity);
const a = single.root.children.get('id-a')!;
const b = single.root.children.get('id-b')!;
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: null, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(single.root) } }
{ controllerId: 'ctrlId', parent: null, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(single.root, 'ctrlId') } }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.Expandable, item: { ...convert.TestItem.from(single.tree.get('id-a')!.actual) } }
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.Expandable, item: { ...convert.TestItem.from(a, 'ctrlId') } }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(single.tree.get('id-b')!.actual) }
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(b, 'ctrlId') }
],
[
TestDiffOpType.Update,
@ -92,19 +95,19 @@ suite('ExtHost Testing', () => {
],
[
TestDiffOpType.Update,
{ extId: 'id-a', expand: TestItemExpandState.BusyExpanding }
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.BusyExpanding }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(single.tree.get('id-aa')!.actual) }
{ controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(a.children.get('id-aa')!, 'ctrlId') }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(single.tree.get('id-ab')!.actual) }
{ controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(a.children.get('id-ab')!, 'ctrlId') }
],
[
TestDiffOpType.Update,
{ extId: 'id-a', expand: TestItemExpandState.Expanded }
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded }
],
]);
});
@ -132,7 +135,7 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: 'id-a', item: { description: 'Hello world' } }],
{ extId: new TestId(['ctrlId', 'id-a']).toString(), item: { description: 'Hello world' } }],
]);
});
@ -142,9 +145,12 @@ suite('ExtHost Testing', () => {
single.root.children.delete('id-a');
assert.deepStrictEqual(single.collectDiff(), [
[TestDiffOpType.Remove, 'id-a'],
[TestDiffOpType.Remove, new TestId(['ctrlId', 'id-a']).toString()],
]);
assert.deepStrictEqual([...single.tree].map(n => n.actual.id).sort(), [single.root.id, 'id-b']);
assert.deepStrictEqual(
[...single.tree.keys()].sort(),
[single.root.id, new TestId(['ctrlId', 'id-b']).toString()],
);
assert.strictEqual(single.tree.size, 2);
});
@ -157,13 +163,13 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(single.collectDiff(), [
[TestDiffOpType.Add, {
controllerId: 'ctrlId',
parent: 'id-a',
parent: new TestId(['ctrlId', 'id-a']).toString(),
expand: TestItemExpandState.NotExpandable,
item: convert.TestItem.from(child),
item: convert.TestItem.from(child, 'ctrlId'),
}],
]);
assert.deepStrictEqual(
[...single.tree].map(n => n.actual.id).sort(),
[...single.tree.values()].map(n => n.actual.id).sort(),
[single.root.id, 'id-a', 'id-aa', 'id-ab', 'id-ac', 'id-b'],
);
assert.strictEqual(single.tree.size, 6);
@ -184,7 +190,7 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: 'id-a', expand: TestItemExpandState.Expanded, item: { label: 'Hello world' } },
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded, item: { label: 'Hello world' } },
],
]);
@ -192,7 +198,7 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: 'id-a', item: { label: 'still connected' } }
{ extId: new TestId(['ctrlId', 'id-a']).toString(), item: { label: 'still connected' } }
],
]);
@ -215,11 +221,11 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: 'id-a', expand: TestItemExpandState.Expanded },
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded },
],
[
TestDiffOpType.Update,
{ extId: 'id-ab', item: { label: 'Hello world' } },
{ extId: TestId.fromExtHostTestItem(oldAB, 'ctrlId').toString(), item: { label: 'Hello world' } },
],
]);
@ -229,11 +235,11 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: 'id-aa', item: { label: 'still connected1' } }
{ extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { label: 'still connected1' } }
],
[
TestDiffOpType.Update,
{ extId: 'id-ab', item: { label: 'still connected2' } }
{ extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { label: 'still connected2' } }
],
]);
@ -250,11 +256,11 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Remove,
'id-b',
new TestId(['ctrlId', 'id-b']).toString(),
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: 'id-a', expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(single.tree.get('id-b')!.actual) }
{ controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(b, 'ctrlId') }
],
]);
@ -262,7 +268,7 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: 'id-b', item: { label: 'still connected' } }
{ extId: new TestId(['ctrlId', 'id-a', 'id-b']).toString(), item: { label: 'still connected' } }
],
]);
@ -469,7 +475,7 @@ suite('ExtHost Testing', () => {
assert.strictEqual(tracker.isRunning, true);
assert.deepStrictEqual(proxy.$startedExtensionTestRun.args, [
[{
config: { group: 2, id: 42 },
profile: { group: 2, id: 42 },
controllerId: 'ctrl',
id: tracker.id,
include: [single.root.id],
@ -504,9 +510,9 @@ suite('ExtHost Testing', () => {
'ctrl',
tracker.id,
[
convert.TestItem.from(single.root),
convert.TestItem.from(single.root.children.get('id-a')!),
convert.TestItem.from(single.root.children.get('id-a')!.children.get('id-aa')!),
convert.TestItem.from(single.root, 'ctrlId'),
convert.TestItem.from(single.root.children.get('id-a')!, 'ctrlId'),
convert.TestItem.from(single.root.children.get('id-a')!.children.get('id-aa')!, 'ctrlId'),
]
]);
assert.deepStrictEqual(proxy.$addTestsToRun.args, expectedArgs);
@ -517,8 +523,8 @@ suite('ExtHost Testing', () => {
'ctrl',
tracker.id,
[
convert.TestItem.from(single.root.children.get('id-a')!),
convert.TestItem.from(single.root.children.get('id-a')!.children.get('id-ab')!),
convert.TestItem.from(single.root.children.get('id-a')!, 'ctrlId'),
convert.TestItem.from(single.root.children.get('id-a')!.children.get('id-ab')!, 'ctrlId'),
],
]);
assert.deepStrictEqual(proxy.$addTestsToRun.args, expectedArgs);
@ -543,13 +549,12 @@ suite('ExtHost Testing', () => {
test('excludes tests outside tree or explicitly excluded', () => {
single.expand(single.root.id, Infinity);
const task = c.createTestRun('ctrl', single, {
const task = c.createTestRun('ctrlId', single, {
profile: configuration,
include: [single.root.children.get('id-a')!],
exclude: [single.root.children.get('id-a')!.children.get('id-aa')!],
}, 'hello world', false);
task.setState(single.root.children.get('b')!, TestResultState.Passed);
task.setState(single.root.children.get('id-a')!.children.get('id-aa')!, TestResultState.Passed);
task.setState(single.root.children.get('id-a')!.children.get('id-ab')!, TestResultState.Passed);
@ -558,7 +563,7 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(proxy.$updateTestStateInRun.args, [[
args[0],
args[1],
'id-ab',
new TestId(['ctrlId', 'id-a', 'id-ab']).toString(),
TestResultState.Passed,
undefined,
]]);