Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/files/test/common/watcher.test.ts
3296 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 { Emitter, Event } from '../../../../base/common/event.js';
8
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
9
import { isLinux, isWindows } from '../../../../base/common/platform.js';
10
import { isEqual } from '../../../../base/common/resources.js';
11
import { URI } from '../../../../base/common/uri.js';
12
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
13
import { FileChangeFilter, FileChangesEvent, FileChangeType, IFileChange } from '../../common/files.js';
14
import { coalesceEvents, reviveFileChanges, parseWatcherPatterns, isFiltered } from '../../common/watcher.js';
15
16
class TestFileWatcher extends Disposable {
17
private readonly _onDidFilesChange: Emitter<{ raw: IFileChange[]; event: FileChangesEvent }>;
18
19
constructor() {
20
super();
21
22
this._onDidFilesChange = this._register(new Emitter<{ raw: IFileChange[]; event: FileChangesEvent }>());
23
}
24
25
get onDidFilesChange(): Event<{ raw: IFileChange[]; event: FileChangesEvent }> {
26
return this._onDidFilesChange.event;
27
}
28
29
report(changes: IFileChange[]): void {
30
this.onRawFileEvents(changes);
31
}
32
33
private onRawFileEvents(events: IFileChange[]): void {
34
35
// Coalesce
36
const coalescedEvents = coalesceEvents(events);
37
38
// Emit through event emitter
39
if (coalescedEvents.length > 0) {
40
this._onDidFilesChange.fire({ raw: reviveFileChanges(coalescedEvents), event: this.toFileChangesEvent(coalescedEvents) });
41
}
42
}
43
44
private toFileChangesEvent(changes: IFileChange[]): FileChangesEvent {
45
return new FileChangesEvent(reviveFileChanges(changes), !isLinux);
46
}
47
}
48
49
enum Path {
50
UNIX,
51
WINDOWS,
52
UNC
53
}
54
55
suite('Watcher', () => {
56
57
(isWindows ? test.skip : test)('parseWatcherPatterns - posix', () => {
58
const path = '/users/data/src';
59
let parsedPattern = parseWatcherPatterns(path, ['*.js'])[0];
60
61
assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);
62
assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);
63
assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), false);
64
65
parsedPattern = parseWatcherPatterns(path, ['/users/data/src/*.js'])[0];
66
67
assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);
68
assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);
69
assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), false);
70
71
parsedPattern = parseWatcherPatterns(path, ['/users/data/src/bar/*.js'])[0];
72
73
assert.strictEqual(parsedPattern('/users/data/src/foo.js'), false);
74
assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);
75
assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), true);
76
77
parsedPattern = parseWatcherPatterns(path, ['**/*.js'])[0];
78
79
assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);
80
assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);
81
assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), true);
82
});
83
84
(!isWindows ? test.skip : test)('parseWatcherPatterns - windows', () => {
85
const path = 'c:\\users\\data\\src';
86
let parsedPattern = parseWatcherPatterns(path, ['*.js'])[0];
87
88
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);
89
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);
90
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar/foo.js'), false);
91
92
parsedPattern = parseWatcherPatterns(path, ['c:\\users\\data\\src\\*.js'])[0];
93
94
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);
95
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);
96
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\foo.js'), false);
97
98
parsedPattern = parseWatcherPatterns(path, ['c:\\users\\data\\src\\bar/*.js'])[0];
99
100
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), false);
101
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);
102
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\foo.js'), true);
103
104
parsedPattern = parseWatcherPatterns(path, ['**/*.js'])[0];
105
106
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);
107
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);
108
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\foo.js'), true);
109
});
110
111
ensureNoDisposablesAreLeakedInTestSuite();
112
});
113
114
suite('Watcher Events Normalizer', () => {
115
116
const disposables = new DisposableStore();
117
118
teardown(() => {
119
disposables.clear();
120
});
121
122
test('simple add/update/delete', done => {
123
const watch = disposables.add(new TestFileWatcher());
124
125
const added = URI.file('/users/data/src/added.txt');
126
const updated = URI.file('/users/data/src/updated.txt');
127
const deleted = URI.file('/users/data/src/deleted.txt');
128
129
const raw: IFileChange[] = [
130
{ resource: added, type: FileChangeType.ADDED },
131
{ resource: updated, type: FileChangeType.UPDATED },
132
{ resource: deleted, type: FileChangeType.DELETED },
133
];
134
135
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
136
assert.ok(event);
137
assert.strictEqual(raw.length, 3);
138
assert.ok(event.contains(added, FileChangeType.ADDED));
139
assert.ok(event.contains(updated, FileChangeType.UPDATED));
140
assert.ok(event.contains(deleted, FileChangeType.DELETED));
141
142
done();
143
}));
144
145
watch.report(raw);
146
});
147
148
(isWindows ? [Path.WINDOWS, Path.UNC] : [Path.UNIX]).forEach(path => {
149
test(`delete only reported for top level folder (${path})`, done => {
150
const watch = disposables.add(new TestFileWatcher());
151
152
const deletedFolderA = URI.file(path === Path.UNIX ? '/users/data/src/todelete1' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete1' : '\\\\localhost\\users\\data\\src\\todelete1');
153
const deletedFolderB = URI.file(path === Path.UNIX ? '/users/data/src/todelete2' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete2' : '\\\\localhost\\users\\data\\src\\todelete2');
154
const deletedFolderBF1 = URI.file(path === Path.UNIX ? '/users/data/src/todelete2/file.txt' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete2\\file.txt' : '\\\\localhost\\users\\data\\src\\todelete2\\file.txt');
155
const deletedFolderBF2 = URI.file(path === Path.UNIX ? '/users/data/src/todelete2/more/test.txt' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete2\\more\\test.txt' : '\\\\localhost\\users\\data\\src\\todelete2\\more\\test.txt');
156
const deletedFolderBF3 = URI.file(path === Path.UNIX ? '/users/data/src/todelete2/super/bar/foo.txt' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete2\\super\\bar\\foo.txt' : '\\\\localhost\\users\\data\\src\\todelete2\\super\\bar\\foo.txt');
157
const deletedFileA = URI.file(path === Path.UNIX ? '/users/data/src/deleteme.txt' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\deleteme.txt' : '\\\\localhost\\users\\data\\src\\deleteme.txt');
158
159
const addedFile = URI.file(path === Path.UNIX ? '/users/data/src/added.txt' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\added.txt' : '\\\\localhost\\users\\data\\src\\added.txt');
160
const updatedFile = URI.file(path === Path.UNIX ? '/users/data/src/updated.txt' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\updated.txt' : '\\\\localhost\\users\\data\\src\\updated.txt');
161
162
const raw: IFileChange[] = [
163
{ resource: deletedFolderA, type: FileChangeType.DELETED },
164
{ resource: deletedFolderB, type: FileChangeType.DELETED },
165
{ resource: deletedFolderBF1, type: FileChangeType.DELETED },
166
{ resource: deletedFolderBF2, type: FileChangeType.DELETED },
167
{ resource: deletedFolderBF3, type: FileChangeType.DELETED },
168
{ resource: deletedFileA, type: FileChangeType.DELETED },
169
{ resource: addedFile, type: FileChangeType.ADDED },
170
{ resource: updatedFile, type: FileChangeType.UPDATED }
171
];
172
173
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
174
assert.ok(event);
175
assert.strictEqual(raw.length, 5);
176
177
assert.ok(event.contains(deletedFolderA, FileChangeType.DELETED));
178
assert.ok(event.contains(deletedFolderB, FileChangeType.DELETED));
179
assert.ok(event.contains(deletedFileA, FileChangeType.DELETED));
180
assert.ok(event.contains(addedFile, FileChangeType.ADDED));
181
assert.ok(event.contains(updatedFile, FileChangeType.UPDATED));
182
183
done();
184
}));
185
186
watch.report(raw);
187
});
188
});
189
190
test('event coalescer: ignore CREATE followed by DELETE', done => {
191
const watch = disposables.add(new TestFileWatcher());
192
193
const created = URI.file('/users/data/src/related');
194
const deleted = URI.file('/users/data/src/related');
195
const unrelated = URI.file('/users/data/src/unrelated');
196
197
const raw: IFileChange[] = [
198
{ resource: created, type: FileChangeType.ADDED },
199
{ resource: deleted, type: FileChangeType.DELETED },
200
{ resource: unrelated, type: FileChangeType.UPDATED },
201
];
202
203
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
204
assert.ok(event);
205
assert.strictEqual(raw.length, 1);
206
207
assert.ok(event.contains(unrelated, FileChangeType.UPDATED));
208
209
done();
210
}));
211
212
watch.report(raw);
213
});
214
215
test('event coalescer: flatten DELETE followed by CREATE into CHANGE', done => {
216
const watch = disposables.add(new TestFileWatcher());
217
218
const deleted = URI.file('/users/data/src/related');
219
const created = URI.file('/users/data/src/related');
220
const unrelated = URI.file('/users/data/src/unrelated');
221
222
const raw: IFileChange[] = [
223
{ resource: deleted, type: FileChangeType.DELETED },
224
{ resource: created, type: FileChangeType.ADDED },
225
{ resource: unrelated, type: FileChangeType.UPDATED },
226
];
227
228
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
229
assert.ok(event);
230
assert.strictEqual(raw.length, 2);
231
232
assert.ok(event.contains(deleted, FileChangeType.UPDATED));
233
assert.ok(event.contains(unrelated, FileChangeType.UPDATED));
234
235
done();
236
}));
237
238
watch.report(raw);
239
});
240
241
test('event coalescer: ignore UPDATE when CREATE received', done => {
242
const watch = disposables.add(new TestFileWatcher());
243
244
const created = URI.file('/users/data/src/related');
245
const updated = URI.file('/users/data/src/related');
246
const unrelated = URI.file('/users/data/src/unrelated');
247
248
const raw: IFileChange[] = [
249
{ resource: created, type: FileChangeType.ADDED },
250
{ resource: updated, type: FileChangeType.UPDATED },
251
{ resource: unrelated, type: FileChangeType.UPDATED },
252
];
253
254
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
255
assert.ok(event);
256
assert.strictEqual(raw.length, 2);
257
258
assert.ok(event.contains(created, FileChangeType.ADDED));
259
assert.ok(!event.contains(created, FileChangeType.UPDATED));
260
assert.ok(event.contains(unrelated, FileChangeType.UPDATED));
261
262
done();
263
}));
264
265
watch.report(raw);
266
});
267
268
test('event coalescer: apply DELETE', done => {
269
const watch = disposables.add(new TestFileWatcher());
270
271
const updated = URI.file('/users/data/src/related');
272
const updated2 = URI.file('/users/data/src/related');
273
const deleted = URI.file('/users/data/src/related');
274
const unrelated = URI.file('/users/data/src/unrelated');
275
276
const raw: IFileChange[] = [
277
{ resource: updated, type: FileChangeType.UPDATED },
278
{ resource: updated2, type: FileChangeType.UPDATED },
279
{ resource: unrelated, type: FileChangeType.UPDATED },
280
{ resource: updated, type: FileChangeType.DELETED }
281
];
282
283
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
284
assert.ok(event);
285
assert.strictEqual(raw.length, 2);
286
287
assert.ok(event.contains(deleted, FileChangeType.DELETED));
288
assert.ok(!event.contains(updated, FileChangeType.UPDATED));
289
assert.ok(event.contains(unrelated, FileChangeType.UPDATED));
290
291
done();
292
}));
293
294
watch.report(raw);
295
});
296
297
test('event coalescer: track case renames', done => {
298
const watch = disposables.add(new TestFileWatcher());
299
300
const oldPath = URI.file('/users/data/src/added');
301
const newPath = URI.file('/users/data/src/ADDED');
302
303
const raw: IFileChange[] = [
304
{ resource: newPath, type: FileChangeType.ADDED },
305
{ resource: oldPath, type: FileChangeType.DELETED }
306
];
307
308
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
309
assert.ok(event);
310
assert.strictEqual(raw.length, 2);
311
312
for (const r of raw) {
313
if (isEqual(r.resource, oldPath)) {
314
assert.strictEqual(r.type, FileChangeType.DELETED);
315
} else if (isEqual(r.resource, newPath)) {
316
assert.strictEqual(r.type, FileChangeType.ADDED);
317
} else {
318
assert.fail();
319
}
320
}
321
322
done();
323
}));
324
325
watch.report(raw);
326
});
327
328
test('event type filter', () => {
329
const resource = URI.file('/users/data/src/related');
330
331
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, undefined), false);
332
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, undefined), false);
333
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, undefined), false);
334
335
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED), true);
336
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED | FileChangeFilter.DELETED), true);
337
338
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED), false);
339
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED), false);
340
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED | FileChangeFilter.DELETED), false);
341
342
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED), true);
343
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED | FileChangeFilter.ADDED), true);
344
345
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED), false);
346
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
347
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
348
349
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED), true);
350
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.ADDED), true);
351
352
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.UPDATED), false);
353
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
354
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
355
});
356
357
ensureNoDisposablesAreLeakedInTestSuite();
358
});
359
360