diff options
Diffstat (limited to 'tests/unit_node/_fs')
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 |