/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/unit_node/_fs/_fs_read_test.ts
653 строки
16 KB
Nathan Whitaker
fix(ext/node): handle readv short reads (#36211)
11 авг 2026, 23:45
Не верифицирован
11 авг 2026, 23:45
fa9119e
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. /// <reference types="npm:@types/node" /> import { assert, assertEquals, assertFalse, assertMatch, assertStrictEquals, } from "@std/assert"; import { closeSync, open, openSync, read, readSync } from "node:fs"; import { Buffer } from "node:buffer"; import * as path from "@std/path"; import { open as openPromise } from "node:fs/promises"; async function runReadvFixture<T>( mode: "file" | "pipe-sync" | "pipe-async", ): Promise<T> { const fixture = path.fromFileUrl( new URL("./testdata/readv_short_read.ts", import.meta.url), ); const isPipe = mode !== "file"; const child = new Deno.Command(Deno.execPath(), { args: ["run", "-A", fixture, mode], stdin: isPipe ? "piped" : "null", stdout: "piped", stderr: "piped", signal: AbortSignal.timeout(10_000), }).spawn(); let output: Deno.CommandOutput; try { if (mode === "pipe-async") { const reader = child.stderr.getReader(); let ready = ""; try { const decoder = new TextDecoder(); while (!ready.includes("\n")) { const chunk = await reader.read(); assertFalse(chunk.done); ready += decoder.decode(chunk.value, { stream: true }); } } finally { reader.releaseLock(); } assertEquals(ready, "ready\n"); } if (isPipe) { const writer = child.stdin.getWriter(); await writer.write(new TextEncoder().encode("abc")); writer.releaseLock(); } output = await child.output(); } catch (error) { try { child.kill(); } catch { // The child may have already exited due to the timeout signal. } await child.status; throw error; } finally { if (isPipe) { try { await child.stdin.close(); } catch { // The child's pipe may already be closed on an error path. } } } const stderr = new TextDecoder().decode(output.stderr); assert(output.success, stderr); return JSON.parse(new TextDecoder().decode(output.stdout)); } 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("readv returns short reads and preserves positions", async () => { const result = await runReadvFixture("file"); assertEquals(result, { sync: { bytesRead: 3, eofBytesRead: 0, dataViewBacking: [255, 97, 98, 254], uint16Backing: [253, 252, 99, 0, 0, 0, 251, 250], }, async: { bytesRead: 3, eofBytesRead: 0, buffersMatch: true, callbackWasAsync: true, dataViewBacking: [255, 97, 98, 254], uint16Backing: [253, 252, 99, 0, 0, 0, 251, 250], }, positionedSync: { bytesRead: 2, positioned: [100, 101], cursor: [97], }, positionedAsync: { bytesRead: 2, buffersMatch: true, callbackWasAsync: true, positioned: [100, 101], cursor: [97], }, empty: { sync: 0, async: { bytesRead: 0, buffersMatch: true, callbackWasAsync: true, }, }, invalidZeroLength: { syncCode: "EBADF", async: { code: "EBADF", bytesRead: 0, buffersMatch: true, callbackWasAsync: true, }, }, nonNumberPosition: [98], overlapping: { bytesRead: 4, buffer: [99, 100], }, }); }); Deno.test("readv returns a short pipe read without waiting for EOF", async (t) => { await t.step("sync", async () => { const result = await runReadvFixture("pipe-sync"); assertEquals(result, { bytesRead: 3, buffers: [[97, 98], [99, 0, 0, 0]], }); }); await t.step("async", async () => { const result = await runReadvFixture("pipe-async"); assertEquals(result, { bytesRead: 3, buffersMatch: true, callbackWasAsync: true, timerFired: true, buffers: [[97, 98], [99, 0, 0, 0]], }); }); }); 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(); // deno-lint-ignore no-explicit-any const buf = new (constr as any)( buffer, innerOffset, ) as Int8Array | Uint8Array; 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); } } } } }, }); Deno.test({ name: "readSync: option object parameter works", fn() { const tmpFile = Deno.makeTempFileSync(); Deno.writeTextFileSync(tmpFile, "hello world!"); const fd = openSync(tmpFile, "r"); const buffer = Buffer.alloc(6); const bytesRead = readSync(fd, buffer, { offset: 1, length: 5, position: 6, }); assertStrictEquals(bytesRead, 5); assertStrictEquals(buffer.toString("utf8", 1, 6), "world"); closeSync(fd); Deno.removeSync(tmpFile); }, }); Deno.test({ name: "read: option object parameter works", async fn() { const tmpFile = Deno.makeTempFileSync(); Deno.writeTextFileSync(tmpFile, "hello world!"); const fd = openSync(tmpFile, "r"); const buffer = Buffer.alloc(11); // No Buffer in option object await new Promise<void>((resolve, reject) => { read( fd, buffer, { offset: 0, length: 5, position: -1, }, (err, bytesRead, data) => { if (err) reject(err); assertStrictEquals(bytesRead, 5); assertStrictEquals(data?.toString("utf8", 0, 5), "hello"); resolve(); }, ); }); // Buffer in option object await new Promise<void>((resolve, reject) => { read( fd, { buffer, offset: 6, length: 5, position: 6, }, (err, bytesRead, data) => { if (err) reject(err); assertStrictEquals(bytesRead, 5); assertStrictEquals(data?.toString("utf8", 6, 11), "world"); resolve(); }, ); }); closeSync(fd); Deno.removeSync(tmpFile); }, }); Deno.test({ name: "FileHandle.read: option object parameter works", async fn() { const tmpFile = Deno.makeTempFileSync(); Deno.writeTextFileSync(tmpFile, "hello world!"); await using file = await openPromise(tmpFile, "r"); const buffer = Buffer.alloc(11); // Buffer outside the option object const result = await file.read(buffer, { offset: 0, length: 5, position: -1, }); assertStrictEquals(result.bytesRead, 5); assertStrictEquals(buffer.toString("utf8", 0, 5), "hello"); // Buffer in option object const result2 = await file.read({ buffer, offset: 6, length: 5, position: 6, }); assertStrictEquals(result2.bytesRead, 5); assertStrictEquals(buffer.toString("utf8", 6, 11), "world"); Deno.removeSync(tmpFile); }, });