Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/languageStatus/test/common/languageStatusDedupe.test.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { dispose, IDisposable } from '../../../../../base/common/lifecycle.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
9
10
/**
11
* Tests for the dedicated entry deduplication logic used in LanguageStatus._update.
12
*
13
* The pattern under test mirrors the dedicated-entry loop in languageStatus.ts:
14
* when building the new dedicated entries map, we must check both
15
* `newDedicatedEntries` (for duplicates within the current update) and
16
* `_dedicatedEntries` (for entries from the previous update) to avoid
17
* orphaning entry accessors and leaking their event listeners.
18
*/
19
20
interface MockAccessor extends IDisposable {
21
id: string;
22
updateCount: number;
23
disposed: boolean;
24
update(): void;
25
}
26
27
function createMockAccessor(id: string): MockAccessor {
28
return {
29
id,
30
updateCount: 0,
31
disposed: false,
32
update() { this.updateCount++; },
33
dispose() { this.disposed = true; }
34
};
35
}
36
37
suite('LanguageStatus - Dedicated Entry Deduplication', () => {
38
39
ensureNoDisposablesAreLeakedInTestSuite();
40
41
/**
42
* Simulates the dedicated-entry update loop from LanguageStatus._update,
43
* using the FIXED logic that checks newDedicatedEntries before creating.
44
*/
45
function runDedicatedEntryUpdate(
46
modelDedicatedIds: string[],
47
existingEntries: Map<string, MockAccessor>,
48
createEntry: (id: string) => MockAccessor
49
): Map<string, MockAccessor> {
50
const newDedicatedEntries = new Map<string, MockAccessor>();
51
for (const id of modelDedicatedIds) {
52
let entry = newDedicatedEntries.get(id) ?? existingEntries.get(id);
53
if (!entry) {
54
entry = createEntry(id);
55
} else {
56
entry.update();
57
existingEntries.delete(id);
58
}
59
newDedicatedEntries.set(id, entry);
60
}
61
dispose(existingEntries.values());
62
return newDedicatedEntries;
63
}
64
65
/**
66
* Simulates the OLD (buggy) dedicated-entry update loop that only checks
67
* existingEntries, not newDedicatedEntries.
68
*/
69
function runDedicatedEntryUpdateBuggy(
70
modelDedicatedIds: string[],
71
existingEntries: Map<string, MockAccessor>,
72
createEntry: (id: string) => MockAccessor
73
): Map<string, MockAccessor> {
74
const newDedicatedEntries = new Map<string, MockAccessor>();
75
for (const id of modelDedicatedIds) {
76
let entry = existingEntries.get(id);
77
if (!entry) {
78
entry = createEntry(id);
79
} else {
80
entry.update();
81
existingEntries.delete(id);
82
}
83
newDedicatedEntries.set(id, entry);
84
}
85
dispose(existingEntries.values());
86
return newDedicatedEntries;
87
}
88
89
test('reuses existing entry from previous update', () => {
90
const existing = new Map<string, MockAccessor>();
91
const oldEntry = createMockAccessor('status-A');
92
existing.set('status-A', oldEntry);
93
94
const result = runDedicatedEntryUpdate(['status-A'], existing, createMockAccessor);
95
96
assert.strictEqual(result.get('status-A'), oldEntry, 'should reuse the existing entry');
97
assert.strictEqual(oldEntry.updateCount, 1, 'should have updated the entry');
98
assert.strictEqual(oldEntry.disposed, false, 'should not dispose reused entry');
99
});
100
101
test('creates new entry when none exists', () => {
102
const existing = new Map<string, MockAccessor>();
103
104
const result = runDedicatedEntryUpdate(['status-A'], existing, createMockAccessor);
105
106
const entry = result.get('status-A')!;
107
assert.ok(entry, 'should create a new entry');
108
assert.strictEqual(entry.updateCount, 0, 'should not have called update on new entry');
109
assert.strictEqual(entry.disposed, false, 'new entry should not be disposed');
110
});
111
112
test('disposes entries no longer in model', () => {
113
const existing = new Map<string, MockAccessor>();
114
const oldEntry = createMockAccessor('status-A');
115
existing.set('status-A', oldEntry);
116
117
const result = runDedicatedEntryUpdate([], existing, createMockAccessor);
118
119
assert.strictEqual(result.size, 0, 'should have no entries');
120
assert.strictEqual(oldEntry.disposed, true, 'old entry should be disposed');
121
});
122
123
test('duplicate status IDs - fixed version reuses entry from current update', () => {
124
// This is the core regression test: when model.dedicated contains
125
// duplicate IDs (which can happen momentarily when a status is
126
// re-registered via $setLanguageStatus), the fixed code should
127
// reuse the entry created for the first occurrence instead of
128
// creating a second entry that orphans the first.
129
const existing = new Map<string, MockAccessor>();
130
const createdEntries: MockAccessor[] = [];
131
132
const result = runDedicatedEntryUpdate(
133
['status-A', 'status-A'], // duplicate IDs
134
existing,
135
(id) => { const e = createMockAccessor(id); createdEntries.push(e); return e; }
136
);
137
138
// Fixed: only one entry should be created, and it should be updated
139
// when the duplicate is encountered
140
assert.strictEqual(createdEntries.length, 1, 'should create only one entry');
141
assert.strictEqual(result.size, 1, 'result map should have one entry');
142
assert.strictEqual(createdEntries[0].updateCount, 1, 'entry should be updated once for the duplicate');
143
assert.strictEqual(createdEntries[0].disposed, false, 'the entry should not be disposed');
144
});
145
146
test('duplicate status IDs - buggy version leaks entry', () => {
147
// Demonstrates that the old (buggy) code creates two entries
148
// for duplicate IDs, orphaning the first one.
149
const existing = new Map<string, MockAccessor>();
150
const createdEntries: MockAccessor[] = [];
151
152
const result = runDedicatedEntryUpdateBuggy(
153
['status-A', 'status-A'], // duplicate IDs
154
existing,
155
(id) => { const e = createMockAccessor(id); createdEntries.push(e); return e; }
156
);
157
158
// Buggy: two entries are created, the first is orphaned (overwritten in map)
159
assert.strictEqual(createdEntries.length, 2, 'buggy version creates two entries');
160
assert.strictEqual(result.size, 1, 'result map has one entry (second overwrites first)');
161
// The first entry is orphaned - it's not in the result map and not disposed
162
assert.strictEqual(createdEntries[0].disposed, false, 'first entry is NOT disposed (leaked!)');
163
assert.notStrictEqual(result.get('status-A'), createdEntries[0], 'first entry is not in the result');
164
assert.strictEqual(result.get('status-A'), createdEntries[1], 'second entry is in the result');
165
});
166
167
test('duplicate IDs with existing entry - fixed version reuses existing', () => {
168
// When an existing entry exists and duplicates appear,
169
// the fixed code should reuse the existing entry for the first
170
// occurrence and then reuse it again for the duplicate.
171
const existing = new Map<string, MockAccessor>();
172
const oldEntry = createMockAccessor('status-A');
173
existing.set('status-A', oldEntry);
174
const createdEntries: MockAccessor[] = [];
175
176
const result = runDedicatedEntryUpdate(
177
['status-A', 'status-A'], // duplicate IDs
178
existing,
179
(id) => { const e = createMockAccessor(id); createdEntries.push(e); return e; }
180
);
181
182
assert.strictEqual(createdEntries.length, 0, 'should not create any new entries');
183
assert.strictEqual(result.size, 1, 'result map should have one entry');
184
assert.strictEqual(result.get('status-A'), oldEntry, 'should reuse the existing entry');
185
assert.strictEqual(oldEntry.updateCount, 2, 'should be updated twice (once per duplicate)');
186
assert.strictEqual(oldEntry.disposed, false, 'should not be disposed');
187
});
188
189
test('mixed unique and duplicate IDs', () => {
190
const existing = new Map<string, MockAccessor>();
191
const existingB = createMockAccessor('status-B');
192
existing.set('status-B', existingB);
193
const createdEntries: MockAccessor[] = [];
194
195
const result = runDedicatedEntryUpdate(
196
['status-A', 'status-B', 'status-A'], // A appears twice, B once
197
existing,
198
(id) => { const e = createMockAccessor(id); createdEntries.push(e); return e; }
199
);
200
201
assert.strictEqual(createdEntries.length, 1, 'should create one new entry (for first status-A)');
202
assert.strictEqual(result.size, 2, 'result map should have two entries');
203
assert.strictEqual(result.get('status-A'), createdEntries[0], 'status-A should use created entry');
204
assert.strictEqual(createdEntries[0].updateCount, 1, 'status-A entry updated once for duplicate');
205
assert.strictEqual(result.get('status-B'), existingB, 'status-B should reuse existing entry');
206
assert.strictEqual(existingB.updateCount, 1, 'status-B entry updated once');
207
});
208
});
209
210