Files
scriptc/tests/corpus/5545-fs-options-encoding.cjs
T
Chris Tate 1ffbb28255 Follow Node's getOptions in the fs option checks
The fs validation ladder and the callback dispatch check their options
argument as Node's getOptions does. null, undefined and functions take
the defaults, and a string is the encoding. Numbers, booleans, bigints
and symbols throw ERR_INVALID_ARG_TYPE ('The "options" argument must be
one of type string or object'). assertEncoding lets 'buffer' through;
before, every 'buffer' encoding threw ERR_INVALID_ARG_VALUE.

With 'buffer':
- readFile and readFileSync read the file first, so a missing file
  reports ENOENT. Then buffer.toString('buffer') throws
  ERR_UNKNOWN_ENCODING; an empty file answers "".
- fs.mkdtemp's callback receives the path as a Buffer.
- mkdtempSync, opendirSync, createReadStream and createWriteStream
  throw the fence error.

fs.mkdtemp's callback form now checks its encoding. It also returns its
path in the requested encoding: re-encoded text for names other than
utf8, as the binding does.

Corpus 5545 covers these forms against Node 24.22.
2026-10-15 14:54:07 -05:00

100 lines
4.5 KiB
JavaScript

// fs's getOptions: null, undefined and functions take the defaults, a
// string is the encoding, other non-objects throw ERR_INVALID_ARG_TYPE, and
// assertEncoding lets 'buffer' through. readFile and readFileSync then read
// before buffer.toString('buffer') throws ERR_UNKNOWN_ENCODING (an empty
// file answers ""), and mkdtemp's callback answers its path in the
// requested encoding: a Buffer for 'buffer', re-encoded text otherwise.
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "options-encoding-"));
const file = path.join(dir, "f.txt");
const empty = path.join(dir, "empty.txt");
fs.writeFileSync(file, "hi");
fs.writeFileSync(empty, "");
const prefix = path.join(dir, "t-");
const clean = (text) => String(text).split(dir).join("<dir>");
const describe = (value) =>
Buffer.isBuffer(value) ? `Buffer ${clean(value.toString())}` : `${typeof value} ${clean(value)}`;
const show = (label, fn) => {
try {
console.log(label, "->", describe(fn()));
} catch (error) {
console.log(label, "throws", error.name, error.code, clean(error.message));
}
};
for (const options of [5, 0, true, false, 10n, Symbol("s")]) {
show(`readFile ${typeof options}`, () => fs.readFile(file, options, () => {}));
show(`mkdtemp ${typeof options}`, () => fs.mkdtemp(prefix, options, () => {}));
show(`mkdtempSync ${typeof options}`, () => fs.mkdtempSync(prefix, options));
}
show("mkdtemp bad encoding", () => fs.mkdtemp(prefix, { encoding: "nope" }, () => {}));
show("mkdtemp bad string", () => fs.mkdtemp(prefix, "nope", () => {}));
show("mkdtempSync empty string", () => fs.statSync(fs.mkdtempSync(prefix, "")).isDirectory());
show("mkdtempSync NaN encoding", () => fs.statSync(fs.mkdtempSync(prefix, { encoding: NaN })).isDirectory());
show("readFileSync buffer", () => fs.readFileSync(file, "buffer"));
show("readFileSync buffer object", () => fs.readFileSync(file, { encoding: "buffer" }));
show("readFileSync buffer empty", () => fs.readFileSync(empty, "buffer"));
show("readFileSync buffer missing", () => fs.readFileSync(path.join(dir, "missing"), "buffer"));
const steps = [
(next) => fs.readFile(file, { encoding: "buffer" }, (error, data) => {
console.log("readFile buffer", error && `${error.name} ${error.code} ${error.message}`, data);
next();
}),
(next) => fs.readFile(file, "buffer", (error, data) => {
console.log("readFile buffer string", error && error.code, data);
next();
}),
(next) => fs.readFile(empty, "buffer", (error, data) => {
console.log("readFile buffer empty", error, JSON.stringify(data));
next();
}),
(next) => fs.readFile(path.join(dir, "missing"), { encoding: "buffer" }, (error, data) => {
console.log("readFile buffer missing", error && error.code, data);
next();
}),
(next) => fs.readFile(file, { encoding: "buffer", flag: "r+" }, (error, data) => {
console.log("readFile buffer flag", error && error.code, data);
next();
}),
(next) => fs.mkdtemp(prefix, { encoding: "buffer" }, (error, made) => {
console.log("mkdtemp buffer", error, describe(made).startsWith(`Buffer ${clean(prefix)}`), fs.existsSync(made.toString()));
next();
}),
(next) => fs.mkdtemp(prefix, "buffer", (error, made) => {
console.log("mkdtemp buffer string", error, Buffer.isBuffer(made), made.length === prefix.length + 6);
next();
}),
(next) => fs.mkdtemp(prefix, "hex", (error, made) => {
const decoded = Buffer.from(made, "hex").toString();
console.log("mkdtemp hex", error, typeof made, decoded.startsWith(prefix), fs.existsSync(decoded));
next();
}),
(next) => fs.mkdtemp(prefix, { encoding: "base64" }, (error, made) => {
const decoded = Buffer.from(made, "base64").toString();
console.log("mkdtemp base64", error, decoded.startsWith(prefix), fs.existsSync(decoded));
next();
}),
(next) => fs.mkdtemp(prefix, { encoding: "latin1" }, (error, made) => {
console.log("mkdtemp latin1", error, made.startsWith(prefix));
next();
}),
(next) => fs.mkdtemp(prefix, { encoding: "utf8" }, (error, made) => {
console.log("mkdtemp utf8", error, typeof made, made.startsWith(prefix));
next();
}),
(next) => fs.mkdtemp(path.join(dir, "missing", "t-"), { encoding: "buffer" }, (error, made) => {
console.log("mkdtemp buffer missing", error && error.code, made);
next();
}),
];
const run = (index) => {
if (index < steps.length) return steps[index](() => run(index + 1));
fs.rmSync(dir, { recursive: true });
console.log("removed", fs.existsSync(dir));
};
run(0);