'use strict'; const common = require('../common'); // This test ensures that fs.write accepts "named parameters" object // and doesn't interpret objects as strings const assert = require('assert'); const fs = require('fs'); const tmpdir = require('../common/tmpdir'); const util = require('util'); tmpdir.refresh(); const destInvalid = tmpdir.resolve('rwopt_invalid'); const buffer = Buffer.from('zyx'); function testInvalidCb(fd, expectedCode, buffer, options, callback) { assert.throws( () => fs.write(fd, buffer, common.mustNotMutateObjectDeep(options), common.mustNotCall()), { code: expectedCode } ); callback(0); } function testValidCb(buffer, options, index, callback) { options = common.mustNotMutateObjectDeep(options); const length = options?.length; const offset = options?.offset; const dest = tmpdir.resolve(`rwopt_valid_${index}`); fs.open(dest, 'w', common.mustSucceed((fd) => { fs.write(fd, buffer, options, common.mustSucceed((bytesWritten, bufferWritten) => { const writeBufCopy = Uint8Array.prototype.slice.call(bufferWritten); fs.close(fd, common.mustSucceed(() => { fs.open(dest, 'r', common.mustSucceed((fd) => { fs.read(fd, buffer, options, common.mustSucceed((bytesRead, bufferRead) => { const readBufCopy = Uint8Array.prototype.slice.call(bufferRead); assert.ok(bytesWritten >= bytesRead); if (length !== undefined && length !== null) { assert.strictEqual(bytesWritten, length); assert.strictEqual(bytesRead, length); } if (offset === undefined || offset === 0) { assert.deepStrictEqual(writeBufCopy, readBufCopy); } assert.deepStrictEqual(bufferWritten, bufferRead); fs.close(fd, common.mustSucceed(callback)); })); })); })); })); })); } // Promisify to reduce flakiness const testInvalid = util.promisify(testInvalidCb); const testValid = util.promisify(testValidCb); async function runTests(fd) { // Test if first argument is not wrongly interpreted as ArrayBufferView|string for (const badBuffer of [ undefined, null, true, 42, 42n, Symbol('42'), NaN, [], () => {}, Promise.resolve(new Uint8Array(1)), common.mustNotCall(), common.mustNotMutateObjectDeep({}), {}, { buffer: 'amNotParam' }, { string: 'amNotParam' }, { buffer: new Uint8Array(1).buffer }, new Date(), new String('notPrimitive'), { [Symbol.toPrimitive]: (hint) => 'amObject' }, { toString() { return 'amObject'; } }, ]) { await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', badBuffer, {}); } // First argument (buffer or string) is mandatory await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', undefined, undefined); // Various invalid options await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { length: 5 }); await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { offset: 5 }); await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { length: 1, offset: 3 }); await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { length: -1 }); await testInvalid(fd, 'ERR_OUT_OF_RANGE', buffer, { offset: -1 }); await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, { offset: false }); await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, { offset: true }); await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, true); await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, '42'); await testInvalid(fd, 'ERR_INVALID_ARG_TYPE', buffer, Symbol('42')); // Test compatibility with fs.read counterpart for (const [ index, options ] of [ null, {}, { length: 1 }, { position: 5 }, { length: 1, position: 5 }, { length: 1, position: -1, offset: 2 }, { length: null }, { position: null }, { offset: 1 }, ].entries()) { await testValid(buffer, options, index); } } fs.open(destInvalid, 'w+', common.mustSucceed(async (fd) => { runTests(fd).then(common.mustCall(() => fs.close(fd, common.mustSucceed()))); }));