From 8efdbcdaed7f8eeafa93372fcb3e64edc20b7703 Mon Sep 17 00:00:00 2001 From: Chris Blossom Date: Fri, 12 Jul 2019 11:04:58 -0700 Subject: [PATCH] Prevent race condition on macOS when deleting files (#95) --- index.js | 8 ++++-- test.js | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 1d15158..408a9a6 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,8 @@ module.exports = async (patterns, {force, dryRun, ...options} = {}) => { ...options }; - const files = await globby(patterns, options); + const files = (await globby(patterns, options)) + .sort((a, b) => b.localeCompare(a)); const mapper = async file => { if (!force) { @@ -54,7 +55,10 @@ module.exports.sync = (patterns, {force, dryRun, ...options} = {}) => { ...options }; - return globby.sync(patterns, options).map(file => { + const files = globby.sync(patterns, options) + .sort((a, b) => b.localeCompare(a)); + + return files.map(file => { if (!force) { safeCheck(file); } diff --git a/test.js b/test.js index 036cd32..6d6fee5 100644 --- a/test.js +++ b/test.js @@ -88,9 +88,9 @@ test('don\'t delete files, but return them - async', async t => { }); exists(t, ['1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']); t.deepEqual(deletedFiles, [ - path.join(t.context.tmp, '2.tmp'), + path.join(t.context.tmp, '4.tmp'), path.join(t.context.tmp, '3.tmp'), - path.join(t.context.tmp, '4.tmp') + path.join(t.context.tmp, '2.tmp') ]); }); @@ -101,8 +101,79 @@ test('don\'t delete files, but return them - sync', t => { }); exists(t, ['1.tmp', '2.tmp', '3.tmp', '4.tmp', '.dot.tmp']); t.deepEqual(deletedFiles, [ - path.join(t.context.tmp, '2.tmp'), + path.join(t.context.tmp, '4.tmp'), path.join(t.context.tmp, '3.tmp'), - path.join(t.context.tmp, '4.tmp') + path.join(t.context.tmp, '2.tmp') ]); }); + +// Currently this only testable locally on an osx machine. +// https://github.com/sindresorhus/del/issues/68 +test.serial('does not throw EINVAL - async', async t => { + await del('**/*', { + cwd: t.context.tmp, + dot: true + }); + + const nestedFile = path.resolve(t.context.tmp, 'a/b/c/nested.js'); + const totalAttempts = 200; + + let count = 0; + while (count !== totalAttempts) { + makeDir.sync(nestedFile); + + // eslint-disable-next-line no-await-in-loop + const removed = await del('**/*', { + cwd: t.context.tmp, + dot: true + }); + + const expected = [ + path.resolve(t.context.tmp, 'a/b/c/nested.js'), + path.resolve(t.context.tmp, 'a/b/c'), + path.resolve(t.context.tmp, 'a/b'), + path.resolve(t.context.tmp, 'a') + ]; + + t.deepEqual(removed, expected); + + count += 1; + } + + notExists(t, [...fixtures, 'a']); + t.is(count, totalAttempts); +}); + +test.serial('does not throw EINVAL - sync', t => { + del.sync('**/*', { + cwd: t.context.tmp, + dot: true + }); + + const nestedFile = path.resolve(t.context.tmp, 'a/b/c/nested.js'); + const totalAttempts = 200; + + let count = 0; + while (count !== totalAttempts) { + makeDir.sync(nestedFile); + + const removed = del.sync('**/*', { + cwd: t.context.tmp, + dot: true + }); + + const expected = [ + path.resolve(t.context.tmp, 'a/b/c/nested.js'), + path.resolve(t.context.tmp, 'a/b/c'), + path.resolve(t.context.tmp, 'a/b'), + path.resolve(t.context.tmp, 'a') + ]; + + t.deepEqual(removed, expected); + + count += 1; + } + + notExists(t, [...fixtures, 'a']); + t.is(count, totalAttempts); +});