Compare commits

...

1 Commits

Author SHA1 Message Date
Meghan Denny
0396d3266a node: fix test-fs-promises.js 2025-09-04 15:16:28 -07:00
3 changed files with 544 additions and 0 deletions

View File

@@ -2464,6 +2464,16 @@ pub const Arguments = struct {
if (current.isString()) {
args.encoding = try Encoding.assert(current, ctx, args.encoding);
arguments.eat();
const len = buffer.slice().len;
if (args.encoding == .hex and len % 2 != 0) {
return ctx.ERR(.INVALID_ARG_VALUE, "The argument '{s}' is invalid for data of length {d}. Received '{s}'", .{ "encoding", len, @tagName(args.encoding) }).throw();
}
args.buffer.deinit();
args.buffer = try StringOrBuffer.fromJSWithEncoding(ctx, bun.default_allocator, buffer_value.?, args.encoding) orelse {
return ctx.throwInvalidArgumentTypeValue("buffer", "string or TypedArray", buffer_value.?);
};
}
},
// fs.write(fd, buffer[, offset[, length[, position]]], callback)

View File

@@ -230,6 +230,11 @@ function asyncWrap(fn: any, name: string) {
return wrapped;
}
// most code should use $assert but this code is exposed and tested publicly
function assert(ok: boolean, message: string) {
if (!ok) throw $ERR_INTERNAL_ASSERTION(message);
}
{
const {
writeFile,
@@ -278,6 +283,7 @@ function asyncWrap(fn: any, name: string) {
[Symbol("messaging_transfer_symbol")]() {}
async appendFile(data, options) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("writeFile", fd);
let encoding = "utf8";
@@ -299,6 +305,7 @@ function asyncWrap(fn: any, name: string) {
}
async chmod(mode) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("fchmod", fd);
@@ -311,6 +318,7 @@ function asyncWrap(fn: any, name: string) {
}
async chown(uid, gid) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("fchown", fd);
@@ -323,6 +331,7 @@ function asyncWrap(fn: any, name: string) {
}
async datasync() {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("fdatasync", fd);
@@ -335,6 +344,7 @@ function asyncWrap(fn: any, name: string) {
}
async sync() {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("fsync", fd);
@@ -347,6 +357,7 @@ function asyncWrap(fn: any, name: string) {
}
async read(bufferOrParams, offset, length, position) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("fsync", fd);
@@ -390,6 +401,7 @@ function asyncWrap(fn: any, name: string) {
}
async readv(buffers, position) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("readv", fd);
@@ -402,6 +414,7 @@ function asyncWrap(fn: any, name: string) {
}
async readFile(options) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("readFile", fd);
@@ -418,6 +431,7 @@ function asyncWrap(fn: any, name: string) {
}
async stat(options) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("fstat", fd);
@@ -430,6 +444,7 @@ function asyncWrap(fn: any, name: string) {
}
async truncate(len = 0) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("ftruncate", fd);
@@ -442,6 +457,7 @@ function asyncWrap(fn: any, name: string) {
}
async utimes(atime, mtime) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("futimes", fd);
@@ -454,6 +470,7 @@ function asyncWrap(fn: any, name: string) {
}
async write(buffer, offset, length, position) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("write", fd);
@@ -480,6 +497,7 @@ function asyncWrap(fn: any, name: string) {
}
async writev(buffers, position) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("writev", fd);
@@ -492,6 +510,7 @@ function asyncWrap(fn: any, name: string) {
}
async writeFile(data: string, options: any = "utf8") {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("writeFile", fd);
let encoding: string = "utf8";
@@ -514,6 +533,7 @@ function asyncWrap(fn: any, name: string) {
}
close = () => {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
if (fd === -1) {
return Promise.$resolve();
@@ -551,6 +571,7 @@ function asyncWrap(fn: any, name: string) {
}
readableWebStream(_options = kEmptyObject) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("readableWebStream", fd);
@@ -558,6 +579,7 @@ function asyncWrap(fn: any, name: string) {
}
createReadStream(options = kEmptyObject) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("createReadStream", fd);
return new (require("internal/fs/streams").ReadStream)(undefined, {
@@ -568,6 +590,7 @@ function asyncWrap(fn: any, name: string) {
}
createWriteStream(options = kEmptyObject) {
assert(this[kRefs] !== undefined, "handle must be an instance of FileHandle");
const fd = this[kFd];
throwEBADFIfNecessary("createWriteStream", fd);
return new (require("internal/fs/streams").WriteStream)(undefined, {

View File

@@ -0,0 +1,511 @@
'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/,
}
).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 and <= 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 and <= 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: "The argument 'encoding' is invalid for data of length 3. Received 'hex'"
}
);
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());
}