Path: blob/main/src/vs/platform/files/test/node/nodejsWatcher.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as fs from 'fs';6import assert from 'assert';7import { tmpdir } from 'os';8import { basename, dirname, join } from '../../../../base/common/path.js';9import { Promises, RimRafMode } from '../../../../base/node/pfs.js';10import { getRandomTestPath } from '../../../../base/test/node/testUtils.js';11import { FileChangeFilter, FileChangeType } from '../../common/files.js';12import { INonRecursiveWatchRequest, IRecursiveWatcherWithSubscribe } from '../../common/watcher.js';13import { watchFileContents } from '../../node/watcher/nodejs/nodejsWatcherLib.js';14import { isLinux, isMacintosh, isWindows } from '../../../../base/common/platform.js';15import { getDriveLetter } from '../../../../base/common/extpath.js';16import { ltrim } from '../../../../base/common/strings.js';17import { DeferredPromise, timeout } from '../../../../base/common/async.js';18import { CancellationTokenSource } from '../../../../base/common/cancellation.js';19import { NodeJSWatcher } from '../../node/watcher/nodejs/nodejsWatcher.js';20import { FileAccess } from '../../../../base/common/network.js';21import { extUriBiasedIgnorePathCase } from '../../../../base/common/resources.js';22import { URI } from '../../../../base/common/uri.js';23import { addUNCHostToAllowlist } from '../../../../base/node/unc.js';24import { Emitter, Event } from '../../../../base/common/event.js';25import { TestParcelWatcher } from './parcelWatcher.test.js';2627// this suite has shown flaky runs in Azure pipelines where28// tasks would just hang and timeout after a while (not in29// mocha but generally). as such they will run only on demand30// whenever we update the watcher library.3132suite.skip('File Watcher (node.js)', function () {3334this.timeout(10000);3536class TestNodeJSWatcher extends NodeJSWatcher {3738protected override readonly suspendedWatchRequestPollingInterval = 100;3940private readonly _onDidWatch = this._register(new Emitter<void>());41readonly onDidWatch = this._onDidWatch.event;4243readonly onWatchFail = this._onDidWatchFail.event;4445protected override getUpdateWatchersDelay(): number {46return 0;47}4849protected override async doWatch(requests: INonRecursiveWatchRequest[]): Promise<void> {50await super.doWatch(requests);51for (const watcher of this.watchers) {52await watcher.instance.ready;53}5455this._onDidWatch.fire();56}57}5859let testDir: string;60let watcher: TestNodeJSWatcher;6162let loggingEnabled = false;6364function enableLogging(enable: boolean) {65loggingEnabled = enable;66watcher?.setVerboseLogging(enable);67}6869enableLogging(loggingEnabled);7071setup(async () => {72await createWatcher(undefined);7374// Rule out strange testing conditions by using the realpath75// here. for example, on macOS the tmp dir is potentially a76// symlink in some of the root folders, which is a rather77// unrealisic case for the file watcher.78testDir = URI.file(getRandomTestPath(fs.realpathSync(tmpdir()), 'vsctests', 'filewatcher')).fsPath;7980const sourceDir = FileAccess.asFileUri('vs/platform/files/test/node/fixtures/service').fsPath;8182await Promises.copy(sourceDir, testDir, { preserveSymlinks: false });83});8485async function createWatcher(accessor: IRecursiveWatcherWithSubscribe | undefined) {86await watcher?.stop();87watcher?.dispose();8889watcher = new TestNodeJSWatcher(accessor);90watcher?.setVerboseLogging(loggingEnabled);9192watcher.onDidLogMessage(e => {93if (loggingEnabled) {94console.log(`[non-recursive watcher test message] ${e.message}`);95}96});9798watcher.onDidError(e => {99if (loggingEnabled) {100console.log(`[non-recursive watcher test error] ${e}`);101}102});103}104105teardown(async () => {106await watcher.stop();107watcher.dispose();108109// Possible that the file watcher is still holding110// onto the folders on Windows specifically and the111// unlink would fail. In that case, do not fail the112// test suite.113return Promises.rm(testDir).catch(error => console.error(error));114});115116function toMsg(type: FileChangeType): string {117switch (type) {118case FileChangeType.ADDED: return 'added';119case FileChangeType.DELETED: return 'deleted';120default: return 'changed';121}122}123124async function awaitEvent(service: TestNodeJSWatcher, path: string, type: FileChangeType, correlationId?: number | null, expectedCount?: number): Promise<void> {125if (loggingEnabled) {126console.log(`Awaiting change type '${toMsg(type)}' on file '${path}'`);127}128129// Await the event130await new Promise<void>(resolve => {131let counter = 0;132const disposable = service.onDidChangeFile(events => {133for (const event of events) {134if (extUriBiasedIgnorePathCase.isEqual(event.resource, URI.file(path)) && event.type === type && (correlationId === null || event.cId === correlationId)) {135counter++;136if (typeof expectedCount === 'number' && counter < expectedCount) {137continue; // not yet138}139140disposable.dispose();141resolve();142break;143}144}145});146});147}148149test('basics (folder watch)', async function () {150const request = { path: testDir, excludes: [], recursive: false };151await watcher.watch([request]);152assert.strictEqual(watcher.isSuspended(request), false);153154const instance = Array.from(watcher.watchers)[0].instance;155assert.strictEqual(instance.isReusingRecursiveWatcher, false);156assert.strictEqual(instance.failed, false);157158// New file159const newFilePath = join(testDir, 'newFile.txt');160let changeFuture: Promise<unknown> = awaitEvent(watcher, newFilePath, FileChangeType.ADDED);161await Promises.writeFile(newFilePath, 'Hello World');162await changeFuture;163164// New folder165const newFolderPath = join(testDir, 'New Folder');166changeFuture = awaitEvent(watcher, newFolderPath, FileChangeType.ADDED);167await fs.promises.mkdir(newFolderPath);168await changeFuture;169170// Rename file171let renamedFilePath = join(testDir, 'renamedFile.txt');172changeFuture = Promise.all([173awaitEvent(watcher, newFilePath, FileChangeType.DELETED),174awaitEvent(watcher, renamedFilePath, FileChangeType.ADDED)175]);176await Promises.rename(newFilePath, renamedFilePath);177await changeFuture;178179// Rename folder180let renamedFolderPath = join(testDir, 'Renamed Folder');181changeFuture = Promise.all([182awaitEvent(watcher, newFolderPath, FileChangeType.DELETED),183awaitEvent(watcher, renamedFolderPath, FileChangeType.ADDED)184]);185await Promises.rename(newFolderPath, renamedFolderPath);186await changeFuture;187188// Rename file (same name, different case)189const caseRenamedFilePath = join(testDir, 'RenamedFile.txt');190changeFuture = Promise.all([191awaitEvent(watcher, renamedFilePath, FileChangeType.DELETED),192awaitEvent(watcher, caseRenamedFilePath, FileChangeType.ADDED)193]);194await Promises.rename(renamedFilePath, caseRenamedFilePath);195await changeFuture;196renamedFilePath = caseRenamedFilePath;197198// Rename folder (same name, different case)199const caseRenamedFolderPath = join(testDir, 'REnamed Folder');200changeFuture = Promise.all([201awaitEvent(watcher, renamedFolderPath, FileChangeType.DELETED),202awaitEvent(watcher, caseRenamedFolderPath, FileChangeType.ADDED)203]);204await Promises.rename(renamedFolderPath, caseRenamedFolderPath);205await changeFuture;206renamedFolderPath = caseRenamedFolderPath;207208// Move file209const movedFilepath = join(testDir, 'movedFile.txt');210changeFuture = Promise.all([211awaitEvent(watcher, renamedFilePath, FileChangeType.DELETED),212awaitEvent(watcher, movedFilepath, FileChangeType.ADDED)213]);214await Promises.rename(renamedFilePath, movedFilepath);215await changeFuture;216217// Move folder218const movedFolderpath = join(testDir, 'Moved Folder');219changeFuture = Promise.all([220awaitEvent(watcher, renamedFolderPath, FileChangeType.DELETED),221awaitEvent(watcher, movedFolderpath, FileChangeType.ADDED)222]);223await Promises.rename(renamedFolderPath, movedFolderpath);224await changeFuture;225226// Copy file227const copiedFilepath = join(testDir, 'copiedFile.txt');228changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.ADDED);229await fs.promises.copyFile(movedFilepath, copiedFilepath);230await changeFuture;231232// Copy folder233const copiedFolderpath = join(testDir, 'Copied Folder');234changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.ADDED);235await Promises.copy(movedFolderpath, copiedFolderpath, { preserveSymlinks: false });236await changeFuture;237238// Change file239changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.UPDATED);240await Promises.writeFile(copiedFilepath, 'Hello Change');241await changeFuture;242243// Create new file244const anotherNewFilePath = join(testDir, 'anotherNewFile.txt');245changeFuture = awaitEvent(watcher, anotherNewFilePath, FileChangeType.ADDED);246await Promises.writeFile(anotherNewFilePath, 'Hello Another World');247await changeFuture;248249// Delete file250changeFuture = awaitEvent(watcher, copiedFilepath, FileChangeType.DELETED);251await fs.promises.unlink(copiedFilepath);252await changeFuture;253254// Delete folder255changeFuture = awaitEvent(watcher, copiedFolderpath, FileChangeType.DELETED);256await fs.promises.rmdir(copiedFolderpath);257await changeFuture;258259watcher.dispose();260});261262test('basics (file watch)', async function () {263const filePath = join(testDir, 'lorem.txt');264const request = { path: filePath, excludes: [], recursive: false };265await watcher.watch([request]);266assert.strictEqual(watcher.isSuspended(request), false);267268const instance = Array.from(watcher.watchers)[0].instance;269assert.strictEqual(instance.isReusingRecursiveWatcher, false);270assert.strictEqual(instance.failed, false);271272// Change file273let changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED);274await Promises.writeFile(filePath, 'Hello Change');275await changeFuture;276277// Delete file278changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED);279await fs.promises.unlink(filePath);280await changeFuture;281282// Recreate watcher283await Promises.writeFile(filePath, 'Hello Change');284await watcher.watch([]);285await watcher.watch([{ path: filePath, excludes: [], recursive: false }]);286287// Move file288changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED);289await Promises.rename(filePath, `${filePath}-moved`);290await changeFuture;291});292293test('atomic writes (folder watch)', async function () {294await watcher.watch([{ path: testDir, excludes: [], recursive: false }]);295296// Delete + Recreate file297const newFilePath = join(testDir, 'lorem.txt');298const changeFuture: Promise<unknown> = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED);299await fs.promises.unlink(newFilePath);300Promises.writeFile(newFilePath, 'Hello Atomic World');301await changeFuture;302});303304test('atomic writes (file watch)', async function () {305const filePath = join(testDir, 'lorem.txt');306await watcher.watch([{ path: filePath, excludes: [], recursive: false }]);307308// Delete + Recreate file309const newFilePath = join(filePath);310const changeFuture: Promise<unknown> = awaitEvent(watcher, newFilePath, FileChangeType.UPDATED);311await fs.promises.unlink(newFilePath);312Promises.writeFile(newFilePath, 'Hello Atomic World');313await changeFuture;314});315316test('multiple events (folder watch)', async function () {317await watcher.watch([{ path: testDir, excludes: [], recursive: false }]);318319// multiple add320321const newFilePath1 = join(testDir, 'newFile-1.txt');322const newFilePath2 = join(testDir, 'newFile-2.txt');323const newFilePath3 = join(testDir, 'newFile-3.txt');324325const addedFuture1: Promise<unknown> = awaitEvent(watcher, newFilePath1, FileChangeType.ADDED);326const addedFuture2: Promise<unknown> = awaitEvent(watcher, newFilePath2, FileChangeType.ADDED);327const addedFuture3: Promise<unknown> = awaitEvent(watcher, newFilePath3, FileChangeType.ADDED);328329await Promise.all([330await Promises.writeFile(newFilePath1, 'Hello World 1'),331await Promises.writeFile(newFilePath2, 'Hello World 2'),332await Promises.writeFile(newFilePath3, 'Hello World 3'),333]);334335await Promise.all([addedFuture1, addedFuture2, addedFuture3]);336337// multiple change338339const changeFuture1: Promise<unknown> = awaitEvent(watcher, newFilePath1, FileChangeType.UPDATED);340const changeFuture2: Promise<unknown> = awaitEvent(watcher, newFilePath2, FileChangeType.UPDATED);341const changeFuture3: Promise<unknown> = awaitEvent(watcher, newFilePath3, FileChangeType.UPDATED);342343await Promise.all([344await Promises.writeFile(newFilePath1, 'Hello Update 1'),345await Promises.writeFile(newFilePath2, 'Hello Update 2'),346await Promises.writeFile(newFilePath3, 'Hello Update 3'),347]);348349await Promise.all([changeFuture1, changeFuture2, changeFuture3]);350351// copy with multiple files352353const copyFuture1: Promise<unknown> = awaitEvent(watcher, join(testDir, 'newFile-1-copy.txt'), FileChangeType.ADDED);354const copyFuture2: Promise<unknown> = awaitEvent(watcher, join(testDir, 'newFile-2-copy.txt'), FileChangeType.ADDED);355const copyFuture3: Promise<unknown> = awaitEvent(watcher, join(testDir, 'newFile-3-copy.txt'), FileChangeType.ADDED);356357await Promise.all([358Promises.copy(join(testDir, 'newFile-1.txt'), join(testDir, 'newFile-1-copy.txt'), { preserveSymlinks: false }),359Promises.copy(join(testDir, 'newFile-2.txt'), join(testDir, 'newFile-2-copy.txt'), { preserveSymlinks: false }),360Promises.copy(join(testDir, 'newFile-3.txt'), join(testDir, 'newFile-3-copy.txt'), { preserveSymlinks: false })361]);362363await Promise.all([copyFuture1, copyFuture2, copyFuture3]);364365// multiple delete366367const deleteFuture1: Promise<unknown> = awaitEvent(watcher, newFilePath1, FileChangeType.DELETED);368const deleteFuture2: Promise<unknown> = awaitEvent(watcher, newFilePath2, FileChangeType.DELETED);369const deleteFuture3: Promise<unknown> = awaitEvent(watcher, newFilePath3, FileChangeType.DELETED);370371await Promise.all([372await fs.promises.unlink(newFilePath1),373await fs.promises.unlink(newFilePath2),374await fs.promises.unlink(newFilePath3)375]);376377await Promise.all([deleteFuture1, deleteFuture2, deleteFuture3]);378});379380test('multiple events (file watch)', async function () {381const filePath = join(testDir, 'lorem.txt');382await watcher.watch([{ path: filePath, excludes: [], recursive: false }]);383384// multiple change385386const changeFuture1: Promise<unknown> = awaitEvent(watcher, filePath, FileChangeType.UPDATED);387388await Promise.all([389await Promises.writeFile(filePath, 'Hello Update 1'),390await Promises.writeFile(filePath, 'Hello Update 2'),391await Promises.writeFile(filePath, 'Hello Update 3'),392]);393394await Promise.all([changeFuture1]);395});396397test('excludes can be updated (folder watch)', async function () {398await watcher.watch([{ path: testDir, excludes: ['**'], recursive: false }]);399await watcher.watch([{ path: testDir, excludes: [], recursive: false }]);400401return basicCrudTest(join(testDir, 'files-excludes.txt'));402});403404test('excludes are ignored (file watch)', async function () {405const filePath = join(testDir, 'lorem.txt');406await watcher.watch([{ path: filePath, excludes: ['**'], recursive: false }]);407408return basicCrudTest(filePath, true);409});410411test('includes can be updated (folder watch)', async function () {412await watcher.watch([{ path: testDir, excludes: [], includes: ['nothing'], recursive: false }]);413await watcher.watch([{ path: testDir, excludes: [], recursive: false }]);414415return basicCrudTest(join(testDir, 'files-includes.txt'));416});417418test('non-includes are ignored (file watch)', async function () {419const filePath = join(testDir, 'lorem.txt');420await watcher.watch([{ path: filePath, excludes: [], includes: ['nothing'], recursive: false }]);421422return basicCrudTest(filePath, true);423});424425test('includes are supported (folder watch)', async function () {426await watcher.watch([{ path: testDir, excludes: [], includes: ['**/files-includes.txt'], recursive: false }]);427428return basicCrudTest(join(testDir, 'files-includes.txt'));429});430431test('includes are supported (folder watch, relative pattern explicit)', async function () {432await watcher.watch([{ path: testDir, excludes: [], includes: [{ base: testDir, pattern: 'files-includes.txt' }], recursive: false }]);433434return basicCrudTest(join(testDir, 'files-includes.txt'));435});436437test('includes are supported (folder watch, relative pattern implicit)', async function () {438await watcher.watch([{ path: testDir, excludes: [], includes: ['files-includes.txt'], recursive: false }]);439440return basicCrudTest(join(testDir, 'files-includes.txt'));441});442443test('correlationId is supported', async function () {444const correlationId = Math.random();445await watcher.watch([{ correlationId, path: testDir, excludes: [], recursive: false }]);446447return basicCrudTest(join(testDir, 'newFile.txt'), undefined, correlationId);448});449450(isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (folder watch)', async function () {451const link = join(testDir, 'deep-linked');452const linkTarget = join(testDir, 'deep');453await fs.promises.symlink(linkTarget, link);454455await watcher.watch([{ path: link, excludes: [], recursive: false }]);456457return basicCrudTest(join(link, 'newFile.txt'));458});459460async function basicCrudTest(filePath: string, skipAdd?: boolean, correlationId?: number | null, expectedCount?: number, awaitWatchAfterAdd?: boolean): Promise<void> {461let changeFuture: Promise<unknown>;462463// New file464if (!skipAdd) {465changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED, correlationId, expectedCount);466await Promises.writeFile(filePath, 'Hello World');467await changeFuture;468if (awaitWatchAfterAdd) {469await Event.toPromise(watcher.onDidWatch);470}471}472473// Change file474changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, correlationId, expectedCount);475await Promises.writeFile(filePath, 'Hello Change');476await changeFuture;477478// Delete file479changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, correlationId, expectedCount);480await fs.promises.unlink(await Promises.realpath(filePath)); // support symlinks481await changeFuture;482}483484(isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (file watch)', async function () {485const link = join(testDir, 'lorem.txt-linked');486const linkTarget = join(testDir, 'lorem.txt');487await fs.promises.symlink(linkTarget, link);488489await watcher.watch([{ path: link, excludes: [], recursive: false }]);490491return basicCrudTest(link, true);492});493494(!isWindows /* UNC is windows only */ ? test.skip : test)('unc support (folder watch)', async function () {495addUNCHostToAllowlist('localhost');496497// Local UNC paths are in the form of: \\localhost\c$\my_dir498const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}`;499500await watcher.watch([{ path: uncPath, excludes: [], recursive: false }]);501502return basicCrudTest(join(uncPath, 'newFile.txt'));503});504505(!isWindows /* UNC is windows only */ ? test.skip : test)('unc support (file watch)', async function () {506addUNCHostToAllowlist('localhost');507508// Local UNC paths are in the form of: \\localhost\c$\my_dir509const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}\\lorem.txt`;510511await watcher.watch([{ path: uncPath, excludes: [], recursive: false }]);512513return basicCrudTest(uncPath, true);514});515516(isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing (folder watch)', async function () {517const wrongCase = join(dirname(testDir), basename(testDir).toUpperCase());518519await watcher.watch([{ path: wrongCase, excludes: [], recursive: false }]);520521return basicCrudTest(join(wrongCase, 'newFile.txt'));522});523524(isLinux /* linux: is case sensitive */ ? test.skip : test)('wrong casing (file watch)', async function () {525const filePath = join(testDir, 'LOREM.txt');526await watcher.watch([{ path: filePath, excludes: [], recursive: false }]);527528return basicCrudTest(filePath, true);529});530531test('invalid path does not explode', async function () {532const invalidPath = join(testDir, 'invalid');533534await watcher.watch([{ path: invalidPath, excludes: [], recursive: false }]);535});536537test('watchFileContents', async function () {538const watchedPath = join(testDir, 'lorem.txt');539540const cts = new CancellationTokenSource();541542const readyPromise = new DeferredPromise<void>();543const chunkPromise = new DeferredPromise<void>();544const watchPromise = watchFileContents(watchedPath, () => chunkPromise.complete(), () => readyPromise.complete(), cts.token);545546await readyPromise.p;547548Promises.writeFile(watchedPath, 'Hello World');549550await chunkPromise.p;551552cts.cancel(); // this will resolve `watchPromise`553554return watchPromise;555});556557test('watching same or overlapping paths supported when correlation is applied', async function () {558await watcher.watch([559{ path: testDir, excludes: [], recursive: false, correlationId: 1 }560]);561562await basicCrudTest(join(testDir, 'newFile_1.txt'), undefined, null, 1);563564await watcher.watch([565{ path: testDir, excludes: [], recursive: false, correlationId: 1 },566{ path: testDir, excludes: [], recursive: false, correlationId: 2, },567{ path: testDir, excludes: [], recursive: false, correlationId: undefined }568]);569570await basicCrudTest(join(testDir, 'newFile_2.txt'), undefined, null, 3);571await basicCrudTest(join(testDir, 'otherNewFile.txt'), undefined, null, 3);572});573574test('watching missing path emits watcher fail event', async function () {575const onDidWatchFail = Event.toPromise(watcher.onWatchFail);576577const folderPath = join(testDir, 'missing');578watcher.watch([{ path: folderPath, excludes: [], recursive: true }]);579580await onDidWatchFail;581});582583test('deleting watched path emits watcher fail and delete event when correlated (file watch)', async function () {584const filePath = join(testDir, 'lorem.txt');585586await watcher.watch([{ path: filePath, excludes: [], recursive: false, correlationId: 1 }]);587588const instance = Array.from(watcher.watchers)[0].instance;589590const onDidWatchFail = Event.toPromise(watcher.onWatchFail);591const changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1);592fs.promises.unlink(filePath);593await onDidWatchFail;594await changeFuture;595assert.strictEqual(instance.failed, true);596});597598(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 () {599const folderPath = join(testDir, 'deep');600601await watcher.watch([{ path: folderPath, excludes: [], recursive: false, correlationId: 1 }]);602603const onDidWatchFail = Event.toPromise(watcher.onWatchFail);604const changeFuture = awaitEvent(watcher, folderPath, FileChangeType.DELETED, 1);605Promises.rm(folderPath, RimRafMode.UNLINK);606await onDidWatchFail;607await changeFuture;608});609610test('watch requests support suspend/resume (file, does not exist in beginning)', async function () {611const filePath = join(testDir, 'not-found.txt');612613const onDidWatchFail = Event.toPromise(watcher.onWatchFail);614const request = { path: filePath, excludes: [], recursive: false };615await watcher.watch([request]);616await onDidWatchFail;617assert.strictEqual(watcher.isSuspended(request), 'polling');618619await basicCrudTest(filePath, undefined, null, undefined, true);620await basicCrudTest(filePath, undefined, null, undefined, true);621});622623test('watch requests support suspend/resume (file, exists in beginning)', async function () {624const filePath = join(testDir, 'lorem.txt');625const request = { path: filePath, excludes: [], recursive: false };626await watcher.watch([request]);627628const onDidWatchFail = Event.toPromise(watcher.onWatchFail);629await basicCrudTest(filePath, true);630await onDidWatchFail;631assert.strictEqual(watcher.isSuspended(request), 'polling');632633await basicCrudTest(filePath, undefined, null, undefined, true);634});635636(isWindows /* Windows: does not seem to report this */ ? test.skip : test)('watch requests support suspend/resume (folder, does not exist in beginning)', async function () {637let onDidWatchFail = Event.toPromise(watcher.onWatchFail);638639const folderPath = join(testDir, 'not-found');640const request = { path: folderPath, excludes: [], recursive: false };641await watcher.watch([request]);642await onDidWatchFail;643assert.strictEqual(watcher.isSuspended(request), 'polling');644645let changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED);646let onDidWatch = Event.toPromise(watcher.onDidWatch);647await fs.promises.mkdir(folderPath);648await changeFuture;649await onDidWatch;650651assert.strictEqual(watcher.isSuspended(request), false);652653if (isWindows) { // somehow failing on macOS/Linux654const filePath = join(folderPath, 'newFile.txt');655await basicCrudTest(filePath);656657onDidWatchFail = Event.toPromise(watcher.onWatchFail);658await fs.promises.rmdir(folderPath);659await onDidWatchFail;660661changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED);662onDidWatch = Event.toPromise(watcher.onDidWatch);663await fs.promises.mkdir(folderPath);664await changeFuture;665await onDidWatch;666667await timeout(500); // somehow needed on Linux668669await basicCrudTest(filePath);670}671});672673(isMacintosh /* macOS: does not seem to report this */ ? test.skip : test)('watch requests support suspend/resume (folder, exists in beginning)', async function () {674const folderPath = join(testDir, 'deep');675await watcher.watch([{ path: folderPath, excludes: [], recursive: false }]);676677const filePath = join(folderPath, 'newFile.txt');678await basicCrudTest(filePath);679680const onDidWatchFail = Event.toPromise(watcher.onWatchFail);681await Promises.rm(folderPath);682await onDidWatchFail;683684const changeFuture = awaitEvent(watcher, folderPath, FileChangeType.ADDED);685const onDidWatch = Event.toPromise(watcher.onDidWatch);686await fs.promises.mkdir(folderPath);687await changeFuture;688await onDidWatch;689690await timeout(500); // somehow needed on Linux691692await basicCrudTest(filePath);693});694695test('parcel watcher reused when present for non-recursive file watching (uncorrelated)', function () {696return testParcelWatcherReused(undefined);697});698699test('parcel watcher reused when present for non-recursive file watching (correlated)', function () {700return testParcelWatcherReused(2);701});702703function createParcelWatcher() {704const recursiveWatcher = new TestParcelWatcher();705recursiveWatcher.setVerboseLogging(loggingEnabled);706recursiveWatcher.onDidLogMessage(e => {707if (loggingEnabled) {708console.log(`[recursive watcher test message] ${e.message}`);709}710});711712recursiveWatcher.onDidError(e => {713if (loggingEnabled) {714console.log(`[recursive watcher test error] ${e.error}`);715}716});717718return recursiveWatcher;719}720721async function testParcelWatcherReused(correlationId: number | undefined) {722const recursiveWatcher = createParcelWatcher();723await recursiveWatcher.watch([{ path: testDir, excludes: [], recursive: true, correlationId: 1 }]);724725const recursiveInstance = Array.from(recursiveWatcher.watchers)[0];726assert.strictEqual(recursiveInstance.subscriptionsCount, 0);727728await createWatcher(recursiveWatcher);729730const filePath = join(testDir, 'deep', 'conway.js');731await watcher.watch([{ path: filePath, excludes: [], recursive: false, correlationId }]);732733const { instance } = Array.from(watcher.watchers)[0];734assert.strictEqual(instance.isReusingRecursiveWatcher, true);735assert.strictEqual(recursiveInstance.subscriptionsCount, 1);736737let changeFuture = awaitEvent(watcher, filePath, isMacintosh /* somehow fsevents seems to report still on the initial create from test setup */ ? FileChangeType.ADDED : FileChangeType.UPDATED, correlationId);738await Promises.writeFile(filePath, 'Hello World');739await changeFuture;740741await recursiveWatcher.stop();742recursiveWatcher.dispose();743744await timeout(500); // give the watcher some time to restart745746changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, correlationId);747await Promises.writeFile(filePath, 'Hello World');748await changeFuture;749750assert.strictEqual(instance.isReusingRecursiveWatcher, false);751}752753test('watch requests support suspend/resume (file, does not exist in beginning, parcel watcher reused)', async function () {754const recursiveWatcher = createParcelWatcher();755await recursiveWatcher.watch([{ path: testDir, excludes: [], recursive: true }]);756757await createWatcher(recursiveWatcher);758759const filePath = join(testDir, 'not-found-2.txt');760761const onDidWatchFail = Event.toPromise(watcher.onWatchFail);762const request = { path: filePath, excludes: [], recursive: false };763await watcher.watch([request]);764await onDidWatchFail;765assert.strictEqual(watcher.isSuspended(request), true);766767const changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED);768await Promises.writeFile(filePath, 'Hello World');769await changeFuture;770771assert.strictEqual(watcher.isSuspended(request), false);772});773774test('event type filter (file watch)', async function () {775const filePath = join(testDir, 'lorem.txt');776const request = { path: filePath, excludes: [], recursive: false, filter: FileChangeFilter.UPDATED | FileChangeFilter.DELETED, correlationId: 1 };777await watcher.watch([request]);778779// Change file780let changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, 1);781await Promises.writeFile(filePath, 'Hello Change');782await changeFuture;783784// Delete file785changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1);786await fs.promises.unlink(filePath);787await changeFuture;788});789790test('event type filter (folder watch)', async function () {791const request = { path: testDir, excludes: [], recursive: false, filter: FileChangeFilter.UPDATED | FileChangeFilter.DELETED, correlationId: 1 };792await watcher.watch([request]);793794// Change file795const filePath = join(testDir, 'lorem.txt');796let changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, 1);797await Promises.writeFile(filePath, 'Hello Change');798await changeFuture;799800// Delete file801changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, 1);802await fs.promises.unlink(filePath);803await changeFuture;804});805});806807808