1var path = require('path') 2 3var test = require('tap').test 4 5var readdir = require('graceful-fs').readdir 6var readdirSync = require('graceful-fs').readdirSync 7var rmdir = require('graceful-fs').rmdir 8var statSync = require('graceful-fs').statSync 9var writeFile = require('graceful-fs').writeFile 10var mkdirp = require('mkdirp') 11var mkdtemp = require('tmp').dir 12var tmpFile = require('tmp').file 13var EEXIST = require('errno').code.EEXIST 14var ENOTEMPTY = require('errno').code.ENOTEMPTY 15 16var requireInject = require('require-inject') 17var vacuum = requireInject('../vacuum.js', { 18 'graceful-fs': { 19 'lstat': require('graceful-fs').lstat, 20 'readdir': function (dir, cb) { 21 readdir(dir, function (err, files) { 22 // Simulate racy removal by creating a file after vacuum 23 // thinks the directory is empty 24 if (dir === partialPath && files.length === 0) { 25 tmpFile({dir: dir}, function (err, path, fd) { 26 if (err) throw err 27 cb(err, files) 28 }) 29 } else { 30 cb(err, files) 31 } 32 }) 33 }, 34 'rmdir': function (dir, cb) { 35 rmdir(dir, function (err) { 36 // Force EEXIST error from rmdir if the directory is non-empty 37 var mockErr = EEXIST 38 if (err) { 39 if (err.code === ENOTEMPTY.code) { 40 err.code = err.errno = mockErr.code 41 err.message = mockErr.code + ': ' + mockErr.description + ', ' + err.syscall + ' \'' + dir + '\'' 42 } 43 } 44 cb(err) 45 }) 46 }, 47 'unlink': require('graceful-fs').unlink 48 } 49}) 50 51// CONSTANTS 52var TEMP_OPTIONS = { 53 unsafeCleanup: true, 54 mode: '0700' 55} 56var SHORT_PATH = path.join('i', 'am', 'a', 'path') 57var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a') 58var FULL_PATH = path.join(PARTIAL_PATH, 'file') 59 60var messages = [] 61function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } 62 63var testBase, partialPath, fullPath 64test('xXx setup xXx', function (t) { 65 mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { 66 t.ifError(er, 'temp directory exists') 67 68 testBase = path.resolve(tmpdir, SHORT_PATH) 69 partialPath = path.resolve(tmpdir, PARTIAL_PATH) 70 fullPath = path.resolve(tmpdir, FULL_PATH) 71 72 mkdirp(partialPath, function (er) { 73 t.ifError(er, 'made test path') 74 75 writeFile(fullPath, new Buffer('hi'), function (er) { 76 t.ifError(er, 'made file') 77 78 t.end() 79 }) 80 }) 81 }) 82}) 83 84test('racy removal should quit gracefully', function (t) { 85 vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) { 86 t.ifError(er, 'cleaned up to base') 87 88 t.equal(messages.length, 3, 'got 2 removal & 1 quit message') 89 t.equal(messages[2], 'quitting because new (racy) entries in ' + partialPath) 90 91 var stat 92 var verifyPath = fullPath 93 94 function verify () { stat = statSync(verifyPath) } 95 96 // handle the file separately 97 t.throws(verify, verifyPath + ' cannot be statted') 98 t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone') 99 verifyPath = path.dirname(verifyPath) 100 101 for (var i = 0; i < 4; i++) { 102 t.doesNotThrow(function () { 103 stat = statSync(verifyPath) 104 }, verifyPath + ' can be statted') 105 t.ok(stat && stat.isDirectory(), verifyPath + ' is still a directory') 106 verifyPath = path.dirname(verifyPath) 107 } 108 109 t.doesNotThrow(function () { 110 stat = statSync(testBase) 111 }, testBase + ' can be statted') 112 t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') 113 114 var files = readdirSync(testBase) 115 t.equal(files.length, 1, 'base directory untouched') 116 117 t.end() 118 }) 119}) 120