Skip to content

Commit c11dabf

Browse files
authored
testing: improve decoration syncing (microsoft#159705)
So, two problems. One, we didn't actually fully re-sync testing decorations when explicitly updated by the extension, since we attempted to get the test URI from the _diff_ which would never actually be present on single updates (since the test item URI cannot be changed). I think this was the main problem people saw. So, this fixes that. It also applies a change so that we only sync the ranges to what the extension gives us if the document version is up to date with what was in the extension host. This should avoid syncing decorations to the wrong place--instead just use VS Code's own decoration location tracking until we get a newer update from the extension. Fixes microsoft#158475 Fixes microsoft#153304
1 parent aaa72d5 commit c11dabf

File tree

8 files changed

+51
-26
lines changed

8 files changed

+51
-26
lines changed

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
183183
const extHostWebviewPanels = rpcProtocol.set(ExtHostContext.ExtHostWebviewPanels, new ExtHostWebviewPanels(rpcProtocol, extHostWebviews, extHostWorkspace));
184184
const extHostCustomEditors = rpcProtocol.set(ExtHostContext.ExtHostCustomEditors, new ExtHostCustomEditors(rpcProtocol, extHostDocuments, extensionStoragePaths, extHostWebviews, extHostWebviewPanels));
185185
const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews));
186-
const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostCommands));
186+
const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostCommands, extHostDocumentsAndEditors));
187187
const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol));
188188
rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService));
189189

src/vs/workbench/api/common/extHostTestItem.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { denamespaceTestTag, ITestItem, ITestItemContext } from 'vs/workbench/co
1010
import type * as vscode from 'vscode';
1111
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
1212
import { URI } from 'vs/base/common/uri';
13+
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
1314

1415
const testItemPropAccessor = <K extends keyof vscode.TestItem>(
1516
api: IExtHostTestItemApi,
@@ -163,9 +164,10 @@ export class TestItemRootImpl extends TestItemImpl {
163164
}
164165

165166
export class ExtHostTestItemCollection extends TestItemCollection<TestItemImpl> {
166-
constructor(controllerId: string, controllerLabel: string) {
167+
constructor(controllerId: string, controllerLabel: string, editors: ExtHostDocumentsAndEditors) {
167168
super({
168169
controllerId,
170+
getDocumentVersion: (uri: URI) => editors.getDocument(uri)?.version,
169171
getApiFor: getPrivateApiFor as (impl: TestItemImpl) => ITestItemApi<TestItemImpl>,
170172
getChildren: (item) => item.children as ITestChildrenLike<TestItemImpl>,
171173
root: new TestItemRootImpl(controllerId, controllerLabel),

src/vs/workbench/api/common/extHostTesting.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { isDefined } from 'vs/base/common/types';
1616
import { generateUuid } from 'vs/base/common/uuid';
1717
import { ExtHostTestingShape, ILocationDto, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol';
1818
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
19+
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
1920
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
2021
import { ExtHostTestItemCollection, TestItemImpl, TestItemRootImpl, toItemFromContext } from 'vs/workbench/api/common/extHostTestItem';
2122
import * as Convert from 'vs/workbench/api/common/extHostTypeConverters';
@@ -41,7 +42,11 @@ export class ExtHostTesting implements ExtHostTestingShape {
4142
public onResultsChanged = this.resultsChangedEmitter.event;
4243
public results: ReadonlyArray<vscode.TestRunResult> = [];
4344

44-
constructor(@IExtHostRpcService rpc: IExtHostRpcService, commands: ExtHostCommands) {
45+
constructor(
46+
@IExtHostRpcService rpc: IExtHostRpcService,
47+
commands: ExtHostCommands,
48+
private readonly editors: ExtHostDocumentsAndEditors,
49+
) {
4550
this.proxy = rpc.getProxy(MainContext.MainThreadTesting);
4651
this.observer = new TestObservers(this.proxy);
4752
this.runTracker = new TestRunCoordinator(this.proxy);
@@ -61,7 +66,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
6166
}
6267

6368
const disposable = new DisposableStore();
64-
const collection = disposable.add(new ExtHostTestItemCollection(controllerId, label));
69+
const collection = disposable.add(new ExtHostTestItemCollection(controllerId, label, this.editors));
6570
collection.root.label = label;
6671

6772
const profiles = new Map<number, vscode.TestRunProfile>();

src/vs/workbench/api/test/browser/extHostTesting.test.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Location, Position, Range, TestMessage, TestResultState, TestRunProfile
1717
import { TestDiffOpType, TestItemExpandState, TestMessageType, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes';
1818
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
1919
import type { TestItem, TestRunRequest } from 'vscode';
20+
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
2021

2122
const simplify = (item: TestItem) => ({
2223
id: item.id,
@@ -69,7 +70,9 @@ suite('ExtHost Testing', () => {
6970

7071
let single: TestExtHostTestItemCollection;
7172
setup(() => {
72-
single = new TestExtHostTestItemCollection('ctrlId', 'root');
73+
single = new TestExtHostTestItemCollection('ctrlId', 'root', {
74+
getDocument: () => undefined,
75+
} as Partial<ExtHostDocumentsAndEditors> as ExtHostDocumentsAndEditors);
7376
single.resolveHandler = item => {
7477
if (item === undefined) {
7578
const a = new TestItemImpl('ctrlId', 'id-a', 'a', URI.file('/'));
@@ -150,7 +153,7 @@ suite('ExtHost Testing', () => {
150153
assert.deepStrictEqual(single.collectDiff(), [
151154
{
152155
op: TestDiffOpType.Update,
153-
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { description: 'Hello world' } },
156+
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), docv: undefined, item: { description: 'Hello world' } },
154157
}
155158
]);
156159
});
@@ -251,15 +254,15 @@ suite('ExtHost Testing', () => {
251254
assert.deepStrictEqual(single.collectDiff(), [
252255
{
253256
op: TestDiffOpType.Update,
254-
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded, item: { label: 'Hello world' } },
257+
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded, docv: undefined, item: { label: 'Hello world' } },
255258
},
256259
]);
257260

258261
newA.label = 'still connected';
259262
assert.deepStrictEqual(single.collectDiff(), [
260263
{
261264
op: TestDiffOpType.Update,
262-
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), item: { label: 'still connected' } }
265+
item: { extId: new TestId(['ctrlId', 'id-a']).toString(), docv: undefined, item: { label: 'still connected' } }
263266
},
264267
]);
265268

@@ -286,7 +289,7 @@ suite('ExtHost Testing', () => {
286289
},
287290
{
288291
op: TestDiffOpType.Update,
289-
item: { extId: TestId.fromExtHostTestItem(oldAB, 'ctrlId').toString(), item: { label: 'Hello world' } },
292+
item: { extId: TestId.fromExtHostTestItem(oldAB, 'ctrlId').toString(), docv: undefined, item: { label: 'Hello world' } },
290293
},
291294
]);
292295

@@ -296,11 +299,11 @@ suite('ExtHost Testing', () => {
296299
assert.deepStrictEqual(single.collectDiff(), [
297300
{
298301
op: TestDiffOpType.Update,
299-
item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), item: { label: 'still connected1' } }
302+
item: { extId: new TestId(['ctrlId', 'id-a', 'id-aa']).toString(), docv: undefined, item: { label: 'still connected1' } }
300303
},
301304
{
302305
op: TestDiffOpType.Update,
303-
item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), item: { label: 'still connected2' } }
306+
item: { extId: new TestId(['ctrlId', 'id-a', 'id-ab']).toString(), docv: undefined, item: { label: 'still connected2' } }
304307
},
305308
]);
306309

@@ -330,7 +333,7 @@ suite('ExtHost Testing', () => {
330333
assert.deepStrictEqual(single.collectDiff(), [
331334
{
332335
op: TestDiffOpType.Update,
333-
item: { extId: new TestId(['ctrlId', 'id-a', 'id-b']).toString(), item: { label: 'still connected' } }
336+
item: { extId: new TestId(['ctrlId', 'id-a', 'id-b']).toString(), docv: undefined, item: { label: 'still connected' } }
334337
},
335338
]);
336339

src/vs/workbench/contrib/testing/browser/testingDecorations.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ export class TestingDecorationService extends Disposable implements ITestingDeco
7575
private generation = 0;
7676
private readonly changeEmitter = new Emitter<void>();
7777
private readonly decorationCache = new ResourceMap<{
78-
/** Whether tests in the resource have been updated, requiring rerendering */
79-
testRangesUpdated: boolean;
78+
/** The document version at which ranges have been updated, requiring rerendering */
79+
rangeUpdateVersionId?: number;
8080
/** Counter for the results rendered in the document */
8181
generation: number;
8282
value: TestDecorations<ITestDecoration>;
@@ -115,16 +115,14 @@ export class TestingDecorationService extends Disposable implements ITestingDeco
115115
// is up to date. This prevents issues, as in #138632, #138835, #138922.
116116
this._register(this.testService.onWillProcessDiff(diff => {
117117
for (const entry of diff) {
118-
let uri: URI | undefined | null;
119-
if (entry.op === TestDiffOpType.Add || entry.op === TestDiffOpType.Update) {
120-
uri = entry.item.item?.uri;
121-
} else if (entry.op === TestDiffOpType.Remove) {
122-
uri = this.testService.collection.getNodeById(entry.itemId)?.item.uri;
118+
if (entry.op !== TestDiffOpType.Update || entry.item.docv === undefined || entry.item.item?.range === undefined) {
119+
continue;
123120
}
124121

122+
const uri = this.testService.collection.getNodeById(entry.item.extId)?.item.uri;
125123
const rec = uri && this.decorationCache.get(uri);
126124
if (rec) {
127-
rec.testRangesUpdated = true;
125+
rec.rangeUpdateVersionId = entry.item.docv;
128126
}
129127
}
130128

@@ -160,7 +158,7 @@ export class TestingDecorationService extends Disposable implements ITestingDeco
160158
}
161159

162160
const cached = this.decorationCache.get(resource);
163-
if (cached && cached.generation === this.generation && !cached.testRangesUpdated) {
161+
if (cached && cached.generation === this.generation && (cached.rangeUpdateVersionId === undefined || cached.rangeUpdateVersionId !== model.getVersionId())) {
164162
return cached.value;
165163
}
166164

@@ -194,7 +192,7 @@ export class TestingDecorationService extends Disposable implements ITestingDeco
194192
const gutterEnabled = getTestingConfiguration(this.configurationService, TestingConfigKeys.GutterEnabled);
195193
const uriStr = model.uri.toString();
196194
const cached = this.decorationCache.get(model.uri);
197-
const testRangesUpdated = cached?.testRangesUpdated;
195+
const testRangesUpdated = cached?.rangeUpdateVersionId === model.getVersionId();
198196
const lastDecorations = cached?.value ?? new TestDecorations();
199197
const newDecorations = new TestDecorations<ITestDecoration>();
200198

@@ -298,7 +296,7 @@ export class TestingDecorationService extends Disposable implements ITestingDeco
298296

299297
this.decorationCache.set(model.uri, {
300298
generation: this.generation,
301-
testRangesUpdated: false,
299+
rangeUpdateVersionId: cached?.rangeUpdateVersionId,
302300
value: newDecorations,
303301
});
304302
});

src/vs/workbench/contrib/testing/common/testItemCollection.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
99
import { assertNever } from 'vs/base/common/assert';
1010
import { applyTestItemUpdate, ITestItem, ITestTag, namespaceTestTag, TestDiffOpType, TestItemExpandState, TestsDiff, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes';
1111
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
12+
import { URI } from 'vs/base/common/uri';
1213

1314
/**
1415
* @private
@@ -82,6 +83,9 @@ export interface ITestItemCollectionOptions<T> {
8283
/** Controller ID to use to prefix these test items. */
8384
controllerId: string;
8485

86+
/** Gets the document version at the given URI, if it's open */
87+
getDocumentVersion(uri: URI | undefined): number | undefined;
88+
8589
/** Gets API for the given test item, used to listen for events and set parents. */
8690
getApiFor(item: T): ITestItemApi<T>;
8791

@@ -142,6 +146,7 @@ export interface ITestChildrenLike<T> extends Iterable<[string, T]> {
142146
export interface ITestItemLike {
143147
id: string;
144148
tags: readonly ITestTag[];
149+
uri?: URI;
145150
canResolveChildren: boolean;
146151
}
147152

@@ -283,7 +288,11 @@ export class TestItemCollection<T extends ITestItemLike> extends Disposable {
283288
case TestItemEventOp.SetProp:
284289
this.pushDiff({
285290
op: TestDiffOpType.Update,
286-
item: { extId: internal.fullId.toString(), item: evt.update }
291+
item: {
292+
extId: internal.fullId.toString(),
293+
item: evt.update,
294+
docv: this.options.getDocumentVersion(internal.actual.uri),
295+
}
287296
});
288297
break;
289298
default:

src/vs/workbench/contrib/testing/common/testTypes.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,13 +369,20 @@ export interface ITestItemUpdate {
369369
extId: string;
370370
expand?: TestItemExpandState;
371371
item?: Partial<ITestItem>;
372+
373+
/**
374+
* The document version at the time the operation was made, if the test has
375+
* a URI and the document was open in the extension host.
376+
*/
377+
docv?: number;
372378
}
373379

374380
export namespace ITestItemUpdate {
375381
export interface Serialized {
376382
extId: string;
377383
expand?: TestItemExpandState;
378384
item?: Partial<ITestItem.Serialized>;
385+
docv?: number;
379386
}
380387

381388
export const serialize = (u: ITestItemUpdate): Serialized => {
@@ -392,7 +399,7 @@ export namespace ITestItemUpdate {
392399
if (u.item.sortText !== undefined) { item.sortText = u.item.sortText; }
393400
}
394401

395-
return { extId: u.extId, expand: u.expand, item };
402+
return { extId: u.extId, expand: u.expand, item, docv: u.docv };
396403
};
397404

398405
export const deserialize = (u: Serialized): ITestItemUpdate => {
@@ -408,7 +415,7 @@ export namespace ITestItemUpdate {
408415
if (u.item.sortText !== undefined) { item.sortText = u.item.sortText; }
409416
}
410417

411-
return { extId: u.extId, expand: u.expand, item };
418+
return { extId: u.extId, expand: u.expand, item, docv: u.docv };
412419
};
413420

414421
}

src/vs/workbench/contrib/testing/test/common/testStubs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export class TestTestCollection extends TestItemCollection<TestTestItem> {
8181
getApiFor: t => t.api,
8282
toITestItem: t => t.toTestItem(),
8383
getChildren: t => t.children,
84+
getDocumentVersion: () => undefined,
8485
root: new TestTestItem(controllerId, controllerId, 'root'),
8586
});
8687
}

0 commit comments

Comments
 (0)