-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
144 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,139 +1,185 @@ | ||
'use strict'; | ||
|
||
const join = require('path').join; | ||
const stats = require('fs').statSync; | ||
const write = require('fs').writeFileSync; | ||
const graceful = require('graceful-fs'); | ||
const Promise = require('bluebird'); | ||
const tempy = require('tempy'); | ||
const test = require('tape'); | ||
const premove = require('premove'); | ||
const { join, resolve } = require('path'); | ||
const { existsSync, statSync, writeFileSync } = require('fs'); | ||
const mkdirs = require('../dist'); | ||
|
||
const fn = require('../dist'); | ||
const isWin = process.platform === 'win32'; | ||
|
||
const co = Promise.coroutine; | ||
const isDir = str => stats(str).isDirectory(); | ||
const getFixture = _ => join(tempy.directory(), 'foo/bar/baz'); | ||
const assertDir = (t, dir, mode) => { | ||
mode = mode || 0o777 & (~process.umask()); | ||
(process.platform === 'win32') && (mode = 0o666); | ||
t.is(stats(dir).mode & 0o777, mode); | ||
t.true(isDir(dir)); | ||
test.Test.prototype.exists = function (str, bool, msg) { | ||
msg = msg || (bool ? '~> (setup) exists' : '~> does not exist'); | ||
this.is(existsSync(str), bool, msg); | ||
}; | ||
|
||
test('main', co(function * (t) { | ||
const dir = getFixture(); | ||
const out = yield fn(dir); | ||
t.true(out.length > 0); | ||
assertDir(t, out); | ||
t.end(); | ||
})); | ||
test.Test.prototype.valid = function (dir, mode) { | ||
let stats = statSync(dir); | ||
mode = isWin ? 0o666 : (mode || 0o777 & (~process.umask())); | ||
this.is(stats.mode & 0o777, mode, '~> correct mode'); | ||
this.true(stats.isDirectory(), '~> is a directory'); | ||
}; | ||
|
||
test('`fs` option', co(function * (t) { | ||
const dir = getFixture(); | ||
yield fn(dir, { fs:graceful }); | ||
assertDir(t, dir); | ||
test('exports', t => { | ||
t.is(typeof mkdirs, 'function', 'a function'); | ||
t.end(); | ||
})); | ||
}); | ||
|
||
test('single (relative)', async t => { | ||
let out = await mkdirs('foo'); | ||
t.is(out, resolve('foo'), '~> returns the absolute file path'); | ||
|
||
test('`mode` option', co(function * (t) { | ||
const mode = 0o744; | ||
const dir = getFixture(); | ||
yield fn(dir, { mode }); | ||
assertDir(t, dir, mode); | ||
t.exists(out, true, '~> filepath exists'); | ||
t.valid(out); | ||
|
||
await premove(out); | ||
t.exists(out, false, 'cleanup'); | ||
|
||
// Ensure it's writable | ||
yield fn(dir); | ||
assertDir(t, dir, mode); | ||
t.end(); | ||
})); | ||
}); | ||
|
||
test('single (absolute)', async t => { | ||
let str = resolve('bar'); | ||
let out = await mkdirs(str); | ||
t.is(out, str, '~> returns the absolute file path'); | ||
|
||
t.exists(out, true, '~> filepath exists'); | ||
t.valid(out); | ||
|
||
await premove(out); | ||
t.exists(out, false, 'cleanup'); | ||
|
||
test('dir exists', co(function * (t) { | ||
const dir = yield fn(tempy.directory()); | ||
t.true(dir.length > 0); | ||
assertDir(t, dir); | ||
t.end(); | ||
})); | ||
}); | ||
|
||
test('nested create / recursive', async t => { | ||
let dir = resolve('./foo'); | ||
|
||
let out = await mkdirs('./foo/bar/baz'); | ||
t.exists(out, true, '~> filepath exists'); | ||
t.valid(out); | ||
|
||
await premove(dir); | ||
t.exists(dir, false, 'cleanup'); | ||
|
||
test('file exits', co(function * (t) { | ||
const fp = tempy.file(); | ||
write(fp, ''); | ||
try { | ||
yield fn(fp); | ||
} catch (err) { | ||
t.pass('throws'); | ||
t.is(err.code, 'EEXIST'); | ||
} | ||
t.end(); | ||
})); | ||
}); | ||
|
||
test('option: mode', async t => { | ||
let mode = 0o744; | ||
let out = await mkdirs('hello', { mode }); | ||
|
||
t.exists(out, true, '~> filepath exists'); | ||
t.valid(out, mode); | ||
|
||
await premove(out); | ||
t.exists(out, false, 'cleanup'); | ||
|
||
test('root dir', co(function * (t) { | ||
const dir = yield fn('/'); | ||
t.true(dir.length > 0); | ||
assertDir(t, dir); | ||
t.end(); | ||
})); | ||
}); | ||
|
||
test('option: cwd', async t => { | ||
let dir = resolve('foobar'); | ||
let str = resolve('foobar/foo/bar'); | ||
|
||
let out = await mkdirs('foo/bar', { cwd:dir }); | ||
t.is(out, str, '~> returns the absolute file path'); | ||
|
||
t.exists(out, true, '~> filepath exists'); | ||
t.valid(out); | ||
|
||
await premove(dir); | ||
t.exists(dir, false, 'cleanup'); | ||
|
||
test('race two', co(function * (t) { | ||
const dir = getFixture(); | ||
yield Promise.all([fn(dir), fn(dir)]); | ||
assertDir(t, dir); | ||
t.end(); | ||
})); | ||
}); | ||
|
||
test('race many', co(function * (t) { | ||
const dir = getFixture(); | ||
const all = []; | ||
test('partially exists: directory', async t => { | ||
let foo = resolve('foobar'); | ||
let dir = await mkdirs('foobar/baz'); | ||
let out = await mkdirs('hello/world', { cwd: dir }); | ||
let str = resolve('foobar/baz/hello/world'); | ||
|
||
for (let i = 0; i < 100; i++) { | ||
all.push(fn(dir)); | ||
} | ||
t.is(out, str, '~> returns the absolute file path'); | ||
|
||
t.exists(out, true, '~> filepath exists'); | ||
t.valid(out); | ||
|
||
await premove(foo); | ||
t.exists(foo, false, 'cleanup'); | ||
|
||
yield Promise.all(all); | ||
assertDir(t, dir); | ||
t.end(); | ||
})); | ||
}); | ||
|
||
test('partially exists: file', async t => { | ||
t.plan(9); | ||
|
||
let foo = resolve('foobar'); | ||
let file = join(foo, 'bar'); | ||
|
||
let dir = await mkdirs(foo); | ||
t.exists(dir, true, '~> (setup) dir exists'); | ||
|
||
// create "foobar/bar" as a file | ||
writeFileSync(file, 'asd'); | ||
t.exists(file, true, '~> (setup) file exists'); | ||
|
||
test('handles null bytes in path', co(function * (t) { | ||
const dir = join(tempy.directory(), 'foo\u0000bar'); | ||
try { | ||
yield fn(dir); | ||
await mkdirs('foobar/bar/hello'); | ||
} catch (err) { | ||
t.pass('throws'); | ||
t.is(err.code, 'ENOENT'); | ||
t.true(/null bytes/.test(err.message)); | ||
t.true(err instanceof Error, 'throws Error'); | ||
t.is(err.message, 'ENOTDIR: not a directory', '~> message'); | ||
t.is(err.code, 'ENOTDIR', '~> code'); | ||
t.is(err.path, file, '~> path'); | ||
} | ||
t.end(); | ||
})); | ||
|
||
test('handles invalid path chars (win32)', co(function * (t) { | ||
// We do this to please `nyc` | ||
const platform = process.platform; | ||
Object.defineProperty(process, 'platform', { value:'win32' }); | ||
t.exists('foobar/bar/hello', false, '~> did not create "hello" dir'); | ||
t.exists('foobar/bar', true, '~> file still remains'); | ||
|
||
await premove(foo); | ||
t.exists(foo, false, 'cleanup'); | ||
}); | ||
|
||
test('path with null bytes', async t => { | ||
t.plan(5); | ||
|
||
const dir = join(tempy.directory(), 'foo"bar'); | ||
let dir = resolve('hello'); | ||
let str = resolve('hello/bar\u0000baz'); | ||
|
||
try { | ||
yield fn(dir); | ||
await mkdirs(str); | ||
} catch (err) { | ||
t.pass('throws'); | ||
t.is(err.code, 'EINVAL'); | ||
t.true(/invalid characters/.test(err.message)); | ||
t.true(err instanceof Error, 'throws Error'); | ||
t.is(err.code, 'ERR_INVALID_ARG_VALUE', '~> code'); | ||
t.true(err.message.includes('null bytes'), '~> message'); | ||
} | ||
|
||
Object.defineProperty(process, 'platform', { value:platform }); | ||
t.end(); | ||
})); | ||
t.exists(dir, true, '~> created "hello" base directory'); | ||
|
||
await premove(dir); | ||
t.exists(dir, false, 'cleanup'); | ||
}); | ||
|
||
if (process.platform === 'win32') { | ||
// We assume the `o:\` drive doesn't exist on Windows | ||
test('handles non-existent root', co(function * (t) { | ||
if (isWin) { | ||
// assume the `o:\` drive doesn't exist on Windows | ||
test('handles non-existent root', async t => { | ||
try { | ||
yield fn('o:\\foo'); | ||
await mkdirs('o:\\foo'); | ||
} catch (err) { | ||
t.pass('throws'); | ||
t.is(err.code, 'ENOENT'); | ||
t.true(/no such file or directory/.test(err.message)); | ||
} | ||
t.end(); | ||
})); | ||
}); | ||
|
||
test('(windows) invalid pathname', async t => { | ||
try { | ||
t.plan(4); | ||
await mkdirs('foo"bar'); | ||
} catch (err) { | ||
t.true(err instanceof Error, 'throws Error'); | ||
t.is(err.message, 'EINVAL: invalid characters', '~> message'); | ||
t.is(err.path, 'foo"bar', '~> path'); | ||
t.is(err.code, 'EINVAL', '~> code'); | ||
} | ||
}); | ||
} |