From bea3f1c5501953b9424fc9978e86be61a95dd980 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 21 Aug 2021 17:13:42 +0200 Subject: [PATCH] fs: add docs and tests for `AsyncIterable` support in `fh.writeFile` Refs: https://github.com/nodejs/node/pull/37490 PR-URL: https://github.com/nodejs/node/pull/39836 Reviewed-By: Nitzan Uziely --- doc/api/fs.md | 11 +- .../test-fs-promises-file-handle-writeFile.js | 170 +++++++++++++++++- test/parallel/test-fs-promises-writefile.js | 14 +- 3 files changed, 177 insertions(+), 18 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 6b7f1404858b0a..a0b98867c22e64 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -495,6 +495,9 @@ the end of the file. -* `data` {string|Buffer|Uint8Array|Object} +* `data` {string|Buffer|Uint8Array|Object|AsyncIterable|Iterable + |Stream} * `options` {Object|string} * `encoding` {string|null} The expected character encoding when `data` is a string. **Default:** `'utf8'` * Returns: {Promise} Asynchronously writes data to a file, replacing the file if it already exists. -`data` can be a string, a buffer, or an object with an own `toString` function +`data` can be a string, a buffer, an {AsyncIterable} or {Iterable} object, or an +object with an own `toString` function property. The promise is resolved with no arguments upon success. If `options` is a string, then it specifies the `encoding`. @@ -1187,7 +1192,7 @@ added: v10.0.0 changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/37490 - description: The `data` argument supports `AsyncIterable`, `Iterable` & `Stream`. + description: The `data` argument supports `AsyncIterable`, `Iterable` and `Stream`. - version: v14.17.0 pr-url: https://github.com/nodejs/node/pull/35993 description: The options argument may include an AbortSignal to abort an diff --git a/test/parallel/test-fs-promises-file-handle-writeFile.js b/test/parallel/test-fs-promises-file-handle-writeFile.js index a4ae7fd054b53d..46c9019bc8d8dd 100644 --- a/test/parallel/test-fs-promises-file-handle-writeFile.js +++ b/test/parallel/test-fs-promises-file-handle-writeFile.js @@ -9,6 +9,7 @@ const common = require('../common'); const fs = require('fs'); const { open, writeFile } = fs.promises; const path = require('path'); +const { Readable } = require('stream'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const tmpDir = tmpdir.path; @@ -18,13 +19,15 @@ tmpdir.refresh(); async function validateWriteFile() { const filePathForHandle = path.resolve(tmpDir, 'tmp-write-file2.txt'); const fileHandle = await open(filePathForHandle, 'w+'); - const buffer = Buffer.from('Hello world'.repeat(100), 'utf8'); + try { + const buffer = Buffer.from('Hello world'.repeat(100), 'utf8'); - await fileHandle.writeFile(buffer); - const readFileData = fs.readFileSync(filePathForHandle); - assert.deepStrictEqual(buffer, readFileData); - - await fileHandle.close(); + await fileHandle.writeFile(buffer); + const readFileData = fs.readFileSync(filePathForHandle); + assert.deepStrictEqual(buffer, readFileData); + } finally { + await fileHandle.close(); + } } // Signal aborted while writing file @@ -40,6 +43,155 @@ async function doWriteAndCancel() { }); } -validateWriteFile() - .then(doWriteAndCancel) - .then(common.mustCall()); +const dest = path.resolve(tmpDir, 'tmp.txt'); +const otherDest = path.resolve(tmpDir, 'tmp-2.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'; + } +}; + +async function doWriteStream() { + const fileHandle = await open(dest, 'w+'); + try { + await fileHandle.writeFile(stream); + const expected = 'abc'; + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, expected); + } finally { + await fileHandle.close(); + } +} + +async function doWriteStreamWithCancel() { + const controller = new AbortController(); + const { signal } = controller; + process.nextTick(() => controller.abort()); + const fileHandle = await open(otherDest, 'w+'); + try { + await assert.rejects( + fileHandle.writeFile(stream, { signal }), + { name: 'AbortError' } + ); + } finally { + await fileHandle.close(); + } +} + +async function doWriteIterable() { + const fileHandle = await open(dest, 'w+'); + try { + await fileHandle.writeFile(iterable); + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, iterable.expected); + } finally { + await fileHandle.close(); + } +} + +async function doWriteInvalidIterable() { + const fileHandle = await open(dest, 'w+'); + try { + await Promise.all( + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => + assert.rejects( + fileHandle.writeFile(iterableWith(value)), + { code: 'ERR_INVALID_ARG_TYPE' } + ) + ) + ); + } finally { + await fileHandle.close(); + } +} + +async function doWriteIterableWithEncoding() { + const fileHandle = await open(dest, 'w+'); + try { + await fileHandle.writeFile(stream2, 'latin1'); + const expected = 'ümlaut sechzig'; + const data = fs.readFileSync(dest, 'latin1'); + assert.deepStrictEqual(data, expected); + } finally { + await fileHandle.close(); + } +} + +async function doWriteBufferIterable() { + const fileHandle = await open(dest, 'w+'); + try { + await fileHandle.writeFile(bufferIterable); + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, bufferIterable.expected); + } finally { + await fileHandle.close(); + } +} + +async function doWriteAsyncIterable() { + const fileHandle = await open(dest, 'w+'); + try { + await fileHandle.writeFile(asyncIterable); + const data = fs.readFileSync(dest, 'utf-8'); + assert.deepStrictEqual(data, asyncIterable.expected); + } finally { + await fileHandle.close(); + } +} + +async function doWriteInvalidValues() { + const fileHandle = await open(dest, 'w+'); + try { + await Promise.all( + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => + assert.rejects( + fileHandle.writeFile(value), + { code: 'ERR_INVALID_ARG_TYPE' } + ) + ) + ); + } finally { + await fileHandle.close(); + } +} + +(async () => { + await validateWriteFile(); + await doWriteAndCancel(); + await doWriteStream(); + await doWriteStreamWithCancel(); + await doWriteIterable(); + await doWriteInvalidIterable(); + await doWriteIterableWithEncoding(); + await doWriteBufferIterable(); + await doWriteAsyncIterable(); + await doWriteInvalidValues(); +})().then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-writefile.js b/test/parallel/test-fs-promises-writefile.js index 3caf886ef2662c..2be52ccbaa875f 100644 --- a/test/parallel/test-fs-promises-writefile.js +++ b/test/parallel/test-fs-promises-writefile.js @@ -67,9 +67,10 @@ async function doWriteStreamWithCancel() { const controller = new AbortController(); const { signal } = controller; process.nextTick(() => controller.abort()); - assert.rejects(fsPromises.writeFile(otherDest, stream, { signal }), { - name: 'AbortError' - }); + await assert.rejects( + fsPromises.writeFile(otherDest, stream, { signal }), + { name: 'AbortError' } + ); } async function doWriteIterable() { @@ -121,9 +122,10 @@ async function doWriteWithCancel() { const controller = new AbortController(); const { signal } = controller; process.nextTick(() => controller.abort()); - assert.rejects(fsPromises.writeFile(otherDest, buffer, { signal }), { - name: 'AbortError' - }); + await assert.rejects( + fsPromises.writeFile(otherDest, buffer, { signal }), + { name: 'AbortError' } + ); } async function doAppend() {