node/test/parallel/test-fs-promises.js
Daniel Bayley cff7da7749
src,test: further cleanup references to osx
PR-URL: https://github.com/nodejs/node/pull/53820
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
2024-07-15 20:32:26 +00:00

513 lines
13 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,
lutimes,
mkdir,
mkdtemp,
open,
readFile,
readdir,
readlink,
realpath,
rename,
rmdir,
stat,
statfs,
symlink,
truncate,
unlink,
utimes,
writeFile
} = fsPromises;
const tmpDir = tmpdir.path;
let dirc = 0;
function nextdir() {
return `test${++dirc}`;
}
// fs.promises should be enumerable.
assert.strictEqual(
Object.prototype.propertyIsEnumerable.call(fs, 'promises'),
true
);
{
access(__filename, 0)
.then(common.mustCall());
assert.rejects(
access('this file does not exist', 0),
{
code: 'ENOENT',
name: 'Error',
message: /^ENOENT: no such file or directory, access/,
stack: /at async Function\.rejects/
}
).then(common.mustCall());
assert.rejects(
access(__filename, 8),
{
code: 'ERR_OUT_OF_RANGE',
}
).then(common.mustCall());
assert.rejects(
access(__filename, { [Symbol.toPrimitive]() { return 5; } }),
{
code: 'ERR_INVALID_ARG_TYPE',
}
).then(common.mustCall());
}
function verifyStatObject(stat) {
assert.strictEqual(typeof stat, 'object');
assert.strictEqual(typeof stat.dev, 'number');
assert.strictEqual(typeof stat.mode, 'number');
}
function verifyStatFsObject(stat, isBigint = false) {
const valueType = isBigint ? 'bigint' : 'number';
assert.strictEqual(typeof stat, 'object');
assert.strictEqual(typeof stat.type, valueType);
assert.strictEqual(typeof stat.bsize, valueType);
assert.strictEqual(typeof stat.blocks, valueType);
assert.strictEqual(typeof stat.bfree, valueType);
assert.strictEqual(typeof stat.bavail, valueType);
assert.strictEqual(typeof stat.files, valueType);
assert.strictEqual(typeof stat.ffree, valueType);
}
async function getHandle(dest) {
await copyFile(fixtures.path('baz.js'), dest);
await access(dest);
return open(dest, 'r+');
}
async function executeOnHandle(dest, func) {
let handle;
try {
handle = await getHandle(dest);
await func(handle);
} finally {
if (handle) {
await handle.close();
}
}
}
{
async function doTest() {
tmpdir.refresh();
const dest = path.resolve(tmpDir, 'baz.js');
// handle is object
{
await executeOnHandle(dest, async (handle) => {
assert.strictEqual(typeof handle, 'object');
});
}
// file stats
{
await executeOnHandle(dest, async (handle) => {
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();
});
}
// File system stats
{
const statFs = await statfs(dest);
verifyStatFsObject(statFs);
}
// File system stats bigint
{
const statFs = await statfs(dest, { bigint: true });
verifyStatFsObject(statFs, true);
}
// Test fs.read promises when length to read is zero bytes
{
const dest = path.resolve(tmpDir, 'test1.js');
await executeOnHandle(dest, async (handle) => {
const buf = Buffer.from('DAWGS WIN');
const bufLen = buf.length;
await handle.write(buf);
const ret = await handle.read(Buffer.alloc(bufLen), 0, 0, 0);
assert.strictEqual(ret.bytesRead, 0);
await unlink(dest);
});
}
// Use fallback buffer allocation when first argument is null
{
await executeOnHandle(dest, async (handle) => {
const ret = await handle.read(null, 0, 0, 0);
assert.strictEqual(ret.buffer.length, 16384);
});
}
// TypeError if buffer is not ArrayBufferView or nullable object
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(
async () => handle.read(0, 0, 0, 0),
{ code: 'ERR_INVALID_ARG_TYPE' }
);
});
}
// Bytes written to file match buffer
{
await executeOnHandle(dest, async (handle) => {
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);
});
}
// Truncate file to specified length
{
await executeOnHandle(dest, async (handle) => {
const buf = Buffer.from('hello FileHandle');
const bufLen = buf.length;
await handle.write(buf, 0, bufLen, 0);
const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0);
assert.strictEqual(ret.bytesRead, bufLen);
assert.deepStrictEqual(ret.buffer, buf);
await truncate(dest, 5);
assert.strictEqual((await readFile(dest)).toString(), 'hello');
});
}
// Invalid change of ownership
{
await executeOnHandle(dest, async (handle) => {
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());
}
await assert.rejects(
async () => {
await chown(dest, 1, -2);
},
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "gid" is out of range. ' +
'It must be >= -1 && <= 4294967295. Received -2'
});
await assert.rejects(
async () => {
await handle.chown(1, -2);
},
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "gid" is out of range. ' +
'It must be >= -1 && <= 4294967295. Received -2'
});
});
}
// Set modification times
{
await executeOnHandle(dest, async (handle) => {
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',
name: 'Error'
})(err);
}
});
}
// Set modification times with lutimes
{
const a_time = new Date();
a_time.setMinutes(a_time.getMinutes() - 1);
const m_time = new Date();
m_time.setHours(m_time.getHours() - 1);
await lutimes(dest, a_time, m_time);
const stats = await stat(dest);
assert.strictEqual(a_time.toString(), stats.atime.toString());
assert.strictEqual(m_time.toString(), stats.mtime.toString());
}
// create symlink
{
const newPath = path.resolve(tmpDir, 'baz2.js');
await rename(dest, newPath);
let 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.isMacOS) {
// `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',
name: 'Error',
message: 'The lchmod() method is not implemented'
})
),
]);
}
await unlink(newLink);
}
}
// specify symlink type
{
const dir = path.join(tmpDir, nextdir());
await symlink(tmpDir, dir, 'dir');
const stats = await lstat(dir);
assert.strictEqual(stats.isSymbolicLink(), true);
await unlink(dir);
}
// create hard link
{
const newPath = path.resolve(tmpDir, 'baz2.js');
const newLink = path.resolve(tmpDir, 'baz4.js');
await link(newPath, newLink);
await unlink(newLink);
}
// Testing readdir lists both files and directories
{
const newDir = path.resolve(tmpDir, 'dir');
const newFile = path.resolve(tmpDir, 'foo.js');
await mkdir(newDir);
await writeFile(newFile, 'DAWGS WIN!', 'utf8');
const stats = await stat(newDir);
assert(stats.isDirectory());
const list = await readdir(tmpDir);
assert.notStrictEqual(list.indexOf('dir'), -1);
assert.notStrictEqual(list.indexOf('foo.js'), -1);
await rmdir(newDir);
await unlink(newFile);
}
// Use fallback encoding when input is null
{
const newFile = path.resolve(tmpDir, 'dogs_running.js');
await writeFile(newFile, 'dogs running', { encoding: null });
const fileExists = fs.existsSync(newFile);
assert.strictEqual(fileExists, true);
}
// `mkdir` when options is number.
{
const dir = path.join(tmpDir, nextdir());
await mkdir(dir, 777);
const stats = await stat(dir);
assert(stats.isDirectory());
}
// `mkdir` when options is string.
{
const dir = path.join(tmpDir, nextdir());
await mkdir(dir, '777');
const stats = await stat(dir);
assert(stats.isDirectory());
}
// `mkdirp` when folder does not yet exist.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
await mkdir(dir, { recursive: true });
const 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, '');
await assert.rejects(
mkdir(dir, { recursive: true }),
{
code: 'EEXIST',
message: /EEXIST: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
}
);
}
// `mkdirp` when part of the path is a file.
{
const file = path.join(tmpDir, nextdir(), nextdir());
const dir = path.join(file, nextdir(), nextdir());
await mkdir(path.dirname(file));
await writeFile(file, '');
await assert.rejects(
mkdir(dir, { recursive: true }),
{
code: 'ENOTDIR',
message: /ENOTDIR: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
}
);
}
// mkdirp ./
{
const dir = path.resolve(tmpDir, `${nextdir()}/./${nextdir()}`);
await mkdir(dir, { recursive: true });
const stats = await stat(dir);
assert(stats.isDirectory());
}
// mkdirp ../
{
const dir = path.resolve(tmpDir, `${nextdir()}/../${nextdir()}`);
await mkdir(dir, { recursive: true });
const stats = await stat(dir);
assert(stats.isDirectory());
}
// fs.mkdirp requires the recursive option to be of type boolean.
// Everything else generates an error.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
['', 1, {}, [], null, Symbol('test'), () => {}].forEach((recursive) => {
assert.rejects(
// mkdir() expects to get a boolean value for options.recursive.
async () => mkdir(dir, { recursive }),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
).then(common.mustCall());
});
}
// `mkdtemp` with invalid numeric prefix
{
await mkdtemp(path.resolve(tmpDir, 'FOO'));
await assert.rejects(
// mkdtemp() expects to get a string prefix.
async () => mkdtemp(1),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
}
// Regression test for https://github.com/nodejs/node/issues/38168
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(
async () => handle.write('abc', 0, 'hex'),
{
code: 'ERR_INVALID_ARG_VALUE',
message: /'encoding' is invalid for data of length 3/
}
);
const ret = await handle.write('abcd', 0, 'hex');
assert.strictEqual(ret.bytesWritten, 2);
});
}
// Test prototype methods calling with contexts other than FileHandle
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(() => handle.stat.call({}), {
code: 'ERR_INTERNAL_ASSERTION',
message: /handle must be an instance of FileHandle/
});
});
}
}
doTest().then(common.mustCall());
}