mirror of
https://github.com/denoland/deno.git
synced 2025-09-25 11:49:11 +00:00

Some checks are pending
ci / build libs (push) Blocked by required conditions
ci / test debug windows-x86_64 (push) Blocked by required conditions
ci / test release windows-x86_64 (push) Blocked by required conditions
ci / pre-build (push) Waiting to run
ci / test debug linux-aarch64 (push) Blocked by required conditions
ci / test release linux-aarch64 (push) Blocked by required conditions
ci / test debug macos-aarch64 (push) Blocked by required conditions
ci / test release macos-aarch64 (push) Blocked by required conditions
ci / bench release linux-x86_64 (push) Blocked by required conditions
ci / lint debug linux-x86_64 (push) Blocked by required conditions
ci / lint debug macos-x86_64 (push) Blocked by required conditions
ci / lint debug windows-x86_64 (push) Blocked by required conditions
ci / test debug linux-x86_64 (push) Blocked by required conditions
ci / test release linux-x86_64 (push) Blocked by required conditions
ci / test debug macos-x86_64 (push) Blocked by required conditions
ci / test release macos-x86_64 (push) Blocked by required conditions
ci / publish canary (push) Blocked by required conditions
Closes #30570 Changes in this PR: - Implement `ino`, `nlink`, and `blocks` properties of `Deno.FileInfo` on Windows. These changes are automatically reflected to the corresponding node stat function. In order to do so, I had to tinker with the [createByteStruct](a3a904da14/ext/fs/30_fs.js (L297)
) function to create another optional int type, apart from `?u64`. It's common for small sized files on Windows (particularly NTFS file system) to have a `Stats.blocks` property of 0, and currently all 0 values with type `?u64` will be coerced into `null` by `createByteStruct`. - Refactor the `BigIntStats` and `Stats` class, to use the same class with Node.js that are provided from [utils.mjs](7f8e488c36/ext/node/polyfills/internal/fs/utils.mjs (L577)
). Also ensures that all properties are not `null` or `undefined`. - Addresses the `prefer-primordials` lint rule.
352 lines
9.4 KiB
TypeScript
352 lines
9.4 KiB
TypeScript
// Copyright 2018-2025 the Deno authors. MIT license.
|
|
|
|
import { BigIntStats, Stats } from "node:fs";
|
|
import {
|
|
assert,
|
|
assertEquals,
|
|
assertNotStrictEquals,
|
|
assertStrictEquals,
|
|
assertStringIncludes,
|
|
} from "@std/assert";
|
|
|
|
/** Asserts that an error thrown in a callback will not be wrongly caught. */
|
|
export async function assertCallbackErrorUncaught(
|
|
{ prelude, invocation, cleanup }: {
|
|
/** Any code which needs to run before the actual invocation (notably, any import statements). */
|
|
prelude?: string;
|
|
/**
|
|
* The start of the invocation of the function, e.g. `open("foo.txt", `.
|
|
* The callback will be added after it.
|
|
*/
|
|
invocation: string;
|
|
/** Called after the subprocess is finished but before running the assertions, e.g. to clean up created files. */
|
|
cleanup?: () => Promise<void> | void;
|
|
},
|
|
) {
|
|
// Since the error has to be uncaught, and that will kill the Deno process,
|
|
// the only way to test this is to spawn a subprocess.
|
|
const p = new Deno.Command(Deno.execPath(), {
|
|
args: [
|
|
"eval",
|
|
`${prelude ?? ""}
|
|
|
|
${invocation}(err) => {
|
|
// If the bug is present and the callback is called again with an error,
|
|
// don't throw another error, so if the subprocess fails we know it had the correct behaviour.
|
|
if (!err) throw new Error("success");
|
|
});`,
|
|
],
|
|
stderr: "piped",
|
|
});
|
|
const { stderr, success } = await p.output();
|
|
const error = new TextDecoder().decode(stderr);
|
|
await cleanup?.();
|
|
assert(!success);
|
|
assertStringIncludes(error, "Error: success");
|
|
}
|
|
|
|
const numberFields = [
|
|
"dev",
|
|
"mode",
|
|
"nlink",
|
|
"uid",
|
|
"gid",
|
|
"rdev",
|
|
"blksize",
|
|
"ino",
|
|
"size",
|
|
"blocks",
|
|
"atimeMs",
|
|
"mtimeMs",
|
|
"ctimeMs",
|
|
"birthtimeMs",
|
|
];
|
|
|
|
const bigintFields = [
|
|
...numberFields,
|
|
"atimeNs",
|
|
"mtimeNs",
|
|
"ctimeNs",
|
|
"birthtimeNs",
|
|
];
|
|
|
|
const dateFields = [
|
|
"atime",
|
|
"mtime",
|
|
"ctime",
|
|
"birthtime",
|
|
];
|
|
|
|
export function assertStats(actual: Stats, expected: Deno.FileInfo) {
|
|
[...numberFields, ...dateFields].forEach(function (k) {
|
|
assert(k in actual, `${k} should be in Stats`);
|
|
assertNotStrictEquals(
|
|
actual[k as keyof Stats],
|
|
undefined,
|
|
`${k} should not be undefined`,
|
|
);
|
|
assertNotStrictEquals(
|
|
actual[k as keyof Stats],
|
|
null,
|
|
`${k} should not be null`,
|
|
);
|
|
});
|
|
|
|
numberFields.forEach((k) => {
|
|
assertStrictEquals(
|
|
typeof actual[k as keyof Stats],
|
|
"number",
|
|
`${k} should be a number`,
|
|
);
|
|
});
|
|
|
|
dateFields.forEach((k) => {
|
|
assert(actual[k as keyof Stats] instanceof Date, `${k} should be a Date`);
|
|
});
|
|
|
|
// Some properties from Deno.FileInfo may be null,
|
|
// while node:fs Stats always has number / Date properties.
|
|
// So we only check properties that are not null in Deno.FileInfo.
|
|
assertEquals(actual.dev, expected.dev);
|
|
if (expected.gid) assertEquals(actual.gid, expected.gid, "Stats.gid");
|
|
assertEquals(actual.size, expected.size, "Stats.size");
|
|
if (expected.blksize) {
|
|
assertEquals(actual.blksize, expected.blksize, "Stats.blksize");
|
|
}
|
|
if (expected.blocks) {
|
|
assertEquals(actual.blocks, expected.blocks, "Stats.blocks");
|
|
}
|
|
if (expected.ino) assertEquals(actual.ino, expected.ino, "Stats.ino");
|
|
if (expected.mode) assertEquals(actual.mode, expected.mode, "Stats.mode");
|
|
if (expected.nlink) assertEquals(actual.nlink, expected.nlink, "Stats.nlink");
|
|
if (expected.rdev) assertEquals(actual.rdev, expected.rdev, "Stats.rdev");
|
|
if (expected.uid) assertEquals(actual.uid, expected.uid, "Stats.uid");
|
|
if (expected.atime?.getTime()) {
|
|
assertEquals(
|
|
actual.atime.getTime(),
|
|
expected.atime.getTime(),
|
|
"Stats.atime",
|
|
);
|
|
assertEquals(actual.atimeMs, expected.atime.getTime(), "Stats.atimeMs");
|
|
}
|
|
if (expected.mtime?.getTime()) {
|
|
assertEquals(
|
|
actual.mtime.getTime(),
|
|
expected.mtime.getTime(),
|
|
"Stats.mtime",
|
|
);
|
|
assertEquals(actual.mtimeMs, expected.mtime.getTime(), "Stats.mtimeMs");
|
|
}
|
|
if (expected.birthtime?.getTime()) {
|
|
assertEquals(
|
|
actual.birthtime.getTime(),
|
|
expected.birthtime.getTime(),
|
|
"Stats.birthtime",
|
|
);
|
|
assertEquals(
|
|
actual.birthtimeMs,
|
|
expected.birthtime.getTime(),
|
|
"Stats.birthtimeMs",
|
|
);
|
|
}
|
|
if (expected.ctime?.getTime()) {
|
|
assertEquals(
|
|
actual.ctime.getTime(),
|
|
expected.ctime.getTime(),
|
|
"Stats.ctime",
|
|
);
|
|
assertEquals(actual.ctimeMs, expected.ctime.getTime(), "Stats.ctimeMs");
|
|
}
|
|
assertEquals(actual.isFile(), expected.isFile, "Stats.isFile");
|
|
assertEquals(actual.isDirectory(), expected.isDirectory, "Stats.isDirectory");
|
|
assertEquals(
|
|
actual.isSymbolicLink(),
|
|
expected.isSymlink,
|
|
"Stats.isSymbolicLink",
|
|
);
|
|
assertEquals(
|
|
actual.isBlockDevice(),
|
|
Deno.build.os === "windows" ? false : expected.isBlockDevice,
|
|
"Stats.isBlockDevice",
|
|
);
|
|
assertEquals(
|
|
actual.isFIFO(),
|
|
Deno.build.os === "windows" ? false : expected.isFifo,
|
|
"Stats.isFIFO",
|
|
);
|
|
assertEquals(
|
|
actual.isCharacterDevice(),
|
|
Deno.build.os === "windows" ? false : expected.isCharDevice,
|
|
"Stats.isCharacterDevice",
|
|
);
|
|
assertEquals(
|
|
actual.isSocket(),
|
|
Deno.build.os === "windows" ? false : expected.isSocket,
|
|
"Stats.isSocket",
|
|
);
|
|
}
|
|
|
|
function toBigInt(num?: number | null) {
|
|
if (num === undefined || num === null) return null;
|
|
return BigInt(num);
|
|
}
|
|
|
|
export function assertStatsBigInt(
|
|
actual: BigIntStats,
|
|
expected: Deno.FileInfo,
|
|
) {
|
|
[...bigintFields, ...dateFields].forEach(function (k) {
|
|
assert(k in actual, `${k} should be in BigIntStats`);
|
|
assertNotStrictEquals(
|
|
actual[k as keyof BigIntStats],
|
|
undefined,
|
|
`${k} should not be undefined`,
|
|
);
|
|
assertNotStrictEquals(
|
|
actual[k as keyof BigIntStats],
|
|
null,
|
|
`${k} should not be null`,
|
|
);
|
|
});
|
|
|
|
bigintFields.forEach((k) => {
|
|
assertStrictEquals(
|
|
typeof actual[k as keyof BigIntStats],
|
|
"bigint",
|
|
`${k} should be a bigint`,
|
|
);
|
|
});
|
|
|
|
dateFields.forEach((k) => {
|
|
assert(
|
|
actual[k as keyof BigIntStats] instanceof Date,
|
|
`${k} should be a Date`,
|
|
);
|
|
});
|
|
|
|
// Some properties from Deno.FileInfo may be null,
|
|
// while node:fs BigIntStats always has bigint / Date properties.
|
|
// So we only check properties that are not null in Deno.FileInfo.
|
|
assertEquals(actual.dev, toBigInt(expected.dev), "BigIntStats.dev");
|
|
if (expected.gid) {
|
|
assertEquals(actual.gid, toBigInt(expected.gid), "BigIntStats.gid");
|
|
}
|
|
assertEquals(actual.size, toBigInt(expected.size), "BigIntStats.size");
|
|
if (expected.blksize) {
|
|
assertEquals(
|
|
actual.blksize,
|
|
toBigInt(expected.blksize),
|
|
"BigIntStats.blksize",
|
|
);
|
|
}
|
|
if (expected.blocks) {
|
|
assertEquals(
|
|
actual.blocks,
|
|
toBigInt(expected.blocks),
|
|
"BigIntStats.blocks",
|
|
);
|
|
}
|
|
if (expected.ino) {
|
|
assertEquals(actual.ino, toBigInt(expected.ino), "BigIntStats.ino");
|
|
}
|
|
if (expected.mode) {
|
|
assertEquals(actual.mode, toBigInt(expected.mode), "BigIntStats.mode");
|
|
}
|
|
if (expected.nlink) {
|
|
assertEquals(actual.nlink, toBigInt(expected.nlink), "BigIntStats.nlink");
|
|
}
|
|
if (expected.rdev) {
|
|
assertEquals(actual.rdev, toBigInt(expected.rdev), "BigIntStats.rdev");
|
|
}
|
|
if (expected.uid) {
|
|
assertEquals(actual.uid, toBigInt(expected.uid), "BigIntStats.uid");
|
|
}
|
|
if (expected.atime?.getTime()) {
|
|
assertEquals(
|
|
actual.atime.getTime(),
|
|
expected.atime.getTime(),
|
|
"BigIntStats.atime",
|
|
);
|
|
assertEquals(
|
|
actual.atimeMs,
|
|
toBigInt(expected.atime.getTime()),
|
|
"BigIntStats.atimeMs",
|
|
);
|
|
assertEquals(
|
|
actual.atimeNs,
|
|
toBigInt(expected.atime.getTime()) as bigint * 1000000n,
|
|
"BigIntStats.atimeNs",
|
|
);
|
|
}
|
|
if (expected.mtime?.getTime()) {
|
|
assertEquals(
|
|
actual.mtime.getTime(),
|
|
expected.mtime.getTime(),
|
|
"BigIntStats.mtime",
|
|
);
|
|
assertEquals(
|
|
actual.mtimeMs,
|
|
toBigInt(expected.mtime.getTime()),
|
|
"BigIntStats.mtimeMs",
|
|
);
|
|
assertEquals(
|
|
actual.mtimeNs,
|
|
toBigInt(expected.mtime.getTime()) as bigint * 1000000n,
|
|
"BigIntStats.mtimeNs",
|
|
);
|
|
}
|
|
if (expected.birthtime?.getTime()) {
|
|
assertEquals(
|
|
actual.birthtime.getTime(),
|
|
expected.birthtime.getTime(),
|
|
"BigIntStats.birthtime",
|
|
);
|
|
assertEquals(
|
|
actual.birthtimeMs,
|
|
toBigInt(expected.birthtime.getTime()),
|
|
"BigIntStats.birthtimeMs",
|
|
);
|
|
assertEquals(
|
|
actual.birthtimeNs,
|
|
toBigInt(expected.birthtime.getTime()) as bigint * 1000000n,
|
|
"BigIntStats.birthtimeNs",
|
|
);
|
|
}
|
|
if (expected.ctime?.getTime()) {
|
|
assertEquals(
|
|
actual.ctime.getTime(),
|
|
expected.ctime.getTime(),
|
|
"BigIntStats.ctime",
|
|
);
|
|
assertEquals(
|
|
actual.ctimeMs,
|
|
toBigInt(expected.ctime.getTime()),
|
|
"BigIntStats.ctimeMs",
|
|
);
|
|
assertEquals(
|
|
actual.ctimeNs,
|
|
toBigInt(expected.ctime.getTime()) as bigint * 1000000n,
|
|
"BigIntStats.ctimeNs",
|
|
);
|
|
}
|
|
assertEquals(
|
|
actual.isBlockDevice(),
|
|
Deno.build.os === "windows" ? false : expected.isBlockDevice,
|
|
"BigIntStats.isBlockDevice",
|
|
);
|
|
assertEquals(
|
|
actual.isFIFO(),
|
|
Deno.build.os === "windows" ? false : expected.isFifo,
|
|
"BigIntStats.isFIFO",
|
|
);
|
|
assertEquals(
|
|
actual.isCharacterDevice(),
|
|
Deno.build.os === "windows" ? false : expected.isCharDevice,
|
|
"BigIntStats.isCharacterDevice",
|
|
);
|
|
assertEquals(
|
|
actual.isSocket(),
|
|
Deno.build.os === "windows" ? false : expected.isSocket,
|
|
"BigIntStats.isSocket",
|
|
);
|
|
}
|