From 3ac95f2d0ca18650a5a53ad9254ef01fd9673de2 Mon Sep 17 00:00:00 2001 From: Jimmy Leung <43258070+hkleungai@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:49:57 +0800 Subject: [PATCH 1/2] doc,test: widen fsPromises.appendFile()'s data type, add missing tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From lib/internal/fs/promises.js, one can see fsPromises.appendFile() is simply a wrapper on fsPromises.writeFile(). Hence if writeFile() can take non-buffer data argument, so can appendFile() too. To justify the doc change, I have also restuctured the relevant tests, to enhance the test coverage. They now includes the below test cases consistently. - await do(Append|Write)Buffer(); - await do(Append|Write)BufferAndCancel(); - await do(Append|Write)String(); - await do(Append|Write)Stream(); - await do(Append|Write)StreamWithCancel(); - await do(Append|Write)Iterable(); - await do(Append|Write)InvalidIterable(); - await do(Append|Write)IterableWithEncoding(); - await do(Append|Write)BufferIterable(); - await do(Append|Write)AsyncIterable(); - await do(Append|Write)LargeIterable(); - await do(Append|Write)InvalidValues(); - await do(Append|Write)TypedArrays(); Note that test/parallel/test-fs-promises-writefile-typedarray.js is subsumed by test/parallel/test-fs-promises-file-handle-writeFile.js, and test/parallel/test-fs-promises-writefile-with-fd.js becomes a subcase in test/parallel/test-fs-promises-file-handle-writeFile.js. Refs: https://github.com/nodejs/node/pull/41677 Signed-off-by: Jimmy Leung PR-URL: https://github.com/nodejs/node/pull/64279 Reviewed-By: James M Snell Reviewed-By: René --- doc/api/fs.md | 15 +- test/parallel/test-fs-promises-appendfile.js | 191 +++++++++++++ ...est-fs-promises-file-handle-append-file.js | 260 ++++++++++++++++-- .../test-fs-promises-file-handle-writeFile.js | 93 ++++++- .../test-fs-promises-writefile-typedarray.js | 24 -- .../test-fs-promises-writefile-with-fd.js | 35 --- test/parallel/test-fs-promises-writefile.js | 58 ++-- 7 files changed, 542 insertions(+), 134 deletions(-) create mode 100644 test/parallel/test-fs-promises-appendfile.js delete mode 100644 test/parallel/test-fs-promises-writefile-typedarray.js delete mode 100644 test/parallel/test-fs-promises-writefile-with-fd.js diff --git a/doc/api/fs.md b/doc/api/fs.md index 51e1a6f40b985a..9cb84007b3b815 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -196,7 +196,7 @@ changes: strings anymore. --> -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `signal` {AbortSignal|undefined} allows aborting an in-progress writeFile. **Default:** `undefined` @@ -1007,7 +1007,7 @@ changes: strings anymore. --> -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} * `options` {Object|string} * `encoding` {string|null} The expected character encoding when `data` is a string. **Default:** `'utf8'` @@ -1238,10 +1238,15 @@ changes: - v20.10.0 pr-url: https://github.com/nodejs/node/pull/50095 description: The `flush` option is now supported. + - version: + - v15.14.0 + - v14.18.0 + pr-url: https://github.com/nodejs/node/pull/37490 + description: The `data` argument supports `AsyncIterable`, `Iterable`, and `Stream`. --> * `path` {string|Buffer|URL|FileHandle} filename or {FileHandle} -* `data` {string|Buffer} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `mode` {integer} **Default:** `0o666` @@ -1251,7 +1256,7 @@ changes: * Returns: {Promise} Fulfills with `undefined` upon success. Asynchronously append data to a file, creating the file if it does not yet -exist. `data` can be a string or a {Buffer}. +`data` can be a string, a buffer, an {AsyncIterable}, or an {Iterable} object. If `options` is a string, then it specifies the `encoding`. @@ -2265,7 +2270,7 @@ changes: --> * `file` {string|Buffer|URL|FileHandle} filename or `FileHandle` -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `mode` {integer} **Default:** `0o666` diff --git a/test/parallel/test-fs-promises-appendfile.js b/test/parallel/test-fs-promises-appendfile.js new file mode 100644 index 00000000000000..04a3e4ac9072ec --- /dev/null +++ b/test/parallel/test-fs-promises-appendfile.js @@ -0,0 +1,191 @@ +'use strict'; + +const common = require('../common'); +const fs = require('fs'); +const fsPromises = fs.promises; +const path = require('path'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const tmpDir = tmpdir.path; +const { Readable } = require('stream'); + +tmpdir.refresh(); + +const buffer = Buffer.from('abc'.repeat(1000)); +const stream = Readable.from(['a', 'b', 'c']); +const stream2 = Readable.from(['ümlaut', ' ', 'sechzig']); +const iterable = { + expected: 'abc', + *[Symbol.iterator]() { + yield 'a'; + yield 'b'; + yield 'c'; + } +}; + +const veryLargeIterable = { + expected: 'dogs running'.repeat(512 * 1024), + *[Symbol.iterator]() { + yield Buffer.from('dogs running'.repeat(512 * 1024), 'utf8'); + } +}; + +function iterableWith(value) { + return { + *[Symbol.iterator]() { + yield value; + } + }; +} +const bufferIterable = { + expected: 'abc', + *[Symbol.iterator]() { + yield Buffer.from('a'); + yield Buffer.from('b'); + yield Buffer.from('c'); + } +}; +const asyncIterable = { + expected: 'abc', + async* [Symbol.asyncIterator]() { + yield 'a'; + yield 'b'; + yield 'c'; + } +}; + +let counter = 0; + +async function doAppendString() { + const string = 'x~yz'.repeat(100); + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await fsPromises.appendFile(dest, string); + const data = fs.readFileSync(dest); + const stringAsBuffer = Buffer.from(string, 'utf8'); + assert.deepStrictEqual(stringAsBuffer, data); +} + +async function doAppendBuffer() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await fsPromises.appendFile(dest, buffer); + const data = fs.readFileSync(dest); + assert.deepStrictEqual(data, buffer); +} + +async function doAppendStream() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await fsPromises.appendFile(dest, stream); + const expected = 'abc'; + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, expected); +} + +async function doAppendStreamWithCancel() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const controller = new AbortController(); + const { signal } = controller; + process.nextTick(() => controller.abort()); + await assert.rejects( + fsPromises.appendFile(dest, stream, { signal }), + { name: 'AbortError' } + ); +} + +async function doAppendIterable() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await fsPromises.appendFile(dest, iterable); + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, iterable.expected); +} + +async function doAppendInvalidIterable() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await Promise.all( + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => + assert.rejects(fsPromises.appendFile(dest, iterableWith(value)), { + code: 'ERR_INVALID_ARG_TYPE', + }) + ) + ); +} + +async function doAppendIterableWithEncoding() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await fsPromises.appendFile(dest, stream2, 'latin1'); + const expected = 'ümlaut sechzig'; + const data = fs.readFileSync(dest, 'latin1'); + assert.deepStrictEqual(data, expected); +} + +async function doAppendBufferIterable() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await fsPromises.appendFile(dest, bufferIterable); + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, bufferIterable.expected); +} + +async function doAppendAsyncIterable() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await fsPromises.appendFile(dest, asyncIterable); + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, asyncIterable.expected); +} + +async function doAppendLargeIterable() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await fsPromises.appendFile(dest, veryLargeIterable); + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, veryLargeIterable.expected); +} + +async function doAppendInvalidValues() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + await Promise.all( + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => + assert.rejects(fsPromises.appendFile(dest, value), { + code: 'ERR_INVALID_ARG_TYPE', + }) + ) + ); +} + +async function doAppendBufferAndCancel() { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const controller = new AbortController(); + const { signal } = controller; + process.nextTick(() => controller.abort()); + await assert.rejects( + fsPromises.appendFile(dest, buffer, { signal }), + { name: 'AbortError' } + ); +} + +async function doAppendTypedArrays() { + for (const Constructor of [Uint8Array, Uint16Array, Uint32Array]) { + const dest = path.resolve(tmpDir, `tmp-${counter++}.txt`); + + // Use a file size larger than `kReadFileMaxChunkSize`. + const buffer = Buffer.from('012'.repeat(2 ** 14)); + + const array = new Constructor(buffer.buffer); + await fsPromises.appendFile(dest, array); + const data = await fsPromises.readFile(dest); + assert.deepStrictEqual(data, buffer); + } +} + +(async () => { + await doAppendBuffer(); + await doAppendBufferAndCancel(); + await doAppendString(); + await doAppendStream(); + await doAppendStreamWithCancel(); + await doAppendIterable(); + await doAppendInvalidIterable(); + await doAppendIterableWithEncoding(); + await doAppendBufferIterable(); + await doAppendAsyncIterable(); + await doAppendLargeIterable(); + await doAppendInvalidValues(); + await doAppendTypedArrays(); +})().then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-file-handle-append-file.js b/test/parallel/test-fs-promises-file-handle-append-file.js index 42b6dbee309cec..4c476a6c36ca29 100644 --- a/test/parallel/test-fs-promises-file-handle-append-file.js +++ b/test/parallel/test-fs-promises-file-handle-append-file.js @@ -8,52 +8,256 @@ const common = require('../common'); const fs = require('fs'); const { open } = fs.promises; const path = require('path'); +const { Readable } = require('stream'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const tmpDir = tmpdir.path; tmpdir.refresh(); -async function validateAppendBuffer() { - const filePath = path.resolve(tmpDir, 'tmp-append-file-buffer.txt'); +const stream = Readable.from(['a', 'b', 'c']); +const stream2 = Readable.from(['ümlaut', ' ', 'sechzig']); +const iterable = { + expected: 'abc', + *[Symbol.iterator]() { + yield 'a'; + yield 'b'; + yield 'c'; + } +}; +function iterableWith(value) { + return { + *[Symbol.iterator]() { + yield value; + } + }; +} +const bufferIterable = { + expected: 'abc', + *[Symbol.iterator]() { + yield Buffer.from('a'); + yield Buffer.from('b'); + yield Buffer.from('c'); + } +}; +const asyncIterable = { + expected: 'abc', + async* [Symbol.asyncIterator]() { + yield 'a'; + yield 'b'; + yield 'c'; + } +}; +const veryLargeIterable = { + expected: 'dogs running'.repeat(512 * 1024), + *[Symbol.iterator]() { + yield Buffer.from('dogs running'.repeat(512 * 1024), 'utf8'); + } +}; + +let counter = 0; + +async function doAppendBuffer() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); const fileHandle = await open(filePath, 'a'); - const buffer = Buffer.from('a&Dp'.repeat(100), 'utf8'); + try { + const buffer = Buffer.from('a&Dp'.repeat(100), 'utf8'); - await fileHandle.appendFile(buffer); - const appendedFileData = fs.readFileSync(filePath); - assert.deepStrictEqual(appendedFileData, buffer); + await fileHandle.appendFile(buffer); + const appendedFileData = fs.readFileSync(filePath); + assert.deepStrictEqual(appendedFileData, buffer); - await fileHandle.close(); + } finally { + await fileHandle.close(); + } } -async function validateAppendString() { - const filePath = path.resolve(tmpDir, 'tmp-append-file-string.txt'); +async function doAppendString() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); const fileHandle = await open(filePath, 'a'); - const string = 'x~yz'.repeat(100); + try { + const string = 'x~yz'.repeat(100); - await fileHandle.appendFile(string); - const stringAsBuffer = Buffer.from(string, 'utf8'); - const appendedFileData = fs.readFileSync(filePath); - assert.deepStrictEqual(appendedFileData, stringAsBuffer); + await fileHandle.appendFile(string); + const stringAsBuffer = Buffer.from(string, 'utf8'); + const appendedFileData = fs.readFileSync(filePath); + assert.deepStrictEqual(appendedFileData, stringAsBuffer); + + } finally { + await fileHandle.close(); + } +} - await fileHandle.close(); +async function doAppendBufferAndCancel() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + const buffer = Buffer.from('dogs running'.repeat(512 * 1024), 'utf8'); + const controller = new AbortController(); + const { signal } = controller; + process.nextTick(() => controller.abort()); + await assert.rejects(fileHandle.appendFile(buffer, { signal }), { + name: 'AbortError' + }); + } finally { + await fileHandle.close(); + } +} + +async function doAppendStream() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await fileHandle.appendFile(stream); + const expected = 'abc'; + const data = fs.readFileSync(filePath, 'utf-8'); + assert.deepStrictEqual(data, expected); + } finally { + await fileHandle.close(); + } } -async function doAppendAndCancel() { - const filePathForHandle = path.resolve(tmpDir, 'dogs-running.txt'); - const fileHandle = await open(filePathForHandle, 'w+'); - const buffer = Buffer.from('dogs running'.repeat(512 * 1024), 'utf8'); +async function doAppendStreamWithCancel() { const controller = new AbortController(); const { signal } = controller; process.nextTick(() => controller.abort()); - await assert.rejects(fileHandle.appendFile(buffer, { signal }), { - name: 'AbortError' - }); - await fileHandle.close(); + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await assert.rejects( + fileHandle.appendFile(stream, { signal }), + { name: 'AbortError' } + ); + } finally { + await fileHandle.close(); + } +} + +async function doAppendIterable() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await fileHandle.appendFile(iterable); + const data = fs.readFileSync(filePath, 'utf-8'); + assert.deepStrictEqual(data, iterable.expected); + } finally { + await fileHandle.close(); + } +} + +async function doAppendInvalidIterable() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await Promise.all( + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => + assert.rejects( + fileHandle.appendFile(iterableWith(value)), + { code: 'ERR_INVALID_ARG_TYPE' } + ) + ) + ); + } finally { + await fileHandle.close(); + } +} + +async function doAppendIterableWithEncoding() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await fileHandle.appendFile(stream2, 'latin1'); + const expected = 'ümlaut sechzig'; + const data = fs.readFileSync(filePath, 'latin1'); + assert.deepStrictEqual(data, expected); + } finally { + await fileHandle.close(); + } +} + +async function doAppendBufferIterable() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await fileHandle.appendFile(bufferIterable); + const data = fs.readFileSync(filePath, 'utf-8'); + assert.deepStrictEqual(data, bufferIterable.expected); + } finally { + await fileHandle.close(); + } +} + +async function doAppendAsyncIterable() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await fileHandle.appendFile(asyncIterable); + const data = fs.readFileSync(filePath, 'utf-8'); + assert.deepStrictEqual(data, asyncIterable.expected); + } finally { + await fileHandle.close(); + } +} + +async function doAppendLargeIterable() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await fileHandle.appendFile(veryLargeIterable); + const data = fs.readFileSync(filePath, 'utf-8'); + assert.deepStrictEqual(data, veryLargeIterable.expected); + } finally { + await fileHandle.close(); + } +} + +async function doAppendInvalidValues() { + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + try { + await Promise.all( + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => + assert.rejects( + fileHandle.appendFile(value), + { code: 'ERR_INVALID_ARG_TYPE' } + ) + ) + ); + } finally { + await fileHandle.close(); + } +} + +async function doAppendTypedArrays() { + for (const Constructor of [Uint8Array, Uint16Array, Uint32Array]) { + // Use a file size larger than `kReadFileMaxChunkSize`. + const buffer = Buffer.from('012'.repeat(2 ** 14)); + const array = new Constructor(buffer.buffer); + const filePath = path.resolve(tmpDir, `tmp-${counter++}.txt`); + const fileHandle = await open(filePath, 'a'); + + try { + await fileHandle.writeFile(array); + const data = fs.readFileSync(filePath); + assert.deepStrictEqual(data, buffer); + } finally { + await fileHandle.close(); + } + } } -Promise.all([ - validateAppendBuffer(), - validateAppendString(), - doAppendAndCancel(), -]).then(common.mustCall()); +(async () => { + await doAppendBuffer(); + await doAppendBufferAndCancel(); + await doAppendString(); + await doAppendStream(); + await doAppendStreamWithCancel(); + await doAppendIterable(); + await doAppendInvalidIterable(); + await doAppendIterableWithEncoding(); + await doAppendBufferIterable(); + await doAppendAsyncIterable(); + await doAppendLargeIterable(); + await doAppendInvalidValues(); + await doAppendTypedArrays(); +})().then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-file-handle-writeFile.js b/test/parallel/test-fs-promises-file-handle-writeFile.js index 2c1a80e4f52d49..b08200d4f78c94 100644 --- a/test/parallel/test-fs-promises-file-handle-writeFile.js +++ b/test/parallel/test-fs-promises-file-handle-writeFile.js @@ -1,12 +1,8 @@ 'use strict'; const common = require('../common'); - -// The following tests validate base functionality for the fs.promises -// FileHandle.writeFile method. - const fs = require('fs'); -const { open, writeFile } = fs.promises; +const { open } = fs.promises; const path = require('path'); const { Readable } = require('stream'); const tmpdir = require('../common/tmpdir'); @@ -15,7 +11,7 @@ const tmpDir = tmpdir.path; tmpdir.refresh(); -async function validateWriteFile() { +async function doWriteBuffer() { const filePathForHandle = path.resolve(tmpDir, 'tmp-write-file2.txt'); const fileHandle = await open(filePathForHandle, 'w+'); try { @@ -29,8 +25,22 @@ async function validateWriteFile() { } } -// Signal aborted while writing file -async function doWriteAndCancel() { +async function doWriteString() { + const filePathForHandle = path.resolve(tmpDir, 'tmp-write-string.txt'); + const fileHandle = await open(filePathForHandle, 'w+'); + try { + const string = 'x~yz'.repeat(100); + + await fileHandle.writeFile(string); + const readFileData = fs.readFileSync(filePathForHandle); + const stringAsBuffer = Buffer.from(string, 'utf8'); + assert.deepStrictEqual(stringAsBuffer, readFileData); + } finally { + await fileHandle.close(); + } +} + +async function doWriteBufferAndCancel() { const filePathForHandle = path.resolve(tmpDir, 'dogs-running.txt'); const fileHandle = await open(filePathForHandle, 'w+'); try { @@ -38,7 +48,7 @@ async function doWriteAndCancel() { const controller = new AbortController(); const { signal } = controller; process.nextTick(() => controller.abort()); - await assert.rejects(writeFile(fileHandle, buffer, { signal }), { + await assert.rejects(fileHandle.writeFile(buffer, { signal }), { name: 'AbortError' }); } finally { @@ -58,6 +68,13 @@ const iterable = { yield 'c'; } }; +const veryLargeIterable = { + expected: 'dogs running'.repeat(512 * 1024), + *[Symbol.iterator]() { + yield Buffer.from('dogs running'.repeat(512 * 1024), 'utf8'); + } +}; + function iterableWith(value) { return { *[Symbol.iterator]() { @@ -75,7 +92,7 @@ const bufferIterable = { }; const asyncIterable = { expected: 'abc', - async* [Symbol.asyncIterator]() { + async*[Symbol.asyncIterator]() { yield 'a'; yield 'b'; yield 'c'; @@ -170,6 +187,17 @@ async function doWriteAsyncIterable() { } } +async function doWriteLargeIterable() { + const fileHandle = await open(dest, 'w+'); + try { + await fileHandle.writeFile(veryLargeIterable); + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, veryLargeIterable.expected); + } finally { + await fileHandle.close(); + } +} + async function doWriteInvalidValues() { const fileHandle = await open(dest, 'w+'); try { @@ -186,9 +214,47 @@ async function doWriteInvalidValues() { } } +async function doWriteTypedArrays() { + for (const Constructor of [Uint8Array, Uint16Array, Uint32Array]) { + // Use a file size larger than `kReadFileMaxChunkSize`. + const buffer = Buffer.from('012'.repeat(2 ** 14)); + const array = new Constructor(buffer.buffer); + const fileHandle = await open(dest, 'w+'); + + try { + await fileHandle.writeFile(array); + const data = fs.readFileSync(dest); + assert.deepStrictEqual(data, buffer); + } finally { + await fileHandle.close(); + } + } +} + +async function doWriteFromCurrentPosition() { + const fileHandle = await open(dest, 'w+'); + try { + /* Write only five bytes, so that the position moves to five. */ + const buf = Buffer.from('Hello'); + const { bytesWritten } = await fileHandle.write(buf, 0, 5, null); + assert.strictEqual(bytesWritten, 5); + + /* Write some more with writeFile(). */ + await fileHandle.writeFile('World'); + + const data = fs.readFileSync(dest, 'utf-8'); + + /* New content should be written at position five, instead of zero. */ + assert.strictEqual(data, 'HelloWorld'); + } finally { + await fileHandle.close(); + } +} + (async () => { - await validateWriteFile(); - await doWriteAndCancel(); + await doWriteBuffer(); + await doWriteBufferAndCancel(); + await doWriteString(); await doWriteStream(); await doWriteStreamWithCancel(); await doWriteIterable(); @@ -196,5 +262,8 @@ async function doWriteInvalidValues() { await doWriteIterableWithEncoding(); await doWriteBufferIterable(); await doWriteAsyncIterable(); + await doWriteLargeIterable(); await doWriteInvalidValues(); + await doWriteTypedArrays(); + await doWriteFromCurrentPosition(); })().then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-writefile-typedarray.js b/test/parallel/test-fs-promises-writefile-typedarray.js deleted file mode 100644 index 32d9cffa236e57..00000000000000 --- a/test/parallel/test-fs-promises-writefile-typedarray.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -const common = require('../common'); -const fs = require('fs'); -const fsPromises = fs.promises; -const path = require('path'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const tmpDir = tmpdir.path; - -tmpdir.refresh(); - -const dest = path.resolve(tmpDir, 'tmp.txt'); -// Use a file size larger than `kReadFileMaxChunkSize`. -const buffer = Buffer.from('012'.repeat(2 ** 14)); - -(async () => { - for (const Constructor of [Uint8Array, Uint16Array, Uint32Array]) { - const array = new Constructor(buffer.buffer); - await fsPromises.writeFile(dest, array); - const data = await fsPromises.readFile(dest); - assert.deepStrictEqual(data, buffer); - } -})().then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-writefile-with-fd.js b/test/parallel/test-fs-promises-writefile-with-fd.js deleted file mode 100644 index 7cb9eeeca9a9f2..00000000000000 --- a/test/parallel/test-fs-promises-writefile-with-fd.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -// This test makes sure that `writeFile()` always writes from the current -// position of the file, instead of truncating the file. - -const common = require('../common'); -const assert = require('assert'); -const { readFileSync } = require('fs'); -const { open } = require('fs').promises; - -const tmpdir = require('../common/tmpdir'); -tmpdir.refresh(); - -const fn = tmpdir.resolve('test.txt'); - -async function writeFileTest() { - const handle = await open(fn, 'w'); - - /* Write only five bytes, so that the position moves to five. */ - const buf = Buffer.from('Hello'); - const { bytesWritten } = await handle.write(buf, 0, 5, null); - assert.strictEqual(bytesWritten, 5); - - /* Write some more with writeFile(). */ - await handle.writeFile('World'); - - /* New content should be written at position five, instead of zero. */ - assert.strictEqual(readFileSync(fn).toString(), 'HelloWorld'); - - await handle.close(); -} - - -writeFileTest() - .then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-writefile.js b/test/parallel/test-fs-promises-writefile.js index 25df61b2b48414..da7e345a70791e 100644 --- a/test/parallel/test-fs-promises-writefile.js +++ b/test/parallel/test-fs-promises-writefile.js @@ -14,7 +14,6 @@ tmpdir.refresh(); const dest = path.resolve(tmpDir, 'tmp.txt'); const otherDest = path.resolve(tmpDir, 'tmp-2.txt'); const buffer = Buffer.from('abc'.repeat(1000)); -const buffer2 = Buffer.from('xyz'.repeat(1000)); const stream = Readable.from(['a', 'b', 'c']); const stream2 = Readable.from(['ümlaut', ' ', 'sechzig']); const iterable = { @@ -26,7 +25,7 @@ const iterable = { } }; -const veryLargeBuffer = { +const veryLargeIterable = { expected: 'dogs running'.repeat(512 * 1024), *[Symbol.iterator]() { yield Buffer.from('dogs running'.repeat(512 * 1024), 'utf8'); @@ -57,7 +56,15 @@ const asyncIterable = { } }; -async function doWrite() { +async function doWriteString() { + const string = 'x~yz'.repeat(100); + await fsPromises.writeFile(dest, string); + const data = fs.readFileSync(dest); + const stringAsBuffer = Buffer.from(string, 'utf8'); + assert.deepStrictEqual(stringAsBuffer, data); +} + +async function doWriteBuffer() { await fsPromises.writeFile(dest, buffer); const data = fs.readFileSync(dest); assert.deepStrictEqual(data, buffer); @@ -115,10 +122,10 @@ async function doWriteAsyncIterable() { assert.deepStrictEqual(data, asyncIterable.expected); } -async function doWriteAsyncLargeIterable() { - await fsPromises.writeFile(dest, veryLargeBuffer); +async function doWriteLargeIterable() { + await fsPromises.writeFile(dest, veryLargeIterable); const data = fs.readFileSync(dest, 'utf-8'); - assert.deepStrictEqual(data, veryLargeBuffer.expected); + assert.deepStrictEqual(data, veryLargeIterable.expected); } async function doWriteInvalidValues() { @@ -131,7 +138,7 @@ async function doWriteInvalidValues() { ); } -async function doWriteWithCancel() { +async function doWriteBufferAndCancel() { const controller = new AbortController(); const { signal } = controller; process.nextTick(() => controller.abort()); @@ -141,32 +148,22 @@ async function doWriteWithCancel() { ); } -async function doAppend() { - await fsPromises.appendFile(dest, buffer2, { flag: null }); - const data = fs.readFileSync(dest); - const buf = Buffer.concat([buffer, buffer2]); - assert.deepStrictEqual(buf, data); -} - -async function doRead() { - const data = await fsPromises.readFile(dest); - const buf = fs.readFileSync(dest); - assert.deepStrictEqual(buf, data); -} +async function doWriteTypedArrays() { + for (const Constructor of [Uint8Array, Uint16Array, Uint32Array]) { + // Use a file size larger than `kReadFileMaxChunkSize`. + const buffer = Buffer.from('012'.repeat(2 ** 14)); -async function doReadWithEncoding() { - const data = await fsPromises.readFile(dest, 'utf-8'); - const syncData = fs.readFileSync(dest, 'utf-8'); - assert.strictEqual(typeof data, 'string'); - assert.deepStrictEqual(data, syncData); + const array = new Constructor(buffer.buffer); + await fsPromises.writeFile(dest, array); + const data = await fsPromises.readFile(dest); + assert.deepStrictEqual(data, buffer); + } } (async () => { - await doWrite(); - await doWriteWithCancel(); - await doAppend(); - await doRead(); - await doReadWithEncoding(); + await doWriteBuffer(); + await doWriteBufferAndCancel(); + await doWriteString(); await doWriteStream(); await doWriteStreamWithCancel(); await doWriteIterable(); @@ -174,6 +171,7 @@ async function doReadWithEncoding() { await doWriteIterableWithEncoding(); await doWriteBufferIterable(); await doWriteAsyncIterable(); - await doWriteAsyncLargeIterable(); + await doWriteLargeIterable(); await doWriteInvalidValues(); + await doWriteTypedArrays(); })().then(common.mustCall()); From f7702431de93904cc982e9fe4de4242a226dffd5 Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Wed, 15 Jul 2026 01:52:12 +0900 Subject: [PATCH 2/2] doc: fix typo in tls.md Signed-off-by: Daijiro Wachi PR-URL: https://github.com/nodejs/node/pull/64458 Reviewed-By: Luigi Pinca Reviewed-By: Tierney Cyren --- doc/api/tls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 95335e6f275099..b02d9300c5f8bf 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2321,7 +2321,7 @@ The certificates will be deduplicated before being set as the default. This function only affects the current Node.js thread. Previous sessions cached by the HTTPS agent won't be affected by this change, so -this method should be called before any unwanted cachable TLS connections are +this method should be called before any unwanted cacheable TLS connections are made. To use system CA certificates as the default: