testing: refactor to avoid complex object in ext host comms

For #140975
This commit is contained in:
Connor Peet 2022-02-07 15:35:47 -08:00
parent f728f14c68
commit 1e41d52904
No known key found for this signature in database
GPG key ID: CF8FD2EA0DBC61BD
18 changed files with 490 additions and 265 deletions

View file

@ -351,6 +351,7 @@ export class Range {
*/
public static lift(range: undefined | null): null;
public static lift(range: IRange): Range;
public static lift(range: IRange | undefined | null): Range | null;
public static lift(range: IRange | undefined | null): Range | null {
if (!range) {
return null;

1
src/vs/monaco.d.ts vendored
View file

@ -706,6 +706,7 @@ declare namespace monaco {
*/
static lift(range: undefined | null): null;
static lift(range: IRange): Range;
static lift(range: IRange | undefined | null): Range | null;
/**
* Test if `obj` is an `IRange`.
*/

View file

@ -11,7 +11,7 @@ import { isDefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { ExtensionRunTestsRequest, IFileCoverage, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, SerializedTestMessage, TestDiffOpType, TestResultState, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { ExtensionRunTestsRequest, IFileCoverage, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
@ -20,20 +20,6 @@ import { IMainThreadTestController, ITestRootProvider, ITestService } from 'vs/w
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { ExtHostContext, ExtHostTestingShape, ILocationDto, ITestControllerPatch, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
const reviveDiff = (diff: TestsDiff) => {
for (const entry of diff) {
if (entry[0] === TestDiffOpType.Add || entry[0] === TestDiffOpType.Update) {
const item = entry[1];
if (item.item?.uri) {
item.item.uri = URI.revive(item.item.uri);
}
if (item.item?.range) {
item.item.range = Range.lift(item.item.range);
}
}
}
};
@extHostNamedCustomer(MainContext.MainThreadTesting)
export class MainThreadTesting extends Disposable implements MainThreadTestingShape, ITestRootProvider {
private readonly proxy: ExtHostTestingShape;
@ -99,15 +85,8 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
/**
* @inheritdoc
*/
$addTestsToRun(controllerId: string, runId: string, tests: ITestItem[]): void {
for (const test of tests) {
test.uri = URI.revive(test.uri);
if (test.range) {
test.range = Range.lift(test.range);
}
}
this.withLiveRun(runId, r => r.addTestChainToRun(controllerId, tests));
$addTestsToRun(controllerId: string, runId: string, tests: ITestItem.Serialized[]): void {
this.withLiveRun(runId, r => r.addTestChainToRun(controllerId, tests.map(ITestItem.deserialize)));
}
/**
@ -178,17 +157,11 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
/**
* @inheritdoc
*/
public $appendTestMessagesInRun(runId: string, taskId: string, testId: string, messages: SerializedTestMessage[]): void {
public $appendTestMessagesInRun(runId: string, taskId: string, testId: string, messages: ITestMessage.Serialized[]): void {
const r = this.resultService.getResult(runId);
if (r && r instanceof LiveTestResult) {
for (const message of messages) {
const cast = message as ITestMessage;
if (message.location) {
cast.location!.uri = URI.revive(message.location.uri);
cast.location!.range = Range.lift(message.location.range);
}
r.appendMessage(testId, taskId, cast);
r.appendMessage(testId, taskId, ITestMessage.deserialize(message));
}
}
}
@ -251,7 +224,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
* @inheritdoc
*/
public $subscribeToDiffs(): void {
this.proxy.$acceptDiff(this.testService.collection.getReviverDiff());
this.proxy.$acceptDiff(this.testService.collection.getReviverDiff().map(TestsDiffOp.serialize));
this.diffListener.value = this.testService.onDidProcessDiff(this.proxy.$acceptDiff, this.proxy);
}
@ -265,9 +238,8 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
/**
* @inheritdoc
*/
public $publishDiff(controllerId: string, diff: TestsDiff): void {
reviveDiff(diff);
this.testService.publishDiff(controllerId, diff);
public $publishDiff(controllerId: string, diff: TestsDiffOp.Serialized[]): void {
this.testService.publishDiff(controllerId, diff.map(TestsDiffOp.deserialize));
}
public async $runTests(req: ResolvedTestRunRequest, token: CancellationToken): Promise<string> {

View file

@ -15,18 +15,20 @@ import * as performance from 'vs/base/common/performance';
import Severity from 'vs/base/common/severity';
import { URI, UriComponents } from 'vs/base/common/uri';
import { RenderLineNumbersType, TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange } from 'vs/editor/common/core/range';
import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { EndOfLineSequence } from 'vs/editor/common/model';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
import * as languages from 'vs/editor/common/languages';
import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/languages/languageConfiguration';
import { EndOfLineSequence } from 'vs/editor/common/model';
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IExtensionIdWithVersion } from 'vs/platform/extensionManagement/common/extensionStorage';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as files from 'vs/platform/files/common/files';
import { ResourceLabelFormatter } from 'vs/platform/label/common/label';
@ -35,12 +37,11 @@ import { IMarkerData } from 'vs/platform/markers/common/markers';
import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
import * as quickInput from 'vs/platform/quickinput/common/quickInput';
import { IRemoteConnectionData, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ProvidedPortAttributes, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId, TunnelProviderFeatures } from 'vs/platform/tunnel/common/tunnel';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { ICreateContributedTerminalProfileOptions, IProcessProperty, IShellLaunchConfigDto, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalLocation } from 'vs/platform/terminal/common/terminal';
import { ThemeColor, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IExtensionIdWithVersion } from 'vs/platform/extensionManagement/common/extensionStorage';
import { ProvidedPortAttributes, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId, TunnelProviderFeatures } from 'vs/platform/tunnel/common/tunnel';
import { WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust';
import * as tasks from 'vs/workbench/api/common/shared/tasks';
import { TreeDataTransferDTO } from 'vs/workbench/api/common/shared/treeDataTransfer';
@ -54,22 +55,21 @@ import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output';
import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { CoverageDetails, ExtensionRunTestsRequest, IFileCoverage, ISerializedTestResults, ITestItem, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, RunTestForControllerRequest, SerializedTestMessage, TestResultState, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { CoverageDetails, ExtensionRunTestsRequest, IFileCoverage, ISerializedTestResults, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, RunTestForControllerRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection';
import { InternalTimelineOptions, Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/workbench/services/authentication/common/authentication';
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { IStaticWorkspaceData } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
import { ActivationKind, ExtensionActivationReason, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions';
import { createProxyIdentifier, Dto, IRPCProtocol, SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { ILanguageStatus } from 'vs/workbench/services/languageStatus/common/languageStatusService';
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import * as search from 'vs/workbench/services/search/common/search';
import { IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search';
import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import { IStaticWorkspaceData } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/workbench/services/authentication/common/authentication';
export interface IWorkspaceData extends IStaticWorkspaceData {
folders: { uri: UriComponents; name: string; index: number }[];
@ -2053,7 +2053,7 @@ export interface ExtHostTestingShape {
$runControllerTests(req: RunTestForControllerRequest, token: CancellationToken): Promise<void>;
$cancelExtensionTestRun(runId: string | undefined): void;
/** Handles a diff of tests, as a result of a subscribeToDiffs() call */
$acceptDiff(diff: TestsDiff): void;
$acceptDiff(diff: TestsDiffOp.Serialized[]): void;
/** Publishes that a test run finished. */
$publishTestResults(results: ISerializedTestResults[]): void;
/** Expands a test item's children, by the given number of levels. */
@ -2090,7 +2090,7 @@ export interface MainThreadTestingShape {
/** Stops requesting tests published to VS Code. */
$unsubscribeFromDiffs(): void;
/** Publishes that new tests were available on the given source. */
$publishDiff(controllerId: string, diff: TestsDiff): void;
$publishDiff(controllerId: string, diff: TestsDiffOp.Serialized[]): void;
// --- test run configurations:
@ -2110,11 +2110,11 @@ export interface MainThreadTestingShape {
* Adds tests to the run. The tests are given in descending depth. The first
* item will be a previously-known test, or a test root.
*/
$addTestsToRun(controllerId: string, runId: string, tests: ITestItem[]): void;
$addTestsToRun(controllerId: string, runId: string, tests: ITestItem.Serialized[]): void;
/** Updates the state of a test run in the given run. */
$updateTestStateInRun(runId: string, taskId: string, testId: string, state: TestResultState, duration?: number): void;
/** Appends a message to a test in the run. */
$appendTestMessagesInRun(runId: string, taskId: string, testId: string, messages: SerializedTestMessage[]): void;
$appendTestMessagesInRun(runId: string, taskId: string, testId: string, messages: ITestMessage.Serialized[]): void;
/** Appends raw output to the test run.. */
$appendOutputToRun(runId: string, taskId: string, output: VSBuffer, location?: ILocationDto, testId?: string): void;
/** Triggered when coverage is added to test results. */

View file

@ -21,7 +21,7 @@ import { InvalidTestItemError, TestItemImpl, TestItemRootImpl } from 'vs/workben
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
import { TestRunProfileKind, TestRunRequest } from 'vs/workbench/api/common/extHostTypes';
import { SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
import { AbstractIncrementalTestCollection, CoverageDetails, IFileCoverage, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, ITestItem, RunTestForControllerRequest, TestResultState, TestRunProfileBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { AbstractIncrementalTestCollection, CoverageDetails, IFileCoverage, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, ISerializedTestResults, ITestItem, RunTestForControllerRequest, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId';
import type * as vscode from 'vscode';
@ -123,7 +123,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
this.controllers.set(controllerId, info);
disposable.add(toDisposable(() => this.controllers.delete(controllerId)));
disposable.add(collection.onDidGenerateDiff(diff => proxy.$publishDiff(controllerId, diff)));
disposable.add(collection.onDidGenerateDiff(diff => proxy.$publishDiff(controllerId, diff.map(TestsDiffOp.serialize))));
return controller;
}
@ -220,8 +220,8 @@ export class ExtHostTesting implements ExtHostTestingShape {
* Receives a test update from the main thread. Called (eventually) whenever
* tests change.
*/
public $acceptDiff(diff: TestsDiff): void {
this.observer.applyDiff(diff);
public $acceptDiff(diff: TestsDiffOp.Serialized[]): void {
this.observer.applyDiff(diff.map(TestsDiffOp.deserialize));
}
/**
@ -460,7 +460,7 @@ class TestRunTracker extends Disposable {
return;
}
const chain: ITestItem[] = [];
const chain: ITestItem.Serialized[] = [];
const root = this.dto.colllection.root;
while (true) {
const converted = Convert.TestItem.from(test as TestItemImpl);

View file

@ -30,7 +30,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
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, denamespaceTestTag, DetailType, ICoveredCount, IFileCoverage, ISerializedTestResults, ITestItem, ITestItemContext, ITestTag, namespaceTestTag, SerializedTestErrorMessage, SerializedTestResultItem, TestMessageType } from 'vs/workbench/contrib/testing/common/testCollection';
import { CoverageDetails, denamespaceTestTag, DetailType, ICoveredCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestItemContext, ITestTag, namespaceTestTag, TestMessageType, TestResultItem } 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';
@ -1692,17 +1692,17 @@ export namespace NotebookRendererScript {
}
export namespace TestMessage {
export function from(message: vscode.TestMessage): SerializedTestErrorMessage {
export function from(message: vscode.TestMessage): ITestErrorMessage.Serialized {
return {
message: MarkdownString.fromStrict(message.message) || '',
type: TestMessageType.Error,
expected: message.expectedOutput,
actual: message.actualOutput,
location: message.location ? location.from(message.location) as any : undefined,
location: message.location && ({ range: Range.from(message.location.range), uri: message.location.uri }),
};
}
export function to(item: SerializedTestErrorMessage): vscode.TestMessage {
export function to(item: ITestErrorMessage.Serialized): vscode.TestMessage {
const message = new types.TestMessage(typeof item.message === 'string' ? item.message : MarkdownString.to(item.message));
message.actualOutput = item.actual;
message.expectedOutput = item.expected;
@ -1725,16 +1725,17 @@ export namespace TestItem {
return {
extId: TestId.fromExtHostTestItem(item, ctrlId).toString(),
label: item.label,
uri: item.uri,
uri: URI.revive(item.uri),
busy: false,
tags: item.tags.map(t => TestTag.namespace(ctrlId, t.id)),
range: Range.from(item.range) || null,
range: editorRange.Range.lift(Range.from(item.range)),
description: item.description || null,
sortText: item.sortText || null,
error: item.error ? (MarkdownString.fromStrict(item.error) || null) : null,
};
}
export function toPlain(item: ITestItem): Omit<vscode.TestItem, 'children' | 'invalidate' | 'discoverChildren'> {
export function toPlain(item: ITestItem.Serialized): Omit<vscode.TestItem, 'children' | 'invalidate' | 'discoverChildren'> {
return {
parent: undefined,
error: undefined,
@ -1756,7 +1757,7 @@ export namespace TestItem {
function to(item: ITestItem): TestItemImpl {
const testId = TestId.fromString(item.extId);
const testItem = new TestItemImpl(testId.controllerId, testId.localId, item.label, URI.revive(item.uri));
const testItem = new TestItemImpl(testId.controllerId, testId.localId, item.label, URI.revive(item.uri) || undefined);
testItem.range = Range.to(item.range || undefined);
testItem.description = item.description || undefined;
testItem.sortText = item.sortText || undefined;
@ -1786,7 +1787,7 @@ export namespace TestTag {
}
export namespace TestResults {
const convertTestResultItem = (item: SerializedTestResultItem, byInternalId: Map<string, SerializedTestResultItem>): vscode.TestResultSnapshot => {
const convertTestResultItem = (item: TestResultItem.Serialized, byInternalId: Map<string, TestResultItem.Serialized>): vscode.TestResultSnapshot => {
const snapshot: vscode.TestResultSnapshot = ({
...TestItem.toPlain(item.item),
parent: undefined,
@ -1794,7 +1795,7 @@ export namespace TestResults {
state: t.state as number as types.TestResultState,
duration: t.duration,
messages: t.messages
.filter((m): m is SerializedTestErrorMessage => m.type === TestMessageType.Error)
.filter((m): m is ITestErrorMessage.Serialized => m.type === TestMessageType.Error)
.map(TestMessage.to),
})),
children: item.children
@ -1811,8 +1812,8 @@ export namespace TestResults {
};
export function to(serialized: ISerializedTestResults): vscode.TestRunResult {
const roots: SerializedTestResultItem[] = [];
const byInternalId = new Map<string, SerializedTestResultItem>();
const roots: TestResultItem.Serialized[] = [];
const byInternalId = new Map<string, TestResultItem.Serialized>();
for (const item of serialized.items) {
byInternalId.set(item.item.extId, item);
if (serialized.request.targets.some(t => t.controllerId === item.controllerId && t.testIds.includes(item.item.extId))) {

View file

@ -78,34 +78,34 @@ suite('ExtHost Testing', () => {
const a = single.root.children.get('id-a') as TestItemImpl;
const b = single.root.children.get('id-b') as TestItemImpl;
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: null, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(single.root) } }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(a) } }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(a.children.get('id-aa') as TestItemImpl) }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(a.children.get('id-ab') as TestItemImpl) }
],
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(b) }
],
[
TestDiffOpType.Update,
{ extId: single.root.id, expand: TestItemExpandState.Expanded }
],
{
op: TestDiffOpType.Add,
item: { controllerId: 'ctrlId', parent: null, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(single.root) } }
},
{
op: TestDiffOpType.Add,
item: { controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(a) } }
},
{
op: TestDiffOpType.Add,
item: { controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(a.children.get('id-aa') as TestItemImpl) }
},
{
op: TestDiffOpType.Add,
item: { controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(a.children.get('id-ab') as TestItemImpl) }
},
{
op: TestDiffOpType.Update,
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded }
},
{
op: TestDiffOpType.Add,
item: { controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(b) }
},
{
op: TestDiffOpType.Update,
item: { extId: single.root.id, expand: TestItemExpandState.Expanded }
},
]);
});
@ -130,9 +130,10 @@ suite('ExtHost Testing', () => {
single.root.children.get('id-a')!.description = 'Hello world'; /* item a */
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a']).toString(), item: { description: 'Hello world' } }],
{
op: TestDiffOpType.Update,
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { description: 'Hello world' } },
}
]);
});
@ -142,7 +143,7 @@ suite('ExtHost Testing', () => {
single.root.children.delete('id-a');
assert.deepStrictEqual(single.collectDiff(), [
[TestDiffOpType.Remove, new TestId(['ctrlId', 'id-a']).toString()],
{ op: TestDiffOpType.Remove, itemId: new TestId(['ctrlId', 'id-a']).toString() },
]);
assert.deepStrictEqual(
[...single.tree.keys()].sort(),
@ -158,12 +159,14 @@ suite('ExtHost Testing', () => {
single.root.children.get('id-a')!.children.add(child);
assert.deepStrictEqual(single.collectDiff(), [
[TestDiffOpType.Add, {
controllerId: 'ctrlId',
parent: new TestId(['ctrlId', 'id-a']).toString(),
expand: TestItemExpandState.NotExpandable,
item: convert.TestItem.from(child),
}],
{
op: TestDiffOpType.Add, item: {
controllerId: 'ctrlId',
parent: new TestId(['ctrlId', 'id-a']).toString(),
expand: TestItemExpandState.NotExpandable,
item: convert.TestItem.from(child),
}
},
]);
assert.deepStrictEqual(
[...single.tree.values()].map(n => n.actual.id).sort(),
@ -183,31 +186,35 @@ suite('ExtHost Testing', () => {
single.root.children.get('id-a')!.children.add(child);
assert.deepStrictEqual(single.collectDiff(), [
[TestDiffOpType.AddTag, { ctrlLabel: 'root', id: 'ctrlId\0tag1' }],
[TestDiffOpType.AddTag, { ctrlLabel: 'root', id: 'ctrlId\0tag2' }],
[TestDiffOpType.Add, {
controllerId: 'ctrlId',
parent: new TestId(['ctrlId', 'id-a']).toString(),
expand: TestItemExpandState.NotExpandable,
item: convert.TestItem.from(child),
}],
{ op: TestDiffOpType.AddTag, tag: { ctrlLabel: 'root', id: 'ctrlId\0tag1' } },
{ op: TestDiffOpType.AddTag, tag: { ctrlLabel: 'root', id: 'ctrlId\0tag2' } },
{
op: TestDiffOpType.Add, item: {
controllerId: 'ctrlId',
parent: new TestId(['ctrlId', 'id-a']).toString(),
expand: TestItemExpandState.NotExpandable,
item: convert.TestItem.from(child),
}
},
]);
child.tags = [tag2, tag3];
assert.deepStrictEqual(single.collectDiff(), [
[TestDiffOpType.AddTag, { ctrlLabel: 'root', id: 'ctrlId\0tag3' }],
[TestDiffOpType.Update, {
extId: new TestId(['ctrlId', 'id-a', 'id-ac']).toString(),
item: { tags: ['ctrlId\0tag2', 'ctrlId\0tag3'] }
}],
[TestDiffOpType.RemoveTag, 'ctrlId\0tag1'],
{ op: TestDiffOpType.AddTag, tag: { ctrlLabel: 'root', id: 'ctrlId\0tag3' } },
{
op: TestDiffOpType.Update, item: {
extId: new TestId(['ctrlId', 'id-a', 'id-ac']).toString(),
item: { tags: ['ctrlId\0tag2', 'ctrlId\0tag3'] }
}
},
{ op: TestDiffOpType.RemoveTag, id: 'ctrlId\0tag1' },
]);
const a = single.root.children.get('id-a')!;
a.tags = [tag2];
a.children.replace([]);
assert.deepStrictEqual(single.collectDiff().filter(t => t[0] === TestDiffOpType.RemoveTag), [
[TestDiffOpType.RemoveTag, 'ctrlId\0tag3'],
assert.deepStrictEqual(single.collectDiff().filter(t => t.op === TestDiffOpType.RemoveTag), [
{ op: TestDiffOpType.RemoveTag, id: 'ctrlId\0tag3' },
]);
});
@ -224,18 +231,18 @@ suite('ExtHost Testing', () => {
]);
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded, item: { label: 'Hello world' } },
],
{
op: TestDiffOpType.Update,
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded, item: { label: 'Hello world' } },
},
]);
newA.label = 'still connected';
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a']).toString(), item: { label: 'still connected' } }
],
{
op: TestDiffOpType.Update,
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { label: 'still connected' } }
},
]);
oldA.label = 'no longer connected';
@ -255,28 +262,28 @@ suite('ExtHost Testing', () => {
single.root.children.replace([newA, single.root.children.get('id-b')!]);
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded },
],
[
TestDiffOpType.Update,
{ extId: TestId.fromExtHostTestItem(oldAB, 'ctrlId').toString(), item: { label: 'Hello world' } },
],
{
op: TestDiffOpType.Update,
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded },
},
{
op: TestDiffOpType.Update,
item: { extId: TestId.fromExtHostTestItem(oldAB, 'ctrlId').toString(), item: { label: 'Hello world' } },
},
]);
oldAA.label = 'still connected1';
newAB.label = 'still connected2';
oldAB.label = 'not connected3';
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { label: 'still connected1' } }
],
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { label: 'still connected2' } }
],
{
op: TestDiffOpType.Update,
item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { label: 'still connected1' } }
},
{
op: TestDiffOpType.Update,
item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { label: 'still connected2' } }
},
]);
assert.strictEqual(newAB.parent, newA);
@ -291,22 +298,22 @@ suite('ExtHost Testing', () => {
const a = single.root.children.get('id-a') as TestItemImpl;
a.children.add(b);
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Remove,
new TestId(['ctrlId', 'id-b']).toString(),
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(b) }
],
{
op: TestDiffOpType.Remove,
itemId: new TestId(['ctrlId', 'id-b']).toString(),
},
{
op: TestDiffOpType.Add,
item: { controllerId: 'ctrlId', parent: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(b) }
},
]);
b.label = 'still connected';
assert.deepStrictEqual(single.collectDiff(), [
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a', 'id-b']).toString(), item: { label: 'still connected' } }
],
{
op: TestDiffOpType.Update,
item: { extId: new TestId(['ctrlId', 'id-a', 'id-b']).toString(), item: { label: 'still connected' } }
},
]);
assert.deepStrictEqual([...single.root.children], [single.root.children.get('id-a')]);

View file

@ -138,15 +138,15 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
*/
private applyDiff(diff: TestsDiff) {
for (const op of diff) {
switch (op[0]) {
switch (op.op) {
case TestDiffOpType.Add: {
const item = this.createItem(op[1]);
const item = this.createItem(op.item);
this.storeItem(item);
break;
}
case TestDiffOpType.Update: {
const patch = op[1];
const patch = op.item;
const existing = this.items.get(patch.extId);
if (!existing) {
break;
@ -165,7 +165,7 @@ export class HierarchicalByLocationProjection extends Disposable implements ITes
}
case TestDiffOpType.Remove: {
const toRemove = this.items.get(op[1]);
const toRemove = this.items.get(op.itemId);
if (!toRemove) {
break;
}

View file

@ -115,11 +115,11 @@ export class TestingDecorationService extends Disposable implements ITestingDeco
// is up to date. This prevents issues, as in #138632, #138835, #138922.
this._register(this.testService.onWillProcessDiff(diff => {
for (const entry of diff) {
let uri: URI | undefined;
if (entry[0] === TestDiffOpType.Add || entry[0] === TestDiffOpType.Update) {
uri = entry[1].item?.uri;
} else if (entry[0] === TestDiffOpType.Remove) {
uri = this.testService.collection.getNodeById(entry[1])?.item.uri;
let uri: URI | undefined | null;
if (entry.op === TestDiffOpType.Add || entry.op === TestDiffOpType.Update) {
uri = entry.item.item?.uri;
} else if (entry.op === TestDiffOpType.Remove) {
uri = this.testService.collection.getNodeById(entry.itemId)?.item.uri;
}
const rec = uri && this.decorationCache.get(uri);

View file

@ -84,18 +84,20 @@ export class MainThreadTestCollection extends AbstractIncrementalTestCollection<
* @inheritdoc
*/
public getReviverDiff() {
const ops: TestsDiff = [[TestDiffOpType.IncrementPendingExtHosts, this.pendingRootCount]];
const ops: TestsDiff = [{ op: TestDiffOpType.IncrementPendingExtHosts, amount: this.pendingRootCount }];
const queue = [this.rootIds];
while (queue.length) {
for (const child of queue.pop()!) {
const item = this.items.get(child)!;
ops.push([TestDiffOpType.Add, {
controllerId: item.controllerId,
expand: item.expand,
item: item.item,
parent: item.parent,
}]);
ops.push({
op: TestDiffOpType.Add, item: {
controllerId: item.controllerId,
expand: item.expand,
item: item.item,
parent: item.parent,
}
});
queue.push(item.children);
}
}
@ -122,7 +124,7 @@ export class MainThreadTestCollection extends AbstractIncrementalTestCollection<
public clear() {
const ops: TestsDiff = [];
for (const root of this.roots) {
ops.push([TestDiffOpType.Remove, root.item.extId]);
ops.push({ op: TestDiffOpType.Remove, itemId: root.item.extId });
}
this.roots.clear();

View file

@ -12,7 +12,7 @@ import { assertNever } from 'vs/base/common/types';
import { diffTestItems, ExtHostTestItemEvent, ExtHostTestItemEventOp, getPrivateApiFor, TestItemImpl, TestItemRootImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
// eslint-disable-next-line code-import-patterns
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
import { applyTestItemUpdate, ITestTag, TestDiffOpType, TestItemExpandState, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testCollection';
import { applyTestItemUpdate, ITestTag, namespaceTestTag, 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;
@ -88,14 +88,14 @@ export class SingleUseTestCollection extends Disposable {
public pushDiff(diff: TestsDiffOp) {
// Try to merge updates, since they're invoked per-property
const last = this.diff[this.diff.length - 1];
if (last && diff[0] === TestDiffOpType.Update) {
if (last[0] === TestDiffOpType.Update && last[1].extId === diff[1].extId) {
applyTestItemUpdate(last[1], diff[1]);
if (last && diff.op === TestDiffOpType.Update) {
if (last.op === TestDiffOpType.Update && last.item.extId === diff.item.extId) {
applyTestItemUpdate(last.item, diff.item);
return;
}
if (last[0] === TestDiffOpType.Add && last[1].item.extId === diff[1].extId) {
applyTestItemUpdate(last[1], diff[1]);
if (last.op === TestDiffOpType.Add && last.item.item.extId === diff.item.extId) {
applyTestItemUpdate(last.item, diff.item);
return;
}
}
@ -149,7 +149,7 @@ export class SingleUseTestCollection extends Disposable {
private onTestItemEvent(internal: OwnedCollectionTestItem, evt: ExtHostTestItemEvent) {
switch (evt.op) {
case ExtHostTestItemEventOp.Invalidated:
this.pushDiff([TestDiffOpType.Retire, internal.fullId.toString()]);
this.pushDiff({ op: TestDiffOpType.Retire, itemId: internal.fullId.toString() });
break;
case ExtHostTestItemEventOp.RemoveChild:
@ -177,13 +177,13 @@ export class SingleUseTestCollection extends Disposable {
this.diffTagRefs(value, previous, extId);
break;
case 'range':
this.pushDiff([TestDiffOpType.Update, { extId, item: { range: Convert.Range.from(value) }, }]);
this.pushDiff({ op: TestDiffOpType.Update, item: { extId, item: { range: Convert.Range.from(value) }, } });
break;
case 'error':
this.pushDiff([TestDiffOpType.Update, { extId, item: { error: Convert.MarkdownString.fromStrict(value) || null }, }]);
this.pushDiff({ op: TestDiffOpType.Update, item: { extId, item: { error: Convert.MarkdownString.fromStrict(value) || null }, } });
break;
default:
this.pushDiff([TestDiffOpType.Update, { extId, item: { [key]: value ?? null } }]);
this.pushDiff({ op: TestDiffOpType.Update, item: { extId, item: { [key]: value ?? null } } });
break;
}
break;
@ -221,15 +221,15 @@ export class SingleUseTestCollection extends Disposable {
actual.tags.forEach(this.incrementTagRefs, this);
this.tree.set(internal.fullId.toString(), internal);
this.setItemParent(actual, parent);
this.pushDiff([
TestDiffOpType.Add,
{
this.pushDiff({
op: TestDiffOpType.Add,
item: {
parent: internal.parent && internal.parent.toString(),
controllerId: this.controllerId,
expand: internal.expand,
item: Convert.TestItem.from(actual),
},
]);
});
this.connectItemAndChildren(actual, internal, parent);
return;
@ -271,10 +271,10 @@ export class SingleUseTestCollection extends Disposable {
}
}
this.pushDiff([
TestDiffOpType.Update,
{ extId, item: { tags: newTags.map(v => Convert.TestTag.namespace(this.controllerId, v.id)) } }]
);
this.pushDiff({
op: TestDiffOpType.Update,
item: { extId, item: { tags: newTags.map(v => Convert.TestTag.namespace(this.controllerId, v.id)) } }
});
toDelete.forEach(this.decrementTagRefs, this);
}
@ -285,10 +285,12 @@ export class SingleUseTestCollection extends Disposable {
existing.refCount++;
} else {
this.tags.set(tag.id, { refCount: 1 });
this.pushDiff([TestDiffOpType.AddTag, {
id: Convert.TestTag.namespace(this.controllerId, tag.id),
ctrlLabel: this.root.label,
}]);
this.pushDiff({
op: TestDiffOpType.AddTag, tag: {
id: Convert.TestTag.namespace(this.controllerId, tag.id),
ctrlLabel: this.root.label,
}
});
}
}
@ -296,7 +298,7 @@ export class SingleUseTestCollection extends Disposable {
const existing = this.tags.get(tagId);
if (existing && !--existing.refCount) {
this.tags.delete(tagId);
this.pushDiff([TestDiffOpType.RemoveTag, Convert.TestTag.namespace(this.controllerId, tagId)]);
this.pushDiff({ op: TestDiffOpType.RemoveTag, id: namespaceTestTag(this.controllerId, tagId) });
}
}
@ -345,7 +347,7 @@ export class SingleUseTestCollection extends Disposable {
}
internal.expand = newState;
this.pushDiff([TestDiffOpType.Update, { extId: internal.fullId.toString(), expand: newState }]);
this.pushDiff({ op: TestDiffOpType.Update, item: { extId: internal.fullId.toString(), expand: newState } });
if (newState === TestItemExpandState.Expandable && internal.expandLevels !== undefined) {
this.resolveChildren(internal);
@ -421,7 +423,7 @@ export class SingleUseTestCollection extends Disposable {
}
private pushExpandStateUpdate(internal: OwnedCollectionTestItem) {
this.pushDiff([TestDiffOpType.Update, { extId: internal.fullId.toString(), expand: internal.expand }]);
this.pushDiff({ op: TestDiffOpType.Update, item: { extId: internal.fullId.toString(), expand: internal.expand } });
}
private removeItem(childId: string) {
@ -430,7 +432,7 @@ export class SingleUseTestCollection extends Disposable {
throw new Error('attempting to remove non-existent child');
}
this.pushDiff([TestDiffOpType.Remove, childId]);
this.pushDiff({ op: TestDiffOpType.Remove, itemId: childId });
const queue: (OwnedCollectionTestItem | undefined)[] = [childItem];
while (queue.length) {

View file

@ -5,10 +5,9 @@
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { MarshalledId } from 'vs/base/common/marshalling';
import { URI } from 'vs/base/common/uri';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
export const enum TestResultState {
Unset = 0,
@ -99,6 +98,23 @@ export interface IRichLocation {
uri: URI;
}
export namespace IRichLocation {
export interface Serialize {
range: IRange;
uri: UriComponents;
}
export const serialize = (location: IRichLocation): Serialize => ({
range: location.range.toJSON(),
uri: location.uri.toJSON(),
});
export const deserialize = (location: Serialize): IRichLocation => ({
range: Range.lift(location.range),
uri: URI.revive(location.uri),
});
}
export const enum TestMessageType {
Error,
Info
@ -112,7 +128,31 @@ export interface ITestErrorMessage {
location: IRichLocation | undefined;
}
export type SerializedTestErrorMessage = Dto<ITestErrorMessage>;
export namespace ITestErrorMessage {
export interface Serialized {
message: string | IMarkdownString;
type: TestMessageType.Error;
expected: string | undefined;
actual: string | undefined;
location: IRichLocation.Serialize | undefined;
}
export const serialize = (message: ITestErrorMessage): Serialized => ({
message: message.message,
type: TestMessageType.Error,
expected: message.expected,
actual: message.actual,
location: message.location && IRichLocation.serialize(message.location),
});
export const deserialize = (message: Serialized): ITestErrorMessage => ({
message: message.message,
type: TestMessageType.Error,
expected: message.expected,
actual: message.actual,
location: message.location && IRichLocation.deserialize(message.location),
});
}
export interface ITestOutputMessage {
message: string;
@ -121,18 +161,67 @@ export interface ITestOutputMessage {
location: IRichLocation | undefined;
}
export type SerializedTestOutputMessage = Dto<ITestOutputMessage>;
export namespace ITestOutputMessage {
export interface Serialized {
message: string;
offset: number;
type: TestMessageType.Info;
location: IRichLocation.Serialize | undefined;
}
export type SerializedTestMessage = SerializedTestErrorMessage | SerializedTestOutputMessage;
export const serialize = (message: ITestOutputMessage): Serialized => ({
message: message.message,
type: TestMessageType.Info,
offset: message.offset,
location: message.location && IRichLocation.serialize(message.location),
});
export const deserialize = (message: Serialized): ITestOutputMessage => ({
message: message.message,
type: TestMessageType.Info,
offset: message.offset,
location: message.location && IRichLocation.deserialize(message.location),
});
}
export type ITestMessage = ITestErrorMessage | ITestOutputMessage;
export namespace ITestMessage {
export type Serialized = ITestErrorMessage.Serialized | ITestOutputMessage.Serialized;
export const serialize = (message: ITestMessage): Serialized =>
message.type === TestMessageType.Error ? ITestErrorMessage.serialize(message) : ITestOutputMessage.serialize(message);
export const deserialize = (message: Serialized): ITestMessage =>
message.type === TestMessageType.Error ? ITestErrorMessage.deserialize(message) : ITestOutputMessage.deserialize(message);
}
export interface ITestTaskState {
state: TestResultState;
duration: number | undefined;
messages: ITestMessage[];
}
export namespace ITestTaskState {
export interface Serialized {
state: TestResultState;
duration: number | undefined;
messages: ITestMessage.Serialized[];
}
export const serialize = (state: ITestTaskState): Serialized => ({
state: state.state,
duration: state.duration,
messages: state.messages.map(ITestMessage.serialize),
});
export const deserialize = (state: Serialized): ITestTaskState => ({
state: state.state,
duration: state.duration,
messages: state.messages.map(ITestMessage.deserialize),
});
}
export interface ITestRunTask {
id: string;
name: string | undefined;
@ -166,15 +255,56 @@ export interface ITestItem {
extId: string;
label: string;
tags: string[];
busy?: boolean;
busy: boolean;
children?: never;
uri?: URI;
range: IRange | null;
uri: URI | undefined;
range: Range | null;
description: string | null;
error: string | IMarkdownString | null;
sortText: string | null;
}
export namespace ITestItem {
export interface Serialized {
extId: string;
label: string;
tags: string[];
busy: boolean;
children?: never;
uri: UriComponents | undefined;
range: IRange | null;
description: string | null;
error: string | IMarkdownString | null;
sortText: string | null;
}
export const serialize = (item: ITestItem): Serialized => ({
extId: item.extId,
label: item.label,
tags: item.tags,
busy: item.busy,
children: undefined,
uri: item.uri?.toJSON(),
range: item.range?.toJSON() || null,
description: item.description,
error: item.error,
sortText: item.sortText
});
export const deserialize = (serialized: Serialized): ITestItem => ({
extId: serialized.extId,
label: serialized.label,
tags: serialized.tags,
busy: serialized.busy,
children: undefined,
uri: serialized.uri ? URI.revive(serialized.uri) : undefined,
range: serialized.range ? Range.lift(serialized.range) : null,
description: serialized.description,
error: serialized.error,
sortText: serialized.sortText
});
}
export const enum TestItemExpandState {
NotExpandable,
Expandable,
@ -196,6 +326,29 @@ export interface InternalTestItem {
item: ITestItem;
}
export namespace InternalTestItem {
export interface Serialized {
controllerId: string;
expand: TestItemExpandState;
parent: string | null;
item: ITestItem.Serialized;
}
export const serialize = (item: InternalTestItem): Serialized => ({
controllerId: item.controllerId,
expand: item.expand,
parent: item.parent,
item: ITestItem.serialize(item.item)
});
export const deserialize = (serialized: Serialized): InternalTestItem => ({
controllerId: serialized.controllerId,
expand: serialized.expand,
parent: serialized.parent,
item: ITestItem.deserialize(serialized.item)
});
}
/**
* A partial update made to an existing InternalTestItem.
*/
@ -205,6 +358,48 @@ export interface ITestItemUpdate {
item?: Partial<ITestItem>;
}
export namespace ITestItemUpdate {
export interface Serialized {
extId: string;
expand?: TestItemExpandState;
item?: Partial<ITestItem.Serialized>;
}
export const serialize = (u: ITestItemUpdate): Serialized => {
let item: Partial<ITestItem.Serialized> | undefined;
if (u.item) {
item = {};
if (u.item.label !== undefined) { item.label = u.item.label; }
if (u.item.tags !== undefined) { item.tags = u.item.tags; }
if (u.item.busy !== undefined) { item.busy = u.item.busy; }
if (u.item.uri !== undefined) { item.uri = u.item.uri?.toJSON(); }
if (u.item.range !== undefined) { item.range = u.item.range?.toJSON(); }
if (u.item.description !== undefined) { item.description = u.item.description; }
if (u.item.error !== undefined) { item.error = u.item.error; }
if (u.item.sortText !== undefined) { item.sortText = u.item.sortText; }
}
return { extId: u.extId, expand: u.expand, item };
};
export const deserialize = (u: Serialized): ITestItemUpdate => {
let item: Partial<ITestItem> | undefined;
if (u.item) {
item = {};
if (u.item.label !== undefined) { item.label = u.item.label; }
if (u.item.tags !== undefined) { item.tags = u.item.tags; }
if (u.item.busy !== undefined) { item.busy = u.item.busy; }
if (u.item.range !== undefined) { item.range = u.item.range ? Range.lift(u.item.range) : null; }
if (u.item.description !== undefined) { item.description = u.item.description; }
if (u.item.error !== undefined) { item.error = u.item.error; }
if (u.item.sortText !== undefined) { item.sortText = u.item.sortText; }
}
return { extId: u.extId, expand: u.expand, item };
};
}
export const applyTestItemUpdate = (internal: InternalTestItem | ITestItemUpdate, patch: ITestItemUpdate) => {
if (patch.expand !== undefined) {
internal.expand = patch.expand;
@ -230,21 +425,33 @@ export interface TestResultItem extends InternalTestItem {
ownDuration?: number;
}
export type SerializedTestResultItem = Omit<TestResultItem, 'children' | 'expandable' | 'retired' | 'tasks'>
& { children: string[]; retired: undefined; tasks: Dto<ITestTaskState[]> };
export namespace TestResultItem {
/** Serialized version of the TestResultItem */
export interface Serialized extends InternalTestItem.Serialized {
children: string[];
tasks: ITestTaskState.Serialized[];
ownComputedState: TestResultState;
computedState: TestResultState;
}
export const serialize = (original: TestResultItem, children: string[]): Serialized => ({
...InternalTestItem.serialize(original),
children,
ownComputedState: original.ownComputedState,
computedState: original.computedState,
tasks: original.tasks.map(ITestTaskState.serialize),
});
}
/**
* Test results serialized for transport and storage.
*/
export interface ISerializedTestResults {
/** ID of these test results */
id: string;
/** Time the results were compelted */
completedAt: number;
/** Subset of test result items */
items: SerializedTestResultItem[];
items: TestResultItem.Serialized[];
/** Tasks involved in the run. */
tasks: { id: string; name: string | undefined; messages: ITestOutputMessage[] }[];
tasks: { id: string; name: string | undefined; messages: ITestOutputMessage.Serialized[] }[];
/** Human-readable name of the test run. */
name: string;
/** Test trigger informaton */
@ -311,13 +518,44 @@ export const enum TestDiffOpType {
}
export type TestsDiffOp =
| [op: TestDiffOpType.Add, item: InternalTestItem]
| [op: TestDiffOpType.Update, item: ITestItemUpdate]
| [op: TestDiffOpType.Remove, itemId: string]
| [op: TestDiffOpType.Retire, itemId: string]
| [op: TestDiffOpType.IncrementPendingExtHosts, amount: number]
| [op: TestDiffOpType.AddTag, tag: ITestTagDisplayInfo]
| [op: TestDiffOpType.RemoveTag, id: string];
| { op: TestDiffOpType.Add; item: InternalTestItem }
| { op: TestDiffOpType.Update; item: ITestItemUpdate }
| { op: TestDiffOpType.Remove; itemId: string }
| { op: TestDiffOpType.Retire; itemId: string }
| { op: TestDiffOpType.IncrementPendingExtHosts; amount: number }
| { op: TestDiffOpType.AddTag; tag: ITestTagDisplayInfo }
| { op: TestDiffOpType.RemoveTag; id: string };
export namespace TestsDiffOp {
export type Serialized =
| { op: TestDiffOpType.Add; item: InternalTestItem.Serialized }
| { op: TestDiffOpType.Update; item: ITestItemUpdate.Serialized }
| { op: TestDiffOpType.Remove; itemId: string }
| { op: TestDiffOpType.Retire; itemId: string }
| { op: TestDiffOpType.IncrementPendingExtHosts; amount: number }
| { op: TestDiffOpType.AddTag; tag: ITestTagDisplayInfo }
| { op: TestDiffOpType.RemoveTag; id: string };
export const deserialize = (u: Serialized): TestsDiffOp => {
if (u.op === TestDiffOpType.Add) {
return { op: u.op, item: InternalTestItem.deserialize(u.item) };
} else if (u.op === TestDiffOpType.Update) {
return { op: u.op, item: ITestItemUpdate.deserialize(u.item) };
} else {
return u;
}
};
export const serialize = (u: TestsDiffOp): Serialized => {
if (u.op === TestDiffOpType.Add) {
return { op: u.op, item: InternalTestItem.serialize(u.item) };
} else if (u.op === TestDiffOpType.Update) {
return { op: u.op, item: ITestItemUpdate.serialize(u.item) };
} else {
return u;
}
};
}
/**
* Context for actions taken in the test explorer view.
@ -409,9 +647,9 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
const changes = this.createChangeCollector();
for (const op of diff) {
switch (op[0]) {
switch (op.op) {
case TestDiffOpType.Add: {
const internalTest = op[1];
const internalTest = InternalTestItem.deserialize(op.item);
if (!internalTest.parent) {
const created = this.createItem(internalTest);
this.roots.add(created);
@ -432,7 +670,7 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
}
case TestDiffOpType.Update: {
const patch = op[1];
const patch = ITestItemUpdate.deserialize(op.item);
const existing = this.items.get(patch.extId);
if (!existing) {
break;
@ -453,7 +691,7 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
}
case TestDiffOpType.Remove: {
const toRemove = this.items.get(op[1]);
const toRemove = this.items.get(op.itemId);
if (!toRemove) {
break;
}
@ -465,7 +703,7 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
this.roots.delete(toRemove);
}
const queue: Iterable<string>[] = [[op[1]]];
const queue: Iterable<string>[] = [[op.itemId]];
while (queue.length) {
for (const itemId of queue.pop()!) {
const existing = this.items.get(itemId);
@ -484,19 +722,19 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
}
case TestDiffOpType.Retire:
this.retireTest(op[1]);
this.retireTest(op.itemId);
break;
case TestDiffOpType.IncrementPendingExtHosts:
this.updatePendingRoots(op[1]);
this.updatePendingRoots(op.amount);
break;
case TestDiffOpType.AddTag:
this._tags.set(op[1].id, op[1]);
this._tags.set(op.tag.id, op.tag);
break;
case TestDiffOpType.RemoveTag:
this._tags.delete(op[1]);
this._tags.delete(op.id);
break;
}
}

View file

@ -554,12 +554,7 @@ export class LiveTestResult implements ITestResult {
tasks: this.tasks.map(t => ({ id: t.id, name: t.name, messages: t.otherMessages })),
name: this.name,
request: this.request,
items: [...this.testById.values()].map(entry => ({
...entry,
retired: undefined,
src: undefined,
children: [...entry.children.map(c => c.item.extId)],
})),
items: [...this.testById.values()].map(e => TestResultItem.serialize(e, [...e.children.map(c => c.item.extId)])),
}));
}

View file

@ -279,7 +279,7 @@ export class TestService extends Disposable implements ITestService {
const diff: TestsDiff = [];
for (const root of this.collection.rootItems) {
if (root.controllerId === id) {
diff.push([TestDiffOpType.Remove, root.item.extId]);
diff.push({ op: TestDiffOpType.Remove, itemId: root.item.extId });
}
}

View file

@ -122,8 +122,8 @@ export class TestingAutoRun extends Disposable implements ITestingAutoRun {
store.add(this.testService.onDidProcessDiff(diff => {
for (const entry of diff) {
if (entry[0] === TestDiffOpType.Add) {
const test = entry[1];
if (entry.op === TestDiffOpType.Add) {
const test = entry.item;
const isQueued = Iterable.some(
getCollectionItemParents(this.testService.collection, test),
t => rerunIds.has(test.item.extId),

View file

@ -53,13 +53,13 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => {
test('updates render if second test provider appears', async () => {
harness.flush();
harness.pushDiff([
TestDiffOpType.Add,
{ controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c', 'c', undefined)) },
], [
TestDiffOpType.Add,
{ controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c-a', 'ca', undefined)) },
]);
harness.pushDiff({
op: TestDiffOpType.Add,
item: { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c', 'c', undefined)) },
}, {
op: TestDiffOpType.Add,
item: { controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c-a', 'ca', undefined)) },
});
assert.deepStrictEqual(harness.flush(), [
{ e: 'c', children: [{ e: 'ca' }] },
@ -106,7 +106,17 @@ 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(new TestId(['ctrlId', 'id-a']).toString())!.actual),
item: {
extId: new TestId(['ctrlId', 'id-a']).toString(),
busy: false,
description: null,
error: null,
label: 'a',
range: null,
sortText: null,
tags: [],
uri: undefined,
},
parent: 'id-root',
tasks: [],
retired: false,

View file

@ -42,13 +42,13 @@ suite('Workbench - Testing Explorer Hierarchal by Name Projection', () => {
test('updates render if second test provider appears', async () => {
harness.flush();
harness.pushDiff([
TestDiffOpType.Add,
{ controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c', 'root2', undefined)) },
], [
TestDiffOpType.Add,
{ controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c-a', 'c', undefined)) },
]);
harness.pushDiff({
op: TestDiffOpType.Add,
item: { controllerId: 'ctrl2', parent: null, expand: TestItemExpandState.Expanded, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c', 'root2', undefined)) },
}, {
op: TestDiffOpType.Add,
item: { controllerId: 'ctrl2', parent: new TestId(['ctrl2', 'c']).toString(), expand: TestItemExpandState.NotExpandable, item: Convert.TestItem.from(new TestItemImpl('ctrl2', 'c-a', 'c', undefined)) },
});
assert.deepStrictEqual(harness.flush(), [
{ e: 'root', children: [{ e: 'aa' }, { e: 'ab' }, { e: 'b' }] },

View file

@ -11,8 +11,8 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe
import { NullLogService } from 'vs/platform/log/common/log';
import { SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
import { ITestTaskState, ResolvedTestRunRequest, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
import { TestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
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';
@ -224,7 +224,7 @@ suite('Workbench - Test Results Service', () => {
test('serializes and re-hydrates', async () => {
results.push(r);
r.updateState(new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), 't', TestResultState.Passed);
r.updateState(new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), 't', TestResultState.Passed, 42);
r.markComplete();
await timeout(10); // allow persistImmediately async to happen
@ -240,12 +240,9 @@ suite('Workbench - Test Results Service', () => {
const [rehydrated, actual] = results.getStateById(tests.root.id)!;
const expected: any = { ...r.getStateById(tests.root.id)! };
delete expected.tasks[0].duration; // delete undefined props that don't survive serialization
delete expected.item.range;
delete expected.item.description;
expected.item.uri = actual.item.uri;
assert.deepStrictEqual(actual, { ...expected, src: undefined, retired: true, children: [new TestId(['ctrlId', 'id-a']).toString()] });
expected.item.children = actual.item.children;
assert.deepStrictEqual(actual, { ...expected, retired: true, children: [new TestId(['ctrlId', 'id-a']).toString()] });
assert.deepStrictEqual(rehydrated.counts, r.counts);
assert.strictEqual(typeof rehydrated.completedAt, 'number');
});
@ -292,7 +289,6 @@ suite('Workbench - Test Results Service', () => {
tasks: [{ state, duration: 0, messages: [] }],
computedState: state,
ownComputedState: state,
retired: undefined,
children: [],
}]
}, () => Promise.resolve(bufferToStream(VSBuffer.alloc(0))));