Skip to content

Commit

Permalink
fix fs-open.test.js (#14311)
Browse files Browse the repository at this point in the history
  • Loading branch information
nektro authored Oct 11, 2024
1 parent 05f53dc commit 874c9db
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 14 deletions.
21 changes: 10 additions & 11 deletions src/bun.js/node/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const Shimmer = @import("../bindings/shimmer.zig").Shimmer;
const Syscall = bun.sys;
const URL = @import("../../url.zig").URL;
const Value = std.json.Value;
const validators = @import("./util/validators.zig");

pub const Path = @import("./path.zig");

Expand Down Expand Up @@ -1210,14 +1211,16 @@ pub fn timeLikeFromJS(globalObject: *JSC.JSGlobalObject, value: JSC.JSValue, _:

pub fn modeFromJS(ctx: JSC.C.JSContextRef, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?Mode {
const mode_int = if (value.isNumber()) brk: {
if (!value.isUInt32AsAnyInt()) {
exception.* = ctx.ERR_OUT_OF_RANGE("The value of \"mode\" is out of range. It must be an integer. Received {d}", .{value.asNumber()}).toJS().asObjectRef();
return null;
}
break :brk @as(Mode, @truncate(value.to(Mode)));
const m = validators.validateUint32(ctx, value, "mode", .{}, false) catch return null;
break :brk @as(Mode, @as(u24, @truncate(m)));
} else brk: {
if (value.isUndefinedOrNull()) return null;

if (!value.isString()) {
_ = ctx.throwInvalidArgumentTypeValue("mode", "number", value);
return null;
}

// An easier method of constructing the mode is to use a sequence of
// three octal digits (e.g. 765). The left-most digit (7 in the example),
// specifies the permissions for the file owner. The middle digit (6 in
Expand All @@ -1232,16 +1235,12 @@ pub fn modeFromJS(ctx: JSC.C.JSContextRef, value: JSC.JSValue, exception: JSC.C.
}

break :brk std.fmt.parseInt(Mode, slice, 8) catch {
JSC.throwInvalidArguments("Invalid mode string: must be an octal number", .{}, ctx, exception);
var formatter = bun.JSC.ConsoleObject.Formatter{ .globalThis = ctx };
exception.* = ctx.ERR_INVALID_ARG_VALUE("The argument 'mode' must be a 32-bit unsigned integer or an octal string. Received {}", .{value.toFmt(&formatter)}).toJS().asObjectRef();
return null;
};
};

if (mode_int < 0) {
JSC.throwInvalidArguments("Invalid mode: must be greater than or equal to 0.", .{}, ctx, exception);
return null;
}

return mode_int & 0o777;
}

Expand Down
8 changes: 5 additions & 3 deletions src/bun.js/node/util/validators.zig
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,17 @@ pub fn validateUint32(globalThis: *JSGlobalObject, value: JSValue, comptime name
try throwErrInvalidArgType(globalThis, name_fmt, name_args, "number", value);
}
if (!value.isAnyInt()) {
try throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be an integer. Received {s}", name_args ++ .{value});
var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis };
try throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be an integer. Received {}", name_args ++ .{value.toFmt(&formatter)});
}
const num: i64 = value.asInt52();
const min: i64 = if (greater_than_zero) 1 else 0;
const max: i64 = @intCast(std.math.maxInt(u32));
if (num < min or num > max) {
try throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be >= {d} and <= {d}. Received {s}", name_args ++ .{ min, max, value });
var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis };
try throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be >= {d} and <= {d}. Received {}", name_args ++ .{ min, max, value.toFmt(&formatter) });
}
return @truncate(num);
return @truncate(@as(u63, @intCast(num)));
}

pub fn validateString(globalThis: *JSGlobalObject, value: JSValue, comptime name_fmt: string, name_args: anytype) !void {
Expand Down
102 changes: 102 additions & 0 deletions test/js/node/test/parallel/fs-open.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//#FILE: test-fs-open.js
//#SHA1: 0466ad8882a3256fdd8da5fc8da3167f6dde4fd6
//-----------------
'use strict';
const fs = require('fs');
const path = require('path');

test('fs.openSync throws ENOENT for non-existent file', () => {
expect(() => {
fs.openSync('/8hvftyuncxrt/path/to/file/that/does/not/exist', 'r');
}).toThrow(expect.objectContaining({
code: 'ENOENT',
message: expect.any(String)
}));
});

test('fs.openSync succeeds for existing file', () => {
expect(() => fs.openSync(__filename)).not.toThrow();
});

test('fs.open succeeds with various valid arguments', async () => {
await expect(fs.promises.open(__filename)).resolves.toBeDefined();
await expect(fs.promises.open(__filename, 'r')).resolves.toBeDefined();
await expect(fs.promises.open(__filename, 'rs')).resolves.toBeDefined();
await expect(fs.promises.open(__filename, 'r', 0)).resolves.toBeDefined();
await expect(fs.promises.open(__filename, 'r', null)).resolves.toBeDefined();
});

test('fs.open throws for invalid mode argument', () => {
expect(() => fs.open(__filename, 'r', 'boom', () => {})).toThrow(({
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError',
message: `The argument 'mode' must be a 32-bit unsigned integer or an octal string. Received boom`
}));
expect(() => fs.open(__filename, 'r', 5.5, () => {})).toThrow(({
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: `The value of "mode" is out of range. It must be an integer. Received 5.5`
}));
expect(() => fs.open(__filename, 'r', -7, () => {})).toThrow(({
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: `The value of "mode" is out of range. It must be >= 0 and <= 4294967295. Received -7`
}));
expect(() => fs.open(__filename, 'r', 4304967295, () => {})).toThrow(({
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: `The value of "mode" is out of range. It must be >= 0 and <= 4294967295. Received 4304967295`
}));
});

test('fs.open throws for invalid argument combinations', () => {
const invalidArgs = [[], ['r'], ['r', 0], ['r', 0, 'bad callback']];
invalidArgs.forEach(args => {
expect(() => fs.open(__filename, ...args)).toThrow(expect.objectContaining({
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: expect.any(String)
}));
});
});

test('fs functions throw for invalid path types', () => {
const invalidPaths = [false, 1, [], {}, null, undefined];
invalidPaths.forEach(path => {
expect(() => fs.open(path, 'r', () => {})).toThrow(expect.objectContaining({
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: expect.any(String)
}));
expect(() => fs.openSync(path, 'r')).toThrow(expect.objectContaining({
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: expect.any(String)
}));
expect(fs.promises.open(path, 'r')).rejects.toThrow(expect.objectContaining({
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: expect.any(String)
}));
});
});

test('fs functions throw for invalid modes', () => {
const invalidModes = [false, [], {}];
invalidModes.forEach(mode => {
expect(() => fs.open(__filename, 'r', mode, () => {})).toThrow(expect.objectContaining({
code: 'ERR_INVALID_ARG_TYPE',
message: expect.any(String)
}));
expect(() => fs.openSync(__filename, 'r', mode)).toThrow(expect.objectContaining({
code: 'ERR_INVALID_ARG_TYPE',
message: expect.any(String)
}));
expect(fs.promises.open(__filename, 'r', mode)).rejects.toThrow(expect.objectContaining({
code: 'ERR_INVALID_ARG_TYPE',
message: expect.any(String)
}));
});
});

//<#END_FILE: test-fs-open.js

0 comments on commit 874c9db

Please sign in to comment.