// Copyright 2018-2025 the Deno authors. MIT license. /// import { assert, assertEquals, assertRejects, assertThrows, fail, } from "@std/assert"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { tmpdir } from "node:os"; import { closeSync, constants, copyFileSync, createWriteStream, existsSync, fchmod, fchmodSync, fchown, fchownSync, lstatSync, mkdtempSync, openSync, promises, readFileSync, readSync, Stats, statSync, writeFileSync, } from "node:fs"; import { readFile } from "node:fs/promises"; import { constants as fsPromiseConstants, copyFile, cp, FileHandle, lchown, lutimes, open, stat, writeFile, } from "node:fs/promises"; import process from "node:process"; import { pathToAbsoluteFileUrl } from "../unit/test_util.ts"; Deno.test( "[node/fs writeFileSync] write file without option", () => { const data = "Hello"; const filename = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt"; writeFileSync(filename, data); const dataRead = readFileSync(filename, "utf8"); assert(dataRead === "Hello"); }, ); Deno.test( "[node/fs writeFileSync] write file with option ASCII", () => { const data = "Hello"; const filename = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt"; writeFileSync(filename, data, { encoding: "ascii" }); const dataRead = readFileSync(filename, "utf8"); assert(dataRead === "Hello"); }, ); Deno.test( "[node/fs writeFileSync] write file throws error when encoding is not implemented", () => { const data = "Hello"; const filename = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt"; assertThrows( () => writeFileSync(filename, data, { encoding: "utf16le" }), 'The value "utf16le" is invalid for option "encoding"', ); }, ); Deno.test( "[node/fs existsSync] path", { permissions: { read: true } }, () => { assert(existsSync("tests/testdata/assets/fixture.json")); }, ); Deno.test( "[node/fs existsSync] url", { permissions: { read: true } }, () => { assert(existsSync( pathToAbsoluteFileUrl("tests/testdata/assets/fixture.json"), )); }, ); Deno.test( "[node/fs existsSync] no permission", { permissions: { read: false } }, () => { assertThrows(() => { existsSync("tests/testdata/assets/fixture.json"); }, Deno.errors.NotCapable); }, ); Deno.test( "[node/fs existsSync] not exists", { permissions: { read: true } }, () => { assert(!existsSync("bad_filename")); }, ); Deno.test( "[node/fs/promises constants] is the same as from node:fs", () => { assertEquals(constants, fsPromiseConstants); assertEquals(constants, promises.constants); }, ); Deno.test( "[node/fs statSync] instanceof fs.Stats", () => { const stat = statSync("tests/testdata/assets/fixture.json"); assert(stat); assert(stat instanceof Stats); }, ); Deno.test( "[node/fs statSync] throw error with path information", () => { const file = "non-exist-file"; const fileUrl = new URL(file, import.meta.url); assertThrows(() => { statSync(file); }, "Error: ENOENT: no such file or directory, stat 'non-exist-file'"); assertThrows(() => { statSync(fileUrl); }, `Error: ENOENT: no such file or directory, stat '${fileUrl.pathname}'`); }, ); Deno.test( "[node/fs/promises stat] throw error with path information", async () => { const file = "non-exist-file"; const fileUrl = new URL(file, import.meta.url); try { await stat(file); } catch (error: unknown) { assertEquals( `${error}`, "Error: ENOENT: no such file or directory, stat 'non-exist-file'", ); } try { await stat(fileUrl); } catch (error: unknown) { assertEquals( `${error}`, `Error: ENOENT: no such file or directory, stat '${ fileURLToPath(fileUrl) }'`, ); } }, ); Deno.test( "[node/fs/promises cp] copy file", async () => { const src = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt"; const dest = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt"; writeFileSync(src, "Hello"); await cp(src, dest); const dataRead = readFileSync(dest, "utf8"); assert(dataRead === "Hello"); }, ); // TODO(kt3k): Delete this test case, and instead enable the compat case // `test/parallel/test-fs-writestream-open-write.js`, when we update // `tests/node_compat/runner/suite`. Deno.test("[node/fs createWriteStream", async () => { const { promise, resolve, reject } = Promise.withResolvers(); const tempDir = await Deno.makeTempDir(); const file = join(tempDir, "file.txt"); try { const w = createWriteStream(file); w.on("open", () => { w.write("hello, "); process.nextTick(() => { w.write("world"); w.end(); }); }); w.on("close", async () => { try { assertEquals(await Deno.readTextFile(file), "hello, world"); resolve(); } catch (e) { reject(e); } }); await promise; } finally { await Deno.remove(tempDir, { recursive: true }); } }); Deno.test( "[node/fs lstatSync] supports throwIfNoEntry option", () => { const result = lstatSync("non-existing-path", { throwIfNoEntry: false }); assertEquals(result, undefined); }, ); // Test for https://github.com/denoland/deno/issues/23707 Deno.test( "[node/fs/promises read] respect position argument", async () => { const file = mkdtempSync(join(tmpdir(), "foo-")) + "/test.bin"; await writeFile(file, ""); const res: number[] = []; let fd: FileHandle | undefined; try { fd = await open(file, "r+"); for (let i = 0; i <= 5; i++) { const buffer = new Uint8Array([i]); await fd.write(buffer, 0, 1, i + 10); } for (let i = 10; i <= 15; i++) { const buffer = new Uint8Array(1); await fd.read(buffer, 0, 1, i); res.push(Number(buffer.toString())); } } finally { await fd?.close(); } assertEquals(res, [0, 1, 2, 3, 4, 5]); }, ); Deno.test("[node/fs] readSync works", () => { const fd = openSync("tests/testdata/assets/hello.txt", "r"); const buf = new Uint8Array(256); const bytesRead = readSync(fd!, buf); assertEquals(bytesRead, 12); closeSync(fd!); }); Deno.test("[node/fs] copyFile COPYFILE_EXCL works", async () => { const dir = mkdtempSync(join(tmpdir(), "foo-")); const src = join(dir, "src.txt"); const dest = join(dir, "dest.txt"); await writeFile(src, ""); await copyFile(src, dest, fsPromiseConstants.COPYFILE_EXCL); assert(existsSync(dest)); await assertRejects(() => copyFile(src, dest, fsPromiseConstants.COPYFILE_EXCL) ); const dest2 = join(dir, "dest2.txt"); copyFileSync(src, dest2, fsPromiseConstants.COPYFILE_EXCL); assert(existsSync(dest2)); assertThrows(() => copyFileSync(src, dest2, fsPromiseConstants.COPYFILE_EXCL) ); }); Deno.test("[node/fs] statSync throws ENOENT for invalid path containing colon in it", () => { // deno-lint-ignore no-explicit-any const err: any = assertThrows(() => { // Note: Deno.stat throws ERROR_INVALID_NAME (os error 123) instead of // ERROR_FILE_NOT_FOUND (os error 2) on windows. This case checks that // ERROR_INVALID_NAME is mapped to ENOENT correctly on node compat layer. statSync("jsr:@std/assert"); }); assertEquals(err.code, "ENOENT"); }); Deno.test("[node/fs] readFile aborted with signal", async () => { const src = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt"; await writeFile(src, "Hello"); const signal = AbortSignal.abort(); await assertRejects( () => readFile(src, { signal }), DOMException, "The signal has been aborted", ); }); async function execCmd(cmd: string) { const dec = new TextDecoder(); const [bin, ...args] = cmd.split(" "); const command = new Deno.Command(bin, { args }); const { code, stdout, stderr } = await command.output(); if (code !== 0) { throw new Error( `Command failed with code ${code}: ${cmd} - ${dec.decode(stderr)}`, ); } return dec.decode(stdout).trim(); } Deno.test("[node/fs] fchown and fchownSync", { ignore: Deno.build.os === "windows", }, async () => { const file = mkdtempSync(join(tmpdir(), "foo-")) + "/test.txt"; await writeFile(file, "Hello"); const uid = await execCmd("id -u"); const gid = await execCmd("id -g"); const fd = openSync(file, "r+"); // Changing the owner of a file to the current user is not an error. await new Promise((resolve) => fchown(fd, +uid, +gid, (err) => { assertEquals(err, null); resolve(); }) ); fchownSync(fd, +uid, +gid); // Changing the owner of a file to root is an error. await assertRejects(() => new Promise((resolve, reject) => fchown(fd, 0, 0, (err) => { if (err) { reject(err); } else { resolve(); } }) ) ); assertThrows(() => { fchownSync(fd, 0, 0); }); closeSync(fd); }); Deno.test("[node/fs] fchmod works", { ignore: Deno.build.os === "windows", }, async () => { // Prepare const tempFile = await Deno.makeTempFile(); const originalFileMode = (await Deno.lstat(tempFile)).mode; const fd = openSync(tempFile, "r+"); // Execute await new Promise((resolve, reject) => { fchmod(fd, 0o777, (err) => { if (err) { reject(err); } else { resolve(); } }); }) // Assert .then(() => { const newFileMode = Deno.lstatSync(tempFile).mode; assert(newFileMode && originalFileMode); assert(newFileMode === 33279 && newFileMode > originalFileMode); }, (error) => { fail(error); }) .finally(() => { closeSync(fd); Deno.removeSync(tempFile); }); }); Deno.test("[node/fs] fchmodSync works", { ignore: Deno.build.os === "windows", }, () => { // Prepare const tempFile = Deno.makeTempFileSync(); const originalFileMode = Deno.lstatSync(tempFile).mode; const fd = openSync(tempFile, "r+"); // Execute fchmodSync(fd, 0o777); // Assert const newFileMode = Deno.lstatSync(tempFile).mode; assert(newFileMode && originalFileMode); assert(newFileMode === 33279 && newFileMode > originalFileMode); closeSync(fd); Deno.removeSync(tempFile); }); Deno.test("[node/fs/promises] lchown works", { ignore: Deno.build.os === "windows", }, async () => { const tempFile = Deno.makeTempFileSync(); const symlinkPath = tempFile + "-link"; Deno.symlinkSync(tempFile, symlinkPath); const uid = await execCmd("id -u"); const gid = await execCmd("id -g"); await lchown(symlinkPath, +uid, +gid); Deno.removeSync(tempFile); Deno.removeSync(symlinkPath); }); Deno.test("[node/fs/promises] lutimes works", { ignore: Deno.build.os === "windows", }, async () => { const tempFile = Deno.makeTempFileSync(); const symlinkPath = tempFile + "-link"; Deno.symlinkSync(tempFile, symlinkPath); const date = new Date("1970-01-01T00:00:00Z"); await lutimes(symlinkPath, date, date); const stats = Deno.lstatSync(symlinkPath); assertEquals((stats.atime as Date).getTime(), date.getTime()); assertEquals((stats.mtime as Date).getTime(), date.getTime()); Deno.removeSync(tempFile); Deno.removeSync(symlinkPath); }); Deno.test("[node/fs] constants are correct across platforms", () => { assert(constants.R_OK === 4); // Check a handful of constants with different values across platforms if (Deno.build.os === "darwin") { assert(constants.UV_FS_O_FILEMAP === 0); assert(constants.O_CREAT === 0x200); assert(constants.O_DIRECT === undefined); assert(constants.O_NOATIME === undefined); assert(constants.O_SYMLINK === 0x200000); } if (Deno.build.os === "linux") { assert(constants.UV_FS_O_FILEMAP === 0); assert(constants.O_CREAT === 0x40); assert(constants.O_DIRECT !== undefined); // O_DIRECT has different values between architectures assert(constants.O_NOATIME === 0x40000); assert(constants.O_SYMLINK === undefined); } if (Deno.build.os === "windows") { assert(constants.UV_FS_O_FILEMAP === 0x20000000); assert(constants.O_CREAT === 0x100); assert(constants.O_DIRECT === undefined); assert(constants.O_NOATIME === undefined); assert(constants.O_SYMLINK === undefined); } });