1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-20 14:24:48 -05:00
denoland-deno/tests/unit_node/_fs/_fs_read_test.ts
Nathan Whitaker ff4226a3cd
fix(node/fs): Use correct offset and length in node:fs.read and write (#25049)
My fix in #25030 was buggy, I forgot to pass the `byteOffset` and
`byteLength`. Whoops.

I also discovered that fs.read was not respecting the `offset` argument,
and we were constructing a new `Buffer` for the callback instead of just
passing the original one (which is what node does, and the @types/node
definitions also indicate the callback should get the same type).
Fixes #25028.
2024-08-16 09:48:57 -07:00

405 lines
10 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
/// <reference types="npm:@types/node" />
import {
assert,
assertEquals,
assertFalse,
assertMatch,
assertStrictEquals,
} from "@std/assert";
import { read, readSync } from "node:fs";
import { open, openSync } from "node:fs";
import { Buffer } from "node:buffer";
import * as path from "@std/path";
import { closeSync } from "node:fs";
async function readTest<T extends NodeJS.ArrayBufferView>(
testData: string,
buffer: T,
offset: number,
length: number,
position: number | null = null,
expected: (
fd: number,
bytesRead: number | null,
data: T | undefined,
) => void,
) {
let fd1 = 0;
await new Promise<{
fd: number;
bytesRead: number | null;
data: T | 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);
},
});
Deno.test({
name: "read with offset TypedArray buffers",
async fn() {
const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
const testData = path.resolve(moduleDir, "testdata", "hello.txt");
const buffer = new ArrayBuffer(1024);
const bufConstructors = [
Int8Array,
Uint8Array,
];
const offsets = [0, 24, 48];
const resetBuffer = () => {
new Uint8Array(buffer).fill(0);
};
const decoder = new TextDecoder();
for (const constr of bufConstructors) {
// test combinations of buffers internally offset from their backing array buffer,
// and also offset in the read call
for (const innerOffset of offsets) {
for (const offset of offsets) {
// test read
resetBuffer();
const buf = new constr(
buffer,
innerOffset,
);
await readTest(
testData,
buf,
offset,
buf.byteLength - offset,
null,
(_fd, bytesRead, data) => {
assert(data);
assert(bytesRead);
assertStrictEquals(bytesRead, 11);
assertEquals(data == buf, true);
const got = decoder.decode(
data.subarray(
offset,
offset + bytesRead,
),
);
const want = "hello world";
assertEquals(got.length, want.length);
assertEquals(
got,
want,
);
},
);
// test readSync
resetBuffer();
const fd = openSync(testData, "r");
try {
const bytesRead = readSync(
fd,
buf,
offset,
buf.byteLength - offset,
null,
);
assertStrictEquals(bytesRead, 11);
assertEquals(
decoder.decode(buf.subarray(offset, offset + bytesRead)),
"hello world",
);
} finally {
closeSync(fd);
}
}
}
}
},
});