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
5247 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'], false)[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'], false)[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'], false)[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'], false)[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'], true)[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'], true)[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'], true)[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'], true)[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
(isWindows ? test.skip : test)('parseWatcherPatterns - posix (case insensitive)', () => {
112
const path = '/users/data/src';
113
let parsedPattern = parseWatcherPatterns(path, ['*.JS'], false)[0];
114
115
// Case sensitive by default on posix
116
assert.strictEqual(parsedPattern('/users/data/src/foo.js'), false);
117
assert.strictEqual(parsedPattern('/users/data/src/foo.JS'), true);
118
assert.strictEqual(parsedPattern('/users/data/src/foo.Js'), false);
119
120
// Now test with GlobCaseSensitivity.caseInsensitive
121
parsedPattern = parseWatcherPatterns(path, ['*.JS'], true)[0];
122
123
assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);
124
assert.strictEqual(parsedPattern('/users/data/src/foo.JS'), true);
125
assert.strictEqual(parsedPattern('/users/data/src/foo.Js'), true);
126
assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);
127
128
parsedPattern = parseWatcherPatterns(path, ['/users/data/src/*.JS'], true)[0];
129
130
assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);
131
assert.strictEqual(parsedPattern('/users/data/src/foo.JS'), true);
132
assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);
133
assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), false);
134
135
parsedPattern = parseWatcherPatterns(path, ['**/Test*.JS'], true)[0];
136
137
assert.strictEqual(parsedPattern('/users/data/src/test1.js'), true);
138
assert.strictEqual(parsedPattern('/users/data/src/Test1.js'), true);
139
assert.strictEqual(parsedPattern('/users/data/src/TEST1.JS'), true);
140
assert.strictEqual(parsedPattern('/users/data/src/bar/test2.js'), true);
141
assert.strictEqual(parsedPattern('/users/data/src/bar/TEST2.JS'), true);
142
assert.strictEqual(parsedPattern('/users/data/src/foo.js'), false);
143
});
144
145
(!isWindows ? test.skip : test)('parseWatcherPatterns - windows (case insensitive)', () => {
146
const path = 'c:\\users\\data\\src';
147
let parsedPattern = parseWatcherPatterns(path, ['*.JS'], true)[0];
148
149
// Windows is case insensitive by default
150
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);
151
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.JS'), true);
152
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.Js'), true);
153
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);
154
155
// Explicit GlobCaseSensitivity.caseInsensitive should work the same
156
parsedPattern = parseWatcherPatterns(path, ['*.JS'], true)[0];
157
158
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);
159
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.JS'), true);
160
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.Js'), true);
161
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);
162
163
parsedPattern = parseWatcherPatterns(path, ['c:\\users\\data\\src\\*.JS'], true)[0];
164
165
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);
166
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.JS'), true);
167
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);
168
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\foo.js'), false);
169
170
parsedPattern = parseWatcherPatterns(path, ['**/Test*.JS'], true)[0];
171
172
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\test1.js'), true);
173
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\Test1.js'), true);
174
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\TEST1.JS'), true);
175
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\test2.js'), true);
176
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\TEST2.JS'), true);
177
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), false);
178
179
// Test with case sensitive mode explicitly
180
parsedPattern = parseWatcherPatterns(path, ['*.JS'], false)[0];
181
182
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), false);
183
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.JS'), true);
184
assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.Js'), false);
185
});
186
187
ensureNoDisposablesAreLeakedInTestSuite();
188
});
189
190
suite('Watcher Events Normalizer', () => {
191
192
const disposables = new DisposableStore();
193
194
teardown(() => {
195
disposables.clear();
196
});
197
198
test('simple add/update/delete', done => {
199
const watch = disposables.add(new TestFileWatcher());
200
201
const added = URI.file('/users/data/src/added.txt');
202
const updated = URI.file('/users/data/src/updated.txt');
203
const deleted = URI.file('/users/data/src/deleted.txt');
204
205
const raw: IFileChange[] = [
206
{ resource: added, type: FileChangeType.ADDED },
207
{ resource: updated, type: FileChangeType.UPDATED },
208
{ resource: deleted, type: FileChangeType.DELETED },
209
];
210
211
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
212
assert.ok(event);
213
assert.strictEqual(raw.length, 3);
214
assert.ok(event.contains(added, FileChangeType.ADDED));
215
assert.ok(event.contains(updated, FileChangeType.UPDATED));
216
assert.ok(event.contains(deleted, FileChangeType.DELETED));
217
218
done();
219
}));
220
221
watch.report(raw);
222
});
223
224
(isWindows ? [Path.WINDOWS, Path.UNC] : [Path.UNIX]).forEach(path => {
225
test(`delete only reported for top level folder (${path})`, done => {
226
const watch = disposables.add(new TestFileWatcher());
227
228
const deletedFolderA = URI.file(path === Path.UNIX ? '/users/data/src/todelete1' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete1' : '\\\\localhost\\users\\data\\src\\todelete1');
229
const deletedFolderB = URI.file(path === Path.UNIX ? '/users/data/src/todelete2' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete2' : '\\\\localhost\\users\\data\\src\\todelete2');
230
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');
231
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');
232
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');
233
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');
234
235
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');
236
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');
237
238
const raw: IFileChange[] = [
239
{ resource: deletedFolderA, type: FileChangeType.DELETED },
240
{ resource: deletedFolderB, type: FileChangeType.DELETED },
241
{ resource: deletedFolderBF1, type: FileChangeType.DELETED },
242
{ resource: deletedFolderBF2, type: FileChangeType.DELETED },
243
{ resource: deletedFolderBF3, type: FileChangeType.DELETED },
244
{ resource: deletedFileA, type: FileChangeType.DELETED },
245
{ resource: addedFile, type: FileChangeType.ADDED },
246
{ resource: updatedFile, type: FileChangeType.UPDATED }
247
];
248
249
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
250
assert.ok(event);
251
assert.strictEqual(raw.length, 5);
252
253
assert.ok(event.contains(deletedFolderA, FileChangeType.DELETED));
254
assert.ok(event.contains(deletedFolderB, FileChangeType.DELETED));
255
assert.ok(event.contains(deletedFileA, FileChangeType.DELETED));
256
assert.ok(event.contains(addedFile, FileChangeType.ADDED));
257
assert.ok(event.contains(updatedFile, FileChangeType.UPDATED));
258
259
done();
260
}));
261
262
watch.report(raw);
263
});
264
});
265
266
test('event coalescer: ignore CREATE followed by DELETE', done => {
267
const watch = disposables.add(new TestFileWatcher());
268
269
const created = URI.file('/users/data/src/related');
270
const deleted = URI.file('/users/data/src/related');
271
const unrelated = URI.file('/users/data/src/unrelated');
272
273
const raw: IFileChange[] = [
274
{ resource: created, type: FileChangeType.ADDED },
275
{ resource: deleted, type: FileChangeType.DELETED },
276
{ resource: unrelated, type: FileChangeType.UPDATED },
277
];
278
279
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
280
assert.ok(event);
281
assert.strictEqual(raw.length, 1);
282
283
assert.ok(event.contains(unrelated, FileChangeType.UPDATED));
284
285
done();
286
}));
287
288
watch.report(raw);
289
});
290
291
test('event coalescer: flatten DELETE followed by CREATE into CHANGE', done => {
292
const watch = disposables.add(new TestFileWatcher());
293
294
const deleted = URI.file('/users/data/src/related');
295
const created = URI.file('/users/data/src/related');
296
const unrelated = URI.file('/users/data/src/unrelated');
297
298
const raw: IFileChange[] = [
299
{ resource: deleted, type: FileChangeType.DELETED },
300
{ resource: created, type: FileChangeType.ADDED },
301
{ resource: unrelated, type: FileChangeType.UPDATED },
302
];
303
304
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
305
assert.ok(event);
306
assert.strictEqual(raw.length, 2);
307
308
assert.ok(event.contains(deleted, FileChangeType.UPDATED));
309
assert.ok(event.contains(unrelated, FileChangeType.UPDATED));
310
311
done();
312
}));
313
314
watch.report(raw);
315
});
316
317
test('event coalescer: ignore UPDATE when CREATE received', done => {
318
const watch = disposables.add(new TestFileWatcher());
319
320
const created = URI.file('/users/data/src/related');
321
const updated = URI.file('/users/data/src/related');
322
const unrelated = URI.file('/users/data/src/unrelated');
323
324
const raw: IFileChange[] = [
325
{ resource: created, type: FileChangeType.ADDED },
326
{ resource: updated, type: FileChangeType.UPDATED },
327
{ resource: unrelated, type: FileChangeType.UPDATED },
328
];
329
330
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
331
assert.ok(event);
332
assert.strictEqual(raw.length, 2);
333
334
assert.ok(event.contains(created, FileChangeType.ADDED));
335
assert.ok(!event.contains(created, FileChangeType.UPDATED));
336
assert.ok(event.contains(unrelated, FileChangeType.UPDATED));
337
338
done();
339
}));
340
341
watch.report(raw);
342
});
343
344
test('event coalescer: apply DELETE', done => {
345
const watch = disposables.add(new TestFileWatcher());
346
347
const updated = URI.file('/users/data/src/related');
348
const updated2 = URI.file('/users/data/src/related');
349
const deleted = URI.file('/users/data/src/related');
350
const unrelated = URI.file('/users/data/src/unrelated');
351
352
const raw: IFileChange[] = [
353
{ resource: updated, type: FileChangeType.UPDATED },
354
{ resource: updated2, type: FileChangeType.UPDATED },
355
{ resource: unrelated, type: FileChangeType.UPDATED },
356
{ resource: updated, type: FileChangeType.DELETED }
357
];
358
359
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
360
assert.ok(event);
361
assert.strictEqual(raw.length, 2);
362
363
assert.ok(event.contains(deleted, FileChangeType.DELETED));
364
assert.ok(!event.contains(updated, FileChangeType.UPDATED));
365
assert.ok(event.contains(unrelated, FileChangeType.UPDATED));
366
367
done();
368
}));
369
370
watch.report(raw);
371
});
372
373
test('event coalescer: track case renames', done => {
374
const watch = disposables.add(new TestFileWatcher());
375
376
const oldPath = URI.file('/users/data/src/added');
377
const newPath = URI.file('/users/data/src/ADDED');
378
379
const raw: IFileChange[] = [
380
{ resource: newPath, type: FileChangeType.ADDED },
381
{ resource: oldPath, type: FileChangeType.DELETED }
382
];
383
384
disposables.add(watch.onDidFilesChange(({ event, raw }) => {
385
assert.ok(event);
386
assert.strictEqual(raw.length, 2);
387
388
for (const r of raw) {
389
if (isEqual(r.resource, oldPath)) {
390
assert.strictEqual(r.type, FileChangeType.DELETED);
391
} else if (isEqual(r.resource, newPath)) {
392
assert.strictEqual(r.type, FileChangeType.ADDED);
393
} else {
394
assert.fail();
395
}
396
}
397
398
done();
399
}));
400
401
watch.report(raw);
402
});
403
404
test('event type filter', () => {
405
const resource = URI.file('/users/data/src/related');
406
407
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, undefined), false);
408
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, undefined), false);
409
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, undefined), false);
410
411
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED), true);
412
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED | FileChangeFilter.DELETED), true);
413
414
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED), false);
415
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED), false);
416
assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED | FileChangeFilter.DELETED), false);
417
418
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED), true);
419
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED | FileChangeFilter.ADDED), true);
420
421
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED), false);
422
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
423
assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
424
425
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED), true);
426
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.ADDED), true);
427
428
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.UPDATED), false);
429
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
430
assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);
431
});
432
433
ensureNoDisposablesAreLeakedInTestSuite();
434
});
435
436