From caed5a1a44a262882476a36921e009d157e6bb1c Mon Sep 17 00:00:00 2001 From: bluelovers Date: Tue, 7 Mar 2017 00:13:04 -0800 Subject: [PATCH 1/6] Allow preserveTimestamps with move. --- lib/move/index.js | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/move/index.js b/lib/move/index.js index 195ab9f7..f668b751 100644 --- a/lib/move/index.js +++ b/lib/move/index.js @@ -12,6 +12,8 @@ const path = require('path') const remove = require('../remove').remove const mkdirp = require('../mkdirs').mkdirs +const utimes = require('../util/utimes') + function move (source, dest, options, callback) { if (typeof options === 'function') { callback = options @@ -21,6 +23,10 @@ function move (source, dest, options, callback) { const shouldMkdirp = ('mkdirp' in options) ? options.mkdirp : true const overwrite = options.overwrite || options.clobber || false + let optionsCopy = { + preserveTimestamps: ('preserveTimestamps' in options) ? options.preserveTimestamps : true + } + if (shouldMkdirp) { mkdirs() } else { @@ -61,13 +67,13 @@ function move (source, dest, options, callback) { } if (err.code !== 'EXDEV') return callback(err) - moveAcrossDevice(source, dest, overwrite, callback) + moveAcrossDevice(source, dest, overwrite, optionsCopy, callback) }) } else { fs.link(source, dest, err => { if (err) { if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') { - moveAcrossDevice(source, dest, overwrite, callback) + moveAcrossDevice(source, dest, overwrite, optionsCopy, callback) return } callback(err) @@ -79,22 +85,26 @@ function move (source, dest, options, callback) { } } -function moveAcrossDevice (source, dest, overwrite, callback) { +function moveAcrossDevice (source, dest, overwrite, optionsCopy, callback) { fs.stat(source, (err, stat) => { if (err) { callback(err) return } + let optionsTemp = Object.assign({}, optionsCopy, { + stat: stat + }) + if (stat.isDirectory()) { - moveDirAcrossDevice(source, dest, overwrite, callback) + moveDirAcrossDevice(source, dest, overwrite, optionsTemp, callback) } else { - moveFileAcrossDevice(source, dest, overwrite, callback) + moveFileAcrossDevice(source, dest, overwrite, optionsTemp, callback) } }) } -function moveFileAcrossDevice (source, dest, overwrite, callback) { +function moveFileAcrossDevice (source, dest, overwrite, optionsTemp, callback) { const flags = overwrite ? 'w' : 'wx' const ins = fs.createReadStream(source) const outs = fs.createWriteStream(dest, { flags }) @@ -112,7 +122,13 @@ function moveFileAcrossDevice (source, dest, overwrite, callback) { if (err.code === 'EISDIR' || err.code === 'EPERM') { moveDirAcrossDevice(source, dest, overwrite, callback) } else { - callback(err) + if (optionsTemp.preserveTimestamps) { + utimes.utimesMillis(dest, optionsTemp.stat.atime, optionsTemp.stat.mtime, function (err) { + return callback(err) + }) + } else { + callback(err) + } } }) }) @@ -132,8 +148,9 @@ function moveFileAcrossDevice (source, dest, overwrite, callback) { } } -function moveDirAcrossDevice (source, dest, overwrite, callback) { +function moveDirAcrossDevice (source, dest, overwrite, optionsTemp, callback) { const options = { + preserveTimestamps: optionsTemp.preserveTimestamps, overwrite: false } From 708956c1836aa0d1ea253bab5bad1a32ec3b158f Mon Sep 17 00:00:00 2001 From: bluelovers Date: Wed, 8 Mar 2017 21:46:34 +0800 Subject: [PATCH 2/6] not a good test --- .../__tests__/move.preserveTimestamps.test.js | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 lib/move/__tests__/move.preserveTimestamps.test.js diff --git a/lib/move/__tests__/move.preserveTimestamps.test.js b/lib/move/__tests__/move.preserveTimestamps.test.js new file mode 100644 index 00000000..06f878b9 --- /dev/null +++ b/lib/move/__tests__/move.preserveTimestamps.test.js @@ -0,0 +1,94 @@ +/** + * Created by user on 2017/3/8. + */ +'use strict' + +const fs = require('graceful-fs') +const os = require('os') +const fse = require(process.cwd()) +const path = require('path') +const assert = require('assert') +const rimraf = require('rimraf') + +/* global afterEach, beforeEach, describe, it */ + +function createAsyncErrFn(errCode) { + const fn = function () { + fn.callCount++ + const callback = arguments[arguments.length - 1] + setTimeout(() => { + const err = new Error() + err.code = errCode + callback(err) + }, 10 + ) + } + fn.callCount = 0 + return fn +} + +const originalRename = fs.rename +const originalLink = fs.link + +function setUpMockFs(errCode) { + fs.rename = createAsyncErrFn(errCode) + fs.link = createAsyncErrFn(errCode) +} + +function tearDownMockFs() { + fs.rename = originalRename + fs.link = originalLink +} + +describe('move', () => { + let TEST_DIR + + beforeEach(() => { + TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move') + + fse.emptyDirSync(TEST_DIR) + + // Create fixtures: + fs.writeFileSync(path.join(TEST_DIR, 'a-file'), 'sonic the hedgehog\n') + fs.mkdirSync(path.join(TEST_DIR, 'a-folder')) + fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-file'), 'tails\n') + fs.mkdirSync(path.join(TEST_DIR, 'a-folder/another-folder')) + fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-folder/file3'), 'knuckles\n') + } + ) + + afterEach(done => rimraf(TEST_DIR, done)) + + it('should preserve timestamps', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-folder/another-folder/a-file-dest` + + const options = { + preserveTimestamps: true + } + + fs.stat(src, (srcErr, srcStat) => { + assert.ifError(srcErr) + setTimeout(() => { + fse.move(src, dest, options, err => { + assert.ifError(err) + fs.stat(dest, (descErr, destStat) => { + assert.ifError(descErr) + assert.equal(srcStat.atime.getTime(), + destStat.atime.getTime(), + `atime ${srcStat.atime} should match ${destStat.atime}` + ) + assert.equal(srcStat.mtime.getTime(), + destStat.mtime.getTime(), + `mtime ${srcStat.mtime} should match ${destStat.mtime}` + ) + done() + }) + }) + }) + }, 1500) + } + ) + + } +) From 436a674a4514e45e7aef492a208a21bad8df7812 Mon Sep 17 00:00:00 2001 From: bluelovers Date: Wed, 8 Mar 2017 21:59:48 +0800 Subject: [PATCH 3/6] not a food test --- .../__tests__/move.preserveTimestamps.test.js | 124 +++++++----------- 1 file changed, 46 insertions(+), 78 deletions(-) diff --git a/lib/move/__tests__/move.preserveTimestamps.test.js b/lib/move/__tests__/move.preserveTimestamps.test.js index 06f878b9..5fccf802 100644 --- a/lib/move/__tests__/move.preserveTimestamps.test.js +++ b/lib/move/__tests__/move.preserveTimestamps.test.js @@ -12,83 +12,51 @@ const rimraf = require('rimraf') /* global afterEach, beforeEach, describe, it */ -function createAsyncErrFn(errCode) { - const fn = function () { - fn.callCount++ - const callback = arguments[arguments.length - 1] - setTimeout(() => { - const err = new Error() - err.code = errCode - callback(err) - }, 10 - ) - } - fn.callCount = 0 - return fn -} - -const originalRename = fs.rename -const originalLink = fs.link - -function setUpMockFs(errCode) { - fs.rename = createAsyncErrFn(errCode) - fs.link = createAsyncErrFn(errCode) -} - -function tearDownMockFs() { - fs.rename = originalRename - fs.link = originalLink -} - describe('move', () => { - let TEST_DIR - - beforeEach(() => { - TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move') - - fse.emptyDirSync(TEST_DIR) - - // Create fixtures: - fs.writeFileSync(path.join(TEST_DIR, 'a-file'), 'sonic the hedgehog\n') - fs.mkdirSync(path.join(TEST_DIR, 'a-folder')) - fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-file'), 'tails\n') - fs.mkdirSync(path.join(TEST_DIR, 'a-folder/another-folder')) - fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-folder/file3'), 'knuckles\n') - } - ) - - afterEach(done => rimraf(TEST_DIR, done)) - - it('should preserve timestamps', done => { - const src = `${TEST_DIR}/a-file` - const dest = `${TEST_DIR}/a-folder/another-folder/a-file-dest` - - const options = { - preserveTimestamps: true - } - - fs.stat(src, (srcErr, srcStat) => { - assert.ifError(srcErr) - setTimeout(() => { - fse.move(src, dest, options, err => { - assert.ifError(err) - fs.stat(dest, (descErr, destStat) => { - assert.ifError(descErr) - assert.equal(srcStat.atime.getTime(), - destStat.atime.getTime(), - `atime ${srcStat.atime} should match ${destStat.atime}` - ) - assert.equal(srcStat.mtime.getTime(), - destStat.mtime.getTime(), - `mtime ${srcStat.mtime} should match ${destStat.mtime}` - ) - done() - }) - }) + let TEST_DIR + + beforeEach(() => { + TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move') + + fse.emptyDirSync(TEST_DIR) + + // Create fixtures: + fs.writeFileSync(path.join(TEST_DIR, 'a-file'), 'sonic the hedgehog\n') + fs.mkdirSync(path.join(TEST_DIR, 'a-folder')) + fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-file'), 'tails\n') + fs.mkdirSync(path.join(TEST_DIR, 'a-folder/another-folder')) + fs.writeFileSync(path.join(TEST_DIR, 'a-folder/another-folder/file3'), 'knuckles\n') + }) + + afterEach(done => rimraf(TEST_DIR, done)) + + it('should preserve timestamps', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-folder/another-folder/a-file-dest` + + const options = { + preserveTimestamps: true + } + + fs.stat(src, (srcErr, srcStat) => { + assert.ifError(srcErr) + setTimeout(() => { + fse.move(src, dest, options, err => { + assert.ifError(err) + fs.stat(dest, (descErr, destStat) => { + assert.ifError(descErr) + assert.equal(srcStat.atime.getTime(), + destStat.atime.getTime(), + `atime ${srcStat.atime} should match ${destStat.atime}` + ) + assert.equal(srcStat.mtime.getTime(), + destStat.mtime.getTime(), + `mtime ${srcStat.mtime} should match ${destStat.mtime}` + ) + done() }) - }, 1500) - } - ) - - } -) + }) + }) + }, 1500) + }) +}) From f3904ae0c6d437281e53fb639112452fb2718013 Mon Sep 17 00:00:00 2001 From: bluelovers Date: Wed, 8 Mar 2017 22:24:18 +0800 Subject: [PATCH 4/6] should preserve timestamps across devices --- .../__tests__/move.preserveTimestamps.test.js | 84 +++++++++++++++++-- 1 file changed, 76 insertions(+), 8 deletions(-) diff --git a/lib/move/__tests__/move.preserveTimestamps.test.js b/lib/move/__tests__/move.preserveTimestamps.test.js index 5fccf802..fecca4ed 100644 --- a/lib/move/__tests__/move.preserveTimestamps.test.js +++ b/lib/move/__tests__/move.preserveTimestamps.test.js @@ -12,6 +12,55 @@ const rimraf = require('rimraf') /* global afterEach, beforeEach, describe, it */ +function createAsyncErrFn (errCode) { + const fn = function () { + fn.callCount++ + const callback = arguments[arguments.length - 1] + setTimeout(() => { + const err = new Error() + err.code = errCode + callback(err) + }, 10) + } + fn.callCount = 0 + return fn +} + +const originalRename = fs.rename +const originalLink = fs.link + +function setUpMockFs (errCode) { + fs.rename = createAsyncErrFn(errCode) + fs.link = createAsyncErrFn(errCode) +} + +function tearDownMockFs () { + fs.rename = originalRename + fs.link = originalLink +} + +function testFile (src, dest, options) { + return function (file) { + const a = src + const b = dest + const fromStat = fs.statSync(a) + const toStat = fs.statSync(b) + if (options.preserveTimestamps) { + // https://github.com/nodejs/io.js/issues/2069 + if (process.platform !== 'win32') { + assert.strictEqual(toStat.mtime.getTime(), fromStat.mtime.getTime()) + assert.strictEqual(toStat.atime.getTime(), fromStat.atime.getTime()) + } else { + assert.strictEqual(toStat.mtime.getTime(), utimes.timeRemoveMillis(fromStat.mtime.getTime())) + assert.strictEqual(toStat.atime.getTime(), utimes.timeRemoveMillis(fromStat.atime.getTime())) + } + } else { + assert.notEqual(toStat.mtime.getTime(), fromStat.mtime.getTime()) + // the access time might actually be the same, so check only modification time + } + } +} + describe('move', () => { let TEST_DIR @@ -45,14 +94,33 @@ describe('move', () => { assert.ifError(err) fs.stat(dest, (descErr, destStat) => { assert.ifError(descErr) - assert.equal(srcStat.atime.getTime(), - destStat.atime.getTime(), - `atime ${srcStat.atime} should match ${destStat.atime}` - ) - assert.equal(srcStat.mtime.getTime(), - destStat.mtime.getTime(), - `mtime ${srcStat.mtime} should match ${destStat.mtime}` - ) + testFile (src, dest, options) + done() + }) + }) + }) + }, 1500) + }) + + it('should preserve timestamps across devices', done => { + const src = `${TEST_DIR}/a-file` + const dest = `${TEST_DIR}/a-folder/another-folder/a-file-dest` + + const options = { + preserveTimestamps: true + } + + setUpMockFs('EXDEV') + + fs.stat(src, (srcErr, srcStat) => { + assert.ifError(srcErr) + setTimeout(() => { + fse.move(src, dest, options, err => { + assert.ifError(err) + fs.stat(dest, (descErr, destStat) => { + assert.ifError(descErr) + testFile (src, dest, options) + tearDownMockFs() done() }) }) From b517164094e060de86eec52802df091ae07fadd0 Mon Sep 17 00:00:00 2001 From: bluelovers Date: Wed, 8 Mar 2017 22:25:59 +0800 Subject: [PATCH 5/6] add miss code --- lib/move/index.js | 352 +++++++++++++++++++++++----------------------- 1 file changed, 176 insertions(+), 176 deletions(-) diff --git a/lib/move/index.js b/lib/move/index.js index f668b751..44fe13be 100644 --- a/lib/move/index.js +++ b/lib/move/index.js @@ -1,176 +1,176 @@ -'use strict' - -// most of this code was written by Andrew Kelley -// licensed under the BSD license: see -// https://github.com/andrewrk/node-mv/blob/master/package.json - -// this needs a cleanup - -const fs = require('graceful-fs') -const ncp = require('../copy/ncp') -const path = require('path') -const remove = require('../remove').remove -const mkdirp = require('../mkdirs').mkdirs - -const utimes = require('../util/utimes') - -function move (source, dest, options, callback) { - if (typeof options === 'function') { - callback = options - options = {} - } - - const shouldMkdirp = ('mkdirp' in options) ? options.mkdirp : true - const overwrite = options.overwrite || options.clobber || false - - let optionsCopy = { - preserveTimestamps: ('preserveTimestamps' in options) ? options.preserveTimestamps : true - } - - if (shouldMkdirp) { - mkdirs() - } else { - doRename() - } - - function mkdirs () { - mkdirp(path.dirname(dest), err => { - if (err) return callback(err) - doRename() - }) - } - - function doRename () { - if (overwrite) { - fs.rename(source, dest, err => { - if (!err) return callback() - - if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST') { - remove(dest, err => { - if (err) return callback(err) - options.overwrite = false // just overwriteed it, no need to do it again - move(source, dest, options, callback) - }) - return - } - - // weird Windows shit - if (err.code === 'EPERM') { - setTimeout(() => { - remove(dest, err => { - if (err) return callback(err) - options.overwrite = false - move(source, dest, options, callback) - }) - }, 200) - return - } - - if (err.code !== 'EXDEV') return callback(err) - moveAcrossDevice(source, dest, overwrite, optionsCopy, callback) - }) - } else { - fs.link(source, dest, err => { - if (err) { - if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') { - moveAcrossDevice(source, dest, overwrite, optionsCopy, callback) - return - } - callback(err) - return - } - fs.unlink(source, callback) - }) - } - } -} - -function moveAcrossDevice (source, dest, overwrite, optionsCopy, callback) { - fs.stat(source, (err, stat) => { - if (err) { - callback(err) - return - } - - let optionsTemp = Object.assign({}, optionsCopy, { - stat: stat - }) - - if (stat.isDirectory()) { - moveDirAcrossDevice(source, dest, overwrite, optionsTemp, callback) - } else { - moveFileAcrossDevice(source, dest, overwrite, optionsTemp, callback) - } - }) -} - -function moveFileAcrossDevice (source, dest, overwrite, optionsTemp, callback) { - const flags = overwrite ? 'w' : 'wx' - const ins = fs.createReadStream(source) - const outs = fs.createWriteStream(dest, { flags }) - - ins.on('error', err => { - ins.destroy() - outs.destroy() - outs.removeListener('close', onClose) - - // may want to create a directory but `out` line above - // creates an empty file for us: See #108 - // don't care about error here - fs.unlink(dest, () => { - // note: `err` here is from the input stream errror - if (err.code === 'EISDIR' || err.code === 'EPERM') { - moveDirAcrossDevice(source, dest, overwrite, callback) - } else { - if (optionsTemp.preserveTimestamps) { - utimes.utimesMillis(dest, optionsTemp.stat.atime, optionsTemp.stat.mtime, function (err) { - return callback(err) - }) - } else { - callback(err) - } - } - }) - }) - - outs.on('error', err => { - ins.destroy() - outs.destroy() - outs.removeListener('close', onClose) - callback(err) - }) - - outs.once('close', onClose) - ins.pipe(outs) - - function onClose () { - fs.unlink(source, callback) - } -} - -function moveDirAcrossDevice (source, dest, overwrite, optionsTemp, callback) { - const options = { - preserveTimestamps: optionsTemp.preserveTimestamps, - overwrite: false - } - - if (overwrite) { - remove(dest, err => { - if (err) return callback(err) - startNcp() - }) - } else { - startNcp() - } - - function startNcp () { - ncp(source, dest, options, err => { - if (err) return callback(err) - remove(source, callback) - }) - } -} - -module.exports = { - move -} +'use strict' + +// most of this code was written by Andrew Kelley +// licensed under the BSD license: see +// https://github.com/andrewrk/node-mv/blob/master/package.json + +// this needs a cleanup + +const fs = require('graceful-fs') +const ncp = require('../copy/ncp') +const path = require('path') +const remove = require('../remove').remove +const mkdirp = require('../mkdirs').mkdirs + +const utimes = require('../util/utimes') + +function move (source, dest, options, callback) { + if (typeof options === 'function') { + callback = options + options = {} + } + + const shouldMkdirp = ('mkdirp' in options) ? options.mkdirp : true + const overwrite = options.overwrite || options.clobber || false + + let optionsCopy = { + preserveTimestamps: ('preserveTimestamps' in options) ? options.preserveTimestamps : true + } + + if (shouldMkdirp) { + mkdirs() + } else { + doRename() + } + + function mkdirs () { + mkdirp(path.dirname(dest), err => { + if (err) return callback(err) + doRename() + }) + } + + function doRename () { + if (overwrite) { + fs.rename(source, dest, err => { + if (!err) return callback() + + if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST') { + remove(dest, err => { + if (err) return callback(err) + options.overwrite = false // just overwriteed it, no need to do it again + move(source, dest, options, callback) + }) + return + } + + // weird Windows shit + if (err.code === 'EPERM') { + setTimeout(() => { + remove(dest, err => { + if (err) return callback(err) + options.overwrite = false + move(source, dest, options, callback) + }) + }, 200) + return + } + + if (err.code !== 'EXDEV') return callback(err) + moveAcrossDevice(source, dest, overwrite, optionsCopy, callback) + }) + } else { + fs.link(source, dest, err => { + if (err) { + if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') { + moveAcrossDevice(source, dest, overwrite, optionsCopy, callback) + return + } + callback(err) + return + } + fs.unlink(source, callback) + }) + } + } +} + +function moveAcrossDevice (source, dest, overwrite, optionsCopy, callback) { + fs.stat(source, (err, stat) => { + if (err) { + callback(err) + return + } + + let optionsTemp = Object.assign({}, optionsCopy, { + stat: stat + }) + + if (stat.isDirectory()) { + moveDirAcrossDevice(source, dest, overwrite, optionsTemp, callback) + } else { + moveFileAcrossDevice(source, dest, overwrite, optionsTemp, callback) + } + }) +} + +function moveFileAcrossDevice (source, dest, overwrite, optionsTemp, callback) { + const flags = overwrite ? 'w' : 'wx' + const ins = fs.createReadStream(source) + const outs = fs.createWriteStream(dest, { flags }) + + ins.on('error', err => { + ins.destroy() + outs.destroy() + outs.removeListener('close', onClose) + + // may want to create a directory but `out` line above + // creates an empty file for us: See #108 + // don't care about error here + fs.unlink(dest, () => { + // note: `err` here is from the input stream errror + if (err.code === 'EISDIR' || err.code === 'EPERM') { + moveDirAcrossDevice(source, dest, overwrite, optionsTemp, callback) + } else { + if (optionsTemp.preserveTimestamps) { + utimes.utimesMillis(dest, optionsTemp.stat.atime, optionsTemp.stat.mtime, function (err) { + return callback(err) + }) + } else { + callback(err) + } + } + }) + }) + + outs.on('error', err => { + ins.destroy() + outs.destroy() + outs.removeListener('close', onClose) + callback(err) + }) + + outs.once('close', onClose) + ins.pipe(outs) + + function onClose () { + fs.unlink(source, callback) + } +} + +function moveDirAcrossDevice (source, dest, overwrite, optionsTemp, callback) { + const options = { + preserveTimestamps: optionsTemp.preserveTimestamps, + overwrite: false + } + + if (overwrite) { + remove(dest, err => { + if (err) return callback(err) + startNcp() + }) + } else { + startNcp() + } + + function startNcp () { + ncp(source, dest, options, err => { + if (err) return callback(err) + remove(source, callback) + }) + } +} + +module.exports = { + move +} From edc5070ed1295e37d3f4f2480696cfb1e9aac7db Mon Sep 17 00:00:00 2001 From: bluelovers Date: Wed, 8 Mar 2017 22:36:49 +0800 Subject: [PATCH 6/6] feel bad on online test --- lib/move/__tests__/move.preserveTimestamps.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/move/__tests__/move.preserveTimestamps.test.js b/lib/move/__tests__/move.preserveTimestamps.test.js index fecca4ed..879b1a8a 100644 --- a/lib/move/__tests__/move.preserveTimestamps.test.js +++ b/lib/move/__tests__/move.preserveTimestamps.test.js @@ -10,6 +10,8 @@ const path = require('path') const assert = require('assert') const rimraf = require('rimraf') +const utimes = require('../../util/utimes') + /* global afterEach, beforeEach, describe, it */ function createAsyncErrFn (errCode) { @@ -94,7 +96,7 @@ describe('move', () => { assert.ifError(err) fs.stat(dest, (descErr, destStat) => { assert.ifError(descErr) - testFile (src, dest, options) + testFile(src, dest, options) done() }) }) @@ -119,7 +121,7 @@ describe('move', () => { assert.ifError(err) fs.stat(dest, (descErr, destStat) => { assert.ifError(descErr) - testFile (src, dest, options) + testFile(src, dest, options) tearDownMockFs() done() })