Path: blob/main/src/vs/platform/files/test/common/watcher.test.ts
5247 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 assert from 'assert';6import { Emitter, Event } from '../../../../base/common/event.js';7import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';8import { isLinux, isWindows } from '../../../../base/common/platform.js';9import { isEqual } from '../../../../base/common/resources.js';10import { URI } from '../../../../base/common/uri.js';11import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';12import { FileChangeFilter, FileChangesEvent, FileChangeType, IFileChange } from '../../common/files.js';13import { coalesceEvents, reviveFileChanges, parseWatcherPatterns, isFiltered } from '../../common/watcher.js';1415class TestFileWatcher extends Disposable {16private readonly _onDidFilesChange: Emitter<{ raw: IFileChange[]; event: FileChangesEvent }>;1718constructor() {19super();2021this._onDidFilesChange = this._register(new Emitter<{ raw: IFileChange[]; event: FileChangesEvent }>());22}2324get onDidFilesChange(): Event<{ raw: IFileChange[]; event: FileChangesEvent }> {25return this._onDidFilesChange.event;26}2728report(changes: IFileChange[]): void {29this.onRawFileEvents(changes);30}3132private onRawFileEvents(events: IFileChange[]): void {3334// Coalesce35const coalescedEvents = coalesceEvents(events);3637// Emit through event emitter38if (coalescedEvents.length > 0) {39this._onDidFilesChange.fire({ raw: reviveFileChanges(coalescedEvents), event: this.toFileChangesEvent(coalescedEvents) });40}41}4243private toFileChangesEvent(changes: IFileChange[]): FileChangesEvent {44return new FileChangesEvent(reviveFileChanges(changes), !isLinux);45}46}4748enum Path {49UNIX,50WINDOWS,51UNC52}5354suite('Watcher', () => {5556(isWindows ? test.skip : test)('parseWatcherPatterns - posix', () => {57const path = '/users/data/src';58let parsedPattern = parseWatcherPatterns(path, ['*.js'], false)[0];5960assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);61assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);62assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), false);6364parsedPattern = parseWatcherPatterns(path, ['/users/data/src/*.js'], false)[0];6566assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);67assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);68assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), false);6970parsedPattern = parseWatcherPatterns(path, ['/users/data/src/bar/*.js'], false)[0];7172assert.strictEqual(parsedPattern('/users/data/src/foo.js'), false);73assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);74assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), true);7576parsedPattern = parseWatcherPatterns(path, ['**/*.js'], false)[0];7778assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);79assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);80assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), true);81});8283(!isWindows ? test.skip : test)('parseWatcherPatterns - windows', () => {84const path = 'c:\\users\\data\\src';85let parsedPattern = parseWatcherPatterns(path, ['*.js'], true)[0];8687assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);88assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);89assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar/foo.js'), false);9091parsedPattern = parseWatcherPatterns(path, ['c:\\users\\data\\src\\*.js'], true)[0];9293assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);94assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);95assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\foo.js'), false);9697parsedPattern = parseWatcherPatterns(path, ['c:\\users\\data\\src\\bar/*.js'], true)[0];9899assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), false);100assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);101assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\foo.js'), true);102103parsedPattern = parseWatcherPatterns(path, ['**/*.js'], true)[0];104105assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);106assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);107assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\foo.js'), true);108});109110(isWindows ? test.skip : test)('parseWatcherPatterns - posix (case insensitive)', () => {111const path = '/users/data/src';112let parsedPattern = parseWatcherPatterns(path, ['*.JS'], false)[0];113114// Case sensitive by default on posix115assert.strictEqual(parsedPattern('/users/data/src/foo.js'), false);116assert.strictEqual(parsedPattern('/users/data/src/foo.JS'), true);117assert.strictEqual(parsedPattern('/users/data/src/foo.Js'), false);118119// Now test with GlobCaseSensitivity.caseInsensitive120parsedPattern = parseWatcherPatterns(path, ['*.JS'], true)[0];121122assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);123assert.strictEqual(parsedPattern('/users/data/src/foo.JS'), true);124assert.strictEqual(parsedPattern('/users/data/src/foo.Js'), true);125assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);126127parsedPattern = parseWatcherPatterns(path, ['/users/data/src/*.JS'], true)[0];128129assert.strictEqual(parsedPattern('/users/data/src/foo.js'), true);130assert.strictEqual(parsedPattern('/users/data/src/foo.JS'), true);131assert.strictEqual(parsedPattern('/users/data/src/foo.ts'), false);132assert.strictEqual(parsedPattern('/users/data/src/bar/foo.js'), false);133134parsedPattern = parseWatcherPatterns(path, ['**/Test*.JS'], true)[0];135136assert.strictEqual(parsedPattern('/users/data/src/test1.js'), true);137assert.strictEqual(parsedPattern('/users/data/src/Test1.js'), true);138assert.strictEqual(parsedPattern('/users/data/src/TEST1.JS'), true);139assert.strictEqual(parsedPattern('/users/data/src/bar/test2.js'), true);140assert.strictEqual(parsedPattern('/users/data/src/bar/TEST2.JS'), true);141assert.strictEqual(parsedPattern('/users/data/src/foo.js'), false);142});143144(!isWindows ? test.skip : test)('parseWatcherPatterns - windows (case insensitive)', () => {145const path = 'c:\\users\\data\\src';146let parsedPattern = parseWatcherPatterns(path, ['*.JS'], true)[0];147148// Windows is case insensitive by default149assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);150assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.JS'), true);151assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.Js'), true);152assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);153154// Explicit GlobCaseSensitivity.caseInsensitive should work the same155parsedPattern = parseWatcherPatterns(path, ['*.JS'], true)[0];156157assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);158assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.JS'), true);159assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.Js'), true);160assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);161162parsedPattern = parseWatcherPatterns(path, ['c:\\users\\data\\src\\*.JS'], true)[0];163164assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), true);165assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.JS'), true);166assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.ts'), false);167assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\foo.js'), false);168169parsedPattern = parseWatcherPatterns(path, ['**/Test*.JS'], true)[0];170171assert.strictEqual(parsedPattern('c:\\users\\data\\src\\test1.js'), true);172assert.strictEqual(parsedPattern('c:\\users\\data\\src\\Test1.js'), true);173assert.strictEqual(parsedPattern('c:\\users\\data\\src\\TEST1.JS'), true);174assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\test2.js'), true);175assert.strictEqual(parsedPattern('c:\\users\\data\\src\\bar\\TEST2.JS'), true);176assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), false);177178// Test with case sensitive mode explicitly179parsedPattern = parseWatcherPatterns(path, ['*.JS'], false)[0];180181assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.js'), false);182assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.JS'), true);183assert.strictEqual(parsedPattern('c:\\users\\data\\src\\foo.Js'), false);184});185186ensureNoDisposablesAreLeakedInTestSuite();187});188189suite('Watcher Events Normalizer', () => {190191const disposables = new DisposableStore();192193teardown(() => {194disposables.clear();195});196197test('simple add/update/delete', done => {198const watch = disposables.add(new TestFileWatcher());199200const added = URI.file('/users/data/src/added.txt');201const updated = URI.file('/users/data/src/updated.txt');202const deleted = URI.file('/users/data/src/deleted.txt');203204const raw: IFileChange[] = [205{ resource: added, type: FileChangeType.ADDED },206{ resource: updated, type: FileChangeType.UPDATED },207{ resource: deleted, type: FileChangeType.DELETED },208];209210disposables.add(watch.onDidFilesChange(({ event, raw }) => {211assert.ok(event);212assert.strictEqual(raw.length, 3);213assert.ok(event.contains(added, FileChangeType.ADDED));214assert.ok(event.contains(updated, FileChangeType.UPDATED));215assert.ok(event.contains(deleted, FileChangeType.DELETED));216217done();218}));219220watch.report(raw);221});222223(isWindows ? [Path.WINDOWS, Path.UNC] : [Path.UNIX]).forEach(path => {224test(`delete only reported for top level folder (${path})`, done => {225const watch = disposables.add(new TestFileWatcher());226227const deletedFolderA = URI.file(path === Path.UNIX ? '/users/data/src/todelete1' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete1' : '\\\\localhost\\users\\data\\src\\todelete1');228const deletedFolderB = URI.file(path === Path.UNIX ? '/users/data/src/todelete2' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete2' : '\\\\localhost\\users\\data\\src\\todelete2');229const 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');230const 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');231const 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');232const 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');233234const 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');235const 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');236237const raw: IFileChange[] = [238{ resource: deletedFolderA, type: FileChangeType.DELETED },239{ resource: deletedFolderB, type: FileChangeType.DELETED },240{ resource: deletedFolderBF1, type: FileChangeType.DELETED },241{ resource: deletedFolderBF2, type: FileChangeType.DELETED },242{ resource: deletedFolderBF3, type: FileChangeType.DELETED },243{ resource: deletedFileA, type: FileChangeType.DELETED },244{ resource: addedFile, type: FileChangeType.ADDED },245{ resource: updatedFile, type: FileChangeType.UPDATED }246];247248disposables.add(watch.onDidFilesChange(({ event, raw }) => {249assert.ok(event);250assert.strictEqual(raw.length, 5);251252assert.ok(event.contains(deletedFolderA, FileChangeType.DELETED));253assert.ok(event.contains(deletedFolderB, FileChangeType.DELETED));254assert.ok(event.contains(deletedFileA, FileChangeType.DELETED));255assert.ok(event.contains(addedFile, FileChangeType.ADDED));256assert.ok(event.contains(updatedFile, FileChangeType.UPDATED));257258done();259}));260261watch.report(raw);262});263});264265test('event coalescer: ignore CREATE followed by DELETE', done => {266const watch = disposables.add(new TestFileWatcher());267268const created = URI.file('/users/data/src/related');269const deleted = URI.file('/users/data/src/related');270const unrelated = URI.file('/users/data/src/unrelated');271272const raw: IFileChange[] = [273{ resource: created, type: FileChangeType.ADDED },274{ resource: deleted, type: FileChangeType.DELETED },275{ resource: unrelated, type: FileChangeType.UPDATED },276];277278disposables.add(watch.onDidFilesChange(({ event, raw }) => {279assert.ok(event);280assert.strictEqual(raw.length, 1);281282assert.ok(event.contains(unrelated, FileChangeType.UPDATED));283284done();285}));286287watch.report(raw);288});289290test('event coalescer: flatten DELETE followed by CREATE into CHANGE', done => {291const watch = disposables.add(new TestFileWatcher());292293const deleted = URI.file('/users/data/src/related');294const created = URI.file('/users/data/src/related');295const unrelated = URI.file('/users/data/src/unrelated');296297const raw: IFileChange[] = [298{ resource: deleted, type: FileChangeType.DELETED },299{ resource: created, type: FileChangeType.ADDED },300{ resource: unrelated, type: FileChangeType.UPDATED },301];302303disposables.add(watch.onDidFilesChange(({ event, raw }) => {304assert.ok(event);305assert.strictEqual(raw.length, 2);306307assert.ok(event.contains(deleted, FileChangeType.UPDATED));308assert.ok(event.contains(unrelated, FileChangeType.UPDATED));309310done();311}));312313watch.report(raw);314});315316test('event coalescer: ignore UPDATE when CREATE received', done => {317const watch = disposables.add(new TestFileWatcher());318319const created = URI.file('/users/data/src/related');320const updated = URI.file('/users/data/src/related');321const unrelated = URI.file('/users/data/src/unrelated');322323const raw: IFileChange[] = [324{ resource: created, type: FileChangeType.ADDED },325{ resource: updated, type: FileChangeType.UPDATED },326{ resource: unrelated, type: FileChangeType.UPDATED },327];328329disposables.add(watch.onDidFilesChange(({ event, raw }) => {330assert.ok(event);331assert.strictEqual(raw.length, 2);332333assert.ok(event.contains(created, FileChangeType.ADDED));334assert.ok(!event.contains(created, FileChangeType.UPDATED));335assert.ok(event.contains(unrelated, FileChangeType.UPDATED));336337done();338}));339340watch.report(raw);341});342343test('event coalescer: apply DELETE', done => {344const watch = disposables.add(new TestFileWatcher());345346const updated = URI.file('/users/data/src/related');347const updated2 = URI.file('/users/data/src/related');348const deleted = URI.file('/users/data/src/related');349const unrelated = URI.file('/users/data/src/unrelated');350351const raw: IFileChange[] = [352{ resource: updated, type: FileChangeType.UPDATED },353{ resource: updated2, type: FileChangeType.UPDATED },354{ resource: unrelated, type: FileChangeType.UPDATED },355{ resource: updated, type: FileChangeType.DELETED }356];357358disposables.add(watch.onDidFilesChange(({ event, raw }) => {359assert.ok(event);360assert.strictEqual(raw.length, 2);361362assert.ok(event.contains(deleted, FileChangeType.DELETED));363assert.ok(!event.contains(updated, FileChangeType.UPDATED));364assert.ok(event.contains(unrelated, FileChangeType.UPDATED));365366done();367}));368369watch.report(raw);370});371372test('event coalescer: track case renames', done => {373const watch = disposables.add(new TestFileWatcher());374375const oldPath = URI.file('/users/data/src/added');376const newPath = URI.file('/users/data/src/ADDED');377378const raw: IFileChange[] = [379{ resource: newPath, type: FileChangeType.ADDED },380{ resource: oldPath, type: FileChangeType.DELETED }381];382383disposables.add(watch.onDidFilesChange(({ event, raw }) => {384assert.ok(event);385assert.strictEqual(raw.length, 2);386387for (const r of raw) {388if (isEqual(r.resource, oldPath)) {389assert.strictEqual(r.type, FileChangeType.DELETED);390} else if (isEqual(r.resource, newPath)) {391assert.strictEqual(r.type, FileChangeType.ADDED);392} else {393assert.fail();394}395}396397done();398}));399400watch.report(raw);401});402403test('event type filter', () => {404const resource = URI.file('/users/data/src/related');405406assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, undefined), false);407assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, undefined), false);408assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, undefined), false);409410assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED), true);411assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED | FileChangeFilter.DELETED), true);412413assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED), false);414assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED), false);415assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED | FileChangeFilter.DELETED), false);416417assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED), true);418assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED | FileChangeFilter.ADDED), true);419420assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED), false);421assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);422assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);423424assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED), true);425assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.ADDED), true);426427assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.UPDATED), false);428assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);429assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);430});431432ensureNoDisposablesAreLeakedInTestSuite();433});434435436