mirror of
https://github.com/jprichardson/node-fs-extra.git
synced 2026-02-01 17:21:13 +00:00
Add moveSync and its tests
This commit is contained in:
parent
9bef553728
commit
00063917f7
@ -18,6 +18,7 @@ assign(fs, require('./mkdirs'))
|
||||
assign(fs, require('./remove'))
|
||||
assign(fs, require('./json'))
|
||||
assign(fs, require('./move'))
|
||||
assign(fs, require('./move-sync'))
|
||||
assign(fs, require('./empty'))
|
||||
assign(fs, require('./ensure'))
|
||||
assign(fs, require('./output'))
|
||||
|
||||
411
lib/move-sync/__tests__/move-sync.test.js
Normal file
411
lib/move-sync/__tests__/move-sync.test.js
Normal file
@ -0,0 +1,411 @@
|
||||
'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 createSyncErrFn (errCode) {
|
||||
const fn = function () {
|
||||
const err = new Error()
|
||||
err.code = errCode
|
||||
throw err
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
const originalRenameSync = fs.renameSync
|
||||
const originalLinkSync = fs.linkSync
|
||||
|
||||
function setUpMockFs (errCode) {
|
||||
fs.renameSync = createSyncErrFn(errCode)
|
||||
fs.linkSync = createSyncErrFn(errCode)
|
||||
}
|
||||
|
||||
function tearDownMockFs () {
|
||||
fs.renameSync = originalRenameSync
|
||||
fs.linkSync = originalLinkSync
|
||||
}
|
||||
|
||||
describe('moveSync()', () => {
|
||||
let TEST_DIR
|
||||
|
||||
beforeEach(() => {
|
||||
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'move-sync')
|
||||
|
||||
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 not move if src and dest are the same', () => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/a-file`
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
// assert src not affected
|
||||
const contents = fs.readFileSync(src, 'utf8')
|
||||
const expected = /^sonic the hedgehog\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
})
|
||||
|
||||
it('should rename a file on the same device', () => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/a-file-dest`
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest, 'utf8')
|
||||
const expected = /^sonic the hedgehog\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
})
|
||||
|
||||
it('should not overwrite the destination by default', () => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/a-folder/another-file`
|
||||
|
||||
// verify file exists already
|
||||
assert(fs.existsSync(dest))
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ok(err && err.code === 'EEXIST', 'throw EEXIST')
|
||||
}
|
||||
})
|
||||
|
||||
it('should not overwrite if overwrite = false', () => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/a-folder/another-file`
|
||||
|
||||
// verify file exists already
|
||||
assert(fs.existsSync(dest))
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest, {overwrite: false})
|
||||
} catch (err) {
|
||||
assert.ok(err && err.code === 'EEXIST', 'throw EEXIST')
|
||||
}
|
||||
})
|
||||
|
||||
it('should overwrite file if overwrite = true', () => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/a-folder/another-file`
|
||||
|
||||
// verify file exists already
|
||||
assert(fs.existsSync(dest))
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest, {overwrite: true})
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest, 'utf8')
|
||||
const expected = /^sonic the hedgehog\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
})
|
||||
|
||||
it('should overwrite the destination directory if overwrite = true', function (done) {
|
||||
// Tests fail on appveyor/Windows due to
|
||||
// https://github.com/isaacs/node-graceful-fs/issues/98.
|
||||
// Workaround by increasing the timeout by a minute (because
|
||||
// graceful times out after a minute).
|
||||
this.timeout(90000)
|
||||
|
||||
// Create src
|
||||
const src = path.join(TEST_DIR, 'src')
|
||||
fse.ensureDirSync(src)
|
||||
fse.mkdirsSync(path.join(src, 'some-folder'))
|
||||
fs.writeFileSync(path.join(src, 'some-file'), 'hi')
|
||||
|
||||
const dest = path.join(TEST_DIR, 'a-folder')
|
||||
|
||||
// verify dest has stuff in it
|
||||
const pathsBefore = fs.readdirSync(dest)
|
||||
assert(pathsBefore.indexOf('another-file') >= 0)
|
||||
assert(pathsBefore.indexOf('another-folder') >= 0)
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest, {overwrite: true})
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
// verify dest does not have old stuff
|
||||
const pathsAfter = fs.readdirSync(dest)
|
||||
assert.strictEqual(pathsAfter.indexOf('another-file'), -1)
|
||||
assert.strictEqual(pathsAfter.indexOf('another-folder'), -1)
|
||||
|
||||
// verify dest has new stuff
|
||||
assert(pathsAfter.indexOf('some-file') >= 0)
|
||||
assert(pathsAfter.indexOf('some-folder') >= 0)
|
||||
done()
|
||||
})
|
||||
|
||||
/*
|
||||
it('should not create directory structure if mkdirp is false', done => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/does/not/exist/a-file-dest`
|
||||
|
||||
// verify dest directory does not exist
|
||||
assert(!fs.existsSync(path.dirname(dest)))
|
||||
|
||||
fse.move(src, dest, {mkdirp: false}, err => {
|
||||
assert.strictEqual(err.code, 'ENOENT')
|
||||
done()
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
it('should create directory structure by default', () => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/does/not/exist/a-file-dest`
|
||||
|
||||
// verify dest directory does not exist
|
||||
assert(!fs.existsSync(path.dirname(dest)))
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest, 'utf8')
|
||||
const expected = /^sonic the hedgehog\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
})
|
||||
|
||||
it('should work across devices', () => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/a-file-dest`
|
||||
|
||||
setUpMockFs('EXDEV')
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest, 'utf8')
|
||||
const expected = /^sonic the hedgehog\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
tearDownMockFs('EXDEV')
|
||||
})
|
||||
|
||||
it('should move folders', () => {
|
||||
const src = `${TEST_DIR}/a-folder`
|
||||
const dest = `${TEST_DIR}/a-folder-dest`
|
||||
|
||||
// verify it doesn't exist
|
||||
assert(!fs.existsSync(dest))
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest + '/another-file', 'utf8')
|
||||
const expected = /^tails\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
})
|
||||
|
||||
it('should move folders across devices with EISDIR error', () => {
|
||||
const src = `${TEST_DIR}/a-folder`
|
||||
const dest = `${TEST_DIR}/a-folder-dest`
|
||||
|
||||
setUpMockFs('EISDIR')
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest + '/another-folder/file3', 'utf8')
|
||||
const expected = /^knuckles\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
tearDownMockFs('EISDIR')
|
||||
})
|
||||
|
||||
it('should overwrite folders across devices', () => {
|
||||
const src = `${TEST_DIR}/a-folder`
|
||||
const dest = `${TEST_DIR}/a-folder-dest`
|
||||
|
||||
fs.mkdirSync(dest)
|
||||
|
||||
setUpMockFs('EXDEV')
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest, {overwrite: true})
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest + '/another-folder/file3', 'utf8')
|
||||
const expected = /^knuckles\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
tearDownMockFs('EXDEV')
|
||||
})
|
||||
|
||||
it('should move folders across devices with EXDEV error', () => {
|
||||
const src = `${TEST_DIR}/a-folder`
|
||||
const dest = `${TEST_DIR}/a-folder-dest`
|
||||
|
||||
setUpMockFs('EXDEV')
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest + '/another-folder/file3', 'utf8')
|
||||
const expected = /^knuckles\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
tearDownMockFs('EXDEV')
|
||||
})
|
||||
|
||||
it('should move folders across devices with EPERM error', () => {
|
||||
const src = `${TEST_DIR}/a-folder`
|
||||
const dest = `${TEST_DIR}/a-folder-dest`
|
||||
|
||||
setUpMockFs('EPERM')
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest + '/another-folder/file3', 'utf8')
|
||||
const expected = /^knuckles\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
tearDownMockFs('EPERM')
|
||||
})
|
||||
|
||||
it('should move folders across devices with ENOTSUP error', () => {
|
||||
const src = `${TEST_DIR}/a-folder`
|
||||
const dest = `${TEST_DIR}/a-folder-dest`
|
||||
|
||||
setUpMockFs('ENOTSUP')
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest + '/another-folder/file3', 'utf8')
|
||||
const expected = /^knuckles\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
tearDownMockFs('ENOTSUP')
|
||||
})
|
||||
|
||||
describe('clobber', () => {
|
||||
it('should be an alias for overwrite', () => {
|
||||
const src = `${TEST_DIR}/a-file`
|
||||
const dest = `${TEST_DIR}/a-folder/another-file`
|
||||
|
||||
// verify file exists already
|
||||
assert(fs.existsSync(dest))
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest, {clobber: true})
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(dest, 'utf8')
|
||||
const expected = /^sonic the hedgehog\r?\n$/
|
||||
assert.ok(contents.match(expected), `${contents} match ${expected}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('> when trying to a move a folder into itself', () => {
|
||||
it('should produce an error', () => {
|
||||
const SRC_DIR = path.join(TEST_DIR, 'test')
|
||||
const DEST_DIR = path.join(TEST_DIR, 'test', 'test')
|
||||
|
||||
assert(!fs.existsSync(SRC_DIR))
|
||||
fs.mkdirSync(SRC_DIR)
|
||||
assert(fs.existsSync(SRC_DIR))
|
||||
|
||||
try {
|
||||
fse.moveSync(SRC_DIR, DEST_DIR)
|
||||
} catch (err) {
|
||||
assert(err)
|
||||
assert(fs.existsSync(SRC_DIR))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// tested on Linux ubuntu 3.13.0-32-generic #57-Ubuntu SMP i686 i686 GNU/Linux
|
||||
// this won't trigger a bug on Mac OS X Yosimite with a USB drive (/Volumes)
|
||||
// see issue #108
|
||||
describe('> when actually trying to a move a folder across devices', () => {
|
||||
const differentDevice = '/mnt'
|
||||
let __skipTests = false
|
||||
|
||||
// must set this up, if not, exit silently
|
||||
if (!fs.existsSync(differentDevice)) {
|
||||
console.log('Skipping cross-device move test')
|
||||
__skipTests = true
|
||||
}
|
||||
|
||||
// make sure we have permission on device
|
||||
try {
|
||||
fs.writeFileSync(path.join(differentDevice, 'file'), 'hi')
|
||||
} catch (err) {
|
||||
console.log("Can't write to device. Skipping moveSync test.")
|
||||
__skipTests = true
|
||||
}
|
||||
|
||||
const _it = __skipTests ? it.skip : it
|
||||
|
||||
describe('> just the folder', () => {
|
||||
_it('should move the folder', () => {
|
||||
const src = '/mnt/some/weird/dir-really-weird'
|
||||
const dest = path.join(TEST_DIR, 'device-weird')
|
||||
|
||||
if (!fs.existsSync(src)) {
|
||||
fse.mkdirpSync(src)
|
||||
}
|
||||
|
||||
assert(!fs.existsSync(dest))
|
||||
|
||||
assert(fs.lstatSync(src).isDirectory())
|
||||
|
||||
try {
|
||||
fse.moveSync(src, dest)
|
||||
} catch (err) {
|
||||
assert.ifError(err)
|
||||
}
|
||||
assert(fs.existsSync(dest))
|
||||
assert(fs.lstatSync(dest).isDirectory())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
116
lib/move-sync/index.js
Normal file
116
lib/move-sync/index.js
Normal file
@ -0,0 +1,116 @@
|
||||
'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 is the sync version that somehow follows the same pattern.
|
||||
|
||||
const fs = require('graceful-fs')
|
||||
const path = require('path')
|
||||
const copySync = require('../copy-sync').copySync
|
||||
const removeSync = require('../remove').removeSync
|
||||
const mkdirpSync = require('../mkdirs').mkdirsSync
|
||||
|
||||
function moveSync (src, dest, options) {
|
||||
options = options || {}
|
||||
const overwrite = options.overwrite || options.clobber || false
|
||||
|
||||
if (path.resolve(src) === path.resolve(dest)) return
|
||||
|
||||
mkdirpSync(path.dirname(dest))
|
||||
tryRenameSync()
|
||||
|
||||
function tryRenameSync () {
|
||||
if (overwrite) {
|
||||
try {
|
||||
return fs.renameSync(src, dest)
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST' || err.code === 'EPERM') {
|
||||
removeSync(dest)
|
||||
options.overwrite = false // just overwriteed it, no need to do it again
|
||||
return moveSync(src, dest, options)
|
||||
}
|
||||
|
||||
if (err.code !== 'EXDEV') throw err
|
||||
return moveSyncAcrossDevice(src, dest, overwrite)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
fs.linkSync(src, dest)
|
||||
return fs.unlinkSync(src)
|
||||
} catch (err) {
|
||||
if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') {
|
||||
return moveSyncAcrossDevice(src, dest, overwrite)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveSyncAcrossDevice (src, dest, overwrite) {
|
||||
const stat = fs.statSync(src)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
return moveDirSyncAcrossDevice(src, dest, overwrite)
|
||||
} else {
|
||||
return moveFileSyncAcrossDevice(src, dest, overwrite)
|
||||
}
|
||||
}
|
||||
|
||||
function moveFileSyncAcrossDevice (src, dest, overwrite) {
|
||||
const BUF_LENGTH = 64 * 1024
|
||||
const _buff = Buffer.alloc(BUF_LENGTH)
|
||||
|
||||
const flags = overwrite ? 'w' : 'wx'
|
||||
|
||||
try {
|
||||
const fdr = fs.openSync(src, 'r')
|
||||
const stat = fs.fstatSync(fdr)
|
||||
const fdw = fs.openSync(dest, flags, stat.mode)
|
||||
let bytesRead = 1
|
||||
let pos = 0
|
||||
|
||||
while (bytesRead > 0) {
|
||||
bytesRead = fs.readSync(fdr, _buff, 0, BUF_LENGTH, pos)
|
||||
fs.writeSync(fdw, _buff, 0, bytesRead)
|
||||
pos += bytesRead
|
||||
}
|
||||
|
||||
fs.closeSync(fdr)
|
||||
fs.closeSync(fdw)
|
||||
return fs.unlinkSync(src)
|
||||
} catch (err) {
|
||||
// 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.unlinkSync(dest)
|
||||
// note: `err` here is from the fdr error
|
||||
if (err.code === 'EISDIR' || err.code === 'EPERM') {
|
||||
return moveDirSyncAcrossDevice(src, dest, overwrite)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function moveDirSyncAcrossDevice (src, dest, overwrite) {
|
||||
const options = {
|
||||
overwrite: false
|
||||
}
|
||||
|
||||
if (overwrite) {
|
||||
removeSync(dest)
|
||||
tryCopySync()
|
||||
} else {
|
||||
tryCopySync()
|
||||
}
|
||||
|
||||
function tryCopySync () {
|
||||
copySync(src, dest, options)
|
||||
return removeSync(src)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
moveSync
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user