summaryrefslogtreecommitdiff
path: root/tests/unit_node/_fs
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2024-02-10 13:22:13 -0700
committerGitHub <noreply@github.com>2024-02-10 20:22:13 +0000
commitf5e46c9bf2f50d66a953fa133161fc829cecff06 (patch)
tree8faf2f5831c1c7b11d842cd9908d141082c869a5 /tests/unit_node/_fs
parentd2477f780630a812bfd65e3987b70c0d309385bb (diff)
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests -> tests, and updates of relative paths for files. This is the first step towards aggregate all of the integration test files under tests/, which will lead to a set of integration tests that can run without the CLI binary being built. While we could leave these tests under `cli`, it would require us to keep a more complex directory structure for the various test runners. In addition, we have a lot of complexity to ignore various test files in the `cli` project itself (cargo publish exclusion rules, autotests = false, etc). And finally, the `tests/` folder will eventually house the `test_ffi`, `test_napi` and other testing code, reducing the size of the root repo directory. For easier review, the extremely large and noisy "move" is in the first commit (with no changes -- just a move), while the remainder of the changes to actual files is in the second commit.
Diffstat (limited to 'tests/unit_node/_fs')
-rw-r--r--tests/unit_node/_fs/_fs_access_test.ts67
-rw-r--r--tests/unit_node/_fs/_fs_appendFile_test.ts237
-rw-r--r--tests/unit_node/_fs/_fs_chmod_test.ts121
-rw-r--r--tests/unit_node/_fs/_fs_chown_test.ts69
-rw-r--r--tests/unit_node/_fs/_fs_close_test.ts86
-rw-r--r--tests/unit_node/_fs/_fs_copy_test.ts52
-rw-r--r--tests/unit_node/_fs/_fs_dir_test.ts205
-rw-r--r--tests/unit_node/_fs/_fs_dirent_test.ts86
-rw-r--r--tests/unit_node/_fs/_fs_exists_test.ts65
-rw-r--r--tests/unit_node/_fs/_fs_fdatasync_test.ts58
-rw-r--r--tests/unit_node/_fs/_fs_fstat_test.ts90
-rw-r--r--tests/unit_node/_fs/_fs_fsync_test.ts56
-rw-r--r--tests/unit_node/_fs/_fs_ftruncate_test.ts123
-rw-r--r--tests/unit_node/_fs/_fs_futimes_test.ts106
-rw-r--r--tests/unit_node/_fs/_fs_handle_test.ts88
-rw-r--r--tests/unit_node/_fs/_fs_link_test.ts77
-rw-r--r--tests/unit_node/_fs/_fs_lstat_test.ts71
-rw-r--r--tests/unit_node/_fs/_fs_mkdir_test.ts43
-rw-r--r--tests/unit_node/_fs/_fs_mkdtemp_test.ts86
-rw-r--r--tests/unit_node/_fs/_fs_open_test.ts400
-rw-r--r--tests/unit_node/_fs/_fs_opendir_test.ts146
-rw-r--r--tests/unit_node/_fs/_fs_readFile_test.ts123
-rw-r--r--tests/unit_node/_fs/_fs_read_test.ts322
-rw-r--r--tests/unit_node/_fs/_fs_readdir_test.ts96
-rw-r--r--tests/unit_node/_fs/_fs_readlink_test.ts75
-rw-r--r--tests/unit_node/_fs/_fs_realpath_test.ts55
-rw-r--r--tests/unit_node/_fs/_fs_rename_test.ts52
-rw-r--r--tests/unit_node/_fs/_fs_rm_test.ts139
-rw-r--r--tests/unit_node/_fs/_fs_rmdir_test.ts81
-rw-r--r--tests/unit_node/_fs/_fs_stat_test.ts131
-rw-r--r--tests/unit_node/_fs/_fs_symlink_test.ts107
-rw-r--r--tests/unit_node/_fs/_fs_truncate_test.ts95
-rw-r--r--tests/unit_node/_fs/_fs_unlink_test.ts40
-rw-r--r--tests/unit_node/_fs/_fs_utimes_test.ts100
-rw-r--r--tests/unit_node/_fs/_fs_watch_test.ts27
-rw-r--r--tests/unit_node/_fs/_fs_writeFile_test.ts345
-rw-r--r--tests/unit_node/_fs/_fs_write_test.ts51
-rw-r--r--tests/unit_node/_fs/testdata/hello.txt1
38 files changed, 4172 insertions, 0 deletions
diff --git a/tests/unit_node/_fs/_fs_access_test.ts b/tests/unit_node/_fs/_fs_access_test.ts
new file mode 100644
index 000000000..5b5b7f34d
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_access_test.ts
@@ -0,0 +1,67 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import * as fs from "node:fs";
+import { assertRejects, assertThrows } from "@test_util/std/assert/mod.ts";
+
+Deno.test(
+ "[node/fs.access] Uses the owner permission when the user is the owner",
+ { ignore: Deno.build.os === "windows" },
+ async () => {
+ const file = await Deno.makeTempFile();
+ try {
+ await Deno.chmod(file, 0o600);
+ await fs.promises.access(file, fs.constants.R_OK);
+ await fs.promises.access(file, fs.constants.W_OK);
+ await assertRejects(async () => {
+ await fs.promises.access(file, fs.constants.X_OK);
+ });
+ } finally {
+ await Deno.remove(file);
+ }
+ },
+);
+
+Deno.test(
+ "[node/fs.access] doesn't reject on windows",
+ { ignore: Deno.build.os !== "windows" },
+ async () => {
+ const file = await Deno.makeTempFile();
+ try {
+ await fs.promises.access(file, fs.constants.R_OK);
+ await fs.promises.access(file, fs.constants.W_OK);
+ } finally {
+ await Deno.remove(file);
+ }
+ },
+);
+
+Deno.test(
+ "[node/fs.accessSync] Uses the owner permission when the user is the owner",
+ { ignore: Deno.build.os === "windows" },
+ () => {
+ const file = Deno.makeTempFileSync();
+ try {
+ Deno.chmodSync(file, 0o600);
+ fs.accessSync(file, fs.constants.R_OK);
+ fs.accessSync(file, fs.constants.W_OK);
+ assertThrows(() => {
+ fs.accessSync(file, fs.constants.X_OK);
+ });
+ } finally {
+ Deno.removeSync(file);
+ }
+ },
+);
+
+Deno.test(
+ "[node/fs.accessSync] doesn't throw on windows",
+ { ignore: Deno.build.os !== "windows" },
+ () => {
+ const file = Deno.makeTempFileSync();
+ try {
+ fs.accessSync(file, fs.constants.R_OK);
+ fs.accessSync(file, fs.constants.W_OK);
+ } finally {
+ Deno.removeSync(file);
+ }
+ },
+);
diff --git a/tests/unit_node/_fs/_fs_appendFile_test.ts b/tests/unit_node/_fs/_fs_appendFile_test.ts
new file mode 100644
index 000000000..57271efdb
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_appendFile_test.ts
@@ -0,0 +1,237 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, assertThrows, fail } from "@test_util/std/assert/mod.ts";
+import { appendFile, appendFileSync } from "node:fs";
+import { fromFileUrl } from "@test_util/std/path/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+
+const decoder = new TextDecoder("utf-8");
+
+Deno.test({
+ name: "No callback Fn results in Error",
+ fn() {
+ assertThrows(
+ () => {
+ // @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'NoParamCallback'
+ appendFile("some/path", "some data", "utf8");
+ },
+ Error,
+ "The \"cb\" argument must be of type function. Received type string ('utf8')",
+ );
+ },
+});
+
+Deno.test({
+ name: "Unsupported encoding results in error()",
+ fn() {
+ assertThrows(
+ () => {
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ appendFile("some/path", "some data", "made-up-encoding", () => {});
+ },
+ Error,
+ "The argument 'made-up-encoding' is invalid encoding. Received 'encoding'",
+ );
+ assertThrows(
+ () => {
+ appendFile(
+ "some/path",
+ "some data",
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ { encoding: "made-up-encoding" },
+ () => {},
+ );
+ },
+ Error,
+ "The argument 'made-up-encoding' is invalid encoding. Received 'encoding'",
+ );
+ assertThrows(
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ () => appendFileSync("some/path", "some data", "made-up-encoding"),
+ Error,
+ "The argument 'made-up-encoding' is invalid encoding. Received 'encoding'",
+ );
+ assertThrows(
+ () =>
+ appendFileSync("some/path", "some data", {
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ encoding: "made-up-encoding",
+ }),
+ Error,
+ "The argument 'made-up-encoding' is invalid encoding. Received 'encoding'",
+ );
+ },
+});
+
+Deno.test({
+ name: "Async: Data is written to passed in rid",
+ async fn() {
+ const tempFile: string = await Deno.makeTempFile();
+ using file = await Deno.open(tempFile, {
+ create: true,
+ write: true,
+ read: true,
+ });
+ await new Promise<void>((resolve, reject) => {
+ appendFile(file.rid, "hello world", (err) => {
+ if (err) reject();
+ else resolve();
+ });
+ })
+ .then(async () => {
+ const data = await Deno.readFile(tempFile);
+ assertEquals(decoder.decode(data), "hello world");
+ }, () => {
+ fail("No error expected");
+ })
+ .finally(async () => {
+ await Deno.remove(tempFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "Async: Data is written to passed in file path",
+ async fn() {
+ await new Promise<void>((resolve, reject) => {
+ appendFile("_fs_appendFile_test_file.txt", "hello world", (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ })
+ .then(async () => {
+ const data = await Deno.readFile("_fs_appendFile_test_file.txt");
+ assertEquals(decoder.decode(data), "hello world");
+ }, (err) => {
+ fail("No error was expected: " + err);
+ })
+ .finally(async () => {
+ await Deno.remove("_fs_appendFile_test_file.txt");
+ });
+ },
+});
+
+Deno.test({
+ name: "Async: Data is written to passed in URL",
+ async fn() {
+ const fileURL = new URL("_fs_appendFile_test_file.txt", import.meta.url);
+ await new Promise<void>((resolve, reject) => {
+ appendFile(fileURL, "hello world", (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ })
+ .then(async () => {
+ const data = await Deno.readFile(fromFileUrl(fileURL));
+ assertEquals(decoder.decode(data), "hello world");
+ }, (err) => {
+ fail("No error was expected: " + err);
+ })
+ .finally(async () => {
+ await Deno.remove(fromFileUrl(fileURL));
+ });
+ },
+});
+
+Deno.test({
+ name:
+ "Async: Callback is made with error if attempting to append data to an existing file with 'ax' flag",
+ async fn() {
+ const tempFile: string = await Deno.makeTempFile();
+ await new Promise<void>((resolve, reject) => {
+ appendFile(tempFile, "hello world", { flag: "ax" }, (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ })
+ .then(() => fail("Expected error to be thrown"))
+ .catch(() => {})
+ .finally(async () => {
+ await Deno.remove(tempFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "Sync: Data is written to passed in rid",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ using file = Deno.openSync(tempFile, {
+ create: true,
+ write: true,
+ read: true,
+ });
+ appendFileSync(file.rid, "hello world");
+ const data = Deno.readFileSync(tempFile);
+ assertEquals(decoder.decode(data), "hello world");
+ Deno.removeSync(tempFile);
+ },
+});
+
+Deno.test({
+ name: "Sync: Data is written to passed in file path",
+ fn() {
+ appendFileSync("_fs_appendFile_test_file_sync.txt", "hello world");
+ const data = Deno.readFileSync("_fs_appendFile_test_file_sync.txt");
+ assertEquals(decoder.decode(data), "hello world");
+ Deno.removeSync("_fs_appendFile_test_file_sync.txt");
+ },
+});
+
+Deno.test({
+ name:
+ "Sync: error thrown if attempting to append data to an existing file with 'ax' flag",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ assertThrows(
+ () => appendFileSync(tempFile, "hello world", { flag: "ax" }),
+ Error,
+ "",
+ );
+ Deno.removeSync(tempFile);
+ },
+});
+
+Deno.test({
+ name: "Sync: Data is written in Uint8Array to passed in file path",
+ fn() {
+ const testData = new TextEncoder().encode("hello world");
+ appendFileSync("_fs_appendFile_test_file_sync.txt", testData);
+ const data = Deno.readFileSync("_fs_appendFile_test_file_sync.txt");
+ assertEquals(data, testData);
+ Deno.removeSync("_fs_appendFile_test_file_sync.txt");
+ },
+});
+
+Deno.test({
+ name: "Async: Data is written in Uint8Array to passed in file path",
+ async fn() {
+ const testData = new TextEncoder().encode("hello world");
+ await new Promise<void>((resolve, reject) => {
+ appendFile("_fs_appendFile_test_file.txt", testData, (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ })
+ .then(async () => {
+ const data = await Deno.readFile("_fs_appendFile_test_file.txt");
+ assertEquals(data, testData);
+ }, (err) => {
+ fail("No error was expected: " + err);
+ })
+ .finally(async () => {
+ await Deno.remove("_fs_appendFile_test_file.txt");
+ });
+ },
+});
+
+Deno.test("[std/node/fs] appendFile callback isn't called twice if error is thrown", async () => {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { appendFile } from ${JSON.stringify(importUrl)}`,
+ invocation: `appendFile(${JSON.stringify(tempFile)}, "hello world", `,
+ async cleanup() {
+ await Deno.remove(tempFile);
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_chmod_test.ts b/tests/unit_node/_fs/_fs_chmod_test.ts
new file mode 100644
index 000000000..2bddcb293
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_chmod_test.ts
@@ -0,0 +1,121 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertRejects,
+ assertThrows,
+ fail,
+} from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { chmod, chmodSync } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: Permissions are changed (non-Windows)",
+ ignore: Deno.build.os === "windows",
+ async fn() {
+ const tempFile: string = await Deno.makeTempFile();
+ const originalFileMode: number | null = (await Deno.lstat(tempFile)).mode;
+ await new Promise<void>((resolve, reject) => {
+ chmod(tempFile, 0o777, (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ })
+ .then(() => {
+ const newFileMode: number | null = Deno.lstatSync(tempFile).mode;
+ assert(newFileMode && originalFileMode);
+ assert(newFileMode === 33279 && newFileMode > originalFileMode);
+ }, (error) => {
+ fail(error);
+ })
+ .finally(() => {
+ Deno.removeSync(tempFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: don't throw NotSupportedError (Windows)",
+ ignore: Deno.build.os !== "windows",
+ async fn() {
+ const tempFile: string = await Deno.makeTempFile();
+ await new Promise<void>((resolve, reject) => {
+ chmod(tempFile, 0o777, (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ }).finally(() => {
+ Deno.removeSync(tempFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: don't swallow NotFoundError (Windows)",
+ ignore: Deno.build.os !== "windows",
+ async fn() {
+ await assertRejects(async () => {
+ await new Promise<void>((resolve, reject) => {
+ chmod("./__non_existent_file__", 0o777, (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: Permissions are changed (non-Windows)",
+ ignore: Deno.build.os === "windows",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ try {
+ const originalFileMode: number | null = Deno.lstatSync(tempFile).mode;
+ chmodSync(tempFile, "777");
+
+ const newFileMode: number | null = Deno.lstatSync(tempFile).mode;
+ assert(newFileMode && originalFileMode);
+ assert(newFileMode === 33279 && newFileMode > originalFileMode);
+ } finally {
+ Deno.removeSync(tempFile);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: don't throw NotSupportedError (Windows)",
+ ignore: Deno.build.os !== "windows",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ try {
+ chmodSync(tempFile, "777");
+ } finally {
+ Deno.removeSync(tempFile);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: don't swallow NotFoundError (Windows)",
+ ignore: Deno.build.os !== "windows",
+ fn() {
+ assertThrows(() => {
+ chmodSync("./__non_existent_file__", "777");
+ });
+ },
+});
+
+Deno.test({
+ name: "[std/node/fs] chmod callback isn't called twice if error is thrown",
+ async fn() {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { chmod } from ${JSON.stringify(importUrl)}`,
+ invocation: `chmod(${JSON.stringify(tempFile)}, 0o777, `,
+ async cleanup() {
+ await Deno.remove(tempFile);
+ },
+ });
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_chown_test.ts b/tests/unit_node/_fs/_fs_chown_test.ts
new file mode 100644
index 000000000..d4f6ea0e8
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_chown_test.ts
@@ -0,0 +1,69 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { chown, chownSync } from "node:fs";
+
+// chown is difficult to test. Best we can do is set the existing user id/group
+// id again
+const ignore = Deno.build.os === "windows";
+
+Deno.test({
+ ignore,
+ name: "ASYNC: setting existing uid/gid works as expected (non-Windows)",
+ async fn() {
+ const tempFile: string = await Deno.makeTempFile();
+ const originalUserId: number | null = (await Deno.lstat(tempFile)).uid;
+ const originalGroupId: number | null = (await Deno.lstat(tempFile)).gid;
+ await new Promise<void>((resolve, reject) => {
+ chown(tempFile, originalUserId!, originalGroupId!, (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ })
+ .then(() => {
+ const newUserId: number | null = Deno.lstatSync(tempFile).uid;
+ const newGroupId: number | null = Deno.lstatSync(tempFile).gid;
+ assertEquals(newUserId, originalUserId);
+ assertEquals(newGroupId, originalGroupId);
+ }, () => {
+ fail();
+ })
+ .finally(() => {
+ Deno.removeSync(tempFile);
+ });
+ },
+});
+
+Deno.test({
+ ignore,
+ name: "SYNC: setting existing uid/gid works as expected (non-Windows)",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ const originalUserId: number | null = Deno.lstatSync(tempFile).uid;
+ const originalGroupId: number | null = Deno.lstatSync(tempFile).gid;
+ chownSync(tempFile, originalUserId!, originalGroupId!);
+
+ const newUserId: number | null = Deno.lstatSync(tempFile).uid;
+ const newGroupId: number | null = Deno.lstatSync(tempFile).gid;
+ assertEquals(newUserId, originalUserId);
+ assertEquals(newGroupId, originalGroupId);
+ Deno.removeSync(tempFile);
+ },
+});
+
+Deno.test({
+ name: "[std/node/fs] chown callback isn't called twice if error is thrown",
+ ignore: Deno.build.os === "windows",
+ async fn() {
+ const tempFile = await Deno.makeTempFile();
+ const { uid, gid } = await Deno.lstat(tempFile);
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { chown } from ${JSON.stringify(importUrl)}`,
+ invocation: `chown(${JSON.stringify(tempFile)}, ${uid}, ${gid}, `,
+ async cleanup() {
+ await Deno.remove(tempFile);
+ },
+ });
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_close_test.ts b/tests/unit_node/_fs/_fs_close_test.ts
new file mode 100644
index 000000000..155667305
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_close_test.ts
@@ -0,0 +1,86 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assert, assertThrows, fail } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { close, closeSync } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: File is closed",
+ async fn() {
+ const tempFile: string = await Deno.makeTempFile();
+ const file: Deno.FsFile = await Deno.open(tempFile);
+
+ await new Promise<void>((resolve, reject) => {
+ close(file.rid, (err) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .catch(() => fail("No error expected"))
+ .finally(async () => {
+ await Deno.remove(tempFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: Invalid fd",
+ fn() {
+ assertThrows(() => {
+ close(-1, (_err) => {});
+ }, RangeError);
+ },
+});
+
+Deno.test({
+ name: "close callback should be asynchronous",
+ async fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ const file: Deno.FsFile = Deno.openSync(tempFile);
+
+ let foo: string;
+ const promise = new Promise<void>((resolve) => {
+ close(file.rid, () => {
+ assert(foo === "bar");
+ resolve();
+ });
+ foo = "bar";
+ });
+
+ await promise;
+ Deno.removeSync(tempFile);
+ },
+});
+
+Deno.test({
+ name: "SYNC: File is closed",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ const file: Deno.FsFile = Deno.openSync(tempFile);
+
+ closeSync(file.rid);
+ Deno.removeSync(tempFile);
+ },
+});
+
+Deno.test({
+ name: "SYNC: Invalid fd",
+ fn() {
+ assertThrows(() => closeSync(-1));
+ },
+});
+
+Deno.test("[std/node/fs] close callback isn't called twice if error is thrown", async () => {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `
+ import { close } from ${JSON.stringify(importUrl)};
+
+ const file = await Deno.open(${JSON.stringify(tempFile)});
+ `,
+ invocation: "close(file.rid, ",
+ async cleanup() {
+ await Deno.remove(tempFile);
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_copy_test.ts b/tests/unit_node/_fs/_fs_copy_test.ts
new file mode 100644
index 000000000..915ee93bd
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_copy_test.ts
@@ -0,0 +1,52 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import * as path from "@test_util/std/path/mod.ts";
+import { assert } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { copyFile, copyFileSync, existsSync } from "node:fs";
+
+const destFile = "./destination.txt";
+
+Deno.test({
+ name: "[std/node/fs] copy file",
+ fn: async () => {
+ const sourceFile = Deno.makeTempFileSync();
+ const err = await new Promise((resolve) => {
+ copyFile(sourceFile, destFile, (err?: Error | null) => resolve(err));
+ });
+ assert(!err);
+ assert(existsSync(destFile));
+ Deno.removeSync(sourceFile);
+ Deno.removeSync(destFile);
+ },
+});
+
+Deno.test({
+ name: "[std/node/fs] copy file sync",
+ fn: () => {
+ const sourceFile = Deno.makeTempFileSync();
+ copyFileSync(sourceFile, destFile);
+ assert(existsSync(destFile));
+ Deno.removeSync(sourceFile);
+ Deno.removeSync(destFile);
+ },
+});
+
+Deno.test("[std/node/fs] copyFile callback isn't called twice if error is thrown", async () => {
+ // The correct behaviour is not to catch any errors thrown,
+ // but that means there'll be an uncaught error and the test will fail.
+ // So the only way to test this is to spawn a subprocess, and succeed if it has a non-zero exit code.
+ // (assertRejects won't work because there's no way to catch the error.)
+ const tempDir = await Deno.makeTempDir();
+ const tempFile1 = path.join(tempDir, "file1.txt");
+ const tempFile2 = path.join(tempDir, "file2.txt");
+ await Deno.writeTextFile(tempFile1, "hello world");
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { copyFile } from ${JSON.stringify(importUrl)}`,
+ invocation: `copyFile(${JSON.stringify(tempFile1)},
+ ${JSON.stringify(tempFile2)}, `,
+ async cleanup() {
+ await Deno.remove(tempDir, { recursive: true });
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_dir_test.ts b/tests/unit_node/_fs/_fs_dir_test.ts
new file mode 100644
index 000000000..697929fee
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_dir_test.ts
@@ -0,0 +1,205 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assert, assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { Dir as DirOrig, type Dirent } from "node:fs";
+
+// deno-lint-ignore no-explicit-any
+const Dir = DirOrig as any;
+
+Deno.test({
+ name: "Closing current directory with callback is successful",
+ fn() {
+ let calledBack = false;
+ // deno-lint-ignore no-explicit-any
+ new Dir(".").close((valOrErr: any) => {
+ assert(!valOrErr);
+ calledBack = true;
+ });
+ assert(calledBack);
+ },
+});
+
+Deno.test({
+ name: "Closing current directory without callback returns void Promise",
+ async fn() {
+ await new Dir(".").close();
+ },
+});
+
+Deno.test({
+ name: "Closing current directory synchronously works",
+ fn() {
+ new Dir(".").closeSync();
+ },
+});
+
+Deno.test({
+ name: "Path is correctly returned",
+ fn() {
+ assertEquals(new Dir("std/node").path, "std/node");
+
+ const enc: Uint8Array = new TextEncoder().encode("std/node");
+ assertEquals(new Dir(enc).path, "std/node");
+ },
+});
+
+Deno.test({
+ name: "read returns null for empty directory",
+ async fn() {
+ const testDir: string = Deno.makeTempDirSync();
+ try {
+ const file: Dirent | null = await new Dir(testDir).read();
+ assert(file === null);
+
+ let calledBack = false;
+ const fileFromCallback: Dirent | null = await new Dir(
+ testDir,
+ // deno-lint-ignore no-explicit-any
+ ).read((err: any, res: Dirent) => {
+ assert(res === null);
+ assert(err === null);
+ calledBack = true;
+ });
+ assert(fileFromCallback === null);
+ assert(calledBack);
+
+ assertEquals(new Dir(testDir).readSync(), null);
+ } finally {
+ Deno.removeSync(testDir);
+ }
+ },
+});
+
+Deno.test({
+ name: "Async read returns one file at a time",
+ async fn() {
+ const testDir: string = Deno.makeTempDirSync();
+ const f1 = Deno.createSync(testDir + "/foo.txt");
+ f1.close();
+ const f2 = Deno.createSync(testDir + "/bar.txt");
+ f2.close();
+
+ try {
+ let secondCallback = false;
+ const dir = new Dir(testDir);
+ const firstRead: Dirent | null = await dir.read();
+ const secondRead: Dirent | null = await dir.read(
+ // deno-lint-ignore no-explicit-any
+ (_err: any, secondResult: Dirent) => {
+ assert(
+ secondResult.name === "bar.txt" || secondResult.name === "foo.txt",
+ );
+ secondCallback = true;
+ },
+ );
+ const thirdRead: Dirent | null = await dir.read();
+ const fourthRead: Dirent | null = await dir.read();
+
+ if (firstRead?.name === "foo.txt") {
+ assertEquals(secondRead?.name, "bar.txt");
+ } else if (firstRead?.name === "bar.txt") {
+ assertEquals(secondRead?.name, "foo.txt");
+ } else {
+ fail("File not found during read");
+ }
+ assert(secondCallback);
+ assert(thirdRead === null);
+ assert(fourthRead === null);
+ } finally {
+ Deno.removeSync(testDir, { recursive: true });
+ }
+ },
+});
+
+Deno.test({
+ name: "Sync read returns one file at a time",
+ fn() {
+ const testDir: string = Deno.makeTempDirSync();
+ const f1 = Deno.createSync(testDir + "/foo.txt");
+ f1.close();
+ const f2 = Deno.createSync(testDir + "/bar.txt");
+ f2.close();
+
+ try {
+ const dir = new Dir(testDir);
+ const firstRead: Dirent | null = dir.readSync();
+ const secondRead: Dirent | null = dir.readSync();
+ const thirdRead: Dirent | null = dir.readSync();
+ const fourthRead: Dirent | null = dir.readSync();
+
+ if (firstRead?.name === "foo.txt") {
+ assertEquals(secondRead?.name, "bar.txt");
+ } else if (firstRead?.name === "bar.txt") {
+ assertEquals(secondRead?.name, "foo.txt");
+ } else {
+ fail("File not found during read");
+ }
+ assert(thirdRead === null);
+ assert(fourthRead === null);
+ } finally {
+ Deno.removeSync(testDir, { recursive: true });
+ }
+ },
+});
+
+Deno.test({
+ name: "Async iteration over existing directory",
+ async fn() {
+ const testDir: string = Deno.makeTempDirSync();
+ const f1 = Deno.createSync(testDir + "/foo.txt");
+ f1.close();
+ const f2 = Deno.createSync(testDir + "/bar.txt");
+ f2.close();
+
+ try {
+ const dir = new Dir(testDir);
+ const results: Array<string | null> = [];
+
+ for await (const file of dir[Symbol.asyncIterator]()) {
+ results.push(file.name);
+ }
+
+ assert(results.length === 2);
+ assert(results.includes("foo.txt"));
+ assert(results.includes("bar.txt"));
+ } finally {
+ Deno.removeSync(testDir, { recursive: true });
+ }
+ },
+});
+
+Deno.test(
+ "[std/node/fs] Dir.close callback isn't called twice if error is thrown",
+ async () => {
+ const tempDir = await Deno.makeTempDir();
+ await assertCallbackErrorUncaught({
+ prelude: `
+ import { Dir } from "node:fs";
+
+ const dir = new Dir(${JSON.stringify(tempDir)});
+ `,
+ invocation: "dir.close(",
+ async cleanup() {
+ await Deno.remove(tempDir);
+ },
+ });
+ },
+);
+
+Deno.test(
+ "[std/node/fs] Dir.read callback isn't called twice if error is thrown",
+ async () => {
+ const tempDir = await Deno.makeTempDir();
+ await assertCallbackErrorUncaught({
+ prelude: `
+ import { Dir } from "node:fs";
+
+ const dir = new Dir(${JSON.stringify(tempDir)});
+ `,
+ invocation: "dir.read(",
+ async cleanup() {
+ await Deno.remove(tempDir);
+ },
+ });
+ },
+);
diff --git a/tests/unit_node/_fs/_fs_dirent_test.ts b/tests/unit_node/_fs/_fs_dirent_test.ts
new file mode 100644
index 000000000..a42f6a25c
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_dirent_test.ts
@@ -0,0 +1,86 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertEquals,
+ assertThrows,
+} from "@test_util/std/assert/mod.ts";
+import { Dirent as Dirent_ } from "node:fs";
+
+// deno-lint-ignore no-explicit-any
+const Dirent = Dirent_ as any;
+
+class DirEntryMock implements Deno.DirEntry {
+ name = "";
+ isFile = false;
+ isDirectory = false;
+ isSymlink = false;
+}
+
+Deno.test({
+ name: "Directories are correctly identified",
+ fn() {
+ const entry: DirEntryMock = new DirEntryMock();
+ entry.isDirectory = true;
+ entry.isFile = false;
+ entry.isSymlink = false;
+ assert(new Dirent(entry).isDirectory());
+ assert(!new Dirent(entry).isFile());
+ assert(!new Dirent(entry).isSymbolicLink());
+ },
+});
+
+Deno.test({
+ name: "Files are correctly identified",
+ fn() {
+ const entry: DirEntryMock = new DirEntryMock();
+ entry.isDirectory = false;
+ entry.isFile = true;
+ entry.isSymlink = false;
+ assert(!new Dirent(entry).isDirectory());
+ assert(new Dirent(entry).isFile());
+ assert(!new Dirent(entry).isSymbolicLink());
+ },
+});
+
+Deno.test({
+ name: "Symlinks are correctly identified",
+ fn() {
+ const entry: DirEntryMock = new DirEntryMock();
+ entry.isDirectory = false;
+ entry.isFile = false;
+ entry.isSymlink = true;
+ assert(!new Dirent(entry).isDirectory());
+ assert(!new Dirent(entry).isFile());
+ assert(new Dirent(entry).isSymbolicLink());
+ },
+});
+
+Deno.test({
+ name: "File name is correct",
+ fn() {
+ const entry: DirEntryMock = new DirEntryMock();
+ entry.name = "my_file";
+ assertEquals(new Dirent(entry).name, "my_file");
+ },
+});
+
+Deno.test({
+ name: "Socket and FIFO pipes aren't yet available",
+ fn() {
+ const entry: DirEntryMock = new DirEntryMock();
+ assertThrows(
+ () => {
+ new Dirent(entry).isFIFO();
+ },
+ Error,
+ "does not yet support",
+ );
+ assertThrows(
+ () => {
+ new Dirent(entry).isSocket();
+ },
+ Error,
+ "does not yet support",
+ );
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_exists_test.ts b/tests/unit_node/_fs/_fs_exists_test.ts
new file mode 100644
index 000000000..baf959502
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_exists_test.ts
@@ -0,0 +1,65 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertEquals,
+ assertStringIncludes,
+} from "@test_util/std/assert/mod.ts";
+import { exists, existsSync } from "node:fs";
+import { promisify } from "node:util";
+
+Deno.test("[std/node/fs] exists", async function () {
+ const availableFile = await new Promise((resolve) => {
+ const tmpFilePath = Deno.makeTempFileSync();
+ exists(tmpFilePath, (exists: boolean) => {
+ Deno.removeSync(tmpFilePath);
+ resolve(exists);
+ });
+ });
+ const notAvailableFile = await new Promise((resolve) => {
+ exists("./notAvailable.txt", (exists: boolean) => resolve(exists));
+ });
+ assertEquals(availableFile, true);
+ assertEquals(notAvailableFile, false);
+});
+
+Deno.test("[std/node/fs] existsSync", function () {
+ const tmpFilePath = Deno.makeTempFileSync();
+ assertEquals(existsSync(tmpFilePath), true);
+ Deno.removeSync(tmpFilePath);
+ assertEquals(existsSync("./notAvailable.txt"), false);
+});
+
+Deno.test("[std/node/fs] promisify(exists)", async () => {
+ const tmpFilePath = await Deno.makeTempFile();
+ try {
+ const existsPromisified = promisify(exists);
+ assert(await existsPromisified(tmpFilePath));
+ assert(!await existsPromisified("./notAvailable.txt"));
+ } finally {
+ await Deno.remove(tmpFilePath);
+ }
+});
+
+Deno.test("[std/node/fs] exists callback isn't called twice if error is thrown", async () => {
+ // This doesn't use `assertCallbackErrorUncaught()` because `exists()` doesn't return a standard node callback, which is what it expects.
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ const command = new Deno.Command(Deno.execPath(), {
+ args: [
+ "eval",
+ "--no-check",
+ `
+ import { exists } from ${JSON.stringify(importUrl)};
+
+ exists(${JSON.stringify(tempFile)}, (exists) => {
+ // If the bug is present and the callback is called again with false (meaning an error occurred),
+ // don't throw another error, so if the subprocess fails we know it had the correct behaviour.
+ if (exists) throw new Error("success");
+ });`,
+ ],
+ });
+ const { success, stderr } = await command.output();
+ await Deno.remove(tempFile);
+ assert(!success);
+ assertStringIncludes(new TextDecoder().decode(stderr), "Error: success");
+});
diff --git a/tests/unit_node/_fs/_fs_fdatasync_test.ts b/tests/unit_node/_fs/_fs_fdatasync_test.ts
new file mode 100644
index 000000000..7a61bd4c1
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_fdatasync_test.ts
@@ -0,0 +1,58 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { fdatasync, fdatasyncSync } from "node:fs";
+
+Deno.test({
+ name:
+ "ASYNC: flush any pending data operations of the given file stream to disk",
+ async fn() {
+ const filePath = await Deno.makeTempFile();
+ using file = await Deno.open(filePath, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const data = new Uint8Array(64);
+ await file.write(data);
+
+ await new Promise<void>((resolve, reject) => {
+ fdatasync(file.rid, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ async () => {
+ assertEquals(await Deno.readFile(filePath), data);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(async () => {
+ await Deno.remove(filePath);
+ });
+ },
+});
+
+Deno.test({
+ name:
+ "SYNC: flush any pending data operations of the given file stream to disk.",
+ fn() {
+ const filePath = Deno.makeTempFileSync();
+ using file = Deno.openSync(filePath, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const data = new Uint8Array(64);
+ Deno.writeSync(file.rid, data);
+
+ try {
+ fdatasyncSync(file.rid);
+ assertEquals(Deno.readFileSync(filePath), data);
+ } finally {
+ Deno.removeSync(filePath);
+ }
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_fstat_test.ts b/tests/unit_node/_fs/_fs_fstat_test.ts
new file mode 100644
index 000000000..d15ef5a80
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_fstat_test.ts
@@ -0,0 +1,90 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { fstat, fstatSync } from "node:fs";
+import { fail } from "@test_util/std/assert/mod.ts";
+import { assertStats, assertStatsBigInt } from "./_fs_stat_test.ts";
+import type { BigIntStats, Stats } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: get a file Stats",
+ async fn() {
+ const filePath = await Deno.makeTempFile();
+ using file = await Deno.open(filePath);
+
+ await new Promise<Stats>((resolve, reject) => {
+ fstat(file.rid, (err: Error | null, stat: Stats) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then(
+ (stat) => {
+ assertStats(stat, file.statSync());
+ },
+ () => fail(),
+ )
+ .finally(() => {
+ Deno.removeSync(filePath);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: get a file BigInt Stats",
+ async fn() {
+ const filePath = await Deno.makeTempFile();
+ using file = await Deno.open(filePath);
+
+ await new Promise<BigIntStats>((resolve, reject) => {
+ fstat(
+ file.rid,
+ { bigint: true },
+ (err: Error | null, stat: BigIntStats) => {
+ if (err) reject(err);
+ resolve(stat);
+ },
+ );
+ })
+ .then(
+ (stat) => assertStatsBigInt(stat, file.statSync()),
+ () => fail(),
+ )
+ .finally(() => {
+ Deno.removeSync(filePath);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file Stats",
+ fn() {
+ const filePath = Deno.makeTempFileSync();
+ using file = Deno.openSync(filePath);
+
+ try {
+ assertStats(fstatSync(file.rid), file.statSync());
+ } finally {
+ Deno.removeSync(filePath);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file BigInt Stats",
+ fn() {
+ const filePath = Deno.makeTempFileSync();
+ using file = Deno.openSync(filePath);
+
+ try {
+ // HEAD
+ assertStatsBigInt(fstatSync(file.rid, { bigint: true }), file.statSync());
+ //
+ assertStatsBigInt(
+ fstatSync(file.rid, { bigint: true }),
+ Deno.fstatSync(file.rid),
+ );
+ //main
+ } finally {
+ Deno.removeSync(filePath);
+ }
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_fsync_test.ts b/tests/unit_node/_fs/_fs_fsync_test.ts
new file mode 100644
index 000000000..870055c00
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_fsync_test.ts
@@ -0,0 +1,56 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { fsync, fsyncSync } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: flush any pending data of the given file stream to disk",
+ async fn() {
+ const filePath = await Deno.makeTempFile();
+ using file = await Deno.open(filePath, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const size = 64;
+ await file.truncate(size);
+
+ await new Promise<void>((resolve, reject) => {
+ fsync(file.rid, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ async () => {
+ assertEquals((await Deno.stat(filePath)).size, size);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(async () => {
+ await Deno.remove(filePath);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: flush any pending data the given file stream to disk",
+ fn() {
+ const filePath = Deno.makeTempFileSync();
+ using file = Deno.openSync(filePath, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const size = 64;
+ file.truncateSync(size);
+
+ try {
+ fsyncSync(file.rid);
+ assertEquals(Deno.statSync(filePath).size, size);
+ } finally {
+ Deno.removeSync(filePath);
+ }
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_ftruncate_test.ts b/tests/unit_node/_fs/_fs_ftruncate_test.ts
new file mode 100644
index 000000000..1e669fb60
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_ftruncate_test.ts
@@ -0,0 +1,123 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, assertThrows, fail } from "@test_util/std/assert/mod.ts";
+import { ftruncate, ftruncateSync } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: no callback function results in Error",
+ fn() {
+ assertThrows(
+ () => {
+ // @ts-expect-error Argument of type 'number' is not assignable to parameter of type 'NoParamCallback'
+ ftruncate(123, 0);
+ },
+ Error,
+ "No callback function supplied",
+ );
+ },
+});
+
+Deno.test({
+ name: "ASYNC: truncate entire file contents",
+ async fn() {
+ const filePath = Deno.makeTempFileSync();
+ await Deno.writeTextFile(filePath, "hello world");
+ using file = await Deno.open(filePath, {
+ read: true,
+ write: true,
+ create: true,
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ ftruncate(file.rid, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ () => {
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(filePath);
+ assertEquals(fileInfo.size, 0);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(() => {
+ Deno.removeSync(filePath);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: truncate file to a size of precisely len bytes",
+ async fn() {
+ const filePath = Deno.makeTempFileSync();
+ await Deno.writeTextFile(filePath, "hello world");
+ using file = await Deno.open(filePath, {
+ read: true,
+ write: true,
+ create: true,
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ ftruncate(file.rid, 3, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ () => {
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(filePath);
+ assertEquals(fileInfo.size, 3);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(() => {
+ Deno.removeSync(filePath);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: truncate entire file contents",
+ fn() {
+ const filePath = Deno.makeTempFileSync();
+ Deno.writeFileSync(filePath, new TextEncoder().encode("hello world"));
+ using file = Deno.openSync(filePath, {
+ read: true,
+ write: true,
+ create: true,
+ });
+
+ try {
+ ftruncateSync(file.rid);
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(filePath);
+ assertEquals(fileInfo.size, 0);
+ } finally {
+ Deno.removeSync(filePath);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: truncate file to a size of precisely len bytes",
+ fn() {
+ const filePath = Deno.makeTempFileSync();
+ Deno.writeFileSync(filePath, new TextEncoder().encode("hello world"));
+ using file = Deno.openSync(filePath, {
+ read: true,
+ write: true,
+ create: true,
+ });
+
+ try {
+ ftruncateSync(file.rid, 3);
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(filePath);
+ assertEquals(fileInfo.size, 3);
+ } finally {
+ Deno.removeSync(filePath);
+ }
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_futimes_test.ts b/tests/unit_node/_fs/_fs_futimes_test.ts
new file mode 100644
index 000000000..bf3746957
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_futimes_test.ts
@@ -0,0 +1,106 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, assertThrows, fail } from "@test_util/std/assert/mod.ts";
+import { futimes, futimesSync } from "node:fs";
+
+const randomDate = new Date(Date.now() + 1000);
+
+Deno.test({
+ name:
+ "ASYNC: change the file system timestamps of the object referenced by path",
+ async fn() {
+ const filePath = Deno.makeTempFileSync();
+ using file = await Deno.open(filePath, { create: true, write: true });
+
+ await new Promise<void>((resolve, reject) => {
+ futimes(file.rid, randomDate, randomDate, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ () => {
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(filePath);
+ assertEquals(fileInfo.mtime, randomDate);
+ assertEquals(fileInfo.atime, randomDate);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(() => {
+ Deno.removeSync(filePath);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: should throw error if atime is infinity",
+ fn() {
+ assertThrows(
+ () => {
+ futimes(123, Infinity, 0, (_err: Error | null) => {});
+ },
+ Error,
+ "invalid atime, must not be infinity or NaN",
+ );
+ },
+});
+
+Deno.test({
+ name: "ASYNC: should throw error if atime is NaN",
+ fn() {
+ assertThrows(
+ () => {
+ futimes(123, "some string", 0, (_err: Error | null) => {});
+ },
+ Error,
+ "invalid atime, must not be infinity or NaN",
+ );
+ },
+});
+
+Deno.test({
+ name:
+ "SYNC: change the file system timestamps of the object referenced by path",
+ fn() {
+ const filePath = Deno.makeTempFileSync();
+ using file = Deno.openSync(filePath, { create: true, write: true });
+
+ try {
+ futimesSync(file.rid, randomDate, randomDate);
+
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(filePath);
+
+ assertEquals(fileInfo.mtime, randomDate);
+ assertEquals(fileInfo.atime, randomDate);
+ } finally {
+ Deno.removeSync(filePath);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: should throw error if atime is NaN",
+ fn() {
+ assertThrows(
+ () => {
+ futimesSync(123, "some string", 0);
+ },
+ Error,
+ "invalid atime, must not be infinity or NaN",
+ );
+ },
+});
+
+Deno.test({
+ name: "SYNC: should throw error if atime is Infinity",
+ fn() {
+ assertThrows(
+ () => {
+ futimesSync(123, Infinity, 0);
+ },
+ Error,
+ "invalid atime, must not be infinity or NaN",
+ );
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_handle_test.ts b/tests/unit_node/_fs/_fs_handle_test.ts
new file mode 100644
index 000000000..151d4d752
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_handle_test.ts
@@ -0,0 +1,88 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import * as path from "@test_util/std/path/mod.ts";
+import { Buffer } from "node:buffer";
+import * as fs from "node:fs/promises";
+import { assert, assertEquals } from "@test_util/std/assert/mod.ts";
+
+const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+const decoder = new TextDecoder();
+
+Deno.test("readFileSuccess", async function () {
+ const fileHandle = await fs.open(testData);
+ const data = await fileHandle.readFile();
+
+ assert(data instanceof Uint8Array);
+ assertEquals(decoder.decode(data as Uint8Array), "hello world");
+
+ await fileHandle.close();
+});
+
+Deno.test("read", async function () {
+ const fileHandle = await fs.open(testData);
+ const byteLength = "hello world".length;
+
+ const buf = new Buffer(byteLength);
+ await fileHandle.read(buf, 0, byteLength, 0);
+
+ assertEquals(decoder.decode(buf as Uint8Array), "hello world");
+
+ await fileHandle.close();
+});
+
+Deno.test("read specify opt", async function () {
+ const fileHandle = await fs.open(testData);
+ const byteLength = "hello world".length;
+
+ const opt = {
+ buffer: new Buffer(byteLength),
+ offset: 6,
+ length: 5,
+ };
+ let res = await fileHandle.read(opt);
+
+ assertEquals(res.bytesRead, byteLength);
+ assertEquals(new TextDecoder().decode(res.buffer as Uint8Array), "world");
+
+ const opt2 = {
+ buffer: new Buffer(byteLength),
+ length: 5,
+ position: 0,
+ };
+ res = await fileHandle.read(opt2);
+
+ assertEquals(res.bytesRead, byteLength);
+ assertEquals(decoder.decode(res.buffer as Uint8Array), "hello");
+
+ await fileHandle.close();
+});
+
+Deno.test("[node/fs filehandle.write] Write from Buffer", async function () {
+ const tempFile: string = await Deno.makeTempFile();
+ const fileHandle = await fs.open(tempFile, "a+");
+
+ const buffer = Buffer.from("hello world");
+ const res = await fileHandle.write(buffer, 0, 5, 0);
+
+ const data = Deno.readFileSync(tempFile);
+ await Deno.remove(tempFile);
+ await fileHandle.close();
+
+ assertEquals(res.bytesWritten, 5);
+ assertEquals(decoder.decode(data), "hello");
+});
+
+Deno.test("[node/fs filehandle.write] Write from string", async function () {
+ const tempFile: string = await Deno.makeTempFile();
+ const fileHandle = await fs.open(tempFile, "a+");
+
+ const str = "hello world";
+ const res = await fileHandle.write(str);
+
+ const data = Deno.readFileSync(tempFile);
+ await Deno.remove(tempFile);
+ await fileHandle.close();
+
+ assertEquals(res.bytesWritten, 11);
+ assertEquals(decoder.decode(data), "hello world");
+});
diff --git a/tests/unit_node/_fs/_fs_link_test.ts b/tests/unit_node/_fs/_fs_link_test.ts
new file mode 100644
index 000000000..15f15c706
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_link_test.ts
@@ -0,0 +1,77 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import * as path from "@test_util/std/path/mod.ts";
+import { assert, assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { link, linkSync } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: hard linking files works as expected",
+ async fn() {
+ const tempFile: string = await Deno.makeTempFile();
+ const linkedFile: string = tempFile + ".link";
+ await new Promise<void>((res, rej) => {
+ link(tempFile, linkedFile, (err) => {
+ if (err) rej(err);
+ else res();
+ });
+ })
+ .then(() => {
+ assertEquals(Deno.statSync(tempFile), Deno.statSync(linkedFile));
+ }, () => {
+ fail("Expected to succeed");
+ })
+ .finally(() => {
+ Deno.removeSync(tempFile);
+ Deno.removeSync(linkedFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: hard linking files passes error to callback",
+ async fn() {
+ let failed = false;
+ await new Promise<void>((res, rej) => {
+ link("no-such-file", "no-such-file", (err) => {
+ if (err) rej(err);
+ else res();
+ });
+ })
+ .then(() => {
+ fail("Expected to succeed");
+ }, (err) => {
+ assert(err);
+ failed = true;
+ });
+ assert(failed);
+ },
+});
+
+Deno.test({
+ name: "SYNC: hard linking files works as expected",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ const linkedFile: string = tempFile + ".link";
+ linkSync(tempFile, linkedFile);
+
+ assertEquals(Deno.statSync(tempFile), Deno.statSync(linkedFile));
+ Deno.removeSync(tempFile);
+ Deno.removeSync(linkedFile);
+ },
+});
+
+Deno.test("[std/node/fs] link callback isn't called twice if error is thrown", async () => {
+ const tempDir = await Deno.makeTempDir();
+ const tempFile = path.join(tempDir, "file.txt");
+ const linkFile = path.join(tempDir, "link.txt");
+ await Deno.writeTextFile(tempFile, "hello world");
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { link } from ${JSON.stringify(importUrl)}`,
+ invocation: `link(${JSON.stringify(tempFile)},
+ ${JSON.stringify(linkFile)}, `,
+ async cleanup() {
+ await Deno.remove(tempDir, { recursive: true });
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_lstat_test.ts b/tests/unit_node/_fs/_fs_lstat_test.ts
new file mode 100644
index 000000000..ccd21a3cd
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_lstat_test.ts
@@ -0,0 +1,71 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { lstat, lstatSync } from "node:fs";
+import { fail } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { assertStats, assertStatsBigInt } from "./_fs_stat_test.ts";
+import type { BigIntStats, Stats } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: get a file Stats (lstat)",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<Stats>((resolve, reject) => {
+ lstat(file, (err, stat) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then((stat) => {
+ assertStats(stat, Deno.lstatSync(file));
+ }, () => fail())
+ .finally(() => {
+ Deno.removeSync(file);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file Stats (lstat)",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertStats(lstatSync(file), Deno.lstatSync(file));
+ },
+});
+
+Deno.test({
+ name: "ASYNC: get a file BigInt Stats (lstat)",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<BigIntStats>((resolve, reject) => {
+ lstat(file, { bigint: true }, (err, stat) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then(
+ (stat) => assertStatsBigInt(stat, Deno.lstatSync(file)),
+ () => fail(),
+ )
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "SYNC: BigInt Stats (lstat)",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertStatsBigInt(lstatSync(file, { bigint: true }), Deno.lstatSync(file));
+ },
+});
+
+Deno.test("[std/node/fs] lstat callback isn't called twice if error is thrown", async () => {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { lstat } from ${JSON.stringify(importUrl)}`,
+ invocation: `lstat(${JSON.stringify(tempFile)}, `,
+ async cleanup() {
+ await Deno.remove(tempFile);
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_mkdir_test.ts b/tests/unit_node/_fs/_fs_mkdir_test.ts
new file mode 100644
index 000000000..fb7fcf9c5
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_mkdir_test.ts
@@ -0,0 +1,43 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import * as path from "@test_util/std/path/mod.ts";
+import { assert } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { existsSync, mkdir, mkdirSync } from "node:fs";
+
+const tmpDir = "./tmpdir";
+
+Deno.test({
+ name: "[node/fs] mkdir",
+ fn: async () => {
+ const result = await new Promise((resolve) => {
+ mkdir(tmpDir, (err) => {
+ err && resolve(false);
+ resolve(existsSync(tmpDir));
+ Deno.removeSync(tmpDir);
+ });
+ });
+ assert(result);
+ },
+});
+
+Deno.test({
+ name: "[node/fs] mkdirSync",
+ fn: () => {
+ mkdirSync(tmpDir);
+ assert(existsSync(tmpDir));
+ Deno.removeSync(tmpDir);
+ },
+});
+
+Deno.test("[std/node/fs] mkdir callback isn't called twice if error is thrown", async () => {
+ const tempDir = await Deno.makeTempDir();
+ const subdir = path.join(tempDir, "subdir");
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { mkdir } from ${JSON.stringify(importUrl)}`,
+ invocation: `mkdir(${JSON.stringify(subdir)}, `,
+ async cleanup() {
+ await Deno.remove(tempDir, { recursive: true });
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_mkdtemp_test.ts b/tests/unit_node/_fs/_fs_mkdtemp_test.ts
new file mode 100644
index 000000000..9f8975113
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_mkdtemp_test.ts
@@ -0,0 +1,86 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertRejects,
+ assertThrows,
+} from "@test_util/std/assert/mod.ts";
+import { EncodingOption, existsSync, mkdtemp, mkdtempSync } from "node:fs";
+import { env } from "node:process";
+import { promisify } from "node:util";
+
+const prefix = Deno.build.os === "windows"
+ ? env.TEMP + "\\"
+ : (env.TMPDIR || "/tmp") + "/";
+const doesNotExists = "/does/not/exists/";
+const options: EncodingOption = { encoding: "ascii" };
+const badOptions = { encoding: "bogus" };
+
+const mkdtempP = promisify(mkdtemp);
+
+Deno.test({
+ name: "[node/fs] mkdtemp",
+ fn: async () => {
+ const directory = await mkdtempP(prefix);
+ assert(existsSync(directory));
+ Deno.removeSync(directory);
+ },
+});
+
+Deno.test({
+ name: "[node/fs] mkdtemp (does not exists)",
+ fn: async () => {
+ await assertRejects(() => mkdtempP(doesNotExists));
+ },
+});
+
+Deno.test({
+ name: "[node/fs] mkdtemp (with options)",
+ fn: async () => {
+ const directory = await mkdtempP(prefix, options);
+ assert(existsSync(directory));
+ Deno.removeSync(directory);
+ },
+});
+
+Deno.test({
+ name: "[node/fs] mkdtemp (with bad options)",
+ fn: async () => {
+ // @ts-expect-error No overload matches this call
+ await assertRejects(() => mkdtempP(prefix, badOptions));
+ },
+});
+
+Deno.test({
+ name: "[node/fs] mkdtempSync",
+ fn: () => {
+ const directory = mkdtempSync(prefix);
+ const dirExists = existsSync(directory);
+ Deno.removeSync(directory);
+ assert(dirExists);
+ },
+});
+
+Deno.test({
+ name: "[node/fs] mkdtempSync (does not exists)",
+ fn: () => {
+ assertThrows(() => mkdtempSync(doesNotExists));
+ },
+});
+
+Deno.test({
+ name: "[node/fs] mkdtempSync (with options)",
+ fn: () => {
+ const directory = mkdtempSync(prefix, options);
+ const dirExists = existsSync(directory);
+ Deno.removeSync(directory);
+ assert(dirExists);
+ },
+});
+
+Deno.test({
+ name: "[node/fs] mkdtempSync (with bad options)",
+ fn: () => {
+ // @ts-expect-error No overload matches this call
+ assertThrows(() => mkdtempSync(prefix, badOptions));
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_open_test.ts b/tests/unit_node/_fs/_fs_open_test.ts
new file mode 100644
index 000000000..8cb9b0ec2
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_open_test.ts
@@ -0,0 +1,400 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ O_APPEND,
+ O_CREAT,
+ O_EXCL,
+ O_RDONLY,
+ O_RDWR,
+ O_SYNC,
+ O_TRUNC,
+ O_WRONLY,
+} from "node:constants";
+import { assertEquals, assertThrows, fail } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { open, openSync } from "node:fs";
+import { join, parse } from "node:path";
+import { closeSync, existsSync } from "node:fs";
+
+const tempDir = parse(Deno.makeTempFileSync()).dir;
+
+Deno.test({
+ name: "ASYNC: open file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ let fd1: number;
+ await new Promise<number>((resolve, reject) => {
+ open(file, (err, fd) => {
+ if (err) reject(err);
+ resolve(fd);
+ });
+ })
+ .then((fd) => {
+ fd1 = fd;
+ }, () => fail())
+ .finally(() => closeSync(fd1));
+ },
+});
+
+Deno.test({
+ name: "SYNC: open file",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ const fd = openSync(file, "r");
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'a'",
+ fn() {
+ const file = join(tempDir, "some_random_file");
+ const fd = openSync(file, "a");
+ assertEquals(typeof fd, "number");
+ assertEquals(existsSync(file), true);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'ax'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, "ax");
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'a+'",
+ fn() {
+ const file = join(tempDir, "some_random_file2");
+ const fd = openSync(file, "a+");
+ assertEquals(typeof fd, "number");
+ assertEquals(existsSync(file), true);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'ax+'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, "ax+");
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'as'",
+ fn() {
+ const file = join(tempDir, "some_random_file10");
+ const fd = openSync(file, "as");
+ assertEquals(existsSync(file), true);
+ assertEquals(typeof fd, "number");
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'as+'",
+ fn() {
+ const file = join(tempDir, "some_random_file10");
+ const fd = openSync(file, "as+");
+ assertEquals(existsSync(file), true);
+ assertEquals(typeof fd, "number");
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'r'",
+ fn() {
+ const file = join(tempDir, "some_random_file3");
+ assertThrows(() => {
+ openSync(file, "r");
+ }, Error);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'r+'",
+ fn() {
+ const file = join(tempDir, "some_random_file4");
+ assertThrows(() => {
+ openSync(file, "r+");
+ }, Error);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'w'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, "w");
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = join(tempDir, "some_random_file5");
+ const fd2 = openSync(file2, "w");
+ assertEquals(typeof fd2, "number");
+ assertEquals(existsSync(file2), true);
+ closeSync(fd2);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'wx'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, "w");
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file2, "wx");
+ },
+ Error,
+ `EEXIST: file already exists, open '${file2}'`,
+ );
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'w+'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, "w+");
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = join(tempDir, "some_random_file6");
+ const fd2 = openSync(file2, "w+");
+ assertEquals(typeof fd2, "number");
+ assertEquals(existsSync(file2), true);
+ closeSync(fd2);
+ },
+});
+
+Deno.test({
+ name: "open with string flag 'wx+'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, "wx+");
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_APPEND | O_CREAT | O_WRONLY` ('a')",
+ fn() {
+ const file = join(tempDir, "some_random_file");
+ const fd = openSync(file, O_APPEND | O_CREAT | O_WRONLY);
+ assertEquals(typeof fd, "number");
+ assertEquals(existsSync(file), true);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name:
+ "open with numeric flag `O_APPEND | O_CREAT | O_WRONLY | O_EXCL` ('ax')",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, O_APPEND | O_CREAT | O_WRONLY | O_EXCL);
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_APPEND | O_CREAT | O_RDWR` ('a+')",
+ fn() {
+ const file = join(tempDir, "some_random_file2");
+ const fd = openSync(file, O_APPEND | O_CREAT | O_RDWR);
+ assertEquals(typeof fd, "number");
+ assertEquals(existsSync(file), true);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_APPEND | O_CREAT | O_RDWR | O_EXCL` ('ax+')",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, O_APPEND | O_CREAT | O_RDWR | O_EXCL);
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
+
+Deno.test({
+ name:
+ "open with numeric flag `O_APPEND | O_CREAT | O_WRONLY | O_SYNC` ('as')",
+ fn() {
+ const file = join(tempDir, "some_random_file10");
+ const fd = openSync(file, O_APPEND | O_CREAT | O_WRONLY | O_SYNC);
+ assertEquals(existsSync(file), true);
+ assertEquals(typeof fd, "number");
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_APPEND | O_CREAT | O_RDWR | O_SYNC` ('as+')",
+ fn() {
+ const file = join(tempDir, "some_random_file10");
+ const fd = openSync(file, O_APPEND | O_CREAT | O_RDWR | O_SYNC);
+ assertEquals(existsSync(file), true);
+ assertEquals(typeof fd, "number");
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_RDONLY` ('r')",
+ fn() {
+ const file = join(tempDir, "some_random_file3");
+ assertThrows(() => {
+ openSync(file, O_RDONLY);
+ }, Error);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_RDWR` ('r+')",
+ fn() {
+ const file = join(tempDir, "some_random_file4");
+ assertThrows(() => {
+ openSync(file, O_RDWR);
+ }, Error);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_TRUNC | O_CREAT | O_WRONLY` ('w')",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, O_TRUNC | O_CREAT | O_WRONLY);
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = join(tempDir, "some_random_file5");
+ const fd2 = openSync(file2, O_TRUNC | O_CREAT | O_WRONLY);
+ assertEquals(typeof fd2, "number");
+ assertEquals(existsSync(file2), true);
+ closeSync(fd2);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_TRUNC | O_CREAT | O_WRONLY | O_EXCL` ('wx')",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, "w");
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file2, O_TRUNC | O_CREAT | O_WRONLY | O_EXCL);
+ },
+ Error,
+ `EEXIST: file already exists, open '${file2}'`,
+ );
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_TRUNC | O_CREAT | O_RDWR` ('w+')",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, O_TRUNC | O_CREAT | O_RDWR);
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = join(tempDir, "some_random_file6");
+ const fd2 = openSync(file2, O_TRUNC | O_CREAT | O_RDWR);
+ assertEquals(typeof fd2, "number");
+ assertEquals(existsSync(file2), true);
+ closeSync(fd2);
+ },
+});
+
+Deno.test({
+ name: "open with numeric flag `O_TRUNC | O_CREAT | O_RDWR | O_EXCL` ('wx+')",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, O_TRUNC | O_CREAT | O_RDWR | O_EXCL);
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
+
+Deno.test("[std/node/fs] open callback isn't called twice if error is thrown", async () => {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { open } from ${JSON.stringify(importUrl)}`,
+ invocation: `open(${JSON.stringify(tempFile)}, `,
+ async cleanup() {
+ await Deno.remove(tempFile);
+ },
+ });
+
+ Deno.test({
+ name: "SYNC: open file with flag set to 0 (readonly)",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ const fd = openSync(file, 0);
+ closeSync(fd);
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_opendir_test.ts b/tests/unit_node/_fs/_fs_opendir_test.ts
new file mode 100644
index 000000000..d4abb349c
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_opendir_test.ts
@@ -0,0 +1,146 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import {
+ assert,
+ assertEquals,
+ assertFalse,
+ assertInstanceOf,
+ assertThrows,
+} from "@test_util/std/assert/mod.ts";
+import { opendir, opendirSync } from "node:fs";
+import { Buffer } from "node:buffer";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+
+Deno.test("[node/fs] opendir()", async (t) => {
+ const path = await Deno.makeTempDir();
+ const file = await Deno.makeTempFile();
+
+ await t.step(
+ "fails if encoding is invalid",
+ () =>
+ opendir(
+ path,
+ // @ts-expect-error Type '"invalid-encoding"' is not assignable to type 'BufferEncoding | undefined'
+ { encoding: "invalid-encoding" },
+ (err) => assertInstanceOf(err, TypeError),
+ ),
+ );
+
+ await t.step(
+ "fails if bufferSize is invalid",
+ () =>
+ opendir(
+ path,
+ { bufferSize: -1 },
+ (err) => assertInstanceOf(err, RangeError),
+ ),
+ );
+
+ await t.step(
+ "fails if directory does not exist",
+ () =>
+ opendir(
+ "directory-that-does-not-exist",
+ (err) => assertInstanceOf(err, Error),
+ ),
+ );
+
+ await t.step(
+ "fails if not a directory",
+ () =>
+ opendir(
+ file,
+ (err) => assertInstanceOf(err, Error),
+ ),
+ );
+
+ await t.step(
+ "passes if path is a string",
+ () =>
+ opendir(
+ path,
+ (err, dir) => {
+ assertEquals(err, null);
+ assert(dir);
+ },
+ ),
+ );
+
+ await t.step(
+ "passes if path is a Buffer",
+ () =>
+ opendir(
+ Buffer.from(path),
+ (err, dir) => {
+ assertFalse(err);
+ assert(dir);
+ },
+ ),
+ );
+
+ await t.step(
+ "passes if path is a URL",
+ () =>
+ opendir(
+ new URL(`file://` + path),
+ (err, dir) => {
+ assertFalse(err);
+ assert(dir);
+ },
+ ),
+ );
+
+ await t.step("passes if callback isn't called twice", async () => {
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { opendir } from ${JSON.stringify(importUrl)}`,
+ invocation: `opendir(${JSON.stringify(path)}, `,
+ });
+ });
+
+ await Deno.remove(path);
+ await Deno.remove(file);
+});
+
+Deno.test("[node/fs] opendirSync()", async (t) => {
+ const path = await Deno.makeTempDir();
+ const file = await Deno.makeTempFile();
+
+ await t.step("fails if encoding is invalid", () => {
+ assertThrows(
+ // @ts-expect-error Type '"invalid-encoding"' is not assignable to type 'BufferEncoding | undefined'
+ () => opendirSync(path, { encoding: "invalid-encoding" }),
+ TypeError,
+ );
+ });
+
+ await t.step("fails if bufferSize is invalid", () => {
+ assertThrows(
+ () => opendirSync(path, { bufferSize: -1 }),
+ RangeError,
+ );
+ });
+
+ await t.step("fails if directory does not exist", () => {
+ assertThrows(() => opendirSync("directory-that-does-not-exist"));
+ });
+
+ await t.step("fails if not a directory", () => {
+ assertThrows(() => opendirSync(file));
+ });
+
+ await t.step("passes if path is a string", () => {
+ assert(opendirSync(path));
+ });
+
+ await t.step("passes if path is a Buffer", () => {
+ assert(opendirSync(Buffer.from(path)));
+ });
+
+ await t.step("passes if path is a URL", () => {
+ assert(opendirSync(new URL(`file://` + path)));
+ });
+
+ await Deno.remove(path);
+ await Deno.remove(file);
+});
diff --git a/tests/unit_node/_fs/_fs_readFile_test.ts b/tests/unit_node/_fs/_fs_readFile_test.ts
new file mode 100644
index 000000000..00653955d
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_readFile_test.ts
@@ -0,0 +1,123 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { promises, readFile, readFileSync } from "node:fs";
+import * as path from "@test_util/std/path/mod.ts";
+import { assert, assertEquals } from "@test_util/std/assert/mod.ts";
+
+const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+
+Deno.test("readFileSuccess", async function () {
+ const data = await new Promise((res, rej) => {
+ readFile(testData, (err, data) => {
+ if (err) {
+ rej(err);
+ }
+ res(data);
+ });
+ });
+
+ assert(data instanceof Uint8Array);
+ assertEquals(new TextDecoder().decode(data as Uint8Array), "hello world");
+});
+
+Deno.test("readFileEncodeUtf8Success", async function () {
+ const data = await new Promise((res, rej) => {
+ readFile(testData, { encoding: "utf8" }, (err, data) => {
+ if (err) {
+ rej(err);
+ }
+ res(data);
+ });
+ });
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, "hello world");
+});
+
+Deno.test("readFileEncodeHexSuccess", async function () {
+ const data = await new Promise((res, rej) => {
+ readFile(testData, { encoding: "hex" }, (err, data) => {
+ if (err) {
+ rej(err);
+ }
+ res(data);
+ });
+ });
+
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, "68656c6c6f20776f726c64");
+});
+
+Deno.test("readFileEncodeBase64Success", async function () {
+ const data = await new Promise((res, rej) => {
+ readFile(testData, { encoding: "base64" }, (err, data) => {
+ if (err) {
+ rej(err);
+ }
+ res(data);
+ });
+ });
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, "aGVsbG8gd29ybGQ=");
+});
+
+Deno.test("readFileEncodingAsString", async function () {
+ const data = await new Promise((res, rej) => {
+ readFile(testData, "utf8", (err, data) => {
+ if (err) {
+ rej(err);
+ }
+ res(data);
+ });
+ });
+
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, "hello world");
+});
+
+Deno.test("readFileSyncSuccess", function () {
+ const data = readFileSync(testData);
+ assert(data instanceof Uint8Array);
+ assertEquals(new TextDecoder().decode(data as Uint8Array), "hello world");
+});
+
+Deno.test("readFileEncodeUtf8Success", function () {
+ const data = readFileSync(testData, { encoding: "utf8" });
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, "hello world");
+});
+
+Deno.test("readFileEncodeHexSuccess", function () {
+ const data = readFileSync(testData, { encoding: "hex" });
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, "68656c6c6f20776f726c64");
+});
+
+Deno.test("readFileEncodeBase64Success", function () {
+ const data = readFileSync(testData, { encoding: "base64" });
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, "aGVsbG8gd29ybGQ=");
+});
+
+Deno.test("readFileEncodeAsString", function () {
+ const data = readFileSync(testData, "utf8");
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, "hello world");
+});
+
+Deno.test("[std/node/fs] readFile callback isn't called twice if error is thrown", async () => {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { readFile } from ${JSON.stringify(importUrl)}`,
+ invocation: `readFile(${JSON.stringify(tempFile)}, `,
+ async cleanup() {
+ await Deno.remove(tempFile);
+ },
+ });
+});
+
+Deno.test("fs.promises.readFile with no arg call rejects with error correctly", async () => {
+ // @ts-ignore no arg call needs to be supported
+ await promises.readFile().catch((_e) => {});
+});
diff --git a/tests/unit_node/_fs/_fs_read_test.ts b/tests/unit_node/_fs/_fs_read_test.ts
new file mode 100644
index 000000000..de741e377
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_read_test.ts
@@ -0,0 +1,322 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertFalse,
+ assertMatch,
+ assertStrictEquals,
+} from "@test_util/std/assert/mod.ts";
+import { read, readSync } from "node:fs";
+import { open, openSync } from "node:fs";
+import { Buffer } from "node:buffer";
+import * as path from "@test_util/std/path/mod.ts";
+import { closeSync } from "node:fs";
+
+async function readTest(
+ testData: string,
+ buffer: Buffer,
+ offset: number,
+ length: number,
+ position: number | null = null,
+ expected: (
+ fd: number,
+ bytesRead: number | null,
+ data: Buffer | undefined,
+ ) => void,
+) {
+ let fd1 = 0;
+ await new Promise<{
+ fd: number;
+ bytesRead: number | null;
+ data: Buffer | undefined;
+ }>((resolve, reject) => {
+ open(testData, "r", (err, fd) => {
+ if (err) reject(err);
+ read(fd, buffer, offset, length, position, (err, bytesRead, data) => {
+ if (err) reject(err);
+ resolve({ fd, bytesRead, data });
+ });
+ });
+ })
+ .then(({ fd, bytesRead, data }) => {
+ fd1 = fd;
+ expected(fd, bytesRead, data);
+ })
+ .finally(() => closeSync(fd1));
+}
+
+Deno.test({
+ name: "readSuccess",
+ async fn() {
+ const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+ const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+ const buf = Buffer.alloc(1024);
+ await readTest(
+ testData,
+ buf,
+ buf.byteOffset,
+ buf.byteLength,
+ null,
+ (_fd, bytesRead, data) => {
+ assertStrictEquals(bytesRead, 11);
+ assertEquals(data instanceof Buffer, true);
+ assertMatch((data as Buffer).toString(), /hello world/);
+ },
+ );
+ },
+});
+
+Deno.test({
+ name:
+ "[std/node/fs] Read only five bytes, so that the position moves to five",
+ async fn() {
+ const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+ const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+ const buf = Buffer.alloc(5);
+ await readTest(
+ testData,
+ buf,
+ buf.byteOffset,
+ 5,
+ null,
+ (_fd, bytesRead, data) => {
+ assertStrictEquals(bytesRead, 5);
+ assertEquals(data instanceof Buffer, true);
+ assertEquals((data as Buffer).toString(), "hello");
+ },
+ );
+ },
+});
+
+Deno.test({
+ name:
+ "[std/node/fs] position option of fs.read() specifies where to begin reading from in the file",
+ async fn() {
+ const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+ const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+ const fd = openSync(testData, "r");
+ const buf = Buffer.alloc(5);
+ const positions = [6, 0, -1, null];
+ const expected = [
+ [119, 111, 114, 108, 100],
+ [104, 101, 108, 108, 111],
+ [104, 101, 108, 108, 111],
+ [32, 119, 111, 114, 108],
+ ];
+ for (const [i, position] of positions.entries()) {
+ await new Promise((resolve) => {
+ read(
+ fd,
+ {
+ buffer: buf,
+ offset: buf.byteOffset,
+ length: buf.byteLength,
+ position,
+ },
+ (err, bytesRead, data) => {
+ assertEquals(err, null);
+ assertStrictEquals(bytesRead, 5);
+ assertEquals(
+ data,
+ Buffer.from(expected[i]),
+ );
+ return resolve(true);
+ },
+ );
+ });
+ }
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "[std/node/fs] Read fs.read(fd, options, cb) signature",
+ async fn() {
+ const { promise, reject, resolve } = Promise.withResolvers<void>();
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, "r+");
+ const buf = Buffer.alloc(11);
+ read(
+ fd,
+ {
+ buffer: buf,
+ offset: buf.byteOffset,
+ length: buf.byteLength,
+ position: null,
+ },
+ (err, bytesRead, data) => {
+ try {
+ assertEquals(err, null);
+ assertStrictEquals(bytesRead, 8);
+ assertEquals(
+ data,
+ Buffer.from([104, 105, 32, 116, 104, 101, 114, 101, 0, 0, 0]),
+ );
+ } catch (e) {
+ reject(e);
+ return;
+ }
+ resolve();
+ },
+ );
+ closeSync(fd);
+ await promise;
+ },
+});
+
+Deno.test({
+ name: "[std/node/fs] Read fs.read(fd, cb) signature",
+ async fn() {
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi deno");
+ const fd = openSync(file, "r+");
+ read(fd, (err, bytesRead, data) => {
+ try {
+ assertEquals(err, null);
+ assertStrictEquals(bytesRead, 7);
+ assertStrictEquals(data?.byteLength, 16384);
+ } catch (e) {
+ reject(e);
+ return;
+ }
+ resolve();
+ });
+ closeSync(fd);
+ await promise;
+ },
+});
+
+Deno.test({
+ name: "SYNC: readSuccess",
+ fn() {
+ const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+ const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+ const buffer = Buffer.alloc(1024);
+ const fd = openSync(testData, "r");
+ const bytesRead = readSync(
+ fd,
+ buffer,
+ buffer.byteOffset,
+ buffer.byteLength,
+ null,
+ );
+ assertStrictEquals(bytesRead, 11);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "[std/node/fs] Read only two bytes, so that the position moves to two",
+ fn() {
+ const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+ const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+ const buffer = Buffer.alloc(2);
+ const fd = openSync(testData, "r");
+ const bytesRead = readSync(fd, buffer, buffer.byteOffset, 2, null);
+ assertStrictEquals(bytesRead, 2);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name:
+ "[std/node/fs] position option of fs.readSync() specifies where to begin reading from in the file",
+ fn() {
+ const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+ const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+ const fd = openSync(testData, "r");
+ const buf = Buffer.alloc(5);
+ const positions = [6, 0, -1, null];
+ const expected = [
+ [119, 111, 114, 108, 100],
+ [104, 101, 108, 108, 111],
+ [104, 101, 108, 108, 111],
+ [32, 119, 111, 114, 108],
+ ];
+ for (const [i, position] of positions.entries()) {
+ const bytesRead = readSync(
+ fd,
+ buf,
+ buf.byteOffset,
+ buf.byteLength,
+ position,
+ );
+ assertStrictEquals(bytesRead, 5);
+ assertEquals(
+ buf,
+ Buffer.from(expected[i]),
+ );
+ }
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "[std/node/fs] Read fs.readSync(fd, buffer[, options]) signature",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hello deno");
+ const buffer = Buffer.alloc(1024);
+ const fd = openSync(file, "r+");
+ const bytesRead = readSync(fd, buffer, {
+ length: buffer.byteLength,
+ offset: buffer.byteOffset,
+ position: null,
+ });
+ assertStrictEquals(bytesRead, 10);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "[std/node/fs] fs.read is async",
+ async fn(t) {
+ const file = await Deno.makeTempFile();
+ await Deno.writeTextFile(file, "abc");
+
+ await t.step("without position option", async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ let called = false;
+ const fd = openSync(file, "r");
+ read(fd, () => {
+ called = true;
+ closeSync(fd);
+ resolve();
+ });
+ assertFalse(called);
+ await promise;
+ });
+
+ await t.step("with position option", async () => {
+ const { promise, resolve } = Promise.withResolvers<void>();
+ let called = false;
+ const buffer = Buffer.alloc(2);
+ const fd = openSync(file, "r");
+ read(fd, { position: 1, buffer, offset: 0, length: 2 }, () => {
+ called = true;
+ closeSync(fd);
+ resolve();
+ });
+ assertFalse(called);
+ await promise;
+ });
+
+ await Deno.remove(file);
+ },
+});
+
+Deno.test({
+ name: "SYNC: read with no offsetOropts argument",
+ fn() {
+ const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+ const testData = path.resolve(moduleDir, "testdata", "hello.txt");
+ const buffer = Buffer.alloc(1024);
+ const fd = openSync(testData, "r");
+ const _bytesRead = readSync(
+ fd,
+ buffer,
+ );
+ closeSync(fd);
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_readdir_test.ts b/tests/unit_node/_fs/_fs_readdir_test.ts
new file mode 100644
index 000000000..eaacbfc5e
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_readdir_test.ts
@@ -0,0 +1,96 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertNotEquals,
+ fail,
+} from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { readdir, readdirSync } from "node:fs";
+import { join } from "@test_util/std/path/mod.ts";
+
+Deno.test({
+ name: "ASYNC: reading empty directory",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ await new Promise<string[]>((resolve, reject) => {
+ readdir(dir, (err, files) => {
+ if (err) reject(err);
+ resolve(files);
+ });
+ })
+ .then((files) => assertEquals(files, []), () => fail())
+ .finally(() => Deno.removeSync(dir));
+ },
+});
+
+function assertEqualsArrayAnyOrder<T>(actual: T[], expected: T[]) {
+ assertEquals(actual.length, expected.length);
+ for (const item of expected) {
+ const index = actual.indexOf(item);
+ assertNotEquals(index, -1);
+ expected = expected.splice(index, 1);
+ }
+}
+
+Deno.test({
+ name: "ASYNC: reading non-empty directory",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ Deno.writeTextFileSync(join(dir, "file1.txt"), "hi");
+ Deno.writeTextFileSync(join(dir, "file2.txt"), "hi");
+ Deno.mkdirSync(join(dir, "some_dir"));
+ await new Promise<string[]>((resolve, reject) => {
+ readdir(dir, (err, files) => {
+ if (err) reject(err);
+ resolve(files);
+ });
+ })
+ .then(
+ (files) =>
+ assertEqualsArrayAnyOrder(
+ files,
+ ["file1.txt", "some_dir", "file2.txt"],
+ ),
+ () => fail(),
+ )
+ .finally(() => Deno.removeSync(dir, { recursive: true }));
+ },
+});
+
+Deno.test({
+ name: "SYNC: reading empty the directory",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ assertEquals(readdirSync(dir), []);
+ },
+});
+
+Deno.test({
+ name: "SYNC: reading non-empty directory",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ Deno.writeTextFileSync(join(dir, "file1.txt"), "hi");
+ Deno.writeTextFileSync(join(dir, "file2.txt"), "hi");
+ Deno.mkdirSync(join(dir, "some_dir"));
+ assertEqualsArrayAnyOrder(
+ readdirSync(dir),
+ ["file1.txt", "some_dir", "file2.txt"],
+ );
+ },
+});
+
+Deno.test("[std/node/fs] readdir callback isn't called twice if error is thrown", async () => {
+ // The correct behaviour is not to catch any errors thrown,
+ // but that means there'll be an uncaught error and the test will fail.
+ // So the only way to test this is to spawn a subprocess, and succeed if it has a non-zero exit code.
+ // (assertRejects won't work because there's no way to catch the error.)
+ const tempDir = await Deno.makeTempDir();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { readdir } from ${JSON.stringify(importUrl)}`,
+ invocation: `readdir(${JSON.stringify(tempDir)}, `,
+ async cleanup() {
+ await Deno.remove(tempDir);
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_readlink_test.ts b/tests/unit_node/_fs/_fs_readlink_test.ts
new file mode 100644
index 000000000..02d84c6c3
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_readlink_test.ts
@@ -0,0 +1,75 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { readlink, readlinkSync } from "node:fs";
+import { assert, assertEquals } from "@test_util/std/assert/mod.ts";
+import * as path from "@test_util/std/path/mod.ts";
+
+const testDir = Deno.makeTempDirSync();
+const oldname = path.join(testDir, "oldname");
+const newname = path.join(testDir, "newname");
+
+if (Deno.build.os === "windows") {
+ Deno.symlinkSync(oldname, newname, { type: "file" });
+} else {
+ Deno.symlinkSync(oldname, newname);
+}
+
+Deno.test({
+ name: "readlinkSuccess",
+ async fn() {
+ const data = await new Promise((res, rej) => {
+ readlink(newname, (err, data) => {
+ if (err) {
+ rej(err);
+ }
+ res(data);
+ });
+ });
+
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, oldname);
+ },
+});
+
+Deno.test({
+ name: "readlinkEncodeBufferSuccess",
+ async fn() {
+ const data = await new Promise((res, rej) => {
+ readlink(newname, { encoding: "buffer" }, (err, data) => {
+ if (err) {
+ rej(err);
+ }
+ res(data);
+ });
+ });
+
+ assert(data instanceof Uint8Array);
+ assertEquals(new TextDecoder().decode(data as Uint8Array), oldname);
+ },
+});
+
+Deno.test({
+ name: "readlinkSyncSuccess",
+ fn() {
+ const data = readlinkSync(newname);
+ assertEquals(typeof data, "string");
+ assertEquals(data as string, oldname);
+ },
+});
+
+Deno.test({
+ name: "readlinkEncodeBufferSuccess",
+ fn() {
+ const data = readlinkSync(newname, { encoding: "buffer" });
+ assert(data instanceof Uint8Array);
+ assertEquals(new TextDecoder().decode(data as Uint8Array), oldname);
+ },
+});
+
+Deno.test("[std/node/fs] readlink callback isn't called twice if error is thrown", async () => {
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { readlink } from ${JSON.stringify(importUrl)}`,
+ invocation: `readlink(${JSON.stringify(newname)}, `,
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_realpath_test.ts b/tests/unit_node/_fs/_fs_realpath_test.ts
new file mode 100644
index 000000000..6f22ff72a
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_realpath_test.ts
@@ -0,0 +1,55 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import * as path from "@test_util/std/path/mod.ts";
+import { assertEquals } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { realpath, realpathSync } from "node:fs";
+
+Deno.test("realpath", async function () {
+ const tempFile = await Deno.makeTempFile();
+ const tempFileAlias = tempFile + ".alias";
+ await Deno.symlink(tempFile, tempFileAlias);
+ const realPath = await new Promise((resolve, reject) => {
+ realpath(tempFile, (err, path) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ resolve(path);
+ });
+ });
+ const realSymLinkPath = await new Promise((resolve, reject) => {
+ realpath(tempFileAlias, (err, path) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ resolve(path);
+ });
+ });
+ assertEquals(realPath, realSymLinkPath);
+});
+
+Deno.test("realpathSync", function () {
+ const tempFile = Deno.makeTempFileSync();
+ const tempFileAlias = tempFile + ".alias";
+ Deno.symlinkSync(tempFile, tempFileAlias);
+ const realPath = realpathSync(tempFile);
+ const realSymLinkPath = realpathSync(tempFileAlias);
+ assertEquals(realPath, realSymLinkPath);
+});
+
+Deno.test("[std/node/fs] realpath callback isn't called twice if error is thrown", async () => {
+ const tempDir = await Deno.makeTempDir();
+ const tempFile = path.join(tempDir, "file.txt");
+ const linkFile = path.join(tempDir, "link.txt");
+ await Deno.writeTextFile(tempFile, "hello world");
+ await Deno.symlink(tempFile, linkFile, { type: "file" });
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { realpath } from ${JSON.stringify(importUrl)}`,
+ invocation: `realpath(${JSON.stringify(`${tempDir}/link.txt`)}, `,
+ async cleanup() {
+ await Deno.remove(tempDir, { recursive: true });
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_rename_test.ts b/tests/unit_node/_fs/_fs_rename_test.ts
new file mode 100644
index 000000000..dd0a01f8a
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_rename_test.ts
@@ -0,0 +1,52 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { rename, renameSync } from "node:fs";
+import { existsSync } from "node:fs";
+import { join, parse } from "@test_util/std/path/mod.ts";
+
+Deno.test({
+ name: "ASYNC: renaming a file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ const newPath = join(parse(file).dir, `${parse(file).base}_renamed`);
+ await new Promise<void>((resolve, reject) => {
+ rename(file, newPath, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => {
+ assertEquals(existsSync(newPath), true);
+ assertEquals(existsSync(file), false);
+ }, () => fail())
+ .finally(() => {
+ if (existsSync(file)) Deno.removeSync(file);
+ if (existsSync(newPath)) Deno.removeSync(newPath);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: renaming a file",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ const newPath = join(parse(file).dir, `${parse(file).base}_renamed`);
+ renameSync(file, newPath);
+ assertEquals(existsSync(newPath), true);
+ assertEquals(existsSync(file), false);
+ },
+});
+
+Deno.test("[std/node/fs] rename callback isn't called twice if error is thrown", async () => {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { rename } from ${JSON.stringify(importUrl)}`,
+ invocation: `rename(${JSON.stringify(tempFile)},
+ ${JSON.stringify(`${tempFile}.newname`)}, `,
+ async cleanup() {
+ await Deno.remove(`${tempFile}.newname`);
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_rm_test.ts b/tests/unit_node/_fs/_fs_rm_test.ts
new file mode 100644
index 000000000..1cc82a0cc
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_rm_test.ts
@@ -0,0 +1,139 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertRejects,
+ assertThrows,
+ fail,
+} from "@test_util/std/assert/mod.ts";
+import { rm, rmSync } from "node:fs";
+import { existsSync } from "node:fs";
+import { join } from "@test_util/std/path/mod.ts";
+
+Deno.test({
+ name: "ASYNC: removing empty folder",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ await new Promise<void>((resolve, reject) => {
+ rm(dir, { recursive: true }, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => assertEquals(existsSync(dir), false), () => fail())
+ .finally(() => {
+ if (existsSync(dir)) Deno.removeSync(dir);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: removing non-empty folder",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ using _file1 = Deno.createSync(join(dir, "file1.txt"));
+ using _file2 = Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ using _file = Deno.createSync(join(dir, "some_dir", "file.txt"));
+ await new Promise<void>((resolve, reject) => {
+ rm(dir, { recursive: true }, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => assertEquals(existsSync(dir), false), () => fail())
+ .finally(() => {
+ if (existsSync(dir)) Deno.removeSync(dir, { recursive: true });
+ });
+ },
+ ignore: Deno.build.os === "windows",
+});
+
+Deno.test({
+ name: "ASYNC: removing a file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<void>((resolve, reject) => {
+ rm(file, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ });
+
+ assertEquals(existsSync(file), false);
+ },
+});
+
+Deno.test({
+ name: "ASYNC: remove should fail if target does not exist",
+ async fn() {
+ const removePromise = new Promise<void>((resolve, reject) => {
+ rm("/path/to/noexist.text", (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ });
+ await assertRejects(() => removePromise, Error);
+ },
+});
+
+Deno.test({
+ name:
+ "ASYNC: remove should not fail if target does not exist and force option is true",
+ async fn() {
+ await new Promise<void>((resolve, reject) => {
+ rm("/path/to/noexist.text", { force: true }, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: removing empty folder",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ rmSync(dir, { recursive: true });
+ assertEquals(existsSync(dir), false);
+ },
+});
+
+Deno.test({
+ name: "SYNC: removing non-empty folder",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ using _file1 = Deno.createSync(join(dir, "file1.txt"));
+ using _file2 = Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ using _file = Deno.createSync(join(dir, "some_dir", "file.txt"));
+ rmSync(dir, { recursive: true });
+ assertEquals(existsSync(dir), false);
+ },
+ ignore: Deno.build.os === "windows",
+});
+
+Deno.test({
+ name: "SYNC: removing a file",
+ fn() {
+ const file = Deno.makeTempFileSync();
+
+ rmSync(file);
+
+ assertEquals(existsSync(file), false);
+ },
+});
+
+Deno.test({
+ name: "SYNC: remove should fail if target does not exist",
+ fn() {
+ assertThrows(() => rmSync("/path/to/noexist.text"), Error);
+ },
+});
+
+Deno.test({
+ name:
+ "SYNC: remove should not fail if target does not exist and force option is true",
+ fn() {
+ rmSync("/path/to/noexist.text", { force: true });
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_rmdir_test.ts b/tests/unit_node/_fs/_fs_rmdir_test.ts
new file mode 100644
index 000000000..d2b075bdf
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_rmdir_test.ts
@@ -0,0 +1,81 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { rmdir, rmdirSync } from "node:fs";
+import { existsSync } from "node:fs";
+import { join } from "@test_util/std/path/mod.ts";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+
+Deno.test({
+ name: "ASYNC: removing empty folder",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ await new Promise<void>((resolve, reject) => {
+ rmdir(dir, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => assertEquals(existsSync(dir), false), () => fail())
+ .finally(() => {
+ if (existsSync(dir)) Deno.removeSync(dir);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: removing empty folder",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ rmdirSync(dir);
+ assertEquals(existsSync(dir), false);
+ },
+});
+
+Deno.test({
+ name: "ASYNC: removing non-empty folder",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ using _file1 = Deno.createSync(join(dir, "file1.txt"));
+ using _file2 = Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ using _file = Deno.createSync(join(dir, "some_dir", "file.txt"));
+ await new Promise<void>((resolve, reject) => {
+ rmdir(dir, { recursive: true }, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => assertEquals(existsSync(dir), false), () => fail())
+ .finally(() => {
+ if (existsSync(dir)) Deno.removeSync(dir, { recursive: true });
+ });
+ },
+ ignore: Deno.build.os === "windows",
+});
+
+Deno.test({
+ name: "SYNC: removing non-empty folder",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ using _file1 = Deno.createSync(join(dir, "file1.txt"));
+ using _file2 = Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ using _file = Deno.createSync(join(dir, "some_dir", "file.txt"));
+ rmdirSync(dir, { recursive: true });
+ assertEquals(existsSync(dir), false);
+ },
+ ignore: Deno.build.os === "windows",
+});
+
+Deno.test("[std/node/fs] rmdir callback isn't called twice if error is thrown", async () => {
+ // The correct behaviour is not to catch any errors thrown,
+ // but that means there'll be an uncaught error and the test will fail.
+ // So the only way to test this is to spawn a subprocess, and succeed if it has a non-zero exit code.
+ // (assertRejects won't work because there's no way to catch the error.)
+ const tempDir = await Deno.makeTempDir();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { rmdir } from ${JSON.stringify(importUrl)}`,
+ invocation: `rmdir(${JSON.stringify(tempDir)}, `,
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_stat_test.ts b/tests/unit_node/_fs/_fs_stat_test.ts
new file mode 100644
index 000000000..38d5ca985
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_stat_test.ts
@@ -0,0 +1,131 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { BigIntStats, stat, Stats, statSync } from "node:fs";
+import { assertEquals, fail } from "@test_util/std/assert/mod.ts";
+
+export function assertStats(actual: Stats, expected: Deno.FileInfo) {
+ assertEquals(actual.dev, expected.dev);
+ assertEquals(actual.gid, expected.gid);
+ assertEquals(actual.size, expected.size);
+ assertEquals(actual.blksize, expected.blksize);
+ assertEquals(actual.blocks, expected.blocks);
+ assertEquals(actual.ino, expected.ino);
+ assertEquals(actual.gid, expected.gid);
+ assertEquals(actual.mode, expected.mode);
+ assertEquals(actual.nlink, expected.nlink);
+ assertEquals(actual.rdev, expected.rdev);
+ assertEquals(actual.uid, expected.uid);
+ assertEquals(actual.atime?.getTime(), expected.atime?.getTime());
+ assertEquals(actual.mtime?.getTime(), expected.mtime?.getTime());
+ assertEquals(actual.birthtime?.getTime(), expected.birthtime?.getTime());
+ assertEquals(actual.atimeMs ?? undefined, expected.atime?.getTime());
+ assertEquals(actual.mtimeMs ?? undefined, expected.mtime?.getTime());
+ assertEquals(actual.birthtimeMs ?? undefined, expected.birthtime?.getTime());
+ assertEquals(actual.isFile(), expected.isFile);
+ assertEquals(actual.isDirectory(), expected.isDirectory);
+ assertEquals(actual.isSymbolicLink(), expected.isSymlink);
+}
+
+function toBigInt(num?: number | null) {
+ if (num === undefined || num === null) return null;
+ return BigInt(num);
+}
+
+export function assertStatsBigInt(
+ actual: BigIntStats,
+ expected: Deno.FileInfo,
+) {
+ assertEquals(actual.dev, toBigInt(expected.dev));
+ assertEquals(actual.gid, toBigInt(expected.gid));
+ assertEquals(actual.size, toBigInt(expected.size));
+ assertEquals(actual.blksize, toBigInt(expected.blksize));
+ assertEquals(actual.blocks, toBigInt(expected.blocks));
+ assertEquals(actual.ino, toBigInt(expected.ino));
+ assertEquals(actual.gid, toBigInt(expected.gid));
+ assertEquals(actual.mode, toBigInt(expected.mode));
+ assertEquals(actual.nlink, toBigInt(expected.nlink));
+ assertEquals(actual.rdev, toBigInt(expected.rdev));
+ assertEquals(actual.uid, toBigInt(expected.uid));
+ assertEquals(actual.atime?.getTime(), expected.atime?.getTime());
+ assertEquals(actual.mtime?.getTime(), expected.mtime?.getTime());
+ assertEquals(actual.birthtime?.getTime(), expected.birthtime?.getTime());
+ assertEquals(
+ actual.atimeMs === null ? undefined : Number(actual.atimeMs),
+ expected.atime?.getTime(),
+ );
+ assertEquals(
+ actual.mtimeMs === null ? undefined : Number(actual.mtimeMs),
+ expected.mtime?.getTime(),
+ );
+ assertEquals(
+ actual.birthtimeMs === null ? undefined : Number(actual.birthtimeMs),
+ expected.birthtime?.getTime(),
+ );
+ assertEquals(actual.atimeNs === null, actual.atime === null);
+ assertEquals(actual.mtimeNs === null, actual.mtime === null);
+ assertEquals(actual.birthtimeNs === null, actual.birthtime === null);
+ assertEquals(actual.isFile(), expected.isFile);
+ assertEquals(actual.isDirectory(), expected.isDirectory);
+ assertEquals(actual.isSymbolicLink(), expected.isSymlink);
+}
+
+Deno.test({
+ name: "ASYNC: get a file Stats",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<Stats>((resolve, reject) => {
+ stat(file, (err, stat) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then((stat) => assertStats(stat, Deno.statSync(file)), () => fail())
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file Stats",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertStats(statSync(file), Deno.statSync(file));
+ },
+});
+
+Deno.test({
+ name: "ASYNC: get a file BigInt Stats",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<BigIntStats>((resolve, reject) => {
+ stat(file, { bigint: true }, (err, stat) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then(
+ (stat) => assertStatsBigInt(stat, Deno.statSync(file)),
+ () => fail(),
+ )
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file BigInt Stats",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertStatsBigInt(statSync(file, { bigint: true }), Deno.statSync(file));
+ },
+});
+
+Deno.test("[std/node/fs] stat callback isn't called twice if error is thrown", async () => {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { stat } from ${JSON.stringify(importUrl)}`,
+ invocation: `stat(${JSON.stringify(tempFile)}, `,
+ async cleanup() {
+ await Deno.remove(tempFile);
+ },
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_symlink_test.ts b/tests/unit_node/_fs/_fs_symlink_test.ts
new file mode 100644
index 000000000..4e42da293
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_symlink_test.ts
@@ -0,0 +1,107 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assert, assertThrows, fail } from "@test_util/std/assert/mod.ts";
+import { symlink, symlinkSync } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: no callback function results in Error",
+ fn() {
+ assertThrows(
+ () => {
+ // @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'NoParamCallback'
+ symlink("some/path", "some/other/path", "dir");
+ },
+ Error,
+ "No callback function supplied",
+ );
+ },
+});
+
+Deno.test({
+ name: "ASYNC: create symlink point to a file",
+ async fn() {
+ const file: string = Deno.makeTempFileSync();
+ const linkedFile: string = file + ".link";
+
+ await new Promise<void>((resolve, reject) => {
+ symlink(file, linkedFile, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ () => {
+ const stat = Deno.lstatSync(linkedFile);
+ assert(stat.isSymlink);
+ },
+ () => {
+ fail("Expected to succeed");
+ },
+ )
+ .finally(() => {
+ Deno.removeSync(file);
+ Deno.removeSync(linkedFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: create symlink point to a dir",
+ async fn() {
+ const dir: string = Deno.makeTempDirSync();
+ const linkedDir: string = dir + ".link";
+
+ await new Promise<void>((resolve, reject) => {
+ symlink(dir, linkedDir, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ () => {
+ const stat = Deno.lstatSync(linkedDir);
+ assert(stat.isSymlink);
+ },
+ () => {
+ fail("Expected to succeed");
+ },
+ )
+ .finally(() => {
+ Deno.removeSync(dir);
+ Deno.removeSync(linkedDir);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: create symlink point to a file",
+ fn() {
+ const file: string = Deno.makeTempFileSync();
+ const linkedFile: string = file + ".link";
+
+ try {
+ symlinkSync(file, linkedFile);
+ const stat = Deno.lstatSync(linkedFile);
+ assert(stat.isSymlink);
+ } finally {
+ Deno.removeSync(file);
+ Deno.removeSync(linkedFile);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: create symlink point to a dir",
+ fn() {
+ const dir: string = Deno.makeTempDirSync();
+ const linkedDir: string = dir + ".link";
+
+ try {
+ symlinkSync(dir, linkedDir);
+ const stat = Deno.lstatSync(linkedDir);
+ assert(stat.isSymlink);
+ } finally {
+ Deno.removeSync(dir);
+ Deno.removeSync(linkedDir);
+ }
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_truncate_test.ts b/tests/unit_node/_fs/_fs_truncate_test.ts
new file mode 100644
index 000000000..9b7a9c490
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_truncate_test.ts
@@ -0,0 +1,95 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, assertThrows, fail } from "@test_util/std/assert/mod.ts";
+import { truncate, truncateSync } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: no callback function results in Error",
+ fn() {
+ assertThrows(
+ () => {
+ // @ts-expect-error Argument of type 'number' is not assignable to parameter of type 'NoParamCallback'
+ truncate("some/path", 0);
+ },
+ Error,
+ "No callback function supplied",
+ );
+ },
+});
+
+Deno.test({
+ name: "ASYNC: truncate entire file contents",
+ async fn() {
+ const file: string = Deno.makeTempFileSync();
+ await Deno.writeTextFile(file, "hello world");
+
+ await new Promise<void>((resolve, reject) => {
+ truncate(file, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ () => {
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+ assertEquals(fileInfo.size, 0);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "ASYNC: truncate file to a size of precisely len bytes",
+ async fn() {
+ const file: string = Deno.makeTempFileSync();
+ await Deno.writeTextFile(file, "hello world");
+
+ await new Promise<void>((resolve, reject) => {
+ truncate(file, 3, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ () => {
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+ assertEquals(fileInfo.size, 3);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "SYNC: truncate entire file contents",
+ fn() {
+ const file: string = Deno.makeTempFileSync();
+ try {
+ truncateSync(file);
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+ assertEquals(fileInfo.size, 0);
+ } finally {
+ Deno.removeSync(file);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: truncate file to a size of precisely len bytes",
+ fn() {
+ const file: string = Deno.makeTempFileSync();
+ try {
+ truncateSync(file, 3);
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+ assertEquals(fileInfo.size, 3);
+ } finally {
+ Deno.removeSync(file);
+ }
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_unlink_test.ts b/tests/unit_node/_fs/_fs_unlink_test.ts
new file mode 100644
index 000000000..1bdd9ee29
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_unlink_test.ts
@@ -0,0 +1,40 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, fail } from "@test_util/std/assert/mod.ts";
+import { existsSync } from "node:fs";
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { unlink, unlinkSync } from "node:fs";
+
+Deno.test({
+ name: "ASYNC: deleting a file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<void>((resolve, reject) => {
+ unlink(file, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => assertEquals(existsSync(file), false), () => fail())
+ .finally(() => {
+ if (existsSync(file)) Deno.removeSync(file);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: Test deleting a file",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ unlinkSync(file);
+ assertEquals(existsSync(file), false);
+ },
+});
+
+Deno.test("[std/node/fs] unlink callback isn't called twice if error is thrown", async () => {
+ const tempFile = await Deno.makeTempFile();
+ const importUrl = new URL("node:fs", import.meta.url);
+ await assertCallbackErrorUncaught({
+ prelude: `import { unlink } from ${JSON.stringify(importUrl)}`,
+ invocation: `unlink(${JSON.stringify(tempFile)}, `,
+ });
+});
diff --git a/tests/unit_node/_fs/_fs_utimes_test.ts b/tests/unit_node/_fs/_fs_utimes_test.ts
new file mode 100644
index 000000000..1c6c7455e
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_utimes_test.ts
@@ -0,0 +1,100 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, assertThrows, fail } from "@test_util/std/assert/mod.ts";
+import { utimes, utimesSync } from "node:fs";
+
+const randomDate = new Date(Date.now() + 1000);
+
+Deno.test({
+ name:
+ "ASYNC: change the file system timestamps of the object referenced by path",
+ async fn() {
+ const file: string = Deno.makeTempFileSync();
+
+ await new Promise<void>((resolve, reject) => {
+ utimes(file, randomDate, randomDate, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ () => {
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+ assertEquals(fileInfo.mtime, randomDate);
+ assertEquals(fileInfo.mtime, randomDate);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "ASYNC: should throw error if atime is infinity",
+ fn() {
+ assertThrows(
+ () => {
+ utimes("some/path", Infinity, 0, (_err: Error | null) => {});
+ },
+ Error,
+ "invalid atime, must not be infinity or NaN",
+ );
+ },
+});
+
+Deno.test({
+ name: "ASYNC: should throw error if atime is NaN",
+ fn() {
+ assertThrows(
+ () => {
+ utimes("some/path", "some string", 0, (_err: Error | null) => {});
+ },
+ Error,
+ "invalid atime, must not be infinity or NaN",
+ );
+ },
+});
+
+Deno.test({
+ name:
+ "SYNC: change the file system timestamps of the object referenced by path",
+ fn() {
+ const file: string = Deno.makeTempFileSync();
+ try {
+ utimesSync(file, randomDate, randomDate);
+
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+
+ assertEquals(fileInfo.mtime, randomDate);
+ } finally {
+ Deno.removeSync(file);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: should throw error if atime is NaN",
+ fn() {
+ assertThrows(
+ () => {
+ utimesSync("some/path", "some string", 0);
+ },
+ Error,
+ "invalid atime, must not be infinity or NaN",
+ );
+ },
+});
+
+Deno.test({
+ name: "SYNC: should throw error if atime is Infinity",
+ fn() {
+ assertThrows(
+ () => {
+ utimesSync("some/path", Infinity, 0);
+ },
+ Error,
+ "invalid atime, must not be infinity or NaN",
+ );
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_watch_test.ts b/tests/unit_node/_fs/_fs_watch_test.ts
new file mode 100644
index 000000000..ffa6cac45
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_watch_test.ts
@@ -0,0 +1,27 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { watch } from "node:fs";
+import { assertEquals } from "@test_util/std/assert/mod.ts";
+
+function wait(time: number) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, time);
+ });
+}
+
+Deno.test({
+ name: "watching a file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ const result: Array<[string, string | null]> = [];
+ const watcher = watch(
+ file,
+ (eventType, filename) => result.push([eventType, filename]),
+ );
+ await wait(100);
+ Deno.writeTextFileSync(file, "something");
+ await wait(100);
+ watcher.close();
+ await wait(100);
+ assertEquals(result.length >= 1, true);
+ },
+});
diff --git a/tests/unit_node/_fs/_fs_writeFile_test.ts b/tests/unit_node/_fs/_fs_writeFile_test.ts
new file mode 100644
index 000000000..44f1403df
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_writeFile_test.ts
@@ -0,0 +1,345 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertEquals,
+ assertNotEquals,
+ assertRejects,
+ assertThrows,
+} from "@test_util/std/assert/mod.ts";
+import { writeFile, writeFileSync } from "node:fs";
+import * as path from "@test_util/std/path/mod.ts";
+
+type TextEncodings =
+ | "ascii"
+ | "utf8"
+ | "utf-8"
+ | "utf16le"
+ | "ucs2"
+ | "ucs-2"
+ | "base64"
+ | "latin1"
+ | "hex";
+
+const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
+const testDataDir = path.resolve(moduleDir, "testdata");
+const decoder = new TextDecoder("utf-8");
+
+Deno.test("Callback must be a function error", function fn() {
+ assertThrows(
+ () => {
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ writeFile("some/path", "some data", "utf8");
+ },
+ TypeError,
+ "Callback must be a function.",
+ );
+});
+
+Deno.test("Invalid encoding results in error()", function testEncodingErrors() {
+ assertThrows(
+ () => {
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ writeFile("some/path", "some data", "made-up-encoding", () => {});
+ },
+ Error,
+ `The value "made-up-encoding" is invalid for option "encoding"`,
+ );
+
+ assertThrows(
+ () => {
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ writeFileSync("some/path", "some data", "made-up-encoding");
+ },
+ Error,
+ `The value "made-up-encoding" is invalid for option "encoding"`,
+ );
+
+ assertThrows(
+ () => {
+ writeFile(
+ "some/path",
+ "some data",
+ {
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ encoding: "made-up-encoding",
+ },
+ () => {},
+ );
+ },
+ Error,
+ `The value "made-up-encoding" is invalid for option "encoding"`,
+ );
+
+ assertThrows(
+ () => {
+ writeFileSync("some/path", "some data", {
+ // @ts-expect-error Type '"made-up-encoding"' is not assignable to type
+ encoding: "made-up-encoding",
+ });
+ },
+ Error,
+ `The value "made-up-encoding" is invalid for option "encoding"`,
+ );
+});
+
+Deno.test(
+ "Unsupported encoding results in error()",
+ function testUnsupportedEncoding() {
+ assertThrows(
+ () => {
+ writeFile("some/path", "some data", "utf16le", () => {});
+ },
+ Error,
+ `Not implemented: "utf16le" encoding`,
+ );
+
+ assertThrows(
+ () => {
+ writeFileSync("some/path", "some data", "utf16le");
+ },
+ Error,
+ `Not implemented: "utf16le" encoding`,
+ );
+ },
+);
+
+Deno.test(
+ "Data is written to correct rid",
+ async function testCorrectWriteUsingRid() {
+ const tempFile: string = await Deno.makeTempFile();
+ using file = await Deno.open(tempFile, {
+ create: true,
+ write: true,
+ read: true,
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ writeFile(file.rid, "hello world", (err) => {
+ if (err) return reject(err);
+ resolve();
+ });
+ });
+
+ const data = await Deno.readFile(tempFile);
+ await Deno.remove(tempFile);
+ assertEquals(decoder.decode(data), "hello world");
+ },
+);
+
+Deno.test(
+ "Data is written to correct file",
+ async function testCorrectWriteUsingPath() {
+ const res = await new Promise((resolve) => {
+ writeFile("_fs_writeFile_test_file.txt", "hello world", resolve);
+ });
+
+ const data = await Deno.readFile("_fs_writeFile_test_file.txt");
+ await Deno.remove("_fs_writeFile_test_file.txt");
+ assertEquals(res, null);
+ assertEquals(decoder.decode(data), "hello world");
+ },
+);
+
+Deno.test(
+ "Data is written to correct file encodings",
+ async function testCorrectWriteUsingDifferentEncodings() {
+ const encodings = [
+ ["hex", "68656c6c6f20776f726c64"],
+ ["HEX", "68656c6c6f20776f726c64"],
+ ["base64", "aGVsbG8gd29ybGQ="],
+ ["BASE64", "aGVsbG8gd29ybGQ="],
+ ["utf8", "hello world"],
+ ["utf-8", "hello world"],
+ ];
+
+ for (const [encoding, value] of encodings) {
+ const res = await new Promise((resolve) => {
+ writeFile(
+ "_fs_writeFile_test_file.txt",
+ value,
+ encoding as TextEncodings,
+ resolve,
+ );
+ });
+
+ const data = await Deno.readFile("_fs_writeFile_test_file.txt");
+ await Deno.remove("_fs_writeFile_test_file.txt");
+ assertEquals(res, null);
+ assertEquals(decoder.decode(data), "hello world");
+ }
+ },
+);
+
+Deno.test("Path can be an URL", async function testCorrectWriteUsingURL() {
+ const url = new URL(
+ Deno.build.os === "windows"
+ ? "file:///" +
+ path
+ .join(testDataDir, "_fs_writeFile_test_file_url.txt")
+ .replace(/\\/g, "/")
+ : "file://" + path.join(testDataDir, "_fs_writeFile_test_file_url.txt"),
+ );
+ const filePath = path.fromFileUrl(url);
+ const res = await new Promise((resolve) => {
+ writeFile(url, "hello world", resolve);
+ });
+ assert(res === null);
+
+ const data = await Deno.readFile(filePath);
+ await Deno.remove(filePath);
+ assertEquals(res, null);
+ assertEquals(decoder.decode(data), "hello world");
+});
+
+Deno.test("Mode is correctly set", async function testCorrectFileMode() {
+ if (Deno.build.os === "windows") return;
+ const filename = "_fs_writeFile_test_file.txt";
+
+ const res = await new Promise((resolve) => {
+ writeFile(filename, "hello world", { mode: 0o777 }, resolve);
+ });
+
+ const fileInfo = await Deno.stat(filename);
+ await Deno.remove(filename);
+ assertEquals(res, null);
+ assert(fileInfo && fileInfo.mode);
+ assertEquals(fileInfo.mode & 0o777, 0o777);
+});
+
+Deno.test(
+ "Mode is not set when rid is passed",
+ async function testCorrectFileModeRid() {
+ if (Deno.build.os === "windows") return;
+
+ const filename: string = await Deno.makeTempFile();
+ using file = await Deno.open(filename, {
+ create: true,
+ write: true,
+ read: true,
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ writeFile(file.rid, "hello world", { mode: 0o777 }, (err) => {
+ if (err) return reject(err);
+ resolve();
+ });
+ });
+
+ const fileInfo = await Deno.stat(filename);
+ await Deno.remove(filename);
+ assert(fileInfo.mode);
+ assertNotEquals(fileInfo.mode & 0o777, 0o777);
+ },
+);
+
+Deno.test(
+ "Is cancellable with an AbortSignal",
+ async function testIsCancellableWithAbortSignal() {
+ const tempFile: string = await Deno.makeTempFile();
+ const controller = new AbortController();
+ // The "as any" is necessary due to https://github.com/denoland/deno/issues/19527
+ // deno-lint-ignore no-explicit-any
+ const signal = controller.signal as any;
+
+ const writeFilePromise = new Promise<void>((resolve, reject) => {
+ writeFile(tempFile, "hello world", { signal }, (err) => {
+ if (err) return reject(err);
+ resolve();
+ });
+ });
+ controller.abort();
+
+ await assertRejects(
+ () => writeFilePromise,
+ "AbortError",
+ );
+
+ Deno.removeSync(tempFile);
+ },
+);
+
+Deno.test(
+ "Data is written synchronously to correct rid",
+ function testCorrectWriteSyncUsingRid() {
+ const tempFile: string = Deno.makeTempFileSync();
+ using file = Deno.openSync(tempFile, {
+ create: true,
+ write: true,
+ read: true,
+ });
+
+ writeFileSync(file.rid, "hello world");
+
+ const data = Deno.readFileSync(tempFile);
+ Deno.removeSync(tempFile);
+ assertEquals(decoder.decode(data), "hello world");
+ },
+);
+
+Deno.test(
+ "Data is written to correct file encodings",
+ function testCorrectWriteSyncUsingDifferentEncodings() {
+ const encodings = [
+ ["hex", "68656c6c6f20776f726c64"],
+ ["HEX", "68656c6c6f20776f726c64"],
+ ["base64", "aGVsbG8gd29ybGQ="],
+ ["BASE64", "aGVsbG8gd29ybGQ="],
+ ["utf8", "hello world"],
+ ["utf-8", "hello world"],
+ ];
+
+ for (const [encoding, value] of encodings) {
+ const file = "_fs_writeFileSync_test_file";
+ writeFileSync(file, value, encoding as TextEncodings);
+
+ const data = Deno.readFileSync(file);
+ Deno.removeSync(file);
+ assertEquals(decoder.decode(data), "hello world");
+ }
+ },
+);
+
+Deno.test(
+ "Data is written synchronously to correct file",
+ function testCorrectWriteSyncUsingPath() {
+ const file = "_fs_writeFileSync_test_file";
+
+ writeFileSync(file, "hello world");
+
+ const data = Deno.readFileSync(file);
+ Deno.removeSync(file);
+ assertEquals(decoder.decode(data), "hello world");
+ },
+);
+
+Deno.test("sync: Path can be an URL", function testCorrectWriteSyncUsingURL() {
+ const filePath = path.join(
+ testDataDir,
+ "_fs_writeFileSync_test_file_url.txt",
+ );
+ const url = new URL(
+ Deno.build.os === "windows"
+ ? "file:///" + filePath.replace(/\\/g, "/")
+ : "file://" + filePath,
+ );
+ writeFileSync(url, "hello world");
+
+ const data = Deno.readFileSync(filePath);
+ Deno.removeSync(filePath);
+ assertEquals(decoder.decode(data), "hello world");
+});
+
+Deno.test(
+ "Mode is correctly set when writing synchronously",
+ function testCorrectFileModeSync() {
+ if (Deno.build.os === "windows") return;
+ const filename = "_fs_writeFileSync_test_file.txt";
+
+ writeFileSync(filename, "hello world", { mode: 0o777 });
+
+ const fileInfo = Deno.statSync(filename);
+ Deno.removeSync(filename);
+ assert(fileInfo && fileInfo.mode);
+ assertEquals(fileInfo.mode & 0o777, 0o777);
+ },
+);
diff --git a/tests/unit_node/_fs/_fs_write_test.ts b/tests/unit_node/_fs/_fs_write_test.ts
new file mode 100644
index 000000000..7e75f321f
--- /dev/null
+++ b/tests/unit_node/_fs/_fs_write_test.ts
@@ -0,0 +1,51 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { write, writeSync } from "node:fs";
+import { assertEquals } from "@test_util/std/assert/mod.ts";
+import { Buffer } from "node:buffer";
+
+const decoder = new TextDecoder("utf-8");
+
+Deno.test({
+ name: "Data is written to the file with the correct length",
+ async fn() {
+ const tempFile: string = await Deno.makeTempFile();
+ using file = await Deno.open(tempFile, {
+ create: true,
+ write: true,
+ read: true,
+ });
+ const buffer = Buffer.from("hello world");
+ const bytesWrite = await new Promise((resolve, reject) => {
+ write(file.rid, buffer, 0, 5, (err: unknown, nwritten: number) => {
+ if (err) return reject(err);
+ resolve(nwritten);
+ });
+ });
+
+ const data = await Deno.readFile(tempFile);
+ await Deno.remove(tempFile);
+
+ assertEquals(bytesWrite, 5);
+ assertEquals(decoder.decode(data), "hello");
+ },
+});
+
+Deno.test({
+ name: "Data is written synchronously to the file with the correct length",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ using file = Deno.openSync(tempFile, {
+ create: true,
+ write: true,
+ read: true,
+ });
+ const buffer = Buffer.from("hello world");
+ const bytesWrite = writeSync(file.rid, buffer, 0, 5);
+
+ const data = Deno.readFileSync(tempFile);
+ Deno.removeSync(tempFile);
+
+ assertEquals(bytesWrite, 5);
+ assertEquals(decoder.decode(data), "hello");
+ },
+});
diff --git a/tests/unit_node/_fs/testdata/hello.txt b/tests/unit_node/_fs/testdata/hello.txt
new file mode 100644
index 000000000..95d09f2b1
--- /dev/null
+++ b/tests/unit_node/_fs/testdata/hello.txt
@@ -0,0 +1 @@
+hello world \ No newline at end of file