diff --git a/src/macos/FSEventsBackend.cc b/src/macos/FSEventsBackend.cc index 76850d02..b8cec030 100644 --- a/src/macos/FSEventsBackend.cc +++ b/src/macos/FSEventsBackend.cc @@ -71,29 +71,24 @@ void FSEventsCallback( list->update(paths[i]); } else { // If multiple flags were set, then we need to call `stat` to determine if the file really exists. - // We also check our local cache of recently modified files to see if we knew about it before. // This helps disambiguate creates, updates, and deletes. - auto existed = !since && state->tree->find(paths[i]); struct stat file; if (stat(paths[i], &file)) { - // File does not exist now. If it existed before in our local cache, - // or was removed/renamed and we don't have a cache (querying since a snapshot) - // then the file was probably removed. This is not exact since the flags set by - // fsevents get coalesced together (e.g. created & deleted), so there is no way to + // File does not exist, so we have to assume it was removed. This is not exact since the + // flags set by fsevents get coalesced together (e.g. created & deleted), so there is no way to // know whether the create and delete both happened since our snapshot (in which case // we'd rather ignore this event completely). This will result in some extra delete events // being emitted for files we don't know about, but that is the best we can do. - if (existed || (since && (isRemoved || isRenamed))) { - state->tree->remove(paths[i]); - list->remove(paths[i]); - } - + state->tree->remove(paths[i]); + list->remove(paths[i]); continue; } // If the file was modified, and existed before, then this is an update, otherwise a create. - uint64_t mtime = CONVERT_TIME(file.st_birthtimespec); - if (isModified && (existed || mtime <= since)) { + uint64_t ctime = CONVERT_TIME(file.st_birthtimespec); + uint64_t mtime = CONVERT_TIME(file.st_mtimespec); + auto existed = !since && state->tree->find(paths[i]); + if (isModified && (existed || ctime <= since)) { state->tree->update(paths[i], mtime); list->update(paths[i]); } else { diff --git a/test/since.js b/test/since.js index 8bfc527d..afdcae93 100644 --- a/test/since.js +++ b/test/since.js @@ -41,6 +41,13 @@ function testPrecision() { const isSecondPrecision = testPrecision(); describe('since', () => { + const sleep = (ms = 20) => new Promise(resolve => setTimeout(resolve, ms)); + + before(async () => { + // wait for tmp dir to be created. + await sleep(); + }); + after(async () => { try { await fs.unlink(snapshotPath); @@ -48,8 +55,6 @@ describe('since', () => { }); backends.forEach(backend => { - const sleep = (ms = 10) => new Promise(resolve => setTimeout(resolve, ms)); - describe(backend, () => { describe('files', () => { it('should emit when a file is created', async () => { diff --git a/test/watcher.js b/test/watcher.js index d77e02c7..35957622 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -48,7 +48,7 @@ describe('watcher', () => { .toString(31) .slice(2)}`, ); - let ignoreDir, ignoreFile, sub; + let ignoreDir, ignoreFile, fileToRename, dirToRename, sub; before(async () => { tmpDir = path.join( @@ -60,6 +60,10 @@ describe('watcher', () => { fs.mkdirpSync(tmpDir); ignoreDir = getFilename(); ignoreFile = getFilename(); + fileToRename = getFilename(); + dirToRename = getFilename(); + fs.writeFileSync(fileToRename, 'hi'); + fs.mkdirpSync(dirToRename); await new Promise(resolve => setTimeout(resolve, 100)); sub = await watcher.subscribe(tmpDir, fn, { backend, @@ -103,6 +107,16 @@ describe('watcher', () => { ]); }); + it('should emit when an existing file is renamed', async () => { + let f2 = getFilename(); + fs.rename(fileToRename, f2); + let res = await nextEvent(); + assert.deepEqual(res, [ + {type: 'delete', path: fileToRename}, + {type: 'create', path: f2}, + ]); + }); + it('should emit when a file is deleted', async () => { let f = getFilename(); fs.writeFile(f, 'hello world'); @@ -137,6 +151,16 @@ describe('watcher', () => { ]); }); + it('should emit when an existing directory is renamed', async () => { + let f2 = getFilename(); + fs.rename(dirToRename, f2); + let res = await nextEvent(); + assert.deepEqual(res, [ + {type: 'create', path: f2}, + {type: 'delete', path: dirToRename}, + ]); + }); + it('should emit when a directory is deleted', async () => { let f = getFilename(); fs.mkdir(f); @@ -321,17 +345,6 @@ describe('watcher', () => { }); describe('rapid changes', () => { - it('should ignore files that are created and deleted rapidly', async () => { - let f1 = getFilename(); - let f2 = getFilename(); - await fs.writeFile(f1, 'hello world'); - await fs.writeFile(f2, 'hello world'); - fs.unlink(f2); - - let res = await nextEvent(); - assert.deepEqual(res, [{type: 'create', path: f1}]); - }); - it('should coalese create and update events', async () => { let f1 = getFilename(); await fs.writeFile(f1, 'hello world'); @@ -341,29 +354,42 @@ describe('watcher', () => { assert.deepEqual(res, [{type: 'create', path: f1}]); }); - it('should coalese create and rename events', async () => { - let f1 = getFilename(); - let f2 = getFilename(); - await fs.writeFile(f1, 'hello world'); - fs.rename(f1, f2); - - let res = await nextEvent(); - assert.deepEqual(res, [{type: 'create', path: f2}]); - }); - - it('should coalese multiple rename events', async () => { - let f1 = getFilename(); - let f2 = getFilename(); - let f3 = getFilename(); - let f4 = getFilename(); - await fs.writeFile(f1, 'hello world'); - await fs.rename(f1, f2); - await fs.rename(f2, f3); - fs.rename(f3, f4); - - let res = await nextEvent(); - assert.deepEqual(res, [{type: 'create', path: f4}]); - }); + if (backend !== 'fs-events') { + it('should ignore files that are created and deleted rapidly', async () => { + let f1 = getFilename(); + let f2 = getFilename(); + await fs.writeFile(f1, 'hello world'); + await fs.writeFile(f2, 'hello world'); + fs.unlink(f2); + + let res = await nextEvent(); + assert.deepEqual(res, [{type: 'create', path: f1}]); + }); + + it('should coalese create and rename events', async () => { + let f1 = getFilename(); + let f2 = getFilename(); + await fs.writeFile(f1, 'hello world'); + fs.rename(f1, f2); + + let res = await nextEvent(); + assert.deepEqual(res, [{type: 'create', path: f2}]); + }); + + it('should coalese multiple rename events', async () => { + let f1 = getFilename(); + let f2 = getFilename(); + let f3 = getFilename(); + let f4 = getFilename(); + await fs.writeFile(f1, 'hello world'); + await fs.rename(f1, f2); + await fs.rename(f2, f3); + fs.rename(f3, f4); + + let res = await nextEvent(); + assert.deepEqual(res, [{type: 'create', path: f4}]); + }); + } it('should coalese multiple update events', async () => { let f1 = getFilename();