Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: add docs and tests for AsyncIterable support in fh.writeFile #39836

Merged
merged 1 commit into from Aug 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 8 additions & 3 deletions doc/api/fs.md
Expand Up @@ -545,6 +545,9 @@ the end of the file.
<!-- YAML
added: v10.0.0
changes:
- version: v15.14.0
pr-url: https://github.com/nodejs/node/pull/37490
description: The `data` argument supports `AsyncIterable`, `Iterable` and `Stream`.
- version: v14.12.0
pr-url: https://github.com/nodejs/node/pull/34993
description: The `data` parameter will stringify an object with an
Expand All @@ -555,14 +558,16 @@ changes:
strings anymore.
-->

* `data` {string|Buffer|TypedArray|DataView|Object}
* `data` {string|Buffer|TypedArray|DataView|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`.
Expand Down Expand Up @@ -1341,7 +1346,7 @@ added: v10.0.0
changes:
- version: v15.14.0
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: v15.2.0
pr-url: https://github.com/nodejs/node/pull/35993
description: The options argument may include an AbortSignal to abort an
Expand Down
170 changes: 161 additions & 9 deletions test/parallel/test-fs-promises-file-handle-writeFile.js
Expand Up @@ -8,6 +8,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;
Expand All @@ -17,13 +18,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');

await fileHandle.writeFile(buffer);
const readFileData = fs.readFileSync(filePathForHandle);
assert.deepStrictEqual(buffer, readFileData);
try {
const buffer = Buffer.from('Hello world'.repeat(100), 'utf8');

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
Expand All @@ -43,6 +46,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+');
Linkgoron marked this conversation as resolved.
Show resolved Hide resolved
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());
14 changes: 8 additions & 6 deletions test/parallel/test-fs-promises-writefile.js
Expand Up @@ -74,9 +74,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() {
Expand Down Expand Up @@ -134,9 +135,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() {
Expand Down