From 00292e5004533054b2595a8cd15a1af438a10455 Mon Sep 17 00:00:00 2001 From: Erwan Guyader Date: Tue, 6 Jul 2021 11:21:26 +0200 Subject: [PATCH 1/3] Bump `@gyselroth/windows-fsstat` to v1.0.0 This version returns access and modification dates with milliseconds instead of truncating them to the second. --- package.json | 2 +- test/support/helpers/platform.js | 7 +------ test/unit/local/index.js | 9 ++------- yarn.lock | 6 +++--- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index d66a6926f..64d19bd2b 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "stylus": "^0.54.5" }, "optionalDependencies": { - "@gyselroth/windows-fsstat": "https://github.com/taratatach/node-windows-fsstat.git" + "@gyselroth/windows-fsstat": "https://github.com/taratatach/node-windows-fsstat.git#1.0.0" }, "resolutions": { "dtrace-provider": "0.8.8", diff --git a/test/support/helpers/platform.js b/test/support/helpers/platform.js index e206f8310..fbead1218 100644 --- a/test/support/helpers/platform.js +++ b/test/support/helpers/platform.js @@ -73,12 +73,7 @@ function onPlatform(platform /*: string */, spec /*: Function */) { } function localUpdatedAt(date /*: string|Date */) /*: string */ { - if (process.platform === 'win32') { - const win32Date = new Date(date) - // The Windows mtime precision is up to the second only - win32Date.setMilliseconds(0) - return win32Date.toISOString() - } else if (typeof date === 'string') { + if (typeof date === 'string') { return date } else { return date.toISOString() diff --git a/test/unit/local/index.js b/test/unit/local/index.js index e15edecef..63e455368 100644 --- a/test/unit/local/index.js +++ b/test/unit/local/index.js @@ -10,7 +10,6 @@ const should = require('should') const { Local } = require('../../../core/local') const { TMP_DIR_NAME } = require('../../../core/local/constants') -const timestamp = require('../../../core/utils/timestamp') const { sendToTrash } = require('../../../core/utils/fs') const Builders = require('../../support/builders') @@ -561,9 +560,7 @@ describe('Local', function() { should(await syncDir.tree()).deepEqual(['dst/', 'dst/file', 'src/']) should((await syncDir.mtime(dstFile)).getTime()).equal( - process.platform === 'win32' - ? timestamp.fromDate(dstFile.updated_at).getTime() - : new Date(dstFile.updated_at).getTime() + new Date(dstFile.updated_at).getTime() ) should(await syncDir.readFile(dstFile)).equal('foobar') }) @@ -643,9 +640,7 @@ describe('Local', function() { should(await syncDir.tree()).deepEqual(['dst/', 'dst/dir/', 'src/']) should((await syncDir.mtime(dstDir)).getTime()).equal( - process.platform === 'win32' - ? timestamp.fromDate(dstDir.updated_at).getTime() - : new Date(dstDir.updated_at).getTime() + new Date(dstDir.updated_at).getTime() ) }) diff --git a/yarn.lock b/yarn.lock index c3724c35a..91f0dc2eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -224,9 +224,9 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@gyselroth/windows-fsstat@https://github.com/taratatach/node-windows-fsstat.git": - version "0.0.8" - resolved "https://github.com/taratatach/node-windows-fsstat.git#d5295cef25d2c258e0b99a312544b22f78056853" +"@gyselroth/windows-fsstat@https://github.com/taratatach/node-windows-fsstat.git#1.0.0": + version "1.0.0" + resolved "https://github.com/taratatach/node-windows-fsstat.git#ece4512cfb40e58c5c8c00ac587a123a5e9dee52" dependencies: nan "^2.14.0" node-gyp "^5.0.4" From 135ea191ac19a60e543571fcbabba7fbfde7e21c Mon Sep 17 00:00:00 2001 From: Erwan Guyader Date: Tue, 6 Jul 2021 11:30:59 +0200 Subject: [PATCH 2/3] core/local/atom: Get syncPath from Config in steps Atom Watcher steps were passed the client sync path directly by the watcher. However, we'll want to access other attributes of the configuration in some steps so we'll pass the full Config instead and use its syncPath attribute when necessary. --- core/local/atom/add_checksum.js | 7 ++- core/local/atom/add_infos.js | 9 +-- core/local/atom/incomplete_fixer.js | 7 ++- core/local/atom/producer.js | 5 +- core/local/atom/watcher.js | 5 +- core/local/watcher.js | 9 +-- dev/capture/local.js | 20 +++--- test/support/helpers/local.js | 1 + test/unit/local/atom/add_checksum.js | 38 +++++++---- test/unit/local/atom/add_infos.js | 32 +++++----- test/unit/local/atom/incomplete_fixer.js | 80 ++++++++++++------------ test/unit/local/atom/producer.js | 16 ++--- test/unit/local/atom/watcher.js | 1 + 13 files changed, 131 insertions(+), 99 deletions(-) diff --git a/core/local/atom/add_checksum.js b/core/local/atom/add_checksum.js index cdb1df0a6..bcd299867 100644 --- a/core/local/atom/add_checksum.js +++ b/core/local/atom/add_checksum.js @@ -28,6 +28,7 @@ const contentActions = new Set(['created', 'modified', 'renamed', 'scan']) /*:: import type Channel from './channel' +import type { Config } from '../../config' import type { Checksumer } from '../checksumer' import type { AtomEvent } from './event' */ @@ -50,8 +51,10 @@ module.exports = { */ function loop( channel /*: Channel */, - opts /*: { syncPath: string , checksumer: Checksumer } */ + opts /*: { config: Config , checksumer: Checksumer } */ ) /*: Channel */ { + const syncPath = opts.config.syncPath + return channel.asyncMap(async events => { for (const event of events) { try { @@ -63,7 +66,7 @@ function loop( { path: event.path, action: event.action }, 'computing checksum' ) - const absPath = path.join(opts.syncPath, event.path) + const absPath = path.join(syncPath, event.path) event.md5sum = await opts.checksumer.push(absPath) } } catch (err) { diff --git a/core/local/atom/add_infos.js b/core/local/atom/add_infos.js index 39b55521f..548195003 100644 --- a/core/local/atom/add_infos.js +++ b/core/local/atom/add_infos.js @@ -22,6 +22,7 @@ const log = logger({ }) /*:: +import type { Config } from '../../config' import type { Pouch } from '../../pouch' import type { Metadata } from '../../metadata' import type Channel from './channel' @@ -41,8 +42,10 @@ module.exports = { */ function loop( channel /*: Channel */, - opts /*: { syncPath: string, pouch: Pouch } */ + opts /*: { config: Config, pouch: Pouch } */ ) /*: Channel */ { + const syncPath = opts.config.syncPath + return channel.asyncMap(async events => { const batch = [] for (const event of events) { @@ -55,9 +58,7 @@ function loop( if (event.action !== 'initial-scan-done') { if (needsStats(event)) { log.debug({ path: event.path, action: event.action }, 'stat') - event.stats = await stater.stat( - path.join(opts.syncPath, event.path) - ) + event.stats = await stater.stat(path.join(syncPath, event.path)) } if (event.stats) { diff --git a/core/local/atom/incomplete_fixer.js b/core/local/atom/incomplete_fixer.js index c524b8928..ad2ca79be 100644 --- a/core/local/atom/incomplete_fixer.js +++ b/core/local/atom/incomplete_fixer.js @@ -35,6 +35,7 @@ const DELAY = 3000 import type Channel from './channel' import type { AtomEvent, AtomBatch } from './event' import type { Checksumer } from '../checksumer' +import type { Config } from '../../config' import type { Pouch } from '../../pouch' import type { Metadata } from '../../metadata' @@ -44,7 +45,7 @@ type IncompleteItem = { } type IncompleteFixerOptions = { - syncPath: string, + config: Config, checksumer: Checksumer, pouch: Pouch } @@ -116,7 +117,7 @@ function completeEventPaths( async function rebuildIncompleteEvent( previousEvent /*: AtomEvent */, nextEvent /*: AtomEvent */, - opts /*: { syncPath: string , checksumer: Checksumer, pouch: Pouch } */ + opts /*: { config: Config , checksumer: Checksumer, pouch: Pouch } */ ) /*: Promise */ { const { path: rebuiltPath, oldPath: rebuiltOldPath } = completeEventPaths( previousEvent, @@ -127,7 +128,7 @@ async function rebuildIncompleteEvent( return { ignored: true } } - const absPath = path.join(opts.syncPath, rebuiltPath) + const absPath = path.join(opts.config.syncPath, rebuiltPath) const stats = await stater.statMaybe(absPath) const incomplete = stats == null const kind = stats ? stater.kind(stats) : previousEvent.kind diff --git a/core/local/atom/producer.js b/core/local/atom/producer.js index 11d23c06a..533f06104 100644 --- a/core/local/atom/producer.js +++ b/core/local/atom/producer.js @@ -15,6 +15,7 @@ const defaultStater = require('../stater') const logger = require('../../utils/logger') /*:: +import type { Config } from '../../config' import type { Ignore } from '../../ignore' import type { AtomEvent } from './event' import type EventEmitter from 'events' @@ -66,10 +67,10 @@ class Producer { scan: Scanner */ constructor( - opts /*: { syncPath: string, ignore: Ignore, events: EventEmitter } */ + opts /*: { config: Config, ignore: Ignore, events: EventEmitter } */ ) { this.channel = new Channel() - this.syncPath = opts.syncPath + this.syncPath = opts.config.syncPath this.ignore = opts.ignore this.events = opts.events this.watcher = null diff --git a/core/local/atom/watcher.js b/core/local/atom/watcher.js index 5d48d59b9..b51f0f610 100644 --- a/core/local/atom/watcher.js +++ b/core/local/atom/watcher.js @@ -39,6 +39,7 @@ const dispatch = require('./dispatch') const logger = require('../../utils/logger') /*:: +import type { Config } from '../../config' import type { Pouch } from '../../pouch' import type Prep from '../../prep' import type EventEmitter from 'events' @@ -48,7 +49,7 @@ import type { AtomEventsDispatcher } from './dispatch' import type { Scanner } from './producer' type AtomWatcherOptions = { - syncPath: string, + config: Config, onAtomEvents?: AtomEventsDispatcher, prep: Prep, pouch: Pouch, @@ -107,6 +108,7 @@ const stepsInitialState = ( class AtomWatcher { /*:: + config: Config pouch: Pouch events: EventEmitter checksumer: Checksumer @@ -118,6 +120,7 @@ class AtomWatcher { */ constructor(opts /*: AtomWatcherOptions */) { + this.config = opts.config this.pouch = opts.pouch this.events = opts.events this.checksumer = checksumer.init() diff --git a/core/local/watcher.js b/core/local/watcher.js index 576e2b36e..c4eef20df 100644 --- a/core/local/watcher.js +++ b/core/local/watcher.js @@ -14,7 +14,7 @@ const { AtomWatcher } = require('./atom/watcher') const ChokidarWatcher = require('./chokidar/watcher') /*:: -import type { WatcherType } from '../config' +import type { Config } from '../config' import type { AtomEventsDispatcher } from './atom/dispatch' import type { Pouch } from '../pouch' import type Prep from '../prep' @@ -31,10 +31,7 @@ export interface Watcher { } export type LocalWatcherOptions = { - +config: { - +syncPath: string, - +watcherType: WatcherType - }, + config: Config, events: EventEmitter, ignore: Ignore, onAtomEvents?: AtomEventsDispatcher, @@ -57,7 +54,7 @@ function build( if (watcherType === 'atom') { return new AtomWatcher({ - syncPath, + config, prep, pouch, events, diff --git a/dev/capture/local.js b/dev/capture/local.js index 74babe41c..1dcd6c425 100644 --- a/dev/capture/local.js +++ b/dev/capture/local.js @@ -12,7 +12,7 @@ const _ = require('lodash') const path = require('path') const sinon = require('sinon') -const Config = require('../../core/config') +const { Config, watcherType } = require('../../core/config') const { Ignore } = require('../../core/ignore') const { AtomWatcher } = require('../../core/local/atom/watcher') const { Pouch } = require('../../core/pouch') @@ -25,9 +25,17 @@ import type { Scenario } from '../../test/scenarios' const cliDir = path.resolve(path.join(__dirname, '..', '..')) const syncDir = process.env.COZY_DESKTOP_DIR || cliDir -const syncPath = path.join(syncDir, 'tmp', 'local_watcher', 'synced_dir') +const config = new Config(path.join(syncDir, 'tmp', '.cozy-desktop')) +const syncPath = (config.syncPath = path.join( + syncDir, + 'tmp', + 'local_watcher', + 'synced_dir' +)) const outsidePath = path.join(syncDir, 'tmp', 'local_watcher', 'outside') + const abspath = relpath => path.join(syncPath, relpath.replace(/\//g, path.sep)) + const chokidarOptions = { cwd: syncPath, ignored: /(^|[\/\\])\.system-tmp-cozy-drive/, // eslint-disable-line no-useless-escape @@ -172,7 +180,7 @@ const runAndRecordAtomEvents = async scenario => { const events = new EventEmitter() const ignore = new Ignore([]) const capturedBatches = [] - const watcher = new AtomWatcher({ syncPath, prep, pouch, events, ignore }) + const watcher = new AtomWatcher({ config, prep, pouch, events, ignore }) pouch.initialScanDocs = sinon.stub().callsFake(() => []) @@ -197,9 +205,7 @@ const runAndRecordAtomEvents = async scenario => { } const runAndRecordFSEvents = - Config.watcherType() === 'atom' - ? runAndRecordAtomEvents - : runAndRecordChokidarEvents + watcherType() === 'atom' ? runAndRecordAtomEvents : runAndRecordChokidarEvents const captureScenario = (scenario /*: Scenario & {path: string} */) => { if ( @@ -210,7 +216,7 @@ const captureScenario = (scenario /*: Scenario & {path: string} */) => { } return fse - .emptyDir(syncPath) + .emptyDir(config.syncPath) .then(() => fse.emptyDir(outsidePath)) .then(() => setupInitialState(scenario)) .then(() => runAndRecordFSEvents(scenario)) diff --git a/test/support/helpers/local.js b/test/support/helpers/local.js index 760c5eccf..89829bbbd 100644 --- a/test/support/helpers/local.js +++ b/test/support/helpers/local.js @@ -190,6 +190,7 @@ class LocalTestHelpers { const watcher = this._ensureAtomWatcher() const stepOptions = Object.assign( ({ + config: watcher.config, checksumer: watcher.checksumer, scan: watcher.producer.scan, state: watcher.state diff --git a/test/unit/local/atom/add_checksum.js b/test/unit/local/atom/add_checksum.js index fd508ac18..fdbbb5a34 100644 --- a/test/unit/local/atom/add_checksum.js +++ b/test/unit/local/atom/add_checksum.js @@ -2,24 +2,38 @@ /* @flow */ const should = require('should') +const path = require('path') + +const configHelpers = require('../../../support/helpers/config') + const checksumer = require('../../../../core/local/checksumer') const addChecksum = require('../../../../core/local/atom/add_checksum') const Channel = require('../../../../core/local/atom/channel') describe('core/local/atom/add_checksum.loop()', () => { + let config, dirpath, filepath + before(configHelpers.createConfig) + before(function() { + config = this.config + config.syncPath = path.dirname(__dirname) + + dirpath = path.basename(__dirname) + filepath = path.join(dirpath, path.basename(__filename)) + }) + it('should add checksum within a file event', async () => { const batch = [ { action: 'scan', kind: 'file', - path: __filename + path: filepath } ] const channel = new Channel() channel.push(batch) const enhancedChannel = addChecksum.loop(channel, { checksumer: checksumer.init(), - syncPath: '' + config }) const enhancedBatch = await enhancedChannel.pop() should(enhancedBatch) @@ -33,14 +47,14 @@ describe('core/local/atom/add_checksum.loop()', () => { { action: 'scan', kind: 'directory', - path: __dirname + path: dirpath } ] const channel = new Channel() channel.push(batch) const enhancedChannel = addChecksum.loop(channel, { checksumer: checksumer.init(), - syncPath: '' + config }) const enhancedBatch = await enhancedChannel.pop() should(enhancedBatch) @@ -54,7 +68,7 @@ describe('core/local/atom/add_checksum.loop()', () => { { action: 'scan', kind: 'file', - path: __filename, + path: filepath, md5sum: 'checksum' } ] @@ -62,7 +76,7 @@ describe('core/local/atom/add_checksum.loop()', () => { channel.push(batch) const enhancedChannel = addChecksum.loop(channel, { checksumer: checksumer.init(), - syncPath: '' + config }) const enhancedBatch = await enhancedChannel.pop() should(enhancedBatch) @@ -76,27 +90,27 @@ describe('core/local/atom/add_checksum.loop()', () => { const createdEvent = { action: 'created', kind: 'file', - path: __filename + path: filepath } const modifiedEvent = { action: 'modified', kind: 'file', - path: __filename + path: filepath } const scanEvent = { action: 'scan', kind: 'file', - path: __filename + path: filepath } const renamedEvent = { action: 'renamed', kind: 'file', - path: __filename + path: filepath } const ignoredEvent = { action: 'ignored', kind: 'file', - path: __filename + path: filepath } const channel = new Channel() channel.push([ @@ -108,7 +122,7 @@ describe('core/local/atom/add_checksum.loop()', () => { ]) const enhancedChannel = addChecksum.loop(channel, { checksumer: checksumer.init(), - syncPath: '' + config }) const enhancedBatch = await enhancedChannel.pop() should(enhancedBatch).deepEqual([ diff --git a/test/unit/local/atom/add_infos.js b/test/unit/local/atom/add_infos.js index 0a633c6d8..acb07d87d 100644 --- a/test/unit/local/atom/add_infos.js +++ b/test/unit/local/atom/add_infos.js @@ -2,6 +2,7 @@ /* @flow */ const should = require('should') +const path = require('path') const Builders = require('../../../support/builders') const configHelpers = require('../../../support/helpers/config') @@ -13,6 +14,7 @@ const Channel = require('../../../../core/local/atom/channel') describe('core/local/atom/add_infos.loop()', () => { let builders let opts + let filepath, dirpath before('instanciate config', configHelpers.createConfig) beforeEach('instanciate pouch', pouchHelpers.createDatabase) @@ -20,10 +22,10 @@ describe('core/local/atom/add_infos.loop()', () => { builders = new Builders({ pouch: this.pouch }) }) beforeEach('create step opts', async function() { - opts = { - syncPath: '', - pouch: this.pouch - } + this.config.syncPath = path.dirname(__dirname) + opts = this + filepath = path.basename(__filename) + dirpath = path.basename(__dirname) }) it('returns an enhanced batch with infos', async () => { @@ -31,7 +33,7 @@ describe('core/local/atom/add_infos.loop()', () => { { action: 'scan', kind: 'unknown', - path: __filename + path: filepath } ] const channel = new Channel() @@ -48,32 +50,32 @@ describe('core/local/atom/add_infos.loop()', () => { { action: 'deleted', kind: 'directory', - path: __dirname + path: dirpath }, { action: 'ignored', kind: 'directory', - path: __dirname + path: dirpath }, { action: 'scan', kind: 'directory', - path: __dirname + path: dirpath }, { action: 'created', kind: 'directory', - path: __dirname + path: dirpath }, { action: 'modified', kind: 'directory', - path: __dirname + path: dirpath }, { action: 'renamed', kind: 'directory', - path: __dirname + path: dirpath } ] const channel = new Channel() @@ -160,12 +162,12 @@ describe('core/local/atom/add_infos.loop()', () => { { action: 'deleted', kind: 'unknown', - path: __filename + path: filepath }, { action: 'deleted', kind: 'unknown', - path: __dirname + path: dirpath } ] const channel = new Channel() @@ -176,13 +178,13 @@ describe('core/local/atom/add_infos.loop()', () => { { action: 'deleted', kind: 'file', - path: __filename, + path: filepath, [addInfos.STEP_NAME]: { kindConvertedFrom: 'unknown' } }, { action: 'deleted', kind: 'file', - path: __dirname, + path: dirpath, [addInfos.STEP_NAME]: { kindConvertedFrom: 'unknown' } } ]) diff --git a/test/unit/local/atom/incomplete_fixer.js b/test/unit/local/atom/incomplete_fixer.js index ebdf19afe..5d99cdb75 100644 --- a/test/unit/local/atom/incomplete_fixer.js +++ b/test/unit/local/atom/incomplete_fixer.js @@ -42,7 +42,7 @@ describe('core/local/atom/incomplete_fixer', () => { describe('.loop()', () => { it('pushes the result of step() into the output Channel', async function() { - const { syncPath } = this + const { config } = this const src = 'missing' const dst = path.basename(__filename) @@ -63,7 +63,7 @@ describe('core/local/atom/incomplete_fixer', () => { .build() const inputChannel = new Channel() const outputChannel = incompleteFixer.loop(inputChannel, { - syncPath, + config, checksumer, pouch: this.pouch }) @@ -74,7 +74,7 @@ describe('core/local/atom/incomplete_fixer', () => { should(await outputChannel.pop()).deepEqual( await incompleteFixer.step( { incompletes: [{ event: createdEvent, timestamp: Date.now() }] }, - { syncPath, checksumer, pouch: this.pouch } + { config, checksumer, pouch: this.pouch } )([renamedEvent]) ) }) @@ -83,7 +83,7 @@ describe('core/local/atom/incomplete_fixer', () => { describe('.step()', () => { context('without any complete "renamed" event', () => { it('drops incomplete events', async function() { - const { syncPath } = this + const { config } = this const inputBatch = [ builders @@ -116,7 +116,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -127,7 +127,7 @@ describe('core/local/atom/incomplete_fixer', () => { context('with a complete "renamed" event', () => { it('leaves complete events untouched', async function() { - const { syncPath } = this + const { config } = this const src = 'file' const dst = 'foo' @@ -150,7 +150,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -159,7 +159,7 @@ describe('core/local/atom/incomplete_fixer', () => { }) it('rebuilds the all incomplete events matching the "renamed" event old path', async function() { - const { syncPath } = this + const { config } = this await syncDir.makeTree([ 'dst/', @@ -221,7 +221,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -238,35 +238,35 @@ describe('core/local/atom/incomplete_fixer', () => { kind: 'file', action: 'created', md5sum: CHECKSUM, - stats: await stater.stat(path.join(syncPath, 'dst/foo1')) + stats: await stater.stat(path.join(config.syncPath, 'dst/foo1')) }, { path: path.normalize('dst/foo2'), kind: 'file', action: 'modified', md5sum: CHECKSUM, - stats: await stater.stat(path.join(syncPath, 'dst/foo2')) + stats: await stater.stat(path.join(config.syncPath, 'dst/foo2')) }, { path: path.normalize('dst/foo3'), kind: 'file', action: 'deleted', md5sum: CHECKSUM, - stats: await stater.stat(path.join(syncPath, 'dst/foo3')) + stats: await stater.stat(path.join(config.syncPath, 'dst/foo3')) }, { path: path.normalize('dst/foo5'), kind: 'file', action: 'scan', md5sum: CHECKSUM, - stats: await stater.stat(path.join(syncPath, 'dst/foo5')) + stats: await stater.stat(path.join(config.syncPath, 'dst/foo5')) } ] ]) }) it('drops incomplete ignored events matching the "renamed" event old path', async function() { - const { syncPath } = this + const { config } = this await syncDir.makeTree(['dst/', 'dst/file']) const ignoredEvent = builders @@ -286,7 +286,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -295,7 +295,7 @@ describe('core/local/atom/incomplete_fixer', () => { }) it('replaces the completing event if its path is the same as the rebuilt one', async function() { - const { syncPath } = this + const { config } = this const src = 'missing' const dst = path.basename(__filename) @@ -320,7 +320,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -329,7 +329,9 @@ describe('core/local/atom/incomplete_fixer', () => { { path: renamedEvent.path, md5sum: CHECKSUM, - stats: await stater.stat(path.join(syncPath, renamedEvent.path)), + stats: await stater.stat( + path.join(config.syncPath, renamedEvent.path) + ), action: createdEvent.action, kind: createdEvent.kind } @@ -339,7 +341,7 @@ describe('core/local/atom/incomplete_fixer', () => { describe('file renamed then deleted', () => { it('is deleted at its original path', async function() { - const { syncPath } = this + const { config } = this const src = 'src' const dst = 'dst' @@ -364,7 +366,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -391,7 +393,7 @@ describe('core/local/atom/incomplete_fixer', () => { describe('file renamed twice', () => { it('is renamed once as a whole', async function() { - const { syncPath } = this + const { config } = this const src = 'src' const dst1 = 'dst1' @@ -413,7 +415,7 @@ describe('core/local/atom/incomplete_fixer', () => { .oldPath(dst1) .path(dst2) .build() - const stats = await stater.stat(path.join(syncPath, dst2)) + const stats = await stater.stat(path.join(config.syncPath, dst2)) secondRenamedEvent.stats = stats const incompletes = [] @@ -423,7 +425,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -453,7 +455,7 @@ describe('core/local/atom/incomplete_fixer', () => { describe('file renamed three times', () => { it('is renamed once as a whole', async function() { - const { syncPath } = this + const { config } = this const src = 'src' const dst1 = 'dst1' @@ -494,7 +496,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -512,7 +514,7 @@ describe('core/local/atom/incomplete_fixer', () => { md5sum: CHECKSUM, oldPath: src, path: dst3, - stats: await stater.stat(path.join(syncPath, dst3)) + stats: await stater.stat(path.join(config.syncPath, dst3)) } ] ]) @@ -521,7 +523,7 @@ describe('core/local/atom/incomplete_fixer', () => { describe('file renamed and then renamed back to its previous name', () => { it('results in no events at all', async function() { - const { syncPath } = this + const { config } = this const src = 'src' const dst = 'dst' @@ -548,7 +550,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -562,7 +564,7 @@ describe('core/local/atom/incomplete_fixer', () => { describe('file renamed to backup location and replaced by new file', () => { it('is modified once and not deleted', async function() { - const { syncPath } = this + const { config } = this const src = 'src' const tmp = 'src.tmp' @@ -600,7 +602,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch: this.pouch } @@ -647,7 +649,7 @@ describe('core/local/atom/incomplete_fixer', () => { }) it('results in the renamed event', async function() { - const { syncPath, pouch } = this + const { config, pouch } = this await syncDir.ensureFile(dst) const createdEvent = builders @@ -671,7 +673,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch } @@ -696,7 +698,7 @@ describe('core/local/atom/incomplete_fixer', () => { }) it('results in the renamed event followed by the rebuilt modified event', async function() { - const { syncPath, pouch } = this + const { config, pouch } = this await syncDir.ensureFile(dst) const modifiedEvent = builders @@ -720,7 +722,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch } @@ -741,7 +743,7 @@ describe('core/local/atom/incomplete_fixer', () => { kind: 'file', md5sum: CHECKSUM, path: dst, - stats: await stater.stat(path.join(syncPath, dst)) + stats: await stater.stat(path.join(config.syncPath, dst)) } ] ]) @@ -762,7 +764,7 @@ describe('core/local/atom/incomplete_fixer', () => { }) it('results in one renamed event followed by the rebuilt modified event', async function() { - const { syncPath, pouch } = this + const { config, pouch } = this await syncDir.ensureFile(dst2) const modifiedEvent = builders @@ -798,7 +800,7 @@ describe('core/local/atom/incomplete_fixer', () => { const outputBatch = await incompleteFixer.step( { incompletes }, { - syncPath, + config, checksumer, pouch } @@ -820,7 +822,7 @@ describe('core/local/atom/incomplete_fixer', () => { md5sum: CHECKSUM, oldPath: src, path: dst2, - stats: await stater.stat(path.join(syncPath, dst2)) + stats: await stater.stat(path.join(config.syncPath, dst2)) }, { action: 'modified', @@ -839,7 +841,7 @@ describe('core/local/atom/incomplete_fixer', () => { kind: 'file', md5sum: CHECKSUM, path: dst2, - stats: await stater.stat(path.join(syncPath, dst2)) + stats: await stater.stat(path.join(config.syncPath, dst2)) } ] ]) diff --git a/test/unit/local/atom/producer.js b/test/unit/local/atom/producer.js index 102e19434..71e8c04d6 100644 --- a/test/unit/local/atom/producer.js +++ b/test/unit/local/atom/producer.js @@ -3,8 +3,6 @@ const _ = require('lodash') const should = require('should') -const os = require('os') -const fs = require('fs') const path = require('path') const Producer = require('../../../../core/local/atom/producer') @@ -12,23 +10,25 @@ const { Ignore } = require('../../../../core/ignore') const stater = require('../../../../core/local/stater') const EventEmitter = require('events') +const configHelpers = require('../../../support/helpers/config') const { ContextDir } = require('../../../support/helpers/context_dir') const { onPlatforms } = require('../../../support/helpers/platform') onPlatforms(['linux', 'win32'], () => { describe('core/local/atom/producer', () => { let syncDir - let syncPath + let config let ignore let events let producer - beforeEach(() => { - syncPath = fs.mkdtempSync(path.join(os.tmpdir(), 'Cozy Drive.test-')) - syncDir = new ContextDir(syncPath) + beforeEach('instanciate config', configHelpers.createConfig) + beforeEach(function() { + config = this.config + syncDir = new ContextDir(config.syncPath) ignore = new Ignore([]) events = new EventEmitter() - producer = new Producer({ syncPath, ignore, events }) + producer = new Producer({ config, ignore, events }) }) describe('start()', () => { @@ -55,7 +55,7 @@ onPlatforms(['linux', 'win32'], () => { beforeEach(async () => { ignore = new Ignore([ignoredDir]) - producer = new Producer({ syncPath, ignore, events }) + producer = new Producer({ config, ignore, events }) await syncDir.makeTree([ `${ignoredDir}/`, diff --git a/test/unit/local/atom/watcher.js b/test/unit/local/atom/watcher.js index 491141f06..660d7ecbf 100644 --- a/test/unit/local/atom/watcher.js +++ b/test/unit/local/atom/watcher.js @@ -42,6 +42,7 @@ onPlatforms(['linux', 'win32'], () => { it('should reject with the same error', async function() { const watcher = new AtomWatcher({ ...helpers, + config: this.config, ignore: helpers._sync.ignore, syncPath: this.config.syncPath }) From d9c069f95e75a0157c891940a337be5965c6af75 Mon Sep 17 00:00:00 2001 From: Erwan Guyader Date: Tue, 6 Jul 2021 11:43:20 +0200 Subject: [PATCH 3/3] core/local/atom: Migrate Windows updated_at dates Stat dates on Windows were previously truncated to the second while we now get the milliseconds as well. To avoid re-calculating all the local checksums during the initial scan because of the date migration, we'll truncate `scan` events dates to the second during the first initial scan following the publication of v3.28.1 when checking if the checksum of a given file can be reused or not. When publishing v3.28.2 or greater, we can remove `WINDOWS_DATE_MIGRATION_FLAG` and stop truncating dates during the initial scan. Users who will have skipped the version introducing the flag will see all their checksums re-computed. --- core/app.js | 18 ++++ core/config.js | 36 +++++++ core/local/atom/dispatch.js | 9 +- core/local/atom/initial_diff.js | 28 ++++-- package.json | 1 + test/unit/local/atom/dispatch.js | 1 + test/unit/local/atom/initial_diff.js | 134 +++++++++++++++++++++++---- 7 files changed, 202 insertions(+), 25 deletions(-) diff --git a/core/app.js b/core/app.js index ef4eae7e3..d00ed4bb2 100644 --- a/core/app.js +++ b/core/app.js @@ -13,6 +13,7 @@ const url = require('url') const uuid = require('uuid/v4') const https = require('https') const { createGzip } = require('zlib') +const semver = require('semver') require('./globals') const pkg = require('../package.json') @@ -373,6 +374,23 @@ class App { ) wasUpdated = false } + + // TODO: remove with flag WINDOWS_DATE_MIGRATION_FLAG + try { + if ( + semver.lt( + clientInfo.configVersion, + config.WINDOWS_DATE_MIGRATION_APP_VERSION + ) + ) { + this.config.setFlag(config.WINDOWS_DATE_MIGRATION_FLAG, true) + } + } catch (err) { + log.error( + { err, sentry: true }, + `could not set ${config.WINDOWS_DATE_MIGRATION_FLAG} flag` + ) + } } this.instanciate() diff --git a/core/config.js b/core/config.js index 88052b694..db000750e 100644 --- a/core/config.js +++ b/core/config.js @@ -21,6 +21,22 @@ export type WatcherType = 'atom' | 'chokidar' type FileConfig = Object */ +/* Stat dates on Windows were previously truncated to the second while we now + * get the milliseconds as well. + * To avoid re-calculating all the local checksums during the initial scan + * because of the date migration, we'll truncate `scan` events dates to the + * second during the first initial scan following the publication of v3.28.1 + * when checking if the checksum of a given file can be reused or not. + * + * When publishing v3.28.2 or greater, we can remove WINDOWS_DATE_MIGRATION_FLAG + * and stop truncating dates during the initial scan. + * + * Users who will have skipped the version introducing the flag will see all + * their checksums re-computed. + */ +const WINDOWS_DATE_MIGRATION_APP_VERSION = '3.28.1' +const WINDOWS_DATE_MIGRATION_FLAG = 'roundWindowsDatesToSecondInInitialDiff' + const INVALID_CONFIG_ERROR = 'InvalidConfigError' const INVALID_CONFIG_MESSAGE = 'Invalid client configuration' class InvalidConfigError extends Error { @@ -170,6 +186,24 @@ class Config { return _.get(this.fileConfig, 'flags', {}) } + isFlagActive(flagName /*: string */) /*: boolean */ { + return this.flags[flagName] || false + } + + setFlag(flag /*: string */, isActive /*: boolean */) { + if ( + typeof flag !== 'string' || + typeof isActive !== 'boolean' || + flag === '' + ) { + throw new Error( + `Invalid flag or value: [String(${flag})] → "${String(isActive)}"` + ) + } + _.set(this.fileConfig, `flags.${flag}`, isActive) + this.persist() + } + get version() /*: ?string */ { return _.get(this.fileConfig, 'creds.client.softwareVersion', '') } @@ -288,6 +322,8 @@ function validateWatcherType(watcherType /*: ?string */) /*: ?WatcherType */ { module.exports = { INVALID_CONFIG_ERROR, + WINDOWS_DATE_MIGRATION_APP_VERSION, + WINDOWS_DATE_MIGRATION_FLAG, InvalidConfigError, Config, environmentWatcherType, diff --git a/core/local/atom/dispatch.js b/core/local/atom/dispatch.js index 8ee100372..bd107baf4 100644 --- a/core/local/atom/dispatch.js +++ b/core/local/atom/dispatch.js @@ -11,6 +11,7 @@ const _ = require('lodash') const winDetectMove = require('./win_detect_move') const { buildDir, buildFile } = require('../../metadata') +const { WINDOWS_DATE_MIGRATION_FLAG } = require('../../config') const logger = require('../../utils/logger') const STEP_NAME = 'dispatch' @@ -28,6 +29,7 @@ import type { } from './event' import type { WinDetectMoveState } from './win_detect_move' import type EventEmitter from 'events' +import type { Config } from '../../config' import type Prep from '../../prep' import type { Pouch } from '../../pouch' import type { Metadata } from '../../metadata' @@ -41,6 +43,7 @@ type DispatchState = { } type DispatchOptions = { + config: Config, events: EventEmitter, prep: Prep, pouch: Pouch, @@ -133,8 +136,12 @@ async function dispatchEvent( } actions = { - initialScanDone: ({ events }) => { + initialScanDone: ({ config, events }) => { log.info('Initial scan done') + // TODO: remove with flag WINDOWS_DATE_MIGRATION_FLAG + if (config.isFlagActive(WINDOWS_DATE_MIGRATION_FLAG)) { + config.setFlag(WINDOWS_DATE_MIGRATION_FLAG, false) + } events.emit('initial-scan-done') }, diff --git a/core/local/atom/initial_diff.js b/core/local/atom/initial_diff.js index 4074c6117..64324e3f9 100644 --- a/core/local/atom/initial_diff.js +++ b/core/local/atom/initial_diff.js @@ -12,11 +12,13 @@ const _ = require('lodash') const path = require('path') +const { WINDOWS_DATE_MIGRATION_FLAG } = require('../../config') const { kind } = require('../../metadata') const logger = require('../../utils/logger') const Channel = require('./channel') /*:: +import type { Config } from '../../config' import type { Pouch } from '../../pouch' import type { AtomEvent, AtomBatch, EventKind } from './event' import type { Metadata } from '../../metadata' @@ -68,10 +70,10 @@ module.exports = { function loop( channel /*: Channel */, - opts /*: { pouch: Pouch, state: InitialDiffState } */ + opts /*: { config: Config, state: InitialDiffState } */ ) /*: Channel */ { const out = new Channel() - initialDiff(channel, out, opts.state).catch(err => { + initialDiff(channel, out, opts).catch(err => { log.warn({ err }) }) return out @@ -126,7 +128,7 @@ function clearState(state /*: InitialDiffState */) { async function initialDiff( channel /*: Channel */, out /*: Channel */, - state /*: InitialDiffState */ + { config, state } /*: { config: Config, state: InitialDiffState } */ ) /*: Promise */ { // eslint-disable-next-line no-constant-condition while (true) { @@ -140,6 +142,10 @@ async function initialDiff( initialScanDone } } = state + // TODO: remove with flag WINDOWS_DATE_MIGRATION_FLAG + const truncateWindowsDates = config.isFlagActive( + WINDOWS_DATE_MIGRATION_FLAG + ) if (initialScanDone) { out.push(events) @@ -189,7 +195,7 @@ async function initialDiff( deletedIno: was.local.fileid || was.local.ino }) } - } else if (foundUntouchedFile(event, was)) { + } else if (foundUntouchedFile(event, was, truncateWindowsDates)) { _.set(event, [STEP_NAME, 'md5sumReusedFrom'], was.local.path) event.md5sum = was.local.md5sum } @@ -338,8 +344,10 @@ function fixPathsAfterParentMove(renamedEvents, event) { } } -function contentUpdateTime(event) { - return event.stats.mtime.getTime() +function contentUpdateTime(event, truncateWindowsDates) { + return truncateWindowsDates + ? event.stats.mtime.getTime() - event.stats.mtime.getMilliseconds() + : event.stats.mtime.getTime() } function docUpdateTime(oldLocal) { @@ -354,12 +362,16 @@ function foundRenamedOrReplacedDoc(event, was) /*: boolean %checks */ { return was != null && was.local != null && was.local.path !== event.path } -function foundUntouchedFile(event, was) /*: boolean %checks */ { +function foundUntouchedFile( + event, + was, + truncateWindowsDates +) /*: boolean %checks */ { return ( event.kind === 'file' && was != null && was.local != null && was.local.md5sum != null && - contentUpdateTime(event) === docUpdateTime(was.local) + contentUpdateTime(event, truncateWindowsDates) === docUpdateTime(was.local) ) } diff --git a/package.json b/package.json index 64d19bd2b..f8b9a5a87 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "react-markdown": "^4.3.1", "read": "1.0.7", "regedit": "^3.0.3", + "semver": "^7.3.2", "uuid": "^3.3.2", "yargs": "^11.0.0" }, diff --git a/test/unit/local/atom/dispatch.js b/test/unit/local/atom/dispatch.js index 0e3645255..17effad4a 100644 --- a/test/unit/local/atom/dispatch.js +++ b/test/unit/local/atom/dispatch.js @@ -66,6 +66,7 @@ describe('core/local/atom/dispatch.loop()', function() { events = sinon.createStubInstance(SyncState) prep = sinon.createStubInstance(Prep) stepOptions = { + config: this.config, events, prep, pouch: this.pouch, diff --git a/test/unit/local/atom/initial_diff.js b/test/unit/local/atom/initial_diff.js index 168e7139a..a5fa4372c 100644 --- a/test/unit/local/atom/initial_diff.js +++ b/test/unit/local/atom/initial_diff.js @@ -9,11 +9,14 @@ const Builders = require('../../../support/builders') const configHelpers = require('../../../support/helpers/config') const pouchHelpers = require('../../../support/helpers/pouch') +const { WINDOWS_DATE_MIGRATION_FLAG } = require('../../../../core/config') const Channel = require('../../../../core/local/atom/channel') const initialDiff = require('../../../../core/local/atom/initial_diff') const kind = doc => (doc.docType === 'folder' ? 'directory' : 'file') +const sameSecondDate = date => new Date(new Date(date).setMilliseconds(0)) + describe('core/local/atom/initial_diff', () => { let builders @@ -162,7 +165,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([fooRenamed, fooCreated, fooBuzzCreated, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -175,7 +178,11 @@ describe('core/local/atom/initial_diff', () => { it('clears the state after initial-scan-done is received', async function() { const state = await initialDiff.initialState({ pouch: this.pouch }) - const outChannel = initialDiff.loop(channel, { pouch: this.pouch, state }) + const outChannel = initialDiff.loop(channel, { + config: this.config, + pouch: this.pouch, + state + }) // Send normal event const fooScan = builders @@ -234,7 +241,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([barScan, buzzScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -308,6 +315,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([barScan, barBazScan, initialScanDone]) channel = initialDiff.loop(channel, { + config: this.config, pouch: this.pouch, state }) @@ -371,7 +379,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([fooScan, buzzScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -426,7 +434,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([fooScan, barScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -471,7 +479,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([fooScan, barScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -502,7 +510,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -567,7 +575,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([stillEmptyFileScan, sameContentFileScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -604,7 +612,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([dirScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([dirScan, initialScanDone]) @@ -631,12 +639,106 @@ describe('core/local/atom/initial_diff', () => { inputBatch([updatedContentScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([updatedContentScan, initialScanDone]) }) + context('when WINDOWS_DATE_MIGRATION_FLAG is active', () => { + before(function() { + this.config.setFlag(WINDOWS_DATE_MIGRATION_FLAG, true) + }) + after(function() { + this.config.setFlag(WINDOWS_DATE_MIGRATION_FLAG, false) + }) + + it('reuses the checksum of untouched files with a same second modification date', async function() { + const emptyFileUpdateDate = new Date() + const stillEmptyFile = await builders + .metafile() + .path('stillEmptyFile') + .ino(2) + .data('') + .updatedAt(sameSecondDate(emptyFileUpdateDate)) + .upToDate() + .create() + const sameContentFileUpdateDate = new Date() + const sameContentFile = await builders + .metafile() + .path('sameContentFile') + .ino(3) + .data('content') + .updatedAt(sameSecondDate(sameContentFileUpdateDate)) + .upToDate() + .create() + + const state = await initialDiff.initialState({ pouch: this.pouch }) + + const stillEmptyFileScan = builders + .event() + .fromDoc(stillEmptyFile) + .action('scan') + .mtime(emptyFileUpdateDate) + .build() + const sameContentFileScan = builders + .event() + .fromDoc(sameContentFile) + .action('scan') + .ctime(sameContentFileUpdateDate) + .build() + inputBatch([stillEmptyFileScan, sameContentFileScan, initialScanDone]) + + const events = await initialDiff + .loop(channel, { config: this.config, pouch: this.pouch, state }) + .pop() + + should(events).deepEqual([ + { + ...stillEmptyFileScan, + md5sum: stillEmptyFile.md5sum, + initialDiff: { md5sumReusedFrom: stillEmptyFile.path } + }, + { + ...sameContentFileScan, + md5sum: sameContentFile.md5sum, + initialDiff: { md5sumReusedFrom: sameContentFile.path } + }, + initialScanDone + ]) + }) + }) + + context('when WINDOWS_DATE_MIGRATION_FLAG is inactive', () => { + it('does not reuse the checksum of untouched files with a same second modification date', async function() { + const updatedContentUpdateDate = new Date() + const updatedContent = await builders + .metafile() + .path('updatedContent') + .ino(2) + .data('content') + .updatedAt(sameSecondDate(updatedContentUpdateDate)) + .upToDate() + .create() + + const state = await initialDiff.initialState({ pouch: this.pouch }) + + const updatedContentScan = builders + .event() + .fromDoc(updatedContent) + .action('scan') + .mtime(updatedContentUpdateDate) + .build() + inputBatch([updatedContentScan, initialScanDone]) + + const events = await initialDiff + .loop(channel, { config: this.config, pouch: this.pouch, state }) + .pop() + + should(events).deepEqual([updatedContentScan, initialScanDone]) + }) + }) + it('ignores events for unapplied moves', async function() { const wasDir = builders .metadir() @@ -684,7 +786,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([fooScan, fizzScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -748,7 +850,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([parent2Scan, foo2Scan, bar2Scan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -825,7 +927,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([parent2Scan, foo2Scan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() const deletedPath = path.normalize('parent-2/foo-2/bar') @@ -906,7 +1008,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([parent2Scan, fooScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -967,7 +1069,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([parent2Scan, fooScan, initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([ @@ -1002,7 +1104,7 @@ describe('core/local/atom/initial_diff', () => { inputBatch([initialScanDone]) const events = await initialDiff - .loop(channel, { pouch: this.pouch, state }) + .loop(channel, { config: this.config, pouch: this.pouch, state }) .pop() should(events).deepEqual([initialScanDone])