diff --git a/lib/internal/vfs/file_handle.js b/lib/internal/vfs/file_handle.js index e8a37e07f26495..7b60c9def2b5b4 100644 --- a/lib/internal/vfs/file_handle.js +++ b/lib/internal/vfs/file_handle.js @@ -28,6 +28,10 @@ const kMode = Symbol('kMode'); const kPosition = Symbol('kPosition'); const kClosed = Symbol('kClosed'); +function isCurrentPosition(position) { + return position === null || position === undefined || position === -1; +} + /** * Base class for virtual file handles. * Provides the interface that file handles must implement. @@ -467,8 +471,8 @@ class MemoryFileHandle extends VirtualFileHandle { // Get content (resolves dynamic content providers) const content = this.content; - const readPos = position !== null && position !== undefined ? - Number(position) : this.position; + const useCurrentPosition = isCurrentPosition(position); + const readPos = useCurrentPosition ? this.position : Number(position); const available = content.length - readPos; if (available <= 0) { @@ -479,7 +483,7 @@ class MemoryFileHandle extends VirtualFileHandle { content.copy(buffer, offset, readPos, readPos + bytesToRead); // Update position if not using explicit position - if (position === null || position === undefined) { + if (useCurrentPosition) { this.position = readPos + bytesToRead; } @@ -512,10 +516,10 @@ class MemoryFileHandle extends VirtualFileHandle { this.#checkWritable(); // In append mode, always write at the end + const useCurrentPosition = isCurrentPosition(position); const writePos = this.#isAppend() ? this.#size : - (position !== null && position !== undefined ? - Number(position) : this.position); + (useCurrentPosition ? this.position : Number(position)); const data = buffer.subarray(offset, offset + length); // Expand buffer if needed (geometric doubling for amortized O(1) appends) @@ -544,7 +548,7 @@ class MemoryFileHandle extends VirtualFileHandle { } // Update position if not using explicit position - if (position === null || position === undefined) { + if (useCurrentPosition) { this.position = writePos + length; } diff --git a/lib/internal/webstreams/readablestream.js b/lib/internal/webstreams/readablestream.js index ee496f30e5934e..92508613e8883e 100644 --- a/lib/internal/webstreams/readablestream.js +++ b/lib/internal/webstreams/readablestream.js @@ -2480,21 +2480,26 @@ function readableStreamDefaultControllerClose(controller) { } function readableStreamDefaultControllerEnqueue(controller, chunk) { - if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller)) + // Equivalent to readableStreamDefaultControllerCanCloseOrEnqueue() + // followed by isReadableStreamLocked() and + // readableStreamGetNumReadRequests(), but with the state loaded once: + // this runs for every enqueued chunk. + const controllerState = controller[kState]; + const stream = controllerState.stream; + if (controllerState.closeRequested || stream[kState].state !== 'readable') return; - const { - stream, - } = controller[kState]; - - if (isReadableStreamLocked(stream) && - readableStreamGetNumReadRequests(stream)) { + const reader = stream[kState].reader; + if (reader !== undefined && + reader[kState] !== undefined && + reader[kType] === 'ReadableStreamDefaultReader' && + reader[kState].readRequests.length) { readableStreamFulfillReadRequest(stream, chunk, false); } else { try { const chunkSize = FunctionPrototypeCall( - controller[kState].sizeAlgorithm, + controllerState.sizeAlgorithm, undefined, chunk); enqueueValueWithSize(controller, chunk, chunkSize); @@ -2533,22 +2538,27 @@ function readableStreamDefaultControllerGetDesiredSize(controller) { } function readableStreamDefaultControllerShouldCallPull(controller) { - const { - stream, - } = controller[kState]; - if (!readableStreamDefaultControllerCanCloseOrEnqueue(controller) || - !controller[kState].started) + // Single-pass version of the spec's predicate chain (CanCloseOrEnqueue, + // IsLocked, HasDefaultReader, GetNumReadRequests, GetDesiredSize): this + // runs at least once per chunk on every default-stream path. The + // desired-size computation is inlined because the stream state is + // already known to be 'readable' here. + const controllerState = controller[kState]; + const stream = controllerState.stream; + if (controllerState.closeRequested || + stream[kState].state !== 'readable' || + !controllerState.started) return false; - if (isReadableStreamLocked(stream) && - readableStreamGetNumReadRequests(stream)) { + const reader = stream[kState].reader; + if (reader !== undefined && + reader[kState] !== undefined && + reader[kType] === 'ReadableStreamDefaultReader' && + reader[kState].readRequests.length) { return true; } - const desiredSize = readableStreamDefaultControllerGetDesiredSize(controller); - assert(desiredSize !== null); - - return desiredSize > 0; + return controllerState.highWaterMark - controllerState.queueTotalSize > 0; } function readableStreamDefaultControllerCallPullIfNeeded(controller) { @@ -2798,28 +2808,29 @@ function readableByteStreamControllerGetDesiredSize(controller) { } function readableByteStreamControllerShouldCallPull(controller) { - const { - stream, - } = controller[kState]; + // Single-pass version of the spec's predicate chain (HasDefaultReader, + // GetNumReadRequests, HasBYOBReader, GetNumReadIntoRequests, + // GetDesiredSize): this runs at least once per chunk on every byte + // stream path. The desired-size computation is inlined because the + // stream state is already known to be 'readable' here. + const controllerState = controller[kState]; + const stream = controllerState.stream; if (stream[kState].state !== 'readable' || - controller[kState].closeRequested || - !controller[kState].started) { + controllerState.closeRequested || + !controllerState.started) { return false; } - if (readableStreamHasDefaultReader(stream) && - readableStreamGetNumReadRequests(stream) > 0) { - return true; - } - - if (readableStreamHasBYOBReader(stream) && - readableStreamGetNumReadIntoRequests(stream) > 0) { - return true; + const reader = stream[kState].reader; + if (reader !== undefined && reader[kState] !== undefined) { + const type = reader[kType]; + if (type === 'ReadableStreamDefaultReader') { + if (reader[kState].readRequests.length) return true; + } else if (type === 'ReadableStreamBYOBReader') { + if (reader[kState].readIntoRequests.length) return true; + } } - const desiredSize = readableByteStreamControllerGetDesiredSize(controller); - assert(desiredSize !== null); - - return desiredSize > 0; + return controllerState.highWaterMark - controllerState.queueTotalSize > 0; } function readableByteStreamControllerHandleQueueDrain(controller) { diff --git a/lib/internal/webstreams/transformstream.js b/lib/internal/webstreams/transformstream.js index 5b1be9e3aa7a65..535c783a3a31c3 100644 --- a/lib/internal/webstreams/transformstream.js +++ b/lib/internal/webstreams/transformstream.js @@ -258,12 +258,7 @@ function InternalTransferredTransformStream() { readable: undefined, writable: undefined, backpressure: undefined, - backpressureChange: { - __proto__: null, - promise: undefined, - resolve: undefined, - reject: undefined, - }, + backpressureChange: undefined, controller: undefined, }; } @@ -390,12 +385,7 @@ function initializeTransformStream( writable, controller: undefined, backpressure: undefined, - backpressureChange: { - __proto__: null, - promise: undefined, - resolve: undefined, - reject: undefined, - }, + backpressureChange: undefined, }; transformStreamSetBackpressure(stream, true); @@ -429,12 +419,27 @@ function transformStreamUnblockWrite(stream) { transformStreamSetBackpressure(stream, false); } +// The spec's [[backpressureChangePromise]] is only ever observed by the +// source pull algorithm (settles when backpressure next becomes true) and +// by a sink write arriving while backpressure is set (settles when +// backpressure next becomes false). Instead of allocating a fresh promise +// record on every flip, the record is materialized lazily on first +// observation and dropped once settled; flips nobody is waiting on +// allocate nothing. +function transformStreamBackpressureChangePromise(stream) { + const state = stream[kState]; + return (state.backpressureChange ??= PromiseWithResolvers()).promise; +} + function transformStreamSetBackpressure(stream, backpressure) { - assert(stream[kState].backpressure !== backpressure); - if (stream[kState].backpressureChange.promise !== undefined) - stream[kState].backpressureChange.resolve?.(); - stream[kState].backpressureChange = PromiseWithResolvers(); - stream[kState].backpressure = backpressure; + const state = stream[kState]; + assert(state.backpressure !== backpressure); + const backpressureChange = state.backpressureChange; + if (backpressureChange !== undefined) { + state.backpressureChange = undefined; + backpressureChange.resolve(); + } + state.backpressure = backpressure; } function setupTransformStreamDefaultController( @@ -554,7 +559,7 @@ function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) { } = stream[kState]; assert(writable[kState].state === 'writable'); if (stream[kState].backpressure) { - const backpressureChange = stream[kState].backpressureChange.promise; + const backpressureChange = transformStreamBackpressureChangePromise(stream); return PromisePrototypeThen( backpressureChange, () => { @@ -638,9 +643,8 @@ function transformStreamDefaultSinkCloseAlgorithm(stream) { function transformStreamDefaultSourcePullAlgorithm(stream) { assert(stream[kState].backpressure); - assert(stream[kState].backpressureChange.promise !== undefined); transformStreamSetBackpressure(stream, false); - return stream[kState].backpressureChange.promise; + return transformStreamBackpressureChangePromise(stream); } function transformStreamDefaultSourceCancelAlgorithm(stream, reason) { diff --git a/lib/internal/webstreams/util.js b/lib/internal/webstreams/util.js index 836e1de130505b..cd203b77c2d22a 100644 --- a/lib/internal/webstreams/util.js +++ b/lib/internal/webstreams/util.js @@ -134,45 +134,44 @@ function isBrandCheck(brand) { }; } +// The queue helpers below run once per chunk on the hot paths of every +// default readable/writable stream, so they load the controller state a +// single time and don't assert the existence of the queue fields (both +// are unconditionally initialized during controller setup and only ever +// replaced wholesale). function dequeueValue(controller) { - assert(controller[kState].queue !== undefined); - assert(controller[kState].queueTotalSize !== undefined); - assert(controller[kState].queue.length); + const state = controller[kState]; + assert(state.queue.length); const { value, size, - } = ArrayPrototypeShift(controller[kState].queue); - controller[kState].queueTotalSize = - MathMax(0, controller[kState].queueTotalSize - size); + } = ArrayPrototypeShift(state.queue); + state.queueTotalSize = MathMax(0, state.queueTotalSize - size); return value; } function resetQueue(controller) { - assert(controller[kState].queue !== undefined); - assert(controller[kState].queueTotalSize !== undefined); - controller[kState].queue = []; - controller[kState].queueTotalSize = 0; + const state = controller[kState]; + state.queue = []; + state.queueTotalSize = 0; } function peekQueueValue(controller) { - assert(controller[kState].queue !== undefined); - assert(controller[kState].queueTotalSize !== undefined); - assert(controller[kState].queue.length); - return controller[kState].queue[0].value; + const state = controller[kState]; + assert(state.queue.length); + return state.queue[0].value; } function enqueueValueWithSize(controller, value, size) { - assert(controller[kState].queue !== undefined); - assert(controller[kState].queueTotalSize !== undefined); + const state = controller[kState]; const coercedSize = +size; if (NumberIsNaN(coercedSize) || coercedSize < 0 || coercedSize === Infinity) { throw new ERR_INVALID_ARG_VALUE.RangeError('size', size); } - size = coercedSize; - ArrayPrototypePush(controller[kState].queue, { value, size }); - controller[kState].queueTotalSize += size; + ArrayPrototypePush(state.queue, { value, size: coercedSize }); + state.queueTotalSize += coercedSize; } // Arity-specialized variants of the promise-callback wrapper. The generic diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js index bc7f58a05fc2b9..37ef513a4e4166 100644 --- a/lib/internal/webstreams/writablestream.js +++ b/lib/internal/webstreams/writablestream.js @@ -601,6 +601,10 @@ function createWritableStreamState() { __proto__: null, closedPromise: undefined, closeRequest: kNilRequest, + // Mirrors "closeRequest or inFlightCloseRequest is pending"; kept as a + // flag because the predicate runs several times per chunk on the write + // hot path. + closeQueuedOrInFlight: false, inFlightWriteRequest: kNilRequest, inFlightCloseRequest: kNilRequest, pendingAbortRequest: kNilPendingAbortRequest, @@ -742,6 +746,7 @@ function writableStreamClose(stream) { assert(state === 'writable' || state === 'erroring'); assert(!writableStreamCloseQueuedOrInFlight(stream)); stream[kState].closeRequest = PromiseWithResolvers(); + stream[kState].closeQueuedOrInFlight = true; const { promise } = stream[kState].closeRequest; if (writer !== undefined && backpressure && state === 'writable') writer[kState].ready?.resolve?.(); @@ -750,12 +755,13 @@ function writableStreamClose(stream) { } function writableStreamUpdateBackpressure(stream, backpressure) { - assert(stream[kState].state === 'writable'); - assert(!writableStreamCloseQueuedOrInFlight(stream)); + const streamState = stream[kState]; + assert(streamState.state === 'writable'); + assert(!streamState.closeQueuedOrInFlight); const { writer, - } = stream[kState]; - if (writer !== undefined && stream[kState].backpressure !== backpressure) { + } = streamState; + if (writer !== undefined && streamState.backpressure !== backpressure) { if (backpressure) { // The spec replaces [[readyPromise]] with a fresh pending promise; // dropping the cache lets the next observation derive it. @@ -764,7 +770,7 @@ function writableStreamUpdateBackpressure(stream, backpressure) { writer[kState].ready?.resolve?.(); } } - stream[kState].backpressure = backpressure; + streamState.backpressure = backpressure; } function writableStreamStartErroring(stream, reason) { @@ -792,6 +798,7 @@ function writableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { assert(stream[kState].inFlightCloseRequest.promise === undefined); stream[kState].closeRequest.reject?.(stream[kState].storedError); stream[kState].closeRequest = kNilRequest; + stream[kState].closeQueuedOrInFlight = false; } const closedPromiseCache = stream[kState].closedPromise; @@ -857,6 +864,7 @@ function writableStreamFinishInFlightCloseWithError(stream, error) { assert(stream[kState].inFlightCloseRequest.promise !== undefined); stream[kState].inFlightCloseRequest.reject?.(error); stream[kState].inFlightCloseRequest = kNilRequest; + stream[kState].closeQueuedOrInFlight = false; assert(stream[kState].state === 'writable' || stream[kState].state === 'erroring'); if (stream[kState].pendingAbortRequest.abort.promise !== undefined) { @@ -870,6 +878,7 @@ function writableStreamFinishInFlightClose(stream) { assert(stream[kState].inFlightCloseRequest.promise !== undefined); stream[kState].inFlightCloseRequest.resolve?.(); stream[kState].inFlightCloseRequest = kNilRequest; + stream[kState].closeQueuedOrInFlight = false; if (stream[kState].state === 'erroring') { stream[kState].storedError = undefined; if (stream[kState].pendingAbortRequest.abort.promise !== undefined) { @@ -933,11 +942,7 @@ function writableStreamDealWithRejection(stream, error) { } function writableStreamCloseQueuedOrInFlight(stream) { - if (stream[kState].closeRequest.promise === undefined && - stream[kState].inFlightCloseRequest.promise === undefined) { - return false; - } - return true; + return stream[kState].closeQueuedOrInFlight; } function writableStreamAddWriteRequest(stream) { @@ -951,34 +956,34 @@ function writableStreamAddWriteRequest(stream) { } function writableStreamDefaultWriterWrite(writer, chunk) { - const { - stream, - } = writer[kState]; + const writerState = writer[kState]; + const stream = writerState.stream; assert(stream !== undefined); + const streamState = stream[kState]; const { controller, - } = stream[kState]; + } = streamState; const chunkSize = writableStreamDefaultControllerGetChunkSize( controller, chunk); - if (stream !== writer[kState].stream) { + if (stream !== writerState.stream) { return PromiseReject( new ERR_INVALID_STATE.TypeError('Mismatched WritableStreams')); } const { state, - } = stream[kState]; + } = streamState; if (state === 'errored') - return PromiseReject(stream[kState].storedError); + return PromiseReject(streamState.storedError); - if (writableStreamCloseQueuedOrInFlight(stream) || state === 'closed') { + if (streamState.closeQueuedOrInFlight || state === 'closed') { return PromiseReject( new ERR_INVALID_STATE.TypeError('WritableStream is closed')); } if (state === 'erroring') - return PromiseReject(stream[kState].storedError); + return PromiseReject(streamState.storedError); assert(state === 'writable'); @@ -1085,8 +1090,9 @@ function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) { const { stream, } = controller[kState]; - if (!writableStreamCloseQueuedOrInFlight(stream) && - stream[kState].state === 'writable') { + const streamState = stream[kState]; + if (!streamState.closeQueuedOrInFlight && + streamState.state === 'writable') { writableStreamUpdateBackpressure( stream, writableStreamDefaultControllerGetBackpressure(controller)); @@ -1107,12 +1113,13 @@ function writableStreamDefaultControllerProcessWrite(controller, chunk) { // subsequent write instead of allocating two fresh closures per chunk. controller[kState].writeFulfilled = () => { writableStreamFinishInFlightWrite(stream); + const streamState = stream[kState]; const { state, - } = stream[kState]; + } = streamState; assert(state === 'writable' || state === 'erroring'); dequeueValue(controller); - if (!writableStreamCloseQueuedOrInFlight(stream) && + if (!streamState.closeQueuedOrInFlight && state === 'writable') { writableStreamUpdateBackpressure( stream, diff --git a/src/node_task_runner.cc b/src/node_task_runner.cc index 3e15a70e72660c..22c02e83e12ed6 100644 --- a/src/node_task_runner.cc +++ b/src/node_task_runner.cc @@ -171,10 +171,10 @@ std::string EscapeShell(const std::string_view input) { escaped = std::regex_replace(escaped, leadingQuotePairs, ""); escaped = std::regex_replace(escaped, tripleSingleQuote, "\\\""); #else - // Replace single quotes("'") with "\\'" and wrap the result + // Replace single quotes("'") with `'"'"'` and wrap the result // in single quotes. std::string escaped = - std::regex_replace(std::string(input), std::regex("'"), "\\'"); + std::regex_replace(std::string(input), std::regex("'"), "'\"'\"'"); escaped = "'" + escaped + "'"; // Remove excessive quote pairs and handle edge cases static const std::regex tripleSingleQuote("\\\\'''"); diff --git a/test/cctest/test_node_task_runner.cc b/test/cctest/test_node_task_runner.cc index a2c520d2709f2e..1c715124bd88ee 100644 --- a/test/cctest/test_node_task_runner.cc +++ b/test/cctest/test_node_task_runner.cc @@ -28,12 +28,12 @@ TEST_F(TaskRunnerTest, EscapeShell) { {"test words", "'test words'"}, {"$1", "'$1'"}, {"\"$1\"", "'\"$1\"'"}, - {"'$1'", "'\\'$1\\''"}, + {"'$1'", "\"'\"'$1'\"'\"''"}, {"\\$1", "'\\$1'"}, {"--arg=\"$1\"", "'--arg=\"$1\"'"}, {"--arg=node exec -c \"$1\"", "'--arg=node exec -c \"$1\"'"}, - {"--arg=node exec -c '$1'", "'--arg=node exec -c \\'$1\\''"}, - {"'--arg=node exec -c \"$1\"'", "'\\'--arg=node exec -c \"$1\"\\''"} + {"--arg=node exec -c '$1'", "'--arg=node exec -c '\"'\"'$1'\"'\"''"}, + {"'--arg=node exec -c \"$1\"'", "\"'\"'--arg=node exec -c \"$1\"'\"'\"''"} #endif }; diff --git a/test/fixtures/run-script/node_modules/.bin/positional-args b/test/fixtures/run-script/node_modules/.bin/positional-args index 2d8092378ba1da..18c7bfa0e466a7 100755 --- a/test/fixtures/run-script/node_modules/.bin/positional-args +++ b/test/fixtures/run-script/node_modules/.bin/positional-args @@ -1,3 +1,3 @@ #!/bin/bash echo "Arguments: '$@'" -echo "The total number of arguments are: $#" +echo "The total number of arguments is: $#" diff --git a/test/fixtures/run-script/node_modules/.bin/positional-args.bat b/test/fixtures/run-script/node_modules/.bin/positional-args.bat index 4b94ed34d51daf..35c80ceda496ea 100755 --- a/test/fixtures/run-script/node_modules/.bin/positional-args.bat +++ b/test/fixtures/run-script/node_modules/.bin/positional-args.bat @@ -12,4 +12,4 @@ for %%x in (%*) do ( ) @echo Raw '%*' @echo Arguments: '%output%' -@echo The total number of arguments are: %argv% +@echo The total number of arguments is: %argv% diff --git a/test/parallel/test-node-run.js b/test/parallel/test-node-run.js index 26295256849702..e24117f6b1658b 100644 --- a/test/parallel/test-node-run.js +++ b/test/parallel/test-node-run.js @@ -143,15 +143,15 @@ describe('node --run [command]', () => { it('appends positional arguments', async () => { const child = await common.spawnPromisified( process.execPath, - [ '--run', `positional-args${envSuffix}`, '--', '--help "hello world test"', 'A', 'B', 'C'], + [ '--run', `positional-args${envSuffix}`, '--', '--help "hello world test"', 'A', 'B', 'C', 'I think therefore I\'m'], { cwd: fixtures.path('run-script') }, ); if (common.isWindows) { - assert.match(child.stdout, /Arguments: '--help ""hello world test"" A B C'/); + assert.match(child.stdout, /Arguments: '--help ""hello world test"" A B C I think therefore I'm'/); } else { - assert.match(child.stdout, /Arguments: '--help "hello world test" A B C'/); + assert.match(child.stdout, /Arguments: '--help "hello world test" A B C I think therefore I'm'/); } - assert.match(child.stdout, /The total number of arguments are: 4/); + assert.match(child.stdout, /The total number of arguments is: 5/); assert.strictEqual(child.stderr, ''); assert.strictEqual(child.code, 0); }); diff --git a/test/parallel/test-vfs-fd.js b/test/parallel/test-vfs-fd.js index ec9145189da299..f9f6b25f51ce57 100644 --- a/test/parallel/test-vfs-fd.js +++ b/test/parallel/test-vfs-fd.js @@ -80,6 +80,26 @@ const vfs = require('node:vfs'); myVfs.closeSync(fd); } +// Test readSync with Node's current-position sentinel +{ + const myVfs = vfs.create(); + myVfs.writeFileSync('/file.txt', 'hello world'); + + const fd = myVfs.openSync('/file.txt'); + const buffer1 = Buffer.alloc(5); + const buffer2 = Buffer.alloc(6); + + let bytesRead = myVfs.readSync(fd, buffer1, 0, 5, -1); + assert.strictEqual(bytesRead, 5); + assert.strictEqual(buffer1.toString(), 'hello'); + + bytesRead = myVfs.readSync(fd, buffer2, 0, 6, -1); + assert.strictEqual(bytesRead, 6); + assert.strictEqual(buffer2.toString(), ' world'); + + myVfs.closeSync(fd); +} + // Test readSync with explicit position { const myVfs = vfs.create(); diff --git a/test/parallel/test-vfs-file-handle.js b/test/parallel/test-vfs-file-handle.js index 6c653593ce7ea2..d9d919446b8ea6 100644 --- a/test/parallel/test-vfs-file-handle.js +++ b/test/parallel/test-vfs-file-handle.js @@ -82,6 +82,12 @@ myVfs.writeFileSync('/file.txt', 'hello world'); myVfs.writeSync(fd, buf, 0, 3, 0); myVfs.closeSync(fd); + const fd3 = myVfs.openSync('/sync-current.txt', 'w'); + myVfs.writeSync(fd3, Buffer.from('abc'), 0, 3, -1); + myVfs.writeSync(fd3, Buffer.from('def'), 0, 3, -1); + myVfs.closeSync(fd3); + assert.strictEqual(myVfs.readFileSync('/sync-current.txt', 'utf8'), 'abcdef'); + const fd2 = myVfs.openSync('/sync.txt', 'r'); const out = Buffer.alloc(3); myVfs.readSync(fd2, out, 0, 3, 0); diff --git a/test/parallel/test-vfs-fs-openSync.js b/test/parallel/test-vfs-fs-openSync.js index f2c73f0d634469..28fa17d28b3cf6 100644 --- a/test/parallel/test-vfs-fs-openSync.js +++ b/test/parallel/test-vfs-fs-openSync.js @@ -30,6 +30,19 @@ myVfs.mount(mountPoint); fs.closeSync(fd); } +// readSync with position null uses and advances the current file position +{ + const fd = fs.openSync(path.join(mountPoint, 'src/hello.txt'), 'r'); + const b1 = Buffer.alloc(5); + const b2 = Buffer.alloc(6); + + assert.strictEqual(fs.readSync(fd, b1, 0, 5, null), 5); + assert.strictEqual(fs.readSync(fd, b2, 0, 6, null), 6); + assert.strictEqual(b1.toString(), 'hello'); + assert.strictEqual(b2.toString(), ' world'); + fs.closeSync(fd); +} + // openSync + writeSync (buffer) + closeSync { const fd = fs.openSync(path.join(mountPoint, 'src/wfd.txt'), 'w');