node/test/parallel/test-fs-promises.js
Benjamin Coe bdef1b1eb4
fs: implement mkdir recursive (mkdirp)
Implements mkdirp functionality in node_file.cc. The Benefit
of implementing in C++ layer is that the logic is more easily
shared between the Promise and callback implementation and
there are notable performance improvements.

This commit is part of the Tooling Group Initiative.

Refs: https://github.com/nodejs/user-feedback/pull/70

PR-URL: https://github.com/nodejs/node/pull/21875
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Jon Moss <me@jonathanmoss.me>
Reviewed-By: Ron Korving <ron@ronkorving.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: Sam Ruby <rubys@intertwingly.net>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
2018-08-11 12:07:32 -07:00

267 lines
6.8 KiB
JavaScript

'use strict';
const common = require('../common');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const path = require('path');
const fs = require('fs');
const fsPromises = fs.promises;
const {
access,
chmod,
chown,
copyFile,
lchown,
link,
lchmod,
lstat,
mkdir,
mkdtemp,
open,
readFile,
readdir,
readlink,
realpath,
rename,
rmdir,
stat,
symlink,
truncate,
unlink,
utimes,
writeFile
} = fsPromises;
const tmpDir = tmpdir.path;
let dirc = 0;
function nextdir() {
return `test${++dirc}`;
}
// fs.promises should not be enumerable as long as it causes a warning to be
// emitted.
assert.strictEqual(Object.keys(fs).includes('promises'), false);
{
access(__filename, 'r')
.then(common.mustCall());
access('this file does not exist', 'r')
.then(common.mustNotCall())
.catch(common.expectsError({
code: 'ENOENT',
type: Error,
message:
/^ENOENT: no such file or directory, access/
}));
}
function verifyStatObject(stat) {
assert.strictEqual(typeof stat, 'object');
assert.strictEqual(typeof stat.dev, 'number');
assert.strictEqual(typeof stat.mode, 'number');
}
{
async function doTest() {
tmpdir.refresh();
const dest = path.resolve(tmpDir, 'baz.js');
await copyFile(fixtures.path('baz.js'), dest);
await access(dest, 'r');
const handle = await open(dest, 'r+');
assert.strictEqual(typeof handle, 'object');
let stats = await handle.stat();
verifyStatObject(stats);
assert.strictEqual(stats.size, 35);
await handle.truncate(1);
stats = await handle.stat();
verifyStatObject(stats);
assert.strictEqual(stats.size, 1);
stats = await stat(dest);
verifyStatObject(stats);
stats = await handle.stat();
verifyStatObject(stats);
await handle.datasync();
await handle.sync();
const buf = Buffer.from('hello fsPromises');
const bufLen = buf.length;
await handle.write(buf);
const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0);
assert.strictEqual(ret.bytesRead, bufLen);
assert.deepStrictEqual(ret.buffer, buf);
const buf2 = Buffer.from('hello FileHandle');
const buf2Len = buf2.length;
await handle.write(buf2, 0, buf2Len, 0);
const ret2 = await handle.read(Buffer.alloc(buf2Len), 0, buf2Len, 0);
assert.strictEqual(ret2.bytesRead, buf2Len);
assert.deepStrictEqual(ret2.buffer, buf2);
await truncate(dest, 5);
assert.deepStrictEqual((await readFile(dest)).toString(), 'hello');
await chmod(dest, 0o666);
await handle.chmod(0o666);
await chmod(dest, (0o10777));
await handle.chmod(0o10777);
if (!common.isWindows) {
await chown(dest, process.getuid(), process.getgid());
await handle.chown(process.getuid(), process.getgid());
}
assert.rejects(
async () => {
await chown(dest, 1, -1);
},
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError [ERR_OUT_OF_RANGE]',
message: 'The value of "gid" is out of range. ' +
'It must be >= 0 && < 4294967296. Received -1'
});
assert.rejects(
async () => {
await handle.chown(1, -1);
},
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError [ERR_OUT_OF_RANGE]',
message: 'The value of "gid" is out of range. ' +
'It must be >= 0 && < 4294967296. Received -1'
});
await utimes(dest, new Date(), new Date());
try {
await handle.utimes(new Date(), new Date());
} catch (err) {
// Some systems do not have futimes. If there is an error,
// expect it to be ENOSYS
common.expectsError({
code: 'ENOSYS',
type: Error
})(err);
}
await handle.close();
const newPath = path.resolve(tmpDir, 'baz2.js');
await rename(dest, newPath);
stats = await stat(newPath);
verifyStatObject(stats);
if (common.canCreateSymLink()) {
const newLink = path.resolve(tmpDir, 'baz3.js');
await symlink(newPath, newLink);
if (!common.isWindows) {
await lchown(newLink, process.getuid(), process.getgid());
}
stats = await lstat(newLink);
verifyStatObject(stats);
assert.strictEqual(newPath.toLowerCase(),
(await realpath(newLink)).toLowerCase());
assert.strictEqual(newPath.toLowerCase(),
(await readlink(newLink)).toLowerCase());
const newMode = 0o666;
if (common.isOSX) {
// lchmod is only available on macOS
await lchmod(newLink, newMode);
stats = await lstat(newLink);
assert.strictEqual(stats.mode & 0o777, newMode);
} else {
await Promise.all([
assert.rejects(
lchmod(newLink, newMode),
common.expectsError({
code: 'ERR_METHOD_NOT_IMPLEMENTED',
type: Error,
message: 'The lchmod() method is not implemented'
})
)
]);
}
await unlink(newLink);
}
const newLink2 = path.resolve(tmpDir, 'baz4.js');
await link(newPath, newLink2);
await unlink(newLink2);
const newdir = path.resolve(tmpDir, 'dir');
await mkdir(newdir);
stats = await stat(newdir);
assert(stats.isDirectory());
const list = await readdir(tmpDir);
assert.deepStrictEqual(list, ['baz2.js', 'dir']);
await rmdir(newdir);
// mkdirp when folder does not yet exist.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
await mkdir(dir, { recursive: true });
stats = await stat(dir);
assert(stats.isDirectory());
}
// mkdirp when path is a file.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
await mkdir(path.dirname(dir));
await writeFile(dir);
try {
await mkdir(dir, { recursive: true });
throw new Error('unreachable');
} catch (err) {
assert.notStrictEqual(err.message, 'unreachable');
assert.strictEqual(err.code, 'EEXIST');
assert.strictEqual(err.syscall, 'mkdir');
}
}
// mkdirp ./
{
const dir = path.resolve(tmpDir, `${nextdir()}/./${nextdir()}`);
await mkdir(dir, { recursive: true });
stats = await stat(dir);
assert(stats.isDirectory());
}
// mkdirp ../
{
const dir = path.resolve(tmpDir, `${nextdir()}/../${nextdir()}`);
await mkdir(dir, { recursive: true });
stats = await stat(dir);
assert(stats.isDirectory());
}
await mkdtemp(path.resolve(tmpDir, 'FOO'));
assert.rejects(
// mkdtemp() expects to get a string prefix.
async () => mkdtemp(1),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError [ERR_INVALID_ARG_TYPE]'
}
);
}
doTest().then(common.mustCall());
}