summaryrefslogtreecommitdiff
path: root/tests/unit/files_test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/files_test.ts')
-rw-r--r--tests/unit/files_test.ts1095
1 files changed, 1095 insertions, 0 deletions
diff --git a/tests/unit/files_test.ts b/tests/unit/files_test.ts
new file mode 100644
index 000000000..c29092963
--- /dev/null
+++ b/tests/unit/files_test.ts
@@ -0,0 +1,1095 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+// deno-lint-ignore-file no-deprecated-deno-api
+
+import {
+ assert,
+ assertEquals,
+ assertRejects,
+ assertThrows,
+} from "./test_util.ts";
+import { copy } from "@test_util/std/streams/copy.ts";
+
+Deno.test(function filesStdioFileDescriptors() {
+ assertEquals(Deno.stdin.rid, 0);
+ assertEquals(Deno.stdout.rid, 1);
+ assertEquals(Deno.stderr.rid, 2);
+});
+
+Deno.test({ permissions: { read: true } }, async function filesCopyToStdout() {
+ const filename = "tests/testdata/assets/fixture.json";
+ using file = await Deno.open(filename);
+ assert(file instanceof Deno.File);
+ assert(file instanceof Deno.FsFile);
+ assert(file.rid > 2);
+ const bytesWritten = await copy(file, Deno.stdout);
+ const fileSize = Deno.statSync(filename).size;
+ assertEquals(bytesWritten, fileSize);
+});
+
+Deno.test({ permissions: { read: true } }, async function filesIter() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = await Deno.open(filename);
+
+ let totalSize = 0;
+ for await (const buf of Deno.iter(file)) {
+ totalSize += buf.byteLength;
+ }
+
+ assertEquals(totalSize, 12);
+});
+
+Deno.test(
+ { permissions: { read: true } },
+ async function filesIterCustomBufSize() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = await Deno.open(filename);
+
+ let totalSize = 0;
+ let iterations = 0;
+ for await (const buf of Deno.iter(file, { bufSize: 6 })) {
+ totalSize += buf.byteLength;
+ iterations += 1;
+ }
+
+ assertEquals(totalSize, 12);
+ assertEquals(iterations, 2);
+ },
+);
+
+Deno.test({ permissions: { read: true } }, function filesIterSync() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = Deno.openSync(filename);
+
+ let totalSize = 0;
+ for (const buf of Deno.iterSync(file)) {
+ totalSize += buf.byteLength;
+ }
+
+ assertEquals(totalSize, 12);
+});
+
+Deno.test(
+ { permissions: { read: true } },
+ function filesIterSyncCustomBufSize() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = Deno.openSync(filename);
+
+ let totalSize = 0;
+ let iterations = 0;
+ for (const buf of Deno.iterSync(file, { bufSize: 6 })) {
+ totalSize += buf.byteLength;
+ iterations += 1;
+ }
+
+ assertEquals(totalSize, 12);
+ assertEquals(iterations, 2);
+ },
+);
+
+Deno.test(async function readerIter() {
+ // ref: https://github.com/denoland/deno/issues/2330
+ const encoder = new TextEncoder();
+
+ class TestReader implements Deno.Reader {
+ #offset = 0;
+ #buf: Uint8Array;
+
+ constructor(s: string) {
+ this.#buf = new Uint8Array(encoder.encode(s));
+ }
+
+ read(p: Uint8Array): Promise<number | null> {
+ const n = Math.min(p.byteLength, this.#buf.byteLength - this.#offset);
+ p.set(this.#buf.slice(this.#offset, this.#offset + n));
+ this.#offset += n;
+
+ if (n === 0) {
+ return Promise.resolve(null);
+ }
+
+ return Promise.resolve(n);
+ }
+ }
+
+ const reader = new TestReader("hello world!");
+
+ let totalSize = 0;
+ for await (const buf of Deno.iter(reader)) {
+ totalSize += buf.byteLength;
+ }
+
+ assertEquals(totalSize, 12);
+});
+
+Deno.test(async function readerIterSync() {
+ // ref: https://github.com/denoland/deno/issues/2330
+ const encoder = new TextEncoder();
+
+ class TestReader implements Deno.ReaderSync {
+ #offset = 0;
+ #buf: Uint8Array;
+
+ constructor(s: string) {
+ this.#buf = new Uint8Array(encoder.encode(s));
+ }
+
+ readSync(p: Uint8Array): number | null {
+ const n = Math.min(p.byteLength, this.#buf.byteLength - this.#offset);
+ p.set(this.#buf.slice(this.#offset, this.#offset + n));
+ this.#offset += n;
+
+ if (n === 0) {
+ return null;
+ }
+
+ return n;
+ }
+ }
+
+ const reader = new TestReader("hello world!");
+
+ let totalSize = 0;
+ for await (const buf of Deno.iterSync(reader)) {
+ totalSize += buf.byteLength;
+ }
+
+ assertEquals(totalSize, 12);
+});
+
+Deno.test(
+ {
+ permissions: { read: true, write: true },
+ },
+ function openSyncMode() {
+ const path = Deno.makeTempDirSync() + "/test_openSync.txt";
+ using _file = Deno.openSync(path, {
+ write: true,
+ createNew: true,
+ mode: 0o626,
+ });
+ const pathInfo = Deno.statSync(path);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask());
+ }
+ },
+);
+
+Deno.test(
+ {
+ permissions: { read: true, write: true },
+ },
+ async function openMode() {
+ const path = (await Deno.makeTempDir()) + "/test_open.txt";
+ using _file = await Deno.open(path, {
+ write: true,
+ createNew: true,
+ mode: 0o626,
+ });
+ const pathInfo = Deno.statSync(path);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask());
+ }
+ },
+);
+
+Deno.test(
+ {
+ permissions: { read: true, write: true },
+ },
+ function openSyncUrl() {
+ const tempDir = Deno.makeTempDirSync();
+ const fileUrl = new URL(
+ `file://${
+ Deno.build.os === "windows" ? "/" : ""
+ }${tempDir}/test_open.txt`,
+ );
+ using _file = Deno.openSync(fileUrl, {
+ write: true,
+ createNew: true,
+ mode: 0o626,
+ });
+ const pathInfo = Deno.statSync(fileUrl);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask());
+ }
+
+ Deno.removeSync(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ {
+ permissions: { read: true, write: true },
+ },
+ async function openUrl() {
+ const tempDir = await Deno.makeTempDir();
+ const fileUrl = new URL(
+ `file://${
+ Deno.build.os === "windows" ? "/" : ""
+ }${tempDir}/test_open.txt`,
+ );
+ using _file = await Deno.open(fileUrl, {
+ write: true,
+ createNew: true,
+ mode: 0o626,
+ });
+ const pathInfo = Deno.statSync(fileUrl);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask());
+ }
+
+ Deno.removeSync(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ { permissions: { write: false } },
+ async function writePermFailure() {
+ const filename = "tests/hello.txt";
+ const openOptions: Deno.OpenOptions[] = [{ write: true }, { append: true }];
+ for (const options of openOptions) {
+ await assertRejects(async () => {
+ await Deno.open(filename, options);
+ }, Deno.errors.PermissionDenied);
+ }
+ },
+);
+
+Deno.test(async function openOptions() {
+ const filename = "tests/testdata/assets/fixture.json";
+ await assertRejects(
+ async () => {
+ await Deno.open(filename, { write: false });
+ },
+ Error,
+ "OpenOptions requires at least one option to be true",
+ );
+
+ await assertRejects(
+ async () => {
+ await Deno.open(filename, { truncate: true, write: false });
+ },
+ Error,
+ "'truncate' option requires 'write' option",
+ );
+
+ await assertRejects(
+ async () => {
+ await Deno.open(filename, { create: true, write: false });
+ },
+ Error,
+ "'create' or 'createNew' options require 'write' or 'append' option",
+ );
+
+ await assertRejects(
+ async () => {
+ await Deno.open(filename, { createNew: true, append: false });
+ },
+ Error,
+ "'create' or 'createNew' options require 'write' or 'append' option",
+ );
+});
+
+Deno.test({ permissions: { read: false } }, async function readPermFailure() {
+ await assertRejects(async () => {
+ await Deno.open("package.json", { read: true });
+ }, Deno.errors.PermissionDenied);
+});
+
+Deno.test(
+ { permissions: { write: true } },
+ async function writeNullBufferFailure() {
+ const tempDir = Deno.makeTempDirSync();
+ const filename = tempDir + "hello.txt";
+ const w = {
+ write: true,
+ truncate: true,
+ create: true,
+ };
+ using file = await Deno.open(filename, w);
+
+ // writing null should throw an error
+ await assertRejects(
+ async () => {
+ // deno-lint-ignore no-explicit-any
+ await file.write(null as any);
+ },
+ ); // TODO(bartlomieju): Check error kind when dispatch_minimal pipes errors properly
+ await Deno.remove(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ { permissions: { write: true, read: true } },
+ async function readNullBufferFailure() {
+ const tempDir = Deno.makeTempDirSync();
+ const filename = tempDir + "hello.txt";
+ using file = await Deno.open(filename, {
+ read: true,
+ write: true,
+ truncate: true,
+ create: true,
+ });
+
+ // reading into an empty buffer should return 0 immediately
+ const bytesRead = await file.read(new Uint8Array(0));
+ assert(bytesRead === 0);
+
+ // reading file into null buffer should throw an error
+ await assertRejects(async () => {
+ // deno-lint-ignore no-explicit-any
+ await file.read(null as any);
+ }, TypeError);
+ // TODO(bartlomieju): Check error kind when dispatch_minimal pipes errors properly
+
+ await Deno.remove(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ { permissions: { write: false, read: false } },
+ async function readWritePermFailure() {
+ const filename = "tests/hello.txt";
+ await assertRejects(async () => {
+ await Deno.open(filename, { read: true });
+ }, Deno.errors.PermissionDenied);
+ },
+);
+
+Deno.test(
+ { permissions: { write: true, read: true } },
+ async function openNotFound() {
+ await assertRejects(
+ async () => {
+ await Deno.open("bad_file_name");
+ },
+ Deno.errors.NotFound,
+ `open 'bad_file_name'`,
+ );
+ },
+);
+
+Deno.test(
+ { permissions: { write: true, read: true } },
+ function openSyncNotFound() {
+ assertThrows(
+ () => {
+ Deno.openSync("bad_file_name");
+ },
+ Deno.errors.NotFound,
+ `open 'bad_file_name'`,
+ );
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function createFile() {
+ const tempDir = await Deno.makeTempDir();
+ const filename = tempDir + "/test.txt";
+ const f = await Deno.create(filename);
+ let fileInfo = Deno.statSync(filename);
+ assert(fileInfo.isFile);
+ assert(fileInfo.size === 0);
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ await f.write(data);
+ fileInfo = Deno.statSync(filename);
+ assert(fileInfo.size === 5);
+ f.close();
+
+ // TODO(bartlomieju): test different modes
+ await Deno.remove(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function createFileWithUrl() {
+ const tempDir = await Deno.makeTempDir();
+ const fileUrl = new URL(
+ `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`,
+ );
+ const f = await Deno.create(fileUrl);
+ let fileInfo = Deno.statSync(fileUrl);
+ assert(fileInfo.isFile);
+ assert(fileInfo.size === 0);
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ await f.write(data);
+ fileInfo = Deno.statSync(fileUrl);
+ assert(fileInfo.size === 5);
+ f.close();
+
+ await Deno.remove(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function createSyncFile() {
+ const tempDir = await Deno.makeTempDir();
+ const filename = tempDir + "/test.txt";
+ const f = Deno.createSync(filename);
+ let fileInfo = Deno.statSync(filename);
+ assert(fileInfo.isFile);
+ assert(fileInfo.size === 0);
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ await f.write(data);
+ fileInfo = Deno.statSync(filename);
+ assert(fileInfo.size === 5);
+ f.close();
+
+ // TODO(bartlomieju): test different modes
+ await Deno.remove(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function createSyncFileWithUrl() {
+ const tempDir = await Deno.makeTempDir();
+ const fileUrl = new URL(
+ `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`,
+ );
+ const f = Deno.createSync(fileUrl);
+ let fileInfo = Deno.statSync(fileUrl);
+ assert(fileInfo.isFile);
+ assert(fileInfo.size === 0);
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ await f.write(data);
+ fileInfo = Deno.statSync(fileUrl);
+ assert(fileInfo.size === 5);
+ f.close();
+
+ await Deno.remove(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function openModeWrite() {
+ const tempDir = Deno.makeTempDirSync();
+ const encoder = new TextEncoder();
+ const filename = tempDir + "hello.txt";
+ const data = encoder.encode("Hello world!\n");
+ let file = await Deno.open(filename, {
+ create: true,
+ write: true,
+ truncate: true,
+ });
+ // assert file was created
+ let fileInfo = Deno.statSync(filename);
+ assert(fileInfo.isFile);
+ assertEquals(fileInfo.size, 0);
+ // write some data
+ await file.write(data);
+ fileInfo = Deno.statSync(filename);
+ assertEquals(fileInfo.size, 13);
+ // assert we can't read from file
+ let thrown = false;
+ try {
+ const buf = new Uint8Array(20);
+ await file.read(buf);
+ } catch (_e) {
+ thrown = true;
+ } finally {
+ assert(thrown, "'w' mode shouldn't allow to read file");
+ }
+ file.close();
+ // assert that existing file is truncated on open
+ file = await Deno.open(filename, {
+ write: true,
+ truncate: true,
+ });
+ file.close();
+ const fileSize = Deno.statSync(filename).size;
+ assertEquals(fileSize, 0);
+ await Deno.remove(tempDir, { recursive: true });
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function openModeWriteRead() {
+ const tempDir = Deno.makeTempDirSync();
+ const encoder = new TextEncoder();
+ const filename = tempDir + "hello.txt";
+ const data = encoder.encode("Hello world!\n");
+
+ using file = await Deno.open(filename, {
+ write: true,
+ truncate: true,
+ create: true,
+ read: true,
+ });
+ const seekPosition = 0;
+ // assert file was created
+ let fileInfo = Deno.statSync(filename);
+ assert(fileInfo.isFile);
+ assertEquals(fileInfo.size, 0);
+ // write some data
+ await file.write(data);
+ fileInfo = Deno.statSync(filename);
+ assertEquals(fileInfo.size, 13);
+
+ const buf = new Uint8Array(20);
+ // seeking from beginning of a file
+ const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.Start);
+ assertEquals(seekPosition, cursorPosition);
+ const result = await file.read(buf);
+ assertEquals(result, 13);
+
+ await Deno.remove(tempDir, { recursive: true });
+ },
+);
+
+Deno.test({ permissions: { read: true } }, async function seekStart() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = await Deno.open(filename);
+ const seekPosition = 6;
+ // Deliberately move 1 step forward
+ await file.read(new Uint8Array(1)); // "H"
+ // Skipping "Hello "
+ // seeking from beginning of a file plus seekPosition
+ const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.Start);
+ assertEquals(seekPosition, cursorPosition);
+ const buf = new Uint8Array(6);
+ await file.read(buf);
+ const decoded = new TextDecoder().decode(buf);
+ assertEquals(decoded, "world!");
+});
+
+Deno.test({ permissions: { read: true } }, async function seekStartBigInt() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = await Deno.open(filename);
+ const seekPosition = 6n;
+ // Deliberately move 1 step forward
+ await file.read(new Uint8Array(1)); // "H"
+ // Skipping "Hello "
+ // seeking from beginning of a file plus seekPosition
+ const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.Start);
+ assertEquals(seekPosition, BigInt(cursorPosition));
+ const buf = new Uint8Array(6);
+ await file.read(buf);
+ const decoded = new TextDecoder().decode(buf);
+ assertEquals(decoded, "world!");
+});
+
+Deno.test({ permissions: { read: true } }, function seekSyncStart() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = Deno.openSync(filename);
+ const seekPosition = 6;
+ // Deliberately move 1 step forward
+ file.readSync(new Uint8Array(1)); // "H"
+ // Skipping "Hello "
+ // seeking from beginning of a file plus seekPosition
+ const cursorPosition = file.seekSync(seekPosition, Deno.SeekMode.Start);
+ assertEquals(seekPosition, cursorPosition);
+ const buf = new Uint8Array(6);
+ file.readSync(buf);
+ const decoded = new TextDecoder().decode(buf);
+ assertEquals(decoded, "world!");
+});
+
+Deno.test({ permissions: { read: true } }, async function seekCurrent() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = await Deno.open(filename);
+ // Deliberately move 1 step forward
+ await file.read(new Uint8Array(1)); // "H"
+ // Skipping "ello "
+ const seekPosition = 5;
+ // seekPosition is relative to current cursor position after read
+ const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.Current);
+ assertEquals(seekPosition + 1, cursorPosition);
+ const buf = new Uint8Array(6);
+ await file.read(buf);
+ const decoded = new TextDecoder().decode(buf);
+ assertEquals(decoded, "world!");
+});
+
+Deno.test({ permissions: { read: true } }, function seekSyncCurrent() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = Deno.openSync(filename);
+ // Deliberately move 1 step forward
+ file.readSync(new Uint8Array(1)); // "H"
+ // Skipping "ello "
+ const seekPosition = 5;
+ // seekPosition is relative to current cursor position after read
+ const cursorPosition = file.seekSync(seekPosition, Deno.SeekMode.Current);
+ assertEquals(seekPosition + 1, cursorPosition);
+ const buf = new Uint8Array(6);
+ file.readSync(buf);
+ const decoded = new TextDecoder().decode(buf);
+ assertEquals(decoded, "world!");
+});
+
+Deno.test({ permissions: { read: true } }, async function seekEnd() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = await Deno.open(filename);
+ const seekPosition = -6;
+ // seek from end of file that has 12 chars, 12 - 6 = 6
+ const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.End);
+ assertEquals(6, cursorPosition);
+ const buf = new Uint8Array(6);
+ await file.read(buf);
+ const decoded = new TextDecoder().decode(buf);
+ assertEquals(decoded, "world!");
+});
+
+Deno.test({ permissions: { read: true } }, function seekSyncEnd() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = Deno.openSync(filename);
+ const seekPosition = -6;
+ // seek from end of file that has 12 chars, 12 - 6 = 6
+ const cursorPosition = file.seekSync(seekPosition, Deno.SeekMode.End);
+ assertEquals(6, cursorPosition);
+ const buf = new Uint8Array(6);
+ file.readSync(buf);
+ const decoded = new TextDecoder().decode(buf);
+ assertEquals(decoded, "world!");
+});
+
+Deno.test({ permissions: { read: true } }, async function seekMode() {
+ const filename = "tests/testdata/assets/hello.txt";
+ using file = await Deno.open(filename);
+ await assertRejects(
+ async () => {
+ await file.seek(1, -1 as unknown as Deno.SeekMode);
+ },
+ TypeError,
+ "Invalid seek mode",
+ );
+
+ // We should still be able to read the file
+ // since it is still open.
+ const buf = new Uint8Array(1);
+ await file.read(buf); // "H"
+ assertEquals(new TextDecoder().decode(buf), "H");
+});
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ function fileTruncateSyncSuccess() {
+ const filename = Deno.makeTempDirSync() + "/test_fileTruncateSync.txt";
+ using file = Deno.openSync(filename, {
+ create: true,
+ read: true,
+ write: true,
+ });
+
+ file.truncateSync(20);
+ assertEquals(Deno.readFileSync(filename).byteLength, 20);
+ file.truncateSync(5);
+ assertEquals(Deno.readFileSync(filename).byteLength, 5);
+ file.truncateSync(-5);
+ assertEquals(Deno.readFileSync(filename).byteLength, 0);
+
+ Deno.removeSync(filename);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function fileTruncateSuccess() {
+ const filename = Deno.makeTempDirSync() + "/test_fileTruncate.txt";
+ using file = await Deno.open(filename, {
+ create: true,
+ read: true,
+ write: true,
+ });
+
+ await file.truncate(20);
+ assertEquals((await Deno.readFile(filename)).byteLength, 20);
+ await file.truncate(5);
+ assertEquals((await Deno.readFile(filename)).byteLength, 5);
+ await file.truncate(-5);
+ assertEquals((await Deno.readFile(filename)).byteLength, 0);
+
+ await Deno.remove(filename);
+ },
+);
+
+Deno.test({ permissions: { read: true } }, function fileStatSyncSuccess() {
+ using file = Deno.openSync("README.md");
+ const fileInfo = file.statSync();
+ assert(fileInfo.isFile);
+ assert(!fileInfo.isSymlink);
+ assert(!fileInfo.isDirectory);
+ assert(fileInfo.size);
+ assert(fileInfo.atime);
+ assert(fileInfo.mtime);
+ // The `birthtime` field is not available on Linux before kernel version 4.11.
+ assert(fileInfo.birthtime || Deno.build.os === "linux");
+});
+
+Deno.test(async function fileStatSuccess() {
+ using file = await Deno.open("README.md");
+ const fileInfo = await file.stat();
+ assert(fileInfo.isFile);
+ assert(!fileInfo.isSymlink);
+ assert(!fileInfo.isDirectory);
+ assert(fileInfo.size);
+ assert(fileInfo.atime);
+ assert(fileInfo.mtime);
+ // The `birthtime` field is not available on Linux before kernel version 4.11.
+ assert(fileInfo.birthtime || Deno.build.os === "linux");
+});
+
+Deno.test({ permissions: { read: true } }, async function readableStream() {
+ const filename = "tests/testdata/assets/hello.txt";
+ const file = await Deno.open(filename);
+ assert(file.readable instanceof ReadableStream);
+ const chunks = [];
+ for await (const chunk of file.readable) {
+ chunks.push(chunk);
+ }
+ assertEquals(chunks.length, 1);
+ assertEquals(chunks[0].byteLength, 12);
+});
+
+Deno.test(
+ { permissions: { read: true } },
+ async function readableStreamTextEncoderPipe() {
+ const filename = "tests/testdata/assets/hello.txt";
+ const file = await Deno.open(filename);
+ const readable = file.readable.pipeThrough(new TextDecoderStream());
+ const chunks = [];
+ for await (const chunk of readable) {
+ chunks.push(chunk);
+ }
+ assertEquals(chunks.length, 1);
+ assertEquals(chunks[0].length, 12);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function writableStream() {
+ const path = await Deno.makeTempFile();
+ const file = await Deno.open(path, { write: true });
+ assert(file.writable instanceof WritableStream);
+ const readable = new ReadableStream({
+ start(controller) {
+ controller.enqueue(new TextEncoder().encode("hello "));
+ controller.enqueue(new TextEncoder().encode("world!"));
+ controller.close();
+ },
+ });
+ await readable.pipeTo(file.writable);
+ const res = await Deno.readTextFile(path);
+ assertEquals(res, "hello world!");
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function readTextFileNonUtf8() {
+ const path = await Deno.makeTempFile();
+ using file = await Deno.open(path, { write: true });
+ await file.write(new TextEncoder().encode("hello "));
+ await file.write(new Uint8Array([0xC0]));
+
+ const res = await Deno.readTextFile(path);
+ const resSync = Deno.readTextFileSync(path);
+ assertEquals(res, resSync);
+ assertEquals(res, "hello \uFFFD");
+ },
+);
+
+Deno.test(
+ { permissions: { read: true } },
+ async function fsFileExplicitResourceManagement() {
+ let file2: Deno.FsFile;
+
+ {
+ using file = await Deno.open("tests/testdata/assets/hello.txt");
+ file2 = file;
+
+ const stat = file.statSync();
+ assert(stat.isFile);
+ }
+
+ assertThrows(() => file2.statSync(), Deno.errors.BadResource);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true } },
+ async function fsFileExplicitResourceManagementManualClose() {
+ using file = await Deno.open("tests/testdata/assets/hello.txt");
+ file.close();
+ assertThrows(() => file.statSync(), Deno.errors.BadResource); // definitely closed
+ // calling [Symbol.dispose] after manual close is a no-op
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ function fsFileDatasyncSyncSuccess() {
+ const filename = Deno.makeTempDirSync() + "/test_fdatasyncSync.txt";
+ const file = Deno.openSync(filename, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const data = new Uint8Array(64);
+ file.writeSync(data);
+ file.syncDataSync();
+ assertEquals(Deno.readFileSync(filename), data);
+ file.close();
+ Deno.removeSync(filename);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function fsFileDatasyncSuccess() {
+ const filename = (await Deno.makeTempDir()) + "/test_fdatasync.txt";
+ const file = await Deno.open(filename, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const data = new Uint8Array(64);
+ await file.write(data);
+ await file.syncData();
+ assertEquals(await Deno.readFile(filename), data);
+ file.close();
+ await Deno.remove(filename);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ function fsFileSyncSyncSuccess() {
+ const filename = Deno.makeTempDirSync() + "/test_fsyncSync.txt";
+ const file = Deno.openSync(filename, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const size = 64;
+ file.truncateSync(size);
+ file.syncSync();
+ assertEquals(file.statSync().size, size);
+ file.close();
+ Deno.removeSync(filename);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, write: true } },
+ async function fsFileSyncSuccess() {
+ const filename = (await Deno.makeTempDir()) + "/test_fsync.txt";
+ const file = await Deno.open(filename, {
+ read: true,
+ write: true,
+ create: true,
+ });
+ const size = 64;
+ await file.truncate(size);
+ await file.sync();
+ assertEquals((await file.stat()).size, size);
+ file.close();
+ await Deno.remove(filename);
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, run: true, hrtime: true } },
+ async function fsFileLockFileSync() {
+ await runFlockTests({ sync: true });
+ },
+);
+
+Deno.test(
+ { permissions: { read: true, run: true, hrtime: true } },
+ async function fsFileLockFileAsync() {
+ await runFlockTests({ sync: false });
+ },
+);
+
+async function runFlockTests(opts: { sync: boolean }) {
+ assertEquals(
+ await checkFirstBlocksSecond({
+ firstExclusive: true,
+ secondExclusive: false,
+ sync: opts.sync,
+ }),
+ true,
+ "exclusive blocks shared",
+ );
+ assertEquals(
+ await checkFirstBlocksSecond({
+ firstExclusive: false,
+ secondExclusive: true,
+ sync: opts.sync,
+ }),
+ true,
+ "shared blocks exclusive",
+ );
+ assertEquals(
+ await checkFirstBlocksSecond({
+ firstExclusive: true,
+ secondExclusive: true,
+ sync: opts.sync,
+ }),
+ true,
+ "exclusive blocks exclusive",
+ );
+ assertEquals(
+ await checkFirstBlocksSecond({
+ firstExclusive: false,
+ secondExclusive: false,
+ sync: opts.sync,
+ // need to wait for both to enter the lock to prevent the case where the
+ // first process enters and exits the lock before the second even enters
+ waitBothEnteredLock: true,
+ }),
+ false,
+ "shared does not block shared",
+ );
+}
+
+async function checkFirstBlocksSecond(opts: {
+ firstExclusive: boolean;
+ secondExclusive: boolean;
+ sync: boolean;
+ waitBothEnteredLock?: boolean;
+}) {
+ const firstProcess = runFlockTestProcess({
+ exclusive: opts.firstExclusive,
+ sync: opts.sync,
+ });
+ const secondProcess = runFlockTestProcess({
+ exclusive: opts.secondExclusive,
+ sync: opts.sync,
+ });
+ try {
+ const sleep = (time: number) => new Promise((r) => setTimeout(r, time));
+
+ await Promise.all([
+ firstProcess.waitStartup(),
+ secondProcess.waitStartup(),
+ ]);
+
+ await firstProcess.enterLock();
+ await firstProcess.waitEnterLock();
+
+ await secondProcess.enterLock();
+ await sleep(100);
+
+ if (!opts.waitBothEnteredLock) {
+ await firstProcess.exitLock();
+ }
+
+ await secondProcess.waitEnterLock();
+
+ if (opts.waitBothEnteredLock) {
+ await firstProcess.exitLock();
+ }
+
+ await secondProcess.exitLock();
+
+ // collect the final output
+ const firstPsTimes = await firstProcess.getTimes();
+ const secondPsTimes = await secondProcess.getTimes();
+ return firstPsTimes.exitTime < secondPsTimes.enterTime;
+ } finally {
+ await firstProcess.close();
+ await secondProcess.close();
+ }
+}
+
+function runFlockTestProcess(opts: { exclusive: boolean; sync: boolean }) {
+ const path = "tests/testdata/assets/lock_target.txt";
+ const scriptText = `
+ const file = Deno.openSync("${path}");
+
+ // ready signal
+ Deno.stdout.writeSync(new Uint8Array(1));
+ // wait for enter lock signal
+ Deno.stdin.readSync(new Uint8Array(1));
+
+ // entering signal
+ Deno.stdout.writeSync(new Uint8Array(1));
+ // lock and record the entry time
+ ${
+ opts.sync
+ ? `file.lockSync(${opts.exclusive ? "true" : "false"});`
+ : `await file.lock(${opts.exclusive ? "true" : "false"});`
+ }
+ const enterTime = new Date().getTime();
+ // entered signal
+ Deno.stdout.writeSync(new Uint8Array(1));
+
+ // wait for exit lock signal
+ Deno.stdin.readSync(new Uint8Array(1));
+
+ // record the exit time and wait a little bit before releasing
+ // the lock so that the enter time of the next process doesn't
+ // occur at the same time as this exit time
+ const exitTime = new Date().getTime();
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // release the lock
+ ${opts.sync ? "file.unlockSync();" : "await file.unlock();"}
+
+ // exited signal
+ Deno.stdout.writeSync(new Uint8Array(1));
+
+ // output the enter and exit time
+ console.log(JSON.stringify({ enterTime, exitTime }));
+`;
+
+ const process = new Deno.Command(Deno.execPath(), {
+ args: ["eval", "--unstable", scriptText],
+ stdin: "piped",
+ stdout: "piped",
+ stderr: "null",
+ }).spawn();
+
+ const waitSignal = async () => {
+ const reader = process.stdout.getReader({ mode: "byob" });
+ await reader.read(new Uint8Array(1));
+ reader.releaseLock();
+ };
+ const signal = async () => {
+ const writer = process.stdin.getWriter();
+ await writer.write(new Uint8Array(1));
+ writer.releaseLock();
+ };
+
+ return {
+ async waitStartup() {
+ await waitSignal();
+ },
+ async enterLock() {
+ await signal();
+ await waitSignal(); // entering signal
+ },
+ async waitEnterLock() {
+ await waitSignal();
+ },
+ async exitLock() {
+ await signal();
+ await waitSignal();
+ },
+ getTimes: async () => {
+ const { stdout } = await process.output();
+ const text = new TextDecoder().decode(stdout);
+ return JSON.parse(text) as {
+ enterTime: number;
+ exitTime: number;
+ };
+ },
+ close: async () => {
+ await process.status;
+ await process.stdin.close();
+ },
+ };
+}