diff options
author | Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> | 2024-04-01 20:42:49 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-01 20:42:49 -0700 |
commit | 7ad76fd453972e9262985c61840c77b8b8a6dbb7 (patch) | |
tree | a97eb0a22d7931ff9ac161f9be97e1faf1358547 | |
parent | ca5e5c7e9c8bb68beb751c25f2f08f18a59cd76e (diff) |
fix(ext/node): Add fs.readv, fs.readvSync (#23166)
Part of #18218.
Implements `fs.readv` and `fs.readvSync` and enables the corresponding
`node_compat` tests.
-rw-r--r-- | ext/node/lib.rs | 1 | ||||
-rw-r--r-- | ext/node/polyfills/_fs/_fs_readv.ts | 130 | ||||
-rw-r--r-- | ext/node/polyfills/fs.ts | 5 | ||||
-rw-r--r-- | tests/node_compat/config.jsonc | 2 | ||||
-rw-r--r-- | tests/node_compat/test/parallel/test-fs-readv-sync.js | 100 | ||||
-rw-r--r-- | tests/node_compat/test/parallel/test-fs-readv.js | 102 | ||||
-rw-r--r-- | tools/core_import_map.json | 1 | ||||
-rw-r--r-- | tools/node_compat/TODO.md | 2 |
8 files changed, 341 insertions, 2 deletions
diff --git a/ext/node/lib.rs b/ext/node/lib.rs index cf63a5785..85abe49e4 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -371,6 +371,7 @@ deno_core::extension!(deno_node, "_fs/_fs_readdir.ts", "_fs/_fs_readFile.ts", "_fs/_fs_readlink.ts", + "_fs/_fs_readv.ts", "_fs/_fs_realpath.ts", "_fs/_fs_rename.ts", "_fs/_fs_rm.ts", diff --git a/ext/node/polyfills/_fs/_fs_readv.ts b/ext/node/polyfills/_fs/_fs_readv.ts new file mode 100644 index 000000000..7d87c51f7 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_readv.ts @@ -0,0 +1,130 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +// TODO(petamoriken): enable prefer-primordials for node polyfills +// deno-lint-ignore-file prefer-primordials + +import { + ERR_INVALID_ARG_TYPE, + type ErrnoException, +} from "ext:deno_node/internal/errors.ts"; +import { + getValidatedFd, + validateBufferArray, +} from "ext:deno_node/internal/fs/utils.mjs"; +import { maybeCallback } from "ext:deno_node/_fs/_fs_common.ts"; +import { validateInteger } from "ext:deno_node/internal/validators.mjs"; +import * as io from "ext:deno_io/12_io.js"; +import * as fs from "ext:deno_fs/30_fs.js"; + +type Callback = ( + err: ErrnoException | null, + bytesRead: number, + buffers: readonly ArrayBufferView[], +) => void; + +export function readv( + fd: number, + buffers: readonly ArrayBufferView[], + callback: Callback, +): void; +export function readv( + fd: number, + buffers: readonly ArrayBufferView[], + position: number | Callback, + callback?: Callback, +): void { + if (typeof fd !== "number") { + throw new ERR_INVALID_ARG_TYPE("fd", "number", fd); + } + fd = getValidatedFd(fd); + validateBufferArray(buffers); + const cb = maybeCallback(callback || position) as Callback; + let pos: number | null = null; + if (typeof position === "number") { + validateInteger(position, "position", 0); + pos = position; + } + + if (buffers.length === 0) { + process.nextTick(cb, null, 0, buffers); + return; + } + + const innerReadv = async ( + fd: number, + buffers: readonly ArrayBufferView[], + position: number | null, + ) => { + if (typeof position === "number") { + await fs.seek(fd, position, io.SeekMode.Start); + } + + let readTotal = 0; + let readInBuf = 0; + let bufIdx = 0; + let buf = buffers[bufIdx]; + while (bufIdx < buffers.length) { + const nread = await io.read(fd, buf); + if (nread === null) { + break; + } + readInBuf += nread; + if (readInBuf === buf.byteLength) { + readTotal += readInBuf; + readInBuf = 0; + bufIdx += 1; + buf = buffers[bufIdx]; + } + } + readTotal += readInBuf; + + return readTotal; + }; + + innerReadv(fd, buffers, pos).then( + (numRead) => { + cb(null, numRead, buffers); + }, + (err) => cb(err, -1, buffers), + ); +} + +export function readvSync( + fd: number, + buffers: readonly ArrayBufferView[], + position: number | null = null, +): number { + if (typeof fd !== "number") { + throw new ERR_INVALID_ARG_TYPE("fd", "number", fd); + } + fd = getValidatedFd(fd); + validateBufferArray(buffers); + if (buffers.length === 0) { + return 0; + } + if (typeof position === "number") { + validateInteger(position, "position", 0); + fs.seekSync(fd, position, io.SeekMode.Start); + } + + let readTotal = 0; + let readInBuf = 0; + let bufIdx = 0; + let buf = buffers[bufIdx]; + while (bufIdx < buffers.length) { + const nread = io.readSync(fd, buf); + if (nread === null) { + break; + } + readInBuf += nread; + if (readInBuf === buf.byteLength) { + readTotal += readInBuf; + readInBuf = 0; + bufIdx += 1; + buf = buffers[bufIdx]; + } + } + readTotal += readInBuf; + + return readTotal; +} diff --git a/ext/node/polyfills/fs.ts b/ext/node/polyfills/fs.ts index bdf7e4aa6..f788f72b5 100644 --- a/ext/node/polyfills/fs.ts +++ b/ext/node/polyfills/fs.ts @@ -110,6 +110,7 @@ import { import { write, writeSync } from "ext:deno_node/_fs/_fs_write.mjs"; // @deno-types="./_fs/_fs_writev.d.ts" import { writev, writevSync } from "ext:deno_node/_fs/_fs_writev.mjs"; +import { readv, readvSync } from "ext:deno_node/_fs/_fs_readv.ts"; import { writeFile, writeFilePromise, @@ -250,6 +251,8 @@ export default { ReadStream, realpath, realpathSync, + readv, + readvSync, rename, renameSync, rmdir, @@ -353,6 +356,8 @@ export { readlinkSync, ReadStream, readSync, + readv, + readvSync, realpath, realpathSync, rename, diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index bf254faf6..af45cc663 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -334,6 +334,8 @@ "test-fs-readdir-stack-overflow.js", "test-fs-readdir.js", "test-fs-readfile-empty.js", + "test-fs-readv-sync.js", + "test-fs-readv.js", "test-fs-realpath-native.js", "test-fs-rmdir-recursive-sync-warns-not-found.js", "test-fs-rmdir-recursive-sync-warns-on-file.js", diff --git a/tests/node_compat/test/parallel/test-fs-readv-sync.js b/tests/node_compat/test/parallel/test-fs-readv-sync.js new file mode 100644 index 000000000..c09362848 --- /dev/null +++ b/tests/node_compat/test/parallel/test-fs-readv-sync.js @@ -0,0 +1,100 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tools/node_compat/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف'; + +const exptectedBuff = Buffer.from(expected); +const expectedLength = exptectedBuff.length; + +const filename = path.join(tmpdir.path, 'readv_sync.txt'); +fs.writeFileSync(filename, exptectedBuff); + +const allocateEmptyBuffers = (combinedLength) => { + const bufferArr = []; + // Allocate two buffers, each half the size of exptectedBuff + bufferArr[0] = Buffer.alloc(Math.floor(combinedLength / 2)); + bufferArr[1] = Buffer.alloc(combinedLength - bufferArr[0].length); + + return bufferArr; +}; + +// fs.readvSync with array of buffers with all parameters +{ + const fd = fs.openSync(filename, 'r'); + + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + + let read = fs.readvSync(fd, [Buffer.from('')], 0); + assert.strictEqual(read, 0); + + read = fs.readvSync(fd, bufferArr, 0); + assert.strictEqual(read, expectedLength); + + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename))); +} + +// fs.readvSync with array of buffers without position +{ + const fd = fs.openSync(filename, 'r'); + + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + + let read = fs.readvSync(fd, [Buffer.from('')]); + assert.strictEqual(read, 0); + + read = fs.readvSync(fd, bufferArr); + assert.strictEqual(read, expectedLength); + + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename))); +} + +/** + * Testing with incorrect arguments + */ +const wrongInputs = [false, 'test', {}, [{}], ['sdf'], null, undefined]; + +{ + const fd = fs.openSync(filename, 'r'); + + wrongInputs.forEach((wrongInput) => { + assert.throws( + () => fs.readvSync(fd, wrongInput, null), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); + + fs.closeSync(fd); +} + +{ + // fs.readv with wrong fd argument + wrongInputs.forEach((wrongInput) => { + assert.throws( + () => fs.readvSync(wrongInput), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); +} diff --git a/tests/node_compat/test/parallel/test-fs-readv.js b/tests/node_compat/test/parallel/test-fs-readv.js new file mode 100644 index 000000000..b385d5f6f --- /dev/null +++ b/tests/node_compat/test/parallel/test-fs-readv.js @@ -0,0 +1,102 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tools/node_compat/setup.ts`. Do not modify this file manually. + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف'; + +let cnt = 0; +const getFileName = () => path.join(tmpdir.path, `readv_${++cnt}.txt`); +const exptectedBuff = Buffer.from(expected); + +const allocateEmptyBuffers = (combinedLength) => { + const bufferArr = []; + // Allocate two buffers, each half the size of exptectedBuff + bufferArr[0] = Buffer.alloc(Math.floor(combinedLength / 2)); + bufferArr[1] = Buffer.alloc(combinedLength - bufferArr[0].length); + + return bufferArr; +}; + +const getCallback = (fd, bufferArr) => { + return common.mustSucceed((bytesRead, buffers) => { + assert.deepStrictEqual(bufferArr, buffers); + const expectedLength = exptectedBuff.length; + assert.deepStrictEqual(bytesRead, expectedLength); + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(exptectedBuff)); + }); +}; + +// fs.readv with array of buffers with all parameters +{ + const filename = getFileName(); + const fd = fs.openSync(filename, 'w+'); + fs.writeSync(fd, exptectedBuff); + + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + const callback = getCallback(fd, bufferArr); + + fs.readv(fd, bufferArr, 0, callback); +} + +// fs.readv with array of buffers without position +{ + const filename = getFileName(); + fs.writeFileSync(filename, exptectedBuff); + const fd = fs.openSync(filename, 'r'); + + const bufferArr = allocateEmptyBuffers(exptectedBuff.length); + const callback = getCallback(fd, bufferArr); + + fs.readv(fd, bufferArr, callback); +} + +/** + * Testing with incorrect arguments + */ +const wrongInputs = [false, 'test', {}, [{}], ['sdf'], null, undefined]; + +{ + const filename = getFileName(2); + fs.writeFileSync(filename, exptectedBuff); + const fd = fs.openSync(filename, 'r'); + + + wrongInputs.forEach((wrongInput) => { + assert.throws( + () => fs.readv(fd, wrongInput, null, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); + + fs.closeSync(fd); +} + +{ + // fs.readv with wrong fd argument + wrongInputs.forEach((wrongInput) => { + assert.throws( + () => fs.readv(wrongInput, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); +} diff --git a/tools/core_import_map.json b/tools/core_import_map.json index 9e70f52f7..078808aa0 100644 --- a/tools/core_import_map.json +++ b/tools/core_import_map.json @@ -32,6 +32,7 @@ "ext:deno_node/_fs/_fs_mkdir.ts": "../ext/node/polyfills/_fs/_fs_mkdir.ts", "ext:deno_node/_fs/_fs_open.ts": "../ext/node/polyfills/_fs/_fs_open.ts", "ext:deno_node/_fs/_fs_read.ts": "../ext/node/polyfills/_fs/_fs_read.ts", + "ext:deno_node/_fs/_fs_readv.ts": "../ext/node/polyfills/_fs/_fs_readv.ts", "ext:deno_node/_fs/_fs_stat.ts": "../ext/node/polyfills/_fs/_fs_stat.ts", "ext:deno_node/_fs/_fs_watch.ts": "../ext/node/polyfills/_fs/_fs_watch.ts", "ext:deno_node/_fs/_fs_write.mjs": "../ext/node/polyfills/_fs/_fs_write.mjs", diff --git a/tools/node_compat/TODO.md b/tools/node_compat/TODO.md index 3d8306988..ae3e184be 100644 --- a/tools/node_compat/TODO.md +++ b/tools/node_compat/TODO.md @@ -813,8 +813,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-fs-readlink-type-check.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readlink-type-check.js) - [parallel/test-fs-readv-promises.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readv-promises.js) - [parallel/test-fs-readv-promisify.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readv-promisify.js) -- [parallel/test-fs-readv-sync.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readv-sync.js) -- [parallel/test-fs-readv.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-readv.js) - [parallel/test-fs-ready-event-stream.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-ready-event-stream.js) - [parallel/test-fs-realpath-buffer-encoding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-realpath-buffer-encoding.js) - [parallel/test-fs-realpath-on-substed-drive.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-realpath-on-substed-drive.js) |