summaryrefslogtreecommitdiff
path: root/cli/tests
diff options
context:
space:
mode:
authorMax Dahlgren <83047501+maxedahlgren@users.noreply.github.com>2023-03-20 03:10:39 +1100
committerGitHub <noreply@github.com>2023-03-20 01:10:39 +0900
commitbf149d047f4f768ced17d76c47623ca13c90f7e7 (patch)
tree38f372ca22c13e19919fdbba4cf0260bedd4d37e /cli/tests
parent3b1cb8af69b0acf55c485ae3721bdb4d9322798e (diff)
test(ext/node): port _fs tests from std/node (#18262)
Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
Diffstat (limited to 'cli/tests')
-rw-r--r--cli/tests/unit_node/_fs/_fs_access_test.ts4
-rw-r--r--cli/tests/unit_node/_fs/_fs_appendFile_test.ts259
-rw-r--r--cli/tests/unit_node/_fs/_fs_chmod_test.ts121
-rw-r--r--cli/tests/unit_node/_fs/_fs_chown_test.ts72
-rw-r--r--cli/tests/unit_node/_fs/_fs_close_test.ts97
-rw-r--r--cli/tests/unit_node/_fs/_fs_copy_test.ts52
-rw-r--r--cli/tests/unit_node/_fs/_fs_exists_test.ts65
-rw-r--r--cli/tests/unit_node/_fs/_fs_fdatasync_test.ts63
-rw-r--r--cli/tests/unit_node/_fs/_fs_fstat_test.ts83
-rw-r--r--cli/tests/unit_node/_fs/_fs_fsync_test.ts61
-rw-r--r--cli/tests/unit_node/_fs/_fs_ftruncate_test.ts131
-rw-r--r--cli/tests/unit_node/_fs/_fs_futimes_test.ts112
-rw-r--r--cli/tests/unit_node/_fs/_fs_link_test.ts81
-rw-r--r--cli/tests/unit_node/_fs/_fs_lstat_test.ts71
-rw-r--r--cli/tests/unit_node/_fs/_fs_mkdir_test.ts43
-rw-r--r--cli/tests/unit_node/_fs/_fs_mkdtemp_test.ts86
-rw-r--r--cli/tests/unit_node/_fs/_fs_opendir_test.ts146
-rw-r--r--cli/tests/unit_node/_fs/_fs_readFile_test.ts121
-rw-r--r--cli/tests/unit_node/_fs/_fs_readdir_test.ts96
-rw-r--r--cli/tests/unit_node/_fs/_fs_readlink_test.ts78
-rw-r--r--cli/tests/unit_node/_fs/_fs_realpath_test.ts55
-rw-r--r--cli/tests/unit_node/_fs/_fs_rename_test.ts55
-rw-r--r--cli/tests/unit_node/_fs/_fs_rm_test.ts158
-rw-r--r--cli/tests/unit_node/_fs/_fs_rmdir_test.ts104
-rw-r--r--cli/tests/unit_node/_fs/_fs_stat_test.ts134
-rw-r--r--cli/tests/unit_node/_fs/_fs_symlink_test.ts111
-rw-r--r--cli/tests/unit_node/_fs/_fs_truncate_test.ts99
-rw-r--r--cli/tests/unit_node/_fs/_fs_unlink_test.ts43
-rw-r--r--cli/tests/unit_node/_fs/_fs_utimes_test.ts104
-rw-r--r--cli/tests/unit_node/_fs/_fs_write_test.ts53
-rw-r--r--cli/tests/unit_node/_fs/testdata/hello.txt1
-rw-r--r--cli/tests/unit_node/_test_utils.ts43
32 files changed, 2800 insertions, 2 deletions
diff --git a/cli/tests/unit_node/_fs/_fs_access_test.ts b/cli/tests/unit_node/_fs/_fs_access_test.ts
index c3d54f10e..5fd2a5b31 100644
--- a/cli/tests/unit_node/_fs/_fs_access_test.ts
+++ b/cli/tests/unit_node/_fs/_fs_access_test.ts
@@ -11,7 +11,7 @@ Deno.test(
async () => {
const file = await Deno.makeTempFile();
try {
- Deno.chmod(file, 0o600);
+ 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 () => {
@@ -43,7 +43,7 @@ Deno.test(
() => {
const file = Deno.makeTempFileSync();
try {
- Deno.chmod(file, 0o600);
+ Deno.chmodSync(file, 0o600);
fs.accessSync(file, fs.constants.R_OK);
fs.accessSync(file, fs.constants.W_OK);
assertThrows(() => {
diff --git a/cli/tests/unit_node/_fs/_fs_appendFile_test.ts b/cli/tests/unit_node/_fs/_fs_appendFile_test.ts
new file mode 100644
index 000000000..5741938f0
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_appendFile_test.ts
@@ -0,0 +1,259 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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();
+ const file: Deno.FsFile = 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 () => {
+ Deno.close(file.rid);
+ await Deno.remove(tempFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "Async: Data is written to passed in file path",
+ async fn() {
+ const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
+ await new Promise<void>((resolve, reject) => {
+ appendFile("_fs_appendFile_test_file.txt", "hello world", (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ })
+ .then(async () => {
+ assertEquals(Deno.resources(), openResourcesBeforeAppend);
+ 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 openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
+ 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 () => {
+ assertEquals(Deno.resources(), openResourcesBeforeAppend);
+ 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 openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
+ 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");
+ }, () => {
+ assertEquals(Deno.resources(), openResourcesBeforeAppend);
+ })
+ .finally(async () => {
+ await Deno.remove(tempFile);
+ });
+ },
+});
+
+Deno.test({
+ name: "Sync: Data is written to passed in rid",
+ fn() {
+ const tempFile: string = Deno.makeTempFileSync();
+ const file: Deno.FsFile = Deno.openSync(tempFile, {
+ create: true,
+ write: true,
+ read: true,
+ });
+ appendFileSync(file.rid, "hello world");
+ Deno.close(file.rid);
+ 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() {
+ const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
+ appendFileSync("_fs_appendFile_test_file_sync.txt", "hello world");
+ assertEquals(Deno.resources(), openResourcesBeforeAppend);
+ 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 openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
+ const tempFile: string = Deno.makeTempFileSync();
+ assertThrows(
+ () => appendFileSync(tempFile, "hello world", { flag: "ax" }),
+ Error,
+ "",
+ );
+ assertEquals(Deno.resources(), openResourcesBeforeAppend);
+ Deno.removeSync(tempFile);
+ },
+});
+
+Deno.test({
+ name: "Sync: Data is written in Uint8Array to passed in file path",
+ fn() {
+ const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
+ const testData = new TextEncoder().encode("hello world");
+ appendFileSync("_fs_appendFile_test_file_sync.txt", testData);
+ assertEquals(Deno.resources(), openResourcesBeforeAppend);
+ 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 openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
+ 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 () => {
+ assertEquals(Deno.resources(), openResourcesBeforeAppend);
+ 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/cli/tests/unit_node/_fs/_fs_chmod_test.ts b/cli/tests/unit_node/_fs/_fs_chmod_test.ts
new file mode 100644
index 000000000..11f01d1ad
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_chmod_test.ts
@@ -0,0 +1,121 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertRejects,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_chown_test.ts b/cli/tests/unit_node/_fs/_fs_chown_test.ts
new file mode 100644
index 000000000..2174d49d7
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_chown_test.ts
@@ -0,0 +1,72 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_close_test.ts b/cli/tests/unit_node/_fs/_fs_close_test.ts
new file mode 100644
index 000000000..4d0743dbd
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_close_test.ts
@@ -0,0 +1,97 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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);
+
+ assert(Deno.resources()[file.rid]);
+ await new Promise<void>((resolve, reject) => {
+ close(file.rid, (err) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(() => {
+ assert(!Deno.resources()[file.rid]);
+ }, () => {
+ 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);
+
+ assert(Deno.resources()[file.rid]);
+ closeSync(file.rid);
+ assert(!Deno.resources()[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/cli/tests/unit_node/_fs/_fs_copy_test.ts b/cli/tests/unit_node/_fs/_fs_copy_test.ts
new file mode 100644
index 000000000..b08e8d7f3
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_copy_test.ts
@@ -0,0 +1,52 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import * as path from "../../../../test_util/std/path/mod.ts";
+import { assert } from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_exists_test.ts b/cli/tests/unit_node/_fs/_fs_exists_test.ts
new file mode 100644
index 000000000..9af5b3208
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_exists_test.ts
@@ -0,0 +1,65 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertEquals,
+ assertStringIncludes,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_fdatasync_test.ts b/cli/tests/unit_node/_fs/_fs_fdatasync_test.ts
new file mode 100644
index 000000000..d7ccae927
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_fdatasync_test.ts
@@ -0,0 +1,63 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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 file: string = await Deno.makeTempFile();
+ const { rid } = await Deno.open(file, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const data = new Uint8Array(64);
+ await Deno.write(rid, data);
+
+ await new Promise<void>((resolve, reject) => {
+ fdatasync(rid, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ async () => {
+ assertEquals(await Deno.readFile(file), data);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(async () => {
+ Deno.close(rid);
+ await Deno.remove(file);
+ });
+ },
+});
+
+Deno.test({
+ name:
+ "SYNC: flush any pending data operations of the given file stream to disk.",
+ fn() {
+ const file: string = Deno.makeTempFileSync();
+ const { rid } = Deno.openSync(file, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const data = new Uint8Array(64);
+ Deno.writeSync(rid, data);
+
+ try {
+ fdatasyncSync(rid);
+ assertEquals(Deno.readFileSync(file), data);
+ } finally {
+ Deno.close(rid);
+ Deno.removeSync(file);
+ }
+ },
+});
diff --git a/cli/tests/unit_node/_fs/_fs_fstat_test.ts b/cli/tests/unit_node/_fs/_fs_fstat_test.ts
new file mode 100644
index 000000000..70c3db254
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_fstat_test.ts
@@ -0,0 +1,83 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { fstat, fstatSync } from "node:fs";
+import { fail } from "../../../../test_util/std/testing/asserts.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 file = await Deno.makeTempFile();
+ const { rid } = await Deno.open(file);
+
+ await new Promise<Stats>((resolve, reject) => {
+ fstat(rid, (err: Error | null, stat: Stats) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then(
+ (stat) => {
+ assertStats(stat, Deno.fstatSync(rid));
+ },
+ () => fail(),
+ )
+ .finally(() => {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: get a file BigInt Stats",
+ async fn() {
+ const file = await Deno.makeTempFile();
+ const { rid } = await Deno.open(file);
+
+ await new Promise<BigIntStats>((resolve, reject) => {
+ fstat(rid, { bigint: true }, (err: Error | null, stat: BigIntStats) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then(
+ (stat) => assertStatsBigInt(stat, Deno.fstatSync(rid)),
+ () => fail(),
+ )
+ .finally(() => {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file Stats",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ const { rid } = Deno.openSync(file);
+
+ try {
+ assertStats(fstatSync(rid), Deno.fstatSync(rid));
+ } finally {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file BigInt Stats",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ const { rid } = Deno.openSync(file);
+
+ try {
+ assertStatsBigInt(fstatSync(rid, { bigint: true }), Deno.fstatSync(rid));
+ } finally {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ }
+ },
+});
diff --git a/cli/tests/unit_node/_fs/_fs_fsync_test.ts b/cli/tests/unit_node/_fs/_fs_fsync_test.ts
new file mode 100644
index 000000000..586c7d7e6
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_fsync_test.ts
@@ -0,0 +1,61 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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 file: string = await Deno.makeTempFile();
+ const { rid } = await Deno.open(file, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const size = 64;
+ await Deno.ftruncate(rid, size);
+
+ await new Promise<void>((resolve, reject) => {
+ fsync(rid, (err: Error | null) => {
+ if (err !== null) reject();
+ else resolve();
+ });
+ })
+ .then(
+ async () => {
+ assertEquals((await Deno.stat(file)).size, size);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(async () => {
+ await Deno.remove(file);
+ Deno.close(rid);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: flush any pending data the given file stream to disk",
+ fn() {
+ const file: string = Deno.makeTempFileSync();
+ const { rid } = Deno.openSync(file, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const size = 64;
+ Deno.ftruncateSync(rid, size);
+
+ try {
+ fsyncSync(rid);
+ assertEquals(Deno.statSync(file).size, size);
+ } finally {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ }
+ },
+});
diff --git a/cli/tests/unit_node/_fs/_fs_ftruncate_test.ts b/cli/tests/unit_node/_fs/_fs_ftruncate_test.ts
new file mode 100644
index 000000000..46787bec1
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_ftruncate_test.ts
@@ -0,0 +1,131 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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 file: string = Deno.makeTempFileSync();
+ await Deno.writeFile(file, new TextEncoder().encode("hello world"));
+ const { rid } = await Deno.open(file, {
+ read: true,
+ write: true,
+ create: true,
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ ftruncate(rid, (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.close(rid);
+ });
+ },
+});
+
+Deno.test({
+ name: "ASYNC: truncate file to a size of precisely len bytes",
+ async fn() {
+ const file: string = Deno.makeTempFileSync();
+ await Deno.writeFile(file, new TextEncoder().encode("hello world"));
+ const { rid } = await Deno.open(file, {
+ read: true,
+ write: true,
+ create: true,
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ ftruncate(rid, 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.close(rid);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: truncate entire file contents",
+ fn() {
+ const file: string = Deno.makeTempFileSync();
+ Deno.writeFileSync(file, new TextEncoder().encode("hello world"));
+ const { rid } = Deno.openSync(file, {
+ read: true,
+ write: true,
+ create: true,
+ });
+
+ try {
+ ftruncateSync(rid);
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+ assertEquals(fileInfo.size, 0);
+ } finally {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ }
+ },
+});
+
+Deno.test({
+ name: "SYNC: truncate file to a size of precisely len bytes",
+ fn() {
+ const file: string = Deno.makeTempFileSync();
+ Deno.writeFileSync(file, new TextEncoder().encode("hello world"));
+ const { rid } = Deno.openSync(file, {
+ read: true,
+ write: true,
+ create: true,
+ });
+
+ try {
+ ftruncateSync(rid, 3);
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+ assertEquals(fileInfo.size, 3);
+ } finally {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ }
+ },
+});
diff --git a/cli/tests/unit_node/_fs/_fs_futimes_test.ts b/cli/tests/unit_node/_fs/_fs_futimes_test.ts
new file mode 100644
index 000000000..c5b9012a5
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_futimes_test.ts
@@ -0,0 +1,112 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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 file: string = Deno.makeTempFileSync();
+ const { rid } = await Deno.open(file, { create: true, write: true });
+
+ await new Promise<void>((resolve, reject) => {
+ futimes(rid, 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.atime, randomDate);
+ },
+ () => {
+ fail("No error expected");
+ },
+ )
+ .finally(() => {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ });
+ },
+});
+
+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 file: string = Deno.makeTempFileSync();
+ const { rid } = Deno.openSync(file, { create: true, write: true });
+
+ try {
+ futimesSync(rid, randomDate, randomDate);
+
+ const fileInfo: Deno.FileInfo = Deno.lstatSync(file);
+
+ assertEquals(fileInfo.mtime, randomDate);
+ assertEquals(fileInfo.atime, randomDate);
+ } finally {
+ Deno.removeSync(file);
+ Deno.close(rid);
+ }
+ },
+});
+
+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/cli/tests/unit_node/_fs/_fs_link_test.ts b/cli/tests/unit_node/_fs/_fs_link_test.ts
new file mode 100644
index 000000000..a96457eb0
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_link_test.ts
@@ -0,0 +1,81 @@
+// Copyright 2018-2023 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/testing/asserts.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/cli/tests/unit_node/_fs/_fs_lstat_test.ts b/cli/tests/unit_node/_fs/_fs_lstat_test.ts
new file mode 100644
index 000000000..cd1127989
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_lstat_test.ts
@@ -0,0 +1,71 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { lstat, lstatSync } from "node:fs";
+import { fail } from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_mkdir_test.ts b/cli/tests/unit_node/_fs/_fs_mkdir_test.ts
new file mode 100644
index 000000000..0deae9276
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_mkdir_test.ts
@@ -0,0 +1,43 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import * as path from "../../../../test_util/std/path/mod.ts";
+import { assert } from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_mkdtemp_test.ts b/cli/tests/unit_node/_fs/_fs_mkdtemp_test.ts
new file mode 100644
index 000000000..998128596
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_mkdtemp_test.ts
@@ -0,0 +1,86 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertRejects,
+ assertThrows,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_opendir_test.ts b/cli/tests/unit_node/_fs/_fs_opendir_test.ts
new file mode 100644
index 000000000..196bea777
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_opendir_test.ts
@@ -0,0 +1,146 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import {
+ assert,
+ assertEquals,
+ assertFalse,
+ assertInstanceOf,
+ assertThrows,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_readFile_test.ts b/cli/tests/unit_node/_fs/_fs_readFile_test.ts
new file mode 100644
index 000000000..8fa9e7b01
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_readFile_test.ts
@@ -0,0 +1,121 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { assertCallbackErrorUncaught } from "../_test_utils.ts";
+import { readFile, readFileSync } from "node:fs";
+import * as path from "../../../../test_util/std/path/mod.ts";
+import {
+ assert,
+ assertEquals,
+} from "../../../../test_util/std/testing/asserts.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);
+ },
+ });
+});
diff --git a/cli/tests/unit_node/_fs/_fs_readdir_test.ts b/cli/tests/unit_node/_fs/_fs_readdir_test.ts
new file mode 100644
index 000000000..bb1df079f
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_readdir_test.ts
@@ -0,0 +1,96 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertNotEquals,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_readlink_test.ts b/cli/tests/unit_node/_fs/_fs_readlink_test.ts
new file mode 100644
index 000000000..7f0d3cef8
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_readlink_test.ts
@@ -0,0 +1,78 @@
+// Copyright 2018-2023 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/testing/asserts.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/cli/tests/unit_node/_fs/_fs_realpath_test.ts b/cli/tests/unit_node/_fs/_fs_realpath_test.ts
new file mode 100644
index 000000000..871ebe983
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_realpath_test.ts
@@ -0,0 +1,55 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import * as path from "../../../../test_util/std/path/mod.ts";
+import { assertEquals } from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_rename_test.ts b/cli/tests/unit_node/_fs/_fs_rename_test.ts
new file mode 100644
index 000000000..cefd7f61a
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_rename_test.ts
@@ -0,0 +1,55 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_rm_test.ts b/cli/tests/unit_node/_fs/_fs_rm_test.ts
new file mode 100644
index 000000000..e196ee08a
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_rm_test.ts
@@ -0,0 +1,158 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertRejects,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.ts";
+import { rm, rmSync } from "node:fs";
+import { closeSync, 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);
+ });
+ },
+});
+
+function closeRes(before: Deno.ResourceMap, after: Deno.ResourceMap) {
+ for (const key in after) {
+ if (!before[key]) {
+ try {
+ closeSync(Number(key));
+ } catch (error) {
+ return error;
+ }
+ }
+ }
+}
+
+Deno.test({
+ name: "ASYNC: removing non-empty folder",
+ async fn() {
+ const rBefore = Deno.resources();
+ const dir = Deno.makeTempDirSync();
+ Deno.createSync(join(dir, "file1.txt"));
+ Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ 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 });
+ const rAfter = Deno.resources();
+ closeRes(rBefore, rAfter);
+ });
+ },
+ 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 rBefore = Deno.resources();
+ const dir = Deno.makeTempDirSync();
+ Deno.createSync(join(dir, "file1.txt"));
+ Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ Deno.createSync(join(dir, "some_dir", "file.txt"));
+ rmSync(dir, { recursive: true });
+ assertEquals(existsSync(dir), false);
+ // closing resources
+ const rAfter = Deno.resources();
+ closeRes(rBefore, rAfter);
+ },
+ 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/cli/tests/unit_node/_fs/_fs_rmdir_test.ts b/cli/tests/unit_node/_fs/_fs_rmdir_test.ts
new file mode 100644
index 000000000..85aaa2ec3
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_rmdir_test.ts
@@ -0,0 +1,104 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ fail,
+} from "../../../../test_util/std/testing/asserts.ts";
+import { rmdir, rmdirSync } from "node:fs";
+import { closeSync } 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);
+ },
+});
+
+function closeRes(before: Deno.ResourceMap, after: Deno.ResourceMap) {
+ for (const key in after) {
+ if (!before[key]) {
+ try {
+ closeSync(Number(key));
+ } catch (error) {
+ return error;
+ }
+ }
+ }
+}
+
+Deno.test({
+ name: "ASYNC: removing non-empty folder",
+ async fn() {
+ const rBefore = Deno.resources();
+ const dir = Deno.makeTempDirSync();
+ Deno.createSync(join(dir, "file1.txt"));
+ Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ 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 });
+ const rAfter = Deno.resources();
+ closeRes(rBefore, rAfter);
+ });
+ },
+ ignore: Deno.build.os === "windows",
+});
+
+Deno.test({
+ name: "SYNC: removing non-empty folder",
+ fn() {
+ const rBefore = Deno.resources();
+ const dir = Deno.makeTempDirSync();
+ Deno.createSync(join(dir, "file1.txt"));
+ Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ Deno.createSync(join(dir, "some_dir", "file.txt"));
+ rmdirSync(dir, { recursive: true });
+ assertEquals(existsSync(dir), false);
+ // closing resources
+ const rAfter = Deno.resources();
+ closeRes(rBefore, rAfter);
+ },
+ 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/cli/tests/unit_node/_fs/_fs_stat_test.ts b/cli/tests/unit_node/_fs/_fs_stat_test.ts
new file mode 100644
index 000000000..b9e33c507
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_stat_test.ts
@@ -0,0 +1,134 @@
+// Copyright 2018-2023 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/testing/asserts.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/cli/tests/unit_node/_fs/_fs_symlink_test.ts b/cli/tests/unit_node/_fs/_fs_symlink_test.ts
new file mode 100644
index 000000000..72747ebfa
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_symlink_test.ts
@@ -0,0 +1,111 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_truncate_test.ts b/cli/tests/unit_node/_fs/_fs_truncate_test.ts
new file mode 100644
index 000000000..3aff3fac2
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_truncate_test.ts
@@ -0,0 +1,99 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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.writeFile(file, new TextEncoder().encode("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.writeFile(file, new TextEncoder().encode("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/cli/tests/unit_node/_fs/_fs_unlink_test.ts b/cli/tests/unit_node/_fs/_fs_unlink_test.ts
new file mode 100644
index 000000000..a33534fbb
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_unlink_test.ts
@@ -0,0 +1,43 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_utimes_test.ts b/cli/tests/unit_node/_fs/_fs_utimes_test.ts
new file mode 100644
index 000000000..170c0d73d
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_utimes_test.ts
@@ -0,0 +1,104 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ assertEquals,
+ assertThrows,
+ fail,
+} from "../../../../test_util/std/testing/asserts.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/cli/tests/unit_node/_fs/_fs_write_test.ts b/cli/tests/unit_node/_fs/_fs_write_test.ts
new file mode 100644
index 000000000..7baa81b7d
--- /dev/null
+++ b/cli/tests/unit_node/_fs/_fs_write_test.ts
@@ -0,0 +1,53 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { write, writeSync } from "node:fs";
+import { assertEquals } from "../../../../test_util/std/testing/asserts.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();
+ const file: Deno.FsFile = 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);
+ });
+ });
+ Deno.close(file.rid);
+
+ 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();
+ const file: Deno.FsFile = Deno.openSync(tempFile, {
+ create: true,
+ write: true,
+ read: true,
+ });
+ const buffer = Buffer.from("hello world");
+ const bytesWrite = writeSync(file.rid, buffer, 0, 5);
+ Deno.close(file.rid);
+
+ const data = Deno.readFileSync(tempFile);
+ Deno.removeSync(tempFile);
+
+ assertEquals(bytesWrite, 5);
+ assertEquals(decoder.decode(data), "hello");
+ },
+});
diff --git a/cli/tests/unit_node/_fs/testdata/hello.txt b/cli/tests/unit_node/_fs/testdata/hello.txt
new file mode 100644
index 000000000..95d09f2b1
--- /dev/null
+++ b/cli/tests/unit_node/_fs/testdata/hello.txt
@@ -0,0 +1 @@
+hello world \ No newline at end of file
diff --git a/cli/tests/unit_node/_test_utils.ts b/cli/tests/unit_node/_test_utils.ts
new file mode 100644
index 000000000..322067719
--- /dev/null
+++ b/cli/tests/unit_node/_test_utils.ts
@@ -0,0 +1,43 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import {
+ assert,
+ assertStringIncludes,
+} from "../../../test_util/std/testing/asserts.ts";
+
+/** Asserts that an error thrown in a callback will not be wrongly caught. */
+export async function assertCallbackErrorUncaught(
+ { prelude, invocation, cleanup }: {
+ /** Any code which needs to run before the actual invocation (notably, any import statements). */
+ prelude?: string;
+ /**
+ * The start of the invocation of the function, e.g. `open("foo.txt", `.
+ * The callback will be added after it.
+ */
+ invocation: string;
+ /** Called after the subprocess is finished but before running the assertions, e.g. to clean up created files. */
+ cleanup?: () => Promise<void> | void;
+ },
+) {
+ // Since the error has to be uncaught, and that will kill the Deno process,
+ // the only way to test this is to spawn a subprocess.
+ const p = new Deno.Command(Deno.execPath(), {
+ args: [
+ "eval",
+ "--unstable",
+ `${prelude ?? ""}
+
+ ${invocation}(err) => {
+ // If the bug is present and the callback is called again with an error,
+ // don't throw another error, so if the subprocess fails we know it had the correct behaviour.
+ if (!err) throw new Error("success");
+ });`,
+ ],
+ stderr: "piped",
+ });
+ const { stderr, success } = await p.output();
+ const error = new TextDecoder().decode(stderr);
+ await cleanup?.();
+ assert(!success);
+ assertStringIncludes(error, "Error: success");
+}