Path: blob/main/src/vs/platform/files/test/common/watcher.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 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'])[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'])[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'])[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'])[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'])[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'])[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'])[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'])[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});109110ensureNoDisposablesAreLeakedInTestSuite();111});112113suite('Watcher Events Normalizer', () => {114115const disposables = new DisposableStore();116117teardown(() => {118disposables.clear();119});120121test('simple add/update/delete', done => {122const watch = disposables.add(new TestFileWatcher());123124const added = URI.file('/users/data/src/added.txt');125const updated = URI.file('/users/data/src/updated.txt');126const deleted = URI.file('/users/data/src/deleted.txt');127128const raw: IFileChange[] = [129{ resource: added, type: FileChangeType.ADDED },130{ resource: updated, type: FileChangeType.UPDATED },131{ resource: deleted, type: FileChangeType.DELETED },132];133134disposables.add(watch.onDidFilesChange(({ event, raw }) => {135assert.ok(event);136assert.strictEqual(raw.length, 3);137assert.ok(event.contains(added, FileChangeType.ADDED));138assert.ok(event.contains(updated, FileChangeType.UPDATED));139assert.ok(event.contains(deleted, FileChangeType.DELETED));140141done();142}));143144watch.report(raw);145});146147(isWindows ? [Path.WINDOWS, Path.UNC] : [Path.UNIX]).forEach(path => {148test(`delete only reported for top level folder (${path})`, done => {149const watch = disposables.add(new TestFileWatcher());150151const deletedFolderA = URI.file(path === Path.UNIX ? '/users/data/src/todelete1' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete1' : '\\\\localhost\\users\\data\\src\\todelete1');152const deletedFolderB = URI.file(path === Path.UNIX ? '/users/data/src/todelete2' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\todelete2' : '\\\\localhost\\users\\data\\src\\todelete2');153const 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');154const 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');155const 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');156const 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');157158const 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');159const 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');160161const raw: IFileChange[] = [162{ resource: deletedFolderA, type: FileChangeType.DELETED },163{ resource: deletedFolderB, type: FileChangeType.DELETED },164{ resource: deletedFolderBF1, type: FileChangeType.DELETED },165{ resource: deletedFolderBF2, type: FileChangeType.DELETED },166{ resource: deletedFolderBF3, type: FileChangeType.DELETED },167{ resource: deletedFileA, type: FileChangeType.DELETED },168{ resource: addedFile, type: FileChangeType.ADDED },169{ resource: updatedFile, type: FileChangeType.UPDATED }170];171172disposables.add(watch.onDidFilesChange(({ event, raw }) => {173assert.ok(event);174assert.strictEqual(raw.length, 5);175176assert.ok(event.contains(deletedFolderA, FileChangeType.DELETED));177assert.ok(event.contains(deletedFolderB, FileChangeType.DELETED));178assert.ok(event.contains(deletedFileA, FileChangeType.DELETED));179assert.ok(event.contains(addedFile, FileChangeType.ADDED));180assert.ok(event.contains(updatedFile, FileChangeType.UPDATED));181182done();183}));184185watch.report(raw);186});187});188189test('event coalescer: ignore CREATE followed by DELETE', done => {190const watch = disposables.add(new TestFileWatcher());191192const created = URI.file('/users/data/src/related');193const deleted = URI.file('/users/data/src/related');194const unrelated = URI.file('/users/data/src/unrelated');195196const raw: IFileChange[] = [197{ resource: created, type: FileChangeType.ADDED },198{ resource: deleted, type: FileChangeType.DELETED },199{ resource: unrelated, type: FileChangeType.UPDATED },200];201202disposables.add(watch.onDidFilesChange(({ event, raw }) => {203assert.ok(event);204assert.strictEqual(raw.length, 1);205206assert.ok(event.contains(unrelated, FileChangeType.UPDATED));207208done();209}));210211watch.report(raw);212});213214test('event coalescer: flatten DELETE followed by CREATE into CHANGE', done => {215const watch = disposables.add(new TestFileWatcher());216217const deleted = URI.file('/users/data/src/related');218const created = URI.file('/users/data/src/related');219const unrelated = URI.file('/users/data/src/unrelated');220221const raw: IFileChange[] = [222{ resource: deleted, type: FileChangeType.DELETED },223{ resource: created, type: FileChangeType.ADDED },224{ resource: unrelated, type: FileChangeType.UPDATED },225];226227disposables.add(watch.onDidFilesChange(({ event, raw }) => {228assert.ok(event);229assert.strictEqual(raw.length, 2);230231assert.ok(event.contains(deleted, FileChangeType.UPDATED));232assert.ok(event.contains(unrelated, FileChangeType.UPDATED));233234done();235}));236237watch.report(raw);238});239240test('event coalescer: ignore UPDATE when CREATE received', done => {241const watch = disposables.add(new TestFileWatcher());242243const created = URI.file('/users/data/src/related');244const updated = URI.file('/users/data/src/related');245const unrelated = URI.file('/users/data/src/unrelated');246247const raw: IFileChange[] = [248{ resource: created, type: FileChangeType.ADDED },249{ resource: updated, type: FileChangeType.UPDATED },250{ resource: unrelated, type: FileChangeType.UPDATED },251];252253disposables.add(watch.onDidFilesChange(({ event, raw }) => {254assert.ok(event);255assert.strictEqual(raw.length, 2);256257assert.ok(event.contains(created, FileChangeType.ADDED));258assert.ok(!event.contains(created, FileChangeType.UPDATED));259assert.ok(event.contains(unrelated, FileChangeType.UPDATED));260261done();262}));263264watch.report(raw);265});266267test('event coalescer: apply DELETE', done => {268const watch = disposables.add(new TestFileWatcher());269270const updated = URI.file('/users/data/src/related');271const updated2 = URI.file('/users/data/src/related');272const deleted = URI.file('/users/data/src/related');273const unrelated = URI.file('/users/data/src/unrelated');274275const raw: IFileChange[] = [276{ resource: updated, type: FileChangeType.UPDATED },277{ resource: updated2, type: FileChangeType.UPDATED },278{ resource: unrelated, type: FileChangeType.UPDATED },279{ resource: updated, type: FileChangeType.DELETED }280];281282disposables.add(watch.onDidFilesChange(({ event, raw }) => {283assert.ok(event);284assert.strictEqual(raw.length, 2);285286assert.ok(event.contains(deleted, FileChangeType.DELETED));287assert.ok(!event.contains(updated, FileChangeType.UPDATED));288assert.ok(event.contains(unrelated, FileChangeType.UPDATED));289290done();291}));292293watch.report(raw);294});295296test('event coalescer: track case renames', done => {297const watch = disposables.add(new TestFileWatcher());298299const oldPath = URI.file('/users/data/src/added');300const newPath = URI.file('/users/data/src/ADDED');301302const raw: IFileChange[] = [303{ resource: newPath, type: FileChangeType.ADDED },304{ resource: oldPath, type: FileChangeType.DELETED }305];306307disposables.add(watch.onDidFilesChange(({ event, raw }) => {308assert.ok(event);309assert.strictEqual(raw.length, 2);310311for (const r of raw) {312if (isEqual(r.resource, oldPath)) {313assert.strictEqual(r.type, FileChangeType.DELETED);314} else if (isEqual(r.resource, newPath)) {315assert.strictEqual(r.type, FileChangeType.ADDED);316} else {317assert.fail();318}319}320321done();322}));323324watch.report(raw);325});326327test('event type filter', () => {328const resource = URI.file('/users/data/src/related');329330assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, undefined), false);331assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, undefined), false);332assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, undefined), false);333334assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED), true);335assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.UPDATED | FileChangeFilter.DELETED), true);336337assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED), false);338assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED), false);339assert.strictEqual(isFiltered({ resource, type: FileChangeType.ADDED }, FileChangeFilter.ADDED | FileChangeFilter.UPDATED | FileChangeFilter.DELETED), false);340341assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED), true);342assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.UPDATED | FileChangeFilter.ADDED), true);343344assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED), false);345assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);346assert.strictEqual(isFiltered({ resource, type: FileChangeType.DELETED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);347348assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED), true);349assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.ADDED), true);350351assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.UPDATED), false);352assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);353assert.strictEqual(isFiltered({ resource, type: FileChangeType.UPDATED }, FileChangeFilter.ADDED | FileChangeFilter.DELETED | FileChangeFilter.UPDATED), false);354});355356ensureNoDisposablesAreLeakedInTestSuite();357});358359360