Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/files/test/node/nodejsWatcher.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 * as fs from 'fs';
7
import assert from 'assert';
8
import { tmpdir } from 'os';
9
import { basename, dirname, join } from '../../../../base/common/path.js';
10
import { Promises, RimRafMode } from '../../../../base/node/pfs.js';
11
import { getRandomTestPath } from '../../../../base/test/node/testUtils.js';
12
import { FileChangeFilter, FileChangeType } from '../../common/files.js';
13
import { INonRecursiveWatchRequest, IRecursiveWatcherWithSubscribe } from '../../common/watcher.js';
14
import { watchFileContents } from '../../node/watcher/nodejs/nodejsWatcherLib.js';
15
import { isLinux, isMacintosh, isWindows } from '../../../../base/common/platform.js';
16
import { getDriveLetter } from '../../../../base/common/extpath.js';
17
import { ltrim } from '../../../../base/common/strings.js';
18
import { DeferredPromise, timeout } from '../../../../base/common/async.js';
19
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
20
import { NodeJSWatcher } from '../../node/watcher/nodejs/nodejsWatcher.js';
21
import { FileAccess } from '../../../../base/common/network.js';
22
import { extUriBiasedIgnorePathCase } from '../../../../base/common/resources.js';
23
import { URI } from '../../../../base/common/uri.js';
24
import { addUNCHostToAllowlist } from '../../../../base/node/unc.js';
25
import { Emitter, Event } from '../../../../base/common/event.js';
26
import { TestParcelWatcher } from './parcelWatcher.test.js';
27
28
// this suite has shown flaky runs in Azure pipelines where
29
// tasks would just hang and timeout after a while (not in
30
// mocha but generally). as such they will run only on demand
31
// whenever we update the watcher library.
32
33
suite.skip('File Watcher (node.js)', function () {
34
35
this.timeout(10000);
36
37
class TestNodeJSWatcher extends NodeJSWatcher {
38
39
protected override readonly suspendedWatchRequestPollingInterval = 100;
40
41
private readonly _onDidWatch = this._register(new Emitter<void>());
42
readonly onDidWatch = this._onDidWatch.event;
43
44
readonly onWatchFail = this._onDidWatchFail.event;
45
46
protected override getUpdateWatchersDelay(): number {
47
return 0;
48
}
49
50
protected override async doWatch(requests: INonRecursiveWatchRequest[]): Promise<void> {
51
await super.doWatch(requests);
52
for (const watcher of this.watchers) {
53
await watcher.instance.ready;
54
}
55
56
this._onDidWatch.fire();
57
}
58
}
59
60
let testDir: string;
61
let watcher: TestNodeJSWatcher;
62
63
let loggingEnabled = false;
64
65
function enableLogging(enable: boolean) {
66
loggingEnabled = enable;
67
watcher?.setVerboseLogging(enable);
68
}
69
70
enableLogging(loggingEnabled);
71
72
setup(async () => {
73
await createWatcher(undefined);
74
75
// Rule out strange testing conditions by using the realpath
76
// here. for example, on macOS the tmp dir is potentially a
77
// symlink in some of the root folders, which is a rather
78
// unrealisic case for the file watcher.
79
testDir = URI.file(getRandomTestPath(fs.realpathSync(tmpdir()), 'vsctests', 'filewatcher')).fsPath;
80
81
const sourceDir = FileAccess.asFileUri('vs/platform/files/test/node/fixtures/service').fsPath;
82
83
await Promises.copy(sourceDir, testDir, { preserveSymlinks: false });
84
});
85
86
async function createWatcher(accessor: IRecursiveWatcherWithSubscribe | undefined) {
87
await watcher?.stop();
88
watcher?.dispose();
89
90
watcher = new TestNodeJSWatcher(accessor);
91
watcher?.setVerboseLogging(loggingEnabled);
92
93
watcher.onDidLogMessage(e => {
94
if (loggingEnabled) {
95
console.log(`[non-recursive watcher test message] ${e.message}`);
96
}
97
});
98
99
watcher.onDidError(e => {
100
if (loggingEnabled) {
101
console.log(`[non-recursive watcher test error] ${e}`);
102
}
103
});
104
}
105
106
teardown(async () => {
107
await watcher.stop();
108
watcher.dispose();
109
110
// Possible that the file watcher is still holding
111
// onto the folders on Windows specifically and the
112
// unlink would fail. In that case, do not fail the
113
// test suite.
114
return Promises.rm(testDir).catch(error => console.error(error));
115
});
116
117
function toMsg(type: FileChangeType): string {
118
switch (type) {
119
case FileChangeType.ADDED: return 'added';
120
case FileChangeType.DELETED: return 'deleted';
121
default: return 'changed';
122
}
123
}
124
125
async function awaitEvent(service: TestNodeJSWatcher, path: string, type: FileChangeType, correlationId?: number | null, expectedCount?: number): Promise<void> {
126
if (loggingEnabled) {
127
console.log(`Awaiting change type '${toMsg(type)}' on file '${path}'`);
128
}
129
130
// Await the event
131
await new Promise<void>(resolve => {
132
let counter = 0;
133
const disposable = service.onDidChangeFile(events => {
134
for (const event of events) {
135
if (extUriBiasedIgnorePathCase.isEqual(event.resource, URI.file(path)) && event.type === type && (correlationId === null || event.cId === correlationId)) {
136
counter++;
137
if (typeof expectedCount === 'number' && counter < expectedCount) {
138
continue; // not yet
139
}
140
141
disposable.dispose();
142
resolve();
143
break;
144
}
145
}
146
});
147
});
148
}
149
150
test('basics (folder watch)', async function () {
151
const request = { path: testDir, excludes: [], recursive: false };
152
await watcher.watch([request]);
153
assert.strictEqual(watcher.isSuspended(request), false);
154
155
const instance = Array.from(watcher.watchers)[0].instance;
156
assert.strictEqual(instance.isReusingRecursiveWatcher, false);
157
assert.strictEqual(instance.failed, false);
158
159
// New file
160
const newFilePath = join(testDir, 'newFile.txt');
161
let changeFuture: Promise<unknown> = awaitEvent(watcher, newFilePath, FileChangeType.ADDED);
162
await Promises.writeFile(newFilePath, 'Hello World');
163
await changeFuture;
164
165
// New folder
166
const newFolderPath = join(testDir, 'New Folder');
167
changeFuture = awaitEvent(watcher, newFolderPath, FileChangeType.ADDED);
168
await fs.promises.mkdir(newFolderPath);
169
await changeFuture;
170
171
// Rename file
172
let renamedFilePath = join(testDir, 'renamedFile.txt');
173
changeFuture = Promise.all([
174
awaitEvent(watcher, newFilePath, FileChangeType.DELETED),
175
awaitEvent(watcher, renamedFilePath, FileChangeType.ADDED)
176
]);
177
await Promises.rename(newFilePath, renamedFilePath);
178
await changeFuture;
179
180
// Rename folder
181
let renamedFolderPath = join(testDir, 'Renamed Folder');
182
changeFuture = Promise.all([
183
awaitEvent(watcher, newFolderPath, FileChangeType.DELETED),
184
awaitEvent(watcher, renamedFolderPath, FileChangeType.ADDED)
185
]);
186
await Promises.rename(newFolderPath, renamedFolderPath);
187
await changeFuture;
188
189
// Rename file (same name, different case)
190
const caseRenamedFilePath = join(testDir, 'RenamedFile.txt');
191
changeFuture = Promise.all([
192
awaitEvent(watcher, renamedFilePath, FileChangeType.DELETED),
193
awaitEvent(watcher, caseRenamedFilePath, FileChangeType.ADDED)
194
]);
195
await Promises.rename(renamedFilePath, caseRenamedFilePath);
196
await changeFuture;
197
renamedFilePath = caseRenamedFilePath;
198
199
// Rename folder (same name, different case)
200
const caseRenamedFolderPath = join(testDir, 'REnamed Folder');
201
changeFuture = Promise.all([
202
awaitEvent(watcher, renamedFolderPath, FileChangeType.DELETED),
203
awaitEvent(watcher, caseRenamedFolderPath, FileChangeType.ADDED)
204
]);
205
await Promises.rename(renamedFolderPath, caseRenamedFolderPath);
206
await changeFuture;
207
renamedFolderPath = caseRenamedFolderPath;
208
209
// Move file
210
const movedFilepath = join(testDir, 'movedFile.txt');
211
changeFuture = Promise.all([
212
awaitEvent(watcher, renamedFilePath, FileChangeType.DELETED),
213
awaitEvent(watcher, movedFilepath, FileChangeType.ADDED)
214
]);
215
await Promises.rename(renamedFilePath, movedFilepath);
216
await changeFuture;
217
218
// Move folder
219
const movedFolderpath = join(testDir, 'Moved Folder');
220
changeFuture = Promise.all([
221
awaitEvent(watcher, renamedFolderPath, FileChangeType.DELETED),
222
awaitEvent(watcher, movedFolderpath, FileChangeType.ADDED)
223
]);
224
await Promises.rename(renamedFolderPath, movedFolderpath);
225
await changeFuture;
226
227
// Copy file
228
const copiedFilepath = join(testDir, 'copiedFile.txt');
229
changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.ADDED);
230
await fs.promises.copyFile(movedFilepath, copiedFilepath);
231
await changeFuture;
232
233
// Copy folder
234
const copiedFolderpath = join(testDir, 'Copied Folder');
235
changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.ADDED);
236
await Promises.copy(movedFolderpath, copiedFolderpath, { preserveSymlinks: false });
237
await changeFuture;
238
239
// Change file
240
changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.UPDATED);
241
await Promises.writeFile(copiedFilepath, 'Hello Change');
242
await changeFuture;
243
244
// Create new file
245
const anotherNewFilePath = join(testDir, 'anotherNewFile.txt');
246
changeFuture = awaitEvent(watcher, anotherNewFilePath, FileChangeType.ADDED);
247
await Promises.writeFile(anotherNewFilePath, 'Hello Another World');
248
await changeFuture;
249
250
// Delete file
251
changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.DELETED);
252
await fs.promises.unlink(copiedFilepath);
253
await changeFuture;
254
255
// Delete folder
256
changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.DELETED);
257
await fs.promises.rmdir(copiedFolderpath);
258
await changeFuture;
259
260
watcher.dispose();
261
});
262
263
test('basics (file watch)', async function () {
264
const filePath = join(testDir, 'lorem.txt');
265
const request = { path: filePath, excludes: [], recursive: false };
266
await watcher.watch([request]);
267
assert.strictEqual(watcher.isSuspended(request), false);
268
269
const instance = Array.from(watcher.watchers)[0].instance;
270
assert.strictEqual(instance.isReusingRecursiveWatcher, false);
271
assert.strictEqual(instance.failed, false);
272
273
// Change file
274
let changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED);
275
await Promises.writeFile(filePath, 'Hello Change');
276
await changeFuture;
277
278
// Delete file
279
changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED);
280
await fs.promises.unlink(filePath);
281
await changeFuture;
282
283
// Recreate watcher
284
await Promises.writeFile(filePath, 'Hello Change');
285
await watcher.watch([]);
286
await watcher.watch([{ path: filePath, excludes: [], recursive: false }]);
287
288
// Move file
289
changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED);
290
await Promises.rename(filePath, `${filePath}-moved`);
291
await changeFuture;
292
});
293
294
test('atomic writes (folder watch)', async function () {
295
await watcher.watch([{ path: testDir, excludes: [], recursive: false }]);
296
297
// Delete + Recreate file
298
const newFilePath = join(testDir, 'lorem.txt');
299
const changeFuture: Promise<unknown> = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED);
300
await fs.promises.unlink(newFilePath);
301
Promises.writeFile(newFilePath, 'Hello Atomic World');
302
await changeFuture;
303
});
304
305
test('atomic writes (file watch)', async function () {
306
const filePath = join(testDir, 'lorem.txt');
307
await watcher.watch([{ path: filePath, excludes: [], recursive: false }]);
308
309
// Delete + Recreate file
310
const newFilePath = join(filePath);
311
const changeFuture: Promise<unknown> = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED);
312
await fs.promises.unlink(newFilePath);
313
Promises.writeFile(newFilePath, 'Hello Atomic World');
314
await changeFuture;
315
});
316
317
test('multiple events (folder watch)', async function () {
318
await watcher.watch([{ path: testDir, excludes: [], recursive: false }]);
319
320
// multiple add
321
322
const newFilePath1 = join(testDir, 'newFile-1.txt');
323
const newFilePath2 = join(testDir, 'newFile-2.txt');
324
const newFilePath3 = join(testDir, 'newFile-3.txt');
325
326
const addedFuture1: Promise<unknown> = awaitEvent(watcher, newFilePath1, FileChangeType.ADDED);
327
const addedFuture2: Promise<unknown> = awaitEvent(watcher, newFilePath2, FileChangeType.ADDED);
328
const addedFuture3: Promise<unknown> = awaitEvent(watcher, newFilePath3, FileChangeType.ADDED);
329
330
await Promise.all([
331
await Promises.writeFile(newFilePath1, 'Hello World 1'),
332
await Promises.writeFile(newFilePath2, 'Hello World 2'),
333
await Promises.writeFile(newFilePath3, 'Hello World 3'),
334
]);
335
336
await Promise.all([addedFuture1, addedFuture2, addedFuture3]);
337
338
// multiple change
339
340
const changeFuture1: Promise<unknown> = awaitEvent(watcher, newFilePath1, FileChangeType.UPDATED);
341
const changeFuture2: Promise<unknown> = awaitEvent(watcher, newFilePath2, FileChangeType.UPDATED);
342
const changeFuture3: Promise<unknown> = awaitEvent(watcher, newFilePath3, FileChangeType.UPDATED);
343
344
await Promise.all([
345
await Promises.writeFile(newFilePath1, 'Hello Update 1'),
346
await Promises.writeFile(newFilePath2, 'Hello Update 2'),
347
await Promises.writeFile(newFilePath3, 'Hello Update 3'),
348
]);
349
350
await Promise.all([changeFuture1, changeFuture2, changeFuture3]);
351
352
// copy with multiple files
353
354
const copyFuture1: Promise<unknown> = awaitEvent(watcher, join(testDir, 'newFile-1-copy.txt'), FileChangeType.ADDED);
355
const copyFuture2: Promise<unknown> = awaitEvent(watcher, join(testDir, 'newFile-2-copy.txt'), FileChangeType.ADDED);
356
const copyFuture3: Promise<unknown> = awaitEvent(watcher, join(testDir, 'newFile-3-copy.txt'), FileChangeType.ADDED);
357
358
await Promise.all([
359
Promises.copy(join(testDir, 'newFile-1.txt'), join(testDir, 'newFile-1-copy.txt'), { preserveSymlinks: false }),
360
Promises.copy(join(testDir, 'newFile-2.txt'), join(testDir, 'newFile-2-copy.txt'), { preserveSymlinks: false }),
361
Promises.copy(join(testDir, 'newFile-3.txt'), join(testDir, 'newFile-3-copy.txt'), { preserveSymlinks: false })
362
]);
363
364
await Promise.all([copyFuture1, copyFuture2, copyFuture3]);
365
366
// multiple delete
367
368
const deleteFuture1: Promise<unknown> = awaitEvent(watcher, newFilePath1, FileChangeType.DELETED);
369
const deleteFuture2: Promise<unknown> = awaitEvent(watcher, newFilePath2, FileChangeType.DELETED);
370
const deleteFuture3: Promise<unknown> = awaitEvent(watcher, newFilePath3, FileChangeType.DELETED);
371
372
await Promise.all([
373
await fs.promises.unlink(newFilePath1),
374
await fs.promises.unlink(newFilePath2),
375
await fs.promises.unlink(newFilePath3)
376
]);
377
378
await Promise.all([deleteFuture1, deleteFuture2, deleteFuture3]);
379
});
380
381
test('multiple events (file watch)', async function () {
382
const filePath = join(testDir, 'lorem.txt');
383
await watcher.watch([{ path: filePath, excludes: [], recursive: false }]);
384
385
// multiple change
386
387
const changeFuture1: Promise<unknown> = awaitEvent(watcher, filePath, FileChangeType.UPDATED);
388
389
await Promise.all([
390
await Promises.writeFile(filePath, 'Hello Update 1'),
391
await Promises.writeFile(filePath, 'Hello Update 2'),
392
await Promises.writeFile(filePath, 'Hello Update 3'),
393
]);
394
395
await Promise.all([changeFuture1]);
396
});
397
398
test('excludes can be updated (folder watch)', async function () {
399
await watcher.watch([{ path: testDir, excludes: ['**'], recursive: false }]);
400
await watcher.watch([{ path: testDir, excludes: [], recursive: false }]);
401
402
return basicCrudTest(join(testDir, 'files-excludes.txt'));
403
});
404
405
test('excludes are ignored (file watch)', async function () {
406
const filePath = join(testDir, 'lorem.txt');
407
await watcher.watch([{ path: filePath, excludes: ['**'], recursive: false }]);
408
409
return basicCrudTest(filePath, true);
410
});
411
412
test('includes can be updated (folder watch)', async function () {
413
await watcher.watch([{ path: testDir, excludes: [], includes: ['nothing'], recursive: false }]);
414
await watcher.watch([{ path: testDir, excludes: [], recursive: false }]);
415
416
return basicCrudTest(join(testDir, 'files-includes.txt'));
417
});
418
419
test('non-includes are ignored (file watch)', async function () {
420
const filePath = join(testDir, 'lorem.txt');
421
await watcher.watch([{ path: filePath, excludes: [], includes: ['nothing'], recursive: false }]);
422
423
return basicCrudTest(filePath, true);
424
});
425
426
test('includes are supported (folder watch)', async function () {
427
await watcher.watch([{ path: testDir, excludes: [], includes: ['**/files-includes.txt'], recursive: false }]);
428
429
return basicCrudTest(join(testDir, 'files-includes.txt'));
430
});
431
432
test('includes are supported (folder watch, relative pattern explicit)', async function () {
433
await watcher.watch([{ path: testDir, excludes: [], includes: [{ base: testDir, pattern: 'files-includes.txt' }], recursive: false }]);
434
435
return basicCrudTest(join(testDir, 'files-includes.txt'));
436
});
437
438
test('includes are supported (folder watch, relative pattern implicit)', async function () {
439
await watcher.watch([{ path: testDir, excludes: [], includes: ['files-includes.txt'], recursive: false }]);
440
441
return basicCrudTest(join(testDir, 'files-includes.txt'));
442
});
443
444
test('correlationId is supported', async function () {
445
const correlationId = Math.random();
446
await watcher.watch([{ correlationId, path: testDir, excludes: [], recursive: false }]);
447
448
return basicCrudTest(join(testDir, 'newFile.txt'), undefined, correlationId);
449
});
450
451
(isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (folder watch)', async function () {
452
const link = join(testDir, 'deep-linked');
453
const linkTarget = join(testDir, 'deep');
454
await fs.promises.symlink(linkTarget, link);
455
456
await watcher.watch([{ path: link, excludes: [], recursive: false }]);
457
458
return basicCrudTest(join(link, 'newFile.txt'));
459
});
460
461
async function basicCrudTest(filePath: string, skipAdd?: boolean, correlationId?: number | null, expectedCount?: number, awaitWatchAfterAdd?: boolean): Promise<void> {
462
let changeFuture: Promise<unknown>;
463
464
// New file
465
if (!skipAdd) {
466
changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED, correlationId, expectedCount);
467
await Promises.writeFile(filePath, 'Hello World');
468
await changeFuture;
469
if (awaitWatchAfterAdd) {
470
await Event.toPromise(watcher.onDidWatch);
471
}
472
}
473
474
// Change file
475
changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, correlationId, expectedCount);
476
await Promises.writeFile(filePath, 'Hello Change');
477
await changeFuture;
478
479
// Delete file
480
changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, correlationId, expectedCount);
481
await fs.promises.unlink(await Promises.realpath(filePath)); // support symlinks
482
await changeFuture;
483
}
484
485
(isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (file watch)', async function () {
486
const link = join(testDir, 'lorem.txt-linked');
487
const linkTarget = join(testDir, 'lorem.txt');
488
await fs.promises.symlink(linkTarget, link);
489
490
await watcher.watch([{ path: link, excludes: [], recursive: false }]);
491
492
return basicCrudTest(link, true);
493
});
494
495
(!isWindows /* UNC is windows only */ ? test.skip : test)('unc support (folder watch)', async function () {
496
addUNCHostToAllowlist('localhost');
497
498
// Local UNC paths are in the form of: \\localhost\c$\my_dir
499
const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}`;
500
501
await watcher.watch([{ path: uncPath, excludes: [], recursive: false }]);
502
503
return basicCrudTest(join(uncPath, 'newFile.txt'));
504
});
505
506
(!isWindows /* UNC is windows only */ ? test.skip : test)('unc support (file watch)', async function () {
507
addUNCHostToAllowlist('localhost');
508
509
// Local UNC paths are in the form of: \\localhost\c$\my_dir
510
const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}\\lorem.txt`;
511
512
await watcher.watch([{ path: uncPath, excludes: [], recursive: false }]);
513
514
return basicCrudTest(uncPath, true);
515
});
516
517
(isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing (folder watch)', async function () {
518
const wrongCase = join(dirname(testDir), basename(testDir).toUpperCase());
519
520
await watcher.watch([{ path: wrongCase, excludes: [], recursive: false }]);
521
522
return basicCrudTest(join(wrongCase, 'newFile.txt'));
523
});
524
525
(isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing (file watch)', async function () {
526
const filePath = join(testDir, 'LOREM.txt');
527
await watcher.watch([{ path: filePath, excludes: [], recursive: false }]);
528
529
return basicCrudTest(filePath, true);
530
});
531
532
test('invalid path does not explode', async function () {
533
const invalidPath = join(testDir, 'invalid');
534
535
await watcher.watch([{ path: invalidPath, excludes: [], recursive: false }]);
536
});
537
538
test('watchFileContents', async function () {
539
const watchedPath = join(testDir, 'lorem.txt');
540
541
const cts = new CancellationTokenSource();
542
543
const readyPromise = new DeferredPromise<void>();
544
const chunkPromise = new DeferredPromise<void>();
545
const watchPromise = watchFileContents(watchedPath, () => chunkPromise.complete(), () => readyPromise.complete(), cts.token);
546
547
await readyPromise.p;
548
549
Promises.writeFile(watchedPath, 'Hello World');
550
551
await chunkPromise.p;
552
553
cts.cancel(); // this will resolve `watchPromise`
554
555
return watchPromise;
556
});
557
558
test('watching same or overlapping paths supported when correlation is applied', async function () {
559
await watcher.watch([
560
{ path: testDir, excludes: [], recursive: false, correlationId: 1 }
561
]);
562
563
await basicCrudTest(join(testDir, 'newFile_1.txt'), undefined, null, 1);
564
565
await watcher.watch([
566
{ path: testDir, excludes: [], recursive: false, correlationId: 1 },
567
{ path: testDir, excludes: [], recursive: false, correlationId: 2, },
568
{ path: testDir, excludes: [], recursive: false, correlationId: undefined }
569
]);
570
571
await basicCrudTest(join(testDir, 'newFile_2.txt'), undefined, null, 3);
572
await basicCrudTest(join(testDir, 'otherNewFile.txt'), undefined, null, 3);
573
});
574
575
test('watching missing path emits watcher fail event', async function () {
576
const onDidWatchFail = Event.toPromise(watcher.onWatchFail);
577
578
const folderPath = join(testDir, 'missing');
579
watcher.watch([{ path: folderPath, excludes: [], recursive: true }]);
580
581
await onDidWatchFail;
582
});
583
584
test('deleting watched path emits watcher fail and delete event when correlated (file watch)', async function () {
585
const filePath = join(testDir, 'lorem.txt');
586
587
await watcher.watch([{ path: filePath, excludes: [], recursive: false, correlationId: 1 }]);
588
589
const instance = Array.from(watcher.watchers)[0].instance;
590
591
const onDidWatchFail = Event.toPromise(watcher.onWatchFail);
592
const changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1);
593
fs.promises.unlink(filePath);
594
await onDidWatchFail;
595
await changeFuture;
596
assert.strictEqual(instance.failed, true);
597
});
598
599
(isMacintosh || isWindows /* macOS: does not seem to report deletes on folders | Windows: reports on('error') event only */ ? test.skip : test)('deleting watched path emits watcher fail and delete event when correlated (folder watch)', async function () {
600
const folderPath = join(testDir, 'deep');
601
602
await watcher.watch([{ path: folderPath, excludes: [], recursive: false, correlationId: 1 }]);
603
604
const onDidWatchFail = Event.toPromise(watcher.onWatchFail);
605
const changeFuture = awaitEvent(watcher, folderPath, FileChangeType.DELETED, 1);
606
Promises.rm(folderPath, RimRafMode.UNLINK);
607
await onDidWatchFail;
608
await changeFuture;
609
});
610
611
test('watch requests support suspend/resume (file, does not exist in beginning)', async function () {
612
const filePath = join(testDir, 'not-found.txt');
613
614
const onDidWatchFail = Event.toPromise(watcher.onWatchFail);
615
const request = { path: filePath, excludes: [], recursive: false };
616
await watcher.watch([request]);
617
await onDidWatchFail;
618
assert.strictEqual(watcher.isSuspended(request), 'polling');
619
620
await basicCrudTest(filePath, undefined, null, undefined, true);
621
await basicCrudTest(filePath, undefined, null, undefined, true);
622
});
623
624
test('watch requests support suspend/resume (file, exists in beginning)', async function () {
625
const filePath = join(testDir, 'lorem.txt');
626
const request = { path: filePath, excludes: [], recursive: false };
627
await watcher.watch([request]);
628
629
const onDidWatchFail = Event.toPromise(watcher.onWatchFail);
630
await basicCrudTest(filePath, true);
631
await onDidWatchFail;
632
assert.strictEqual(watcher.isSuspended(request), 'polling');
633
634
await basicCrudTest(filePath, undefined, null, undefined, true);
635
});
636
637
(isWindows /* Windows: does not seem to report this */ ? test.skip : test)('watch requests support suspend/resume (folder, does not exist in beginning)', async function () {
638
let onDidWatchFail = Event.toPromise(watcher.onWatchFail);
639
640
const folderPath = join(testDir, 'not-found');
641
const request = { path: folderPath, excludes: [], recursive: false };
642
await watcher.watch([request]);
643
await onDidWatchFail;
644
assert.strictEqual(watcher.isSuspended(request), 'polling');
645
646
let changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED);
647
let onDidWatch = Event.toPromise(watcher.onDidWatch);
648
await fs.promises.mkdir(folderPath);
649
await changeFuture;
650
await onDidWatch;
651
652
assert.strictEqual(watcher.isSuspended(request), false);
653
654
if (isWindows) { // somehow failing on macOS/Linux
655
const filePath = join(folderPath, 'newFile.txt');
656
await basicCrudTest(filePath);
657
658
onDidWatchFail = Event.toPromise(watcher.onWatchFail);
659
await fs.promises.rmdir(folderPath);
660
await onDidWatchFail;
661
662
changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED);
663
onDidWatch = Event.toPromise(watcher.onDidWatch);
664
await fs.promises.mkdir(folderPath);
665
await changeFuture;
666
await onDidWatch;
667
668
await timeout(500); // somehow needed on Linux
669
670
await basicCrudTest(filePath);
671
}
672
});
673
674
(isMacintosh /* macOS: does not seem to report this */ ? test.skip : test)('watch requests support suspend/resume (folder, exists in beginning)', async function () {
675
const folderPath = join(testDir, 'deep');
676
await watcher.watch([{ path: folderPath, excludes: [], recursive: false }]);
677
678
const filePath = join(folderPath, 'newFile.txt');
679
await basicCrudTest(filePath);
680
681
const onDidWatchFail = Event.toPromise(watcher.onWatchFail);
682
await Promises.rm(folderPath);
683
await onDidWatchFail;
684
685
const changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED);
686
const onDidWatch = Event.toPromise(watcher.onDidWatch);
687
await fs.promises.mkdir(folderPath);
688
await changeFuture;
689
await onDidWatch;
690
691
await timeout(500); // somehow needed on Linux
692
693
await basicCrudTest(filePath);
694
});
695
696
test('parcel watcher reused when present for non-recursive file watching (uncorrelated)', function () {
697
return testParcelWatcherReused(undefined);
698
});
699
700
test('parcel watcher reused when present for non-recursive file watching (correlated)', function () {
701
return testParcelWatcherReused(2);
702
});
703
704
function createParcelWatcher() {
705
const recursiveWatcher = new TestParcelWatcher();
706
recursiveWatcher.setVerboseLogging(loggingEnabled);
707
recursiveWatcher.onDidLogMessage(e => {
708
if (loggingEnabled) {
709
console.log(`[recursive watcher test message] ${e.message}`);
710
}
711
});
712
713
recursiveWatcher.onDidError(e => {
714
if (loggingEnabled) {
715
console.log(`[recursive watcher test error] ${e.error}`);
716
}
717
});
718
719
return recursiveWatcher;
720
}
721
722
async function testParcelWatcherReused(correlationId: number | undefined) {
723
const recursiveWatcher = createParcelWatcher();
724
await recursiveWatcher.watch([{ path: testDir, excludes: [], recursive: true, correlationId: 1 }]);
725
726
const recursiveInstance = Array.from(recursiveWatcher.watchers)[0];
727
assert.strictEqual(recursiveInstance.subscriptionsCount, 0);
728
729
await createWatcher(recursiveWatcher);
730
731
const filePath = join(testDir, 'deep', 'conway.js');
732
await watcher.watch([{ path: filePath, excludes: [], recursive: false, correlationId }]);
733
734
const { instance } = Array.from(watcher.watchers)[0];
735
assert.strictEqual(instance.isReusingRecursiveWatcher, true);
736
assert.strictEqual(recursiveInstance.subscriptionsCount, 1);
737
738
let changeFuture = awaitEvent(watcher, filePath, isMacintosh /* somehow fsevents seems to report still on the initial create from test setup */ ? FileChangeType.ADDED : FileChangeType.UPDATED, correlationId);
739
await Promises.writeFile(filePath, 'Hello World');
740
await changeFuture;
741
742
await recursiveWatcher.stop();
743
recursiveWatcher.dispose();
744
745
await timeout(500); // give the watcher some time to restart
746
747
changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, correlationId);
748
await Promises.writeFile(filePath, 'Hello World');
749
await changeFuture;
750
751
assert.strictEqual(instance.isReusingRecursiveWatcher, false);
752
}
753
754
test('watch requests support suspend/resume (file, does not exist in beginning, parcel watcher reused)', async function () {
755
const recursiveWatcher = createParcelWatcher();
756
await recursiveWatcher.watch([{ path: testDir, excludes: [], recursive: true }]);
757
758
await createWatcher(recursiveWatcher);
759
760
const filePath = join(testDir, 'not-found-2.txt');
761
762
const onDidWatchFail = Event.toPromise(watcher.onWatchFail);
763
const request = { path: filePath, excludes: [], recursive: false };
764
await watcher.watch([request]);
765
await onDidWatchFail;
766
assert.strictEqual(watcher.isSuspended(request), true);
767
768
const changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED);
769
await Promises.writeFile(filePath, 'Hello World');
770
await changeFuture;
771
772
assert.strictEqual(watcher.isSuspended(request), false);
773
});
774
775
test('event type filter (file watch)', async function () {
776
const filePath = join(testDir, 'lorem.txt');
777
const request = { path: filePath, excludes: [], recursive: false, filter: FileChangeFilter.UPDATED | FileChangeFilter.DELETED, correlationId: 1 };
778
await watcher.watch([request]);
779
780
// Change file
781
let changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, 1);
782
await Promises.writeFile(filePath, 'Hello Change');
783
await changeFuture;
784
785
// Delete file
786
changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1);
787
await fs.promises.unlink(filePath);
788
await changeFuture;
789
});
790
791
test('event type filter (folder watch)', async function () {
792
const request = { path: testDir, excludes: [], recursive: false, filter: FileChangeFilter.UPDATED | FileChangeFilter.DELETED, correlationId: 1 };
793
await watcher.watch([request]);
794
795
// Change file
796
const filePath = join(testDir, 'lorem.txt');
797
let changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, 1);
798
await Promises.writeFile(filePath, 'Hello Change');
799
await changeFuture;
800
801
// Delete file
802
changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1);
803
await fs.promises.unlink(filePath);
804
await changeFuture;
805
});
806
});
807
808