Compare commits

...

5 Commits

Author SHA1 Message Date
Dylan Conway
adf83f3634 almost fix test-fs-buffer.js 2025-01-02 08:32:21 -08:00
Dylan Conway
f283cda399 fix test-fs-assert-encoding-error.js 2025-01-01 13:00:17 -08:00
Dylan Conway
5a3d273259 fix test-fs-writev.js 2025-01-01 12:32:25 -08:00
Dylan Conway
b192ee2152 add all tests 2025-01-01 12:17:13 -08:00
Dylan Conway
920890ab80 fix test on windows 2025-01-01 10:28:48 -08:00
131 changed files with 13286 additions and 5 deletions

View File

@@ -127,9 +127,6 @@ std::optional<BufferEncodingType> parseEnumeration2(JSGlobalObject& lexicalGloba
case 'h':
case 'H':
// hex
if (encoding[1] == 'e')
if (encoding[2] == 'x' && encoding[3] == '\0')
return BufferEncodingType::hex;
if (WTF::equalIgnoringASCIICase(encoding, "hex"_s))
return BufferEncodingType::hex;
break;

View File

@@ -255,6 +255,14 @@ pub const Async = struct {
const args_: Arguments.Writev = task.args;
const fd = args_.fd.impl().uv();
const bufs = args_.buffers.buffers.items;
// https://github.com/nodejs/node/blob/35742a2d0b3ac1a1eff5ab333b5ae2fef009f00b/lib/fs.js#L953
if (bufs.len == 0) {
task.result = Maybe(Return.Writev){ .result = .{ .bytes_written = 0 } };
task.globalObject.bunVM().eventLoop().enqueueTask(JSC.Task.init(task));
return task.promise.value();
}
const pos: i64 = args_.position orelse -1;
var sum: u64 = 0;

View File

@@ -763,6 +763,12 @@ function ReadStream(this: typeof ReadStream, pathOrFd, options) {
options = { encoding: options };
}
if (options.encoding && options.encoding !== "buffer") {
if (!Buffer.isEncoding(options.encoding)) {
throw $ERR_INVALID_ARG_VALUE("encoding", options.encoding, "is invalid encoding");
}
}
if (!$isObject(options) && !$isCallable(options)) {
throw new TypeError("Expected options to be an object or a string");
}
@@ -1070,6 +1076,16 @@ var WriteStreamClass = (WriteStream = function WriteStream(path, options = defau
throw new TypeError("Expected options to be an object");
}
if (typeof options === "string") {
options = { encoding: options };
}
if (options.encoding && options.encoding !== "buffer") {
if (!Buffer.isEncoding(options.encoding)) {
throw $ERR_INVALID_ARG_VALUE("encoding", options.encoding, "is invalid encoding");
}
}
var {
fs = defaultWriteStreamOptions.fs,
start = defaultWriteStreamOptions.start,

View File

@@ -0,0 +1,238 @@
// Flags: --expose-internals
'use strict';
// This tests that fs.access and fs.accessSync works as expected
// and the errors thrown from these APIs include the desired properties
const common = require('../common');
if (!common.isWindows && process.getuid() === 0)
common.skip('as this test should not be run as `root`');
if (common.isIBMi)
common.skip('IBMi has a different access permission mechanism');
const assert = require('assert');
const fs = require('fs');
const { internalBinding } = require('internal/test/binding');
const { UV_ENOENT } = internalBinding('uv');
const tmpdir = require('../common/tmpdir');
const doesNotExist = tmpdir.resolve('__this_should_not_exist');
const readOnlyFile = tmpdir.resolve('read_only_file');
const readWriteFile = tmpdir.resolve('read_write_file');
function createFileWithPerms(file, mode) {
fs.writeFileSync(file, '');
fs.chmodSync(file, mode);
}
tmpdir.refresh();
createFileWithPerms(readOnlyFile, 0o444);
createFileWithPerms(readWriteFile, 0o666);
// On non-Windows supported platforms, fs.access(readOnlyFile, W_OK, ...)
// always succeeds if node runs as the super user, which is sometimes the
// case for tests running on our continuous testing platform agents.
//
// In this case, this test tries to change its process user id to a
// non-superuser user so that the test that checks for write access to a
// read-only file can be more meaningful.
//
// The change of user id is done after creating the fixtures files for the same
// reason: the test may be run as the superuser within a directory in which
// only the superuser can create files, and thus it may need superuser
// privileges to create them.
//
// There's not really any point in resetting the process' user id to 0 after
// changing it to 'nobody', since in the case that the test runs without
// superuser privilege, it is not possible to change its process user id to
// superuser.
//
// It can prevent the test from removing files created before the change of user
// id, but that's fine. In this case, it is the responsibility of the
// continuous integration platform to take care of that.
let hasWriteAccessForReadonlyFile = false;
if (!common.isWindows && process.getuid() === 0) {
hasWriteAccessForReadonlyFile = true;
try {
process.setuid('nobody');
hasWriteAccessForReadonlyFile = false;
} catch {
// Continue regardless of error.
}
}
assert.strictEqual(typeof fs.constants.F_OK, 'number');
assert.strictEqual(typeof fs.constants.R_OK, 'number');
assert.strictEqual(typeof fs.constants.W_OK, 'number');
assert.strictEqual(typeof fs.constants.X_OK, 'number');
const throwNextTick = (e) => { process.nextTick(() => { throw e; }); };
fs.access(__filename, common.mustCall(function(...args) {
assert.deepStrictEqual(args, [null]);
}));
fs.promises.access(__filename)
.then(common.mustCall())
.catch(throwNextTick);
fs.access(__filename, fs.constants.R_OK, common.mustCall(function(...args) {
assert.deepStrictEqual(args, [null]);
}));
fs.promises.access(__filename, fs.constants.R_OK)
.then(common.mustCall())
.catch(throwNextTick);
fs.access(readOnlyFile, fs.constants.R_OK, common.mustCall(function(...args) {
assert.deepStrictEqual(args, [null]);
}));
fs.promises.access(readOnlyFile, fs.constants.R_OK)
.then(common.mustCall())
.catch(throwNextTick);
{
const expectedError = (err) => {
assert.notStrictEqual(err, null);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.path, doesNotExist);
};
const expectedErrorPromise = (err) => {
expectedError(err);
assert.match(err.stack, /at async Object\.access/);
};
fs.access(doesNotExist, common.mustCall(expectedError));
fs.promises.access(doesNotExist)
.then(common.mustNotCall(), common.mustCall(expectedErrorPromise))
.catch(throwNextTick);
}
{
function expectedError(err) {
assert.strictEqual(this, undefined);
if (hasWriteAccessForReadonlyFile) {
assert.ifError(err);
} else {
assert.notStrictEqual(err, null);
assert.strictEqual(err.path, readOnlyFile);
}
}
fs.access(readOnlyFile, fs.constants.W_OK, common.mustCall(expectedError));
fs.promises.access(readOnlyFile, fs.constants.W_OK)
.then(common.mustNotCall(), common.mustCall(expectedError))
.catch(throwNextTick);
}
{
const expectedError = (err) => {
assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE');
assert.ok(err instanceof TypeError);
return true;
};
assert.throws(
() => { fs.access(100, fs.constants.F_OK, common.mustNotCall()); },
expectedError
);
fs.promises.access(100, fs.constants.F_OK)
.then(common.mustNotCall(), common.mustCall(expectedError))
.catch(throwNextTick);
}
assert.throws(
() => {
fs.access(__filename, fs.constants.F_OK);
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
assert.throws(
() => {
fs.access(__filename, fs.constants.F_OK, common.mustNotMutateObjectDeep({}));
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
// Regular access should not throw.
fs.accessSync(__filename);
const mode = fs.constants.R_OK | fs.constants.W_OK;
fs.accessSync(readWriteFile, mode);
// Invalid modes should throw.
[
false,
1n,
{ [Symbol.toPrimitive]() { return fs.constants.R_OK; } },
[1],
'r',
].forEach((mode, i) => {
console.log(mode, i);
assert.throws(
() => fs.access(readWriteFile, mode, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
}
);
assert.throws(
() => fs.accessSync(readWriteFile, mode),
{
code: 'ERR_INVALID_ARG_TYPE',
}
);
});
// Out of range modes should throw
[
-1,
8,
Infinity,
NaN,
].forEach((mode, i) => {
console.log(mode, i);
assert.throws(
() => fs.access(readWriteFile, mode, common.mustNotCall()),
{
code: 'ERR_OUT_OF_RANGE',
}
);
assert.throws(
() => fs.accessSync(readWriteFile, mode),
{
code: 'ERR_OUT_OF_RANGE',
}
);
});
assert.throws(
() => { fs.accessSync(doesNotExist); },
(err) => {
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.path, doesNotExist);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, access '${doesNotExist}'`
);
assert.strictEqual(err.constructor, Error);
assert.strictEqual(err.syscall, 'access');
assert.strictEqual(err.errno, UV_ENOENT);
return true;
}
);
assert.throws(
() => { fs.accessSync(Buffer.from(doesNotExist)); },
(err) => {
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.path, doesNotExist);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, access '${doesNotExist}'`
);
assert.strictEqual(err.constructor, Error);
assert.strictEqual(err.syscall, 'access');
assert.strictEqual(err.errno, UV_ENOENT);
return true;
}
);

View File

@@ -0,0 +1,114 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('node:assert');
const fs = require('node:fs');
const fsp = require('node:fs/promises');
const test = require('node:test');
const data = 'foo';
let cnt = 0;
function nextFile() {
return tmpdir.resolve(`${cnt++}.out`);
}
tmpdir.refresh();
test('synchronous version', async (t) => {
await t.test('validation', (t) => {
for (const v of ['true', '', 0, 1, [], {}, Symbol()]) {
assert.throws(() => {
fs.appendFileSync(nextFile(), data, { flush: v });
}, { code: 'ERR_INVALID_ARG_TYPE' });
}
});
await t.test('performs flush', (t) => {
const spy = t.mock.method(fs, 'fsyncSync');
const file = nextFile();
fs.appendFileSync(file, data, { flush: true });
const calls = spy.mock.calls;
assert.strictEqual(calls.length, 1);
assert.strictEqual(calls[0].result, undefined);
assert.strictEqual(calls[0].error, undefined);
assert.strictEqual(calls[0].arguments.length, 1);
assert.strictEqual(typeof calls[0].arguments[0], 'number');
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
});
await t.test('does not perform flush', (t) => {
const spy = t.mock.method(fs, 'fsyncSync');
for (const v of [undefined, null, false]) {
const file = nextFile();
fs.appendFileSync(file, data, { flush: v });
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
}
assert.strictEqual(spy.mock.calls.length, 0);
});
});
test('callback version', async (t) => {
await t.test('validation', (t) => {
for (const v of ['true', '', 0, 1, [], {}, Symbol()]) {
assert.throws(() => {
fs.appendFileSync(nextFile(), data, { flush: v });
}, { code: 'ERR_INVALID_ARG_TYPE' });
}
});
await t.test('performs flush', (t, done) => {
const spy = t.mock.method(fs, 'fsync');
const file = nextFile();
fs.appendFile(file, data, { flush: true }, common.mustSucceed(() => {
const calls = spy.mock.calls;
assert.strictEqual(calls.length, 1);
assert.strictEqual(calls[0].result, undefined);
assert.strictEqual(calls[0].error, undefined);
assert.strictEqual(calls[0].arguments.length, 2);
assert.strictEqual(typeof calls[0].arguments[0], 'number');
assert.strictEqual(typeof calls[0].arguments[1], 'function');
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
done();
}));
});
await t.test('does not perform flush', (t, done) => {
const values = [undefined, null, false];
const spy = t.mock.method(fs, 'fsync');
let cnt = 0;
for (const v of values) {
const file = nextFile();
fs.appendFile(file, data, { flush: v }, common.mustSucceed(() => {
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
cnt++;
if (cnt === values.length) {
assert.strictEqual(spy.mock.calls.length, 0);
done();
}
}));
}
});
});
test('promise based version', async (t) => {
await t.test('validation', async (t) => {
for (const v of ['true', '', 0, 1, [], {}, Symbol()]) {
await assert.rejects(() => {
return fsp.appendFile(nextFile(), data, { flush: v });
}, { code: 'ERR_INVALID_ARG_TYPE' });
}
});
await t.test('success path', async (t) => {
for (const v of [undefined, null, false, true]) {
const file = nextFile();
await fsp.appendFile(file, data, { flush: v });
assert.strictEqual(await fsp.readFile(file, 'utf8'), data);
}
});
});

View File

@@ -0,0 +1,102 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const currentFileData = 'ABCD';
const m = 0o600;
const num = 220;
const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const data = fixtures.utf8TestText;
tmpdir.refresh();
// Test that empty file will be created and have content added.
const filename = tmpdir.resolve('append-sync.txt');
fs.appendFileSync(filename, data);
const fileData = fs.readFileSync(filename);
assert.strictEqual(Buffer.byteLength(data), fileData.length);
// Test that appends data to a non empty file.
const filename2 = tmpdir.resolve('append-sync2.txt');
fs.writeFileSync(filename2, currentFileData);
fs.appendFileSync(filename2, data);
const fileData2 = fs.readFileSync(filename2);
assert.strictEqual(Buffer.byteLength(data) + currentFileData.length,
fileData2.length);
// Test that appendFileSync accepts buffers.
const filename3 = tmpdir.resolve('append-sync3.txt');
fs.writeFileSync(filename3, currentFileData);
const buf = Buffer.from(data, 'utf8');
fs.appendFileSync(filename3, buf);
const fileData3 = fs.readFileSync(filename3);
assert.strictEqual(buf.length + currentFileData.length, fileData3.length);
const filename4 = tmpdir.resolve('append-sync4.txt');
fs.writeFileSync(filename4, currentFileData, common.mustNotMutateObjectDeep({ mode: m }));
[
true, false, 0, 1, Infinity, () => {}, {}, [], undefined, null,
].forEach((value) => {
assert.throws(
() => fs.appendFileSync(filename4, value, common.mustNotMutateObjectDeep({ mode: m })),
{ message: /data/, code: 'ERR_INVALID_ARG_TYPE' }
);
});
fs.appendFileSync(filename4, `${num}`, common.mustNotMutateObjectDeep({ mode: m }));
// Windows permissions aren't Unix.
if (!common.isWindows) {
const st = fs.statSync(filename4);
assert.strictEqual(st.mode & 0o700, m);
}
const fileData4 = fs.readFileSync(filename4);
assert.strictEqual(Buffer.byteLength(String(num)) + currentFileData.length,
fileData4.length);
// Test that appendFile accepts file descriptors.
const filename5 = tmpdir.resolve('append-sync5.txt');
fs.writeFileSync(filename5, currentFileData);
const filename5fd = fs.openSync(filename5, 'a+', 0o600);
fs.appendFileSync(filename5fd, data);
fs.closeSync(filename5fd);
const fileData5 = fs.readFileSync(filename5);
assert.strictEqual(Buffer.byteLength(data) + currentFileData.length,
fileData5.length);

View File

@@ -0,0 +1,187 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const currentFileData = 'ABCD';
const fixtures = require('../common/fixtures');
const s = fixtures.utf8TestText;
tmpdir.refresh();
const throwNextTick = (e) => { process.nextTick(() => { throw e; }); };
// Test that empty file will be created and have content added (callback API).
{
const filename = tmpdir.resolve('append.txt');
fs.appendFile(filename, s, common.mustSucceed(() => {
fs.readFile(filename, common.mustSucceed((buffer) => {
assert.strictEqual(Buffer.byteLength(s), buffer.length);
}));
}));
}
// Test that empty file will be created and have content added (promise API).
{
const filename = tmpdir.resolve('append-promise.txt');
fs.promises.appendFile(filename, s)
.then(common.mustCall(() => fs.promises.readFile(filename)))
.then((buffer) => {
assert.strictEqual(Buffer.byteLength(s), buffer.length);
})
.catch(throwNextTick);
}
// Test that appends data to a non-empty file (callback API).
{
const filename = tmpdir.resolve('append-non-empty.txt');
fs.writeFileSync(filename, currentFileData);
fs.appendFile(filename, s, common.mustSucceed(() => {
fs.readFile(filename, common.mustSucceed((buffer) => {
assert.strictEqual(Buffer.byteLength(s) + currentFileData.length,
buffer.length);
}));
}));
}
// Test that appends data to a non-empty file (promise API).
{
const filename = tmpdir.resolve('append-non-empty-promise.txt');
fs.writeFileSync(filename, currentFileData);
fs.promises.appendFile(filename, s)
.then(common.mustCall(() => fs.promises.readFile(filename)))
.then((buffer) => {
assert.strictEqual(Buffer.byteLength(s) + currentFileData.length,
buffer.length);
})
.catch(throwNextTick);
}
// Test that appendFile accepts buffers (callback API).
{
const filename = tmpdir.resolve('append-buffer.txt');
fs.writeFileSync(filename, currentFileData);
const buf = Buffer.from(s, 'utf8');
fs.appendFile(filename, buf, common.mustSucceed(() => {
fs.readFile(filename, common.mustSucceed((buffer) => {
assert.strictEqual(buf.length + currentFileData.length, buffer.length);
}));
}));
}
// Test that appendFile accepts buffers (promises API).
{
const filename = tmpdir.resolve('append-buffer-promises.txt');
fs.writeFileSync(filename, currentFileData);
const buf = Buffer.from(s, 'utf8');
fs.promises.appendFile(filename, buf)
.then(common.mustCall(() => fs.promises.readFile(filename)))
.then((buffer) => {
assert.strictEqual(buf.length + currentFileData.length, buffer.length);
})
.catch(throwNextTick);
}
// Test that appendFile does not accept invalid data type (callback API).
[false, 5, {}, null, undefined].forEach(async (data) => {
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
message: /"data"|"buffer"/
};
const filename = tmpdir.resolve('append-invalid-data.txt');
assert.throws(
() => fs.appendFile(filename, data, common.mustNotCall()),
errObj
);
assert.throws(
() => fs.appendFileSync(filename, data),
errObj
);
await assert.rejects(
fs.promises.appendFile(filename, data),
errObj
);
// The filename shouldn't exist if throwing error.
assert.throws(
() => fs.statSync(filename),
{
code: 'ENOENT',
message: /no such file or directory/
}
);
});
// Test that appendFile accepts file descriptors (callback API).
{
const filename = tmpdir.resolve('append-descriptors.txt');
fs.writeFileSync(filename, currentFileData);
fs.open(filename, 'a+', common.mustSucceed((fd) => {
fs.appendFile(fd, s, common.mustSucceed(() => {
fs.close(fd, common.mustSucceed(() => {
fs.readFile(filename, common.mustSucceed((buffer) => {
assert.strictEqual(Buffer.byteLength(s) + currentFileData.length,
buffer.length);
}));
}));
}));
}));
}
// Test that appendFile accepts file descriptors (promises API).
{
const filename = tmpdir.resolve('append-descriptors-promises.txt');
fs.writeFileSync(filename, currentFileData);
let fd;
fs.promises.open(filename, 'a+')
.then(common.mustCall((fileDescriptor) => {
fd = fileDescriptor;
return fs.promises.appendFile(fd, s);
}))
.then(common.mustCall(() => fd.close()))
.then(common.mustCall(() => fs.promises.readFile(filename)))
.then(common.mustCall((buffer) => {
assert.strictEqual(Buffer.byteLength(s) + currentFileData.length,
buffer.length);
}))
.catch(throwNextTick);
}
assert.throws(
() => fs.appendFile(tmpdir.resolve('append6.txt'), console.log),
{ code: 'ERR_INVALID_ARG_TYPE' });

View File

@@ -0,0 +1,80 @@
'use strict';
const common = require('../common');
const assert = require('node:assert');
const fs = require('node:fs');
const tmpdir = require('../common/tmpdir');
const testPath = tmpdir.resolve('assert-encoding-error');
const options = 'test';
const expectedError = {
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError',
};
assert.throws(() => {
fs.readFile(testPath, options, common.mustNotCall());
}, expectedError);
assert.throws(() => {
fs.readFileSync(testPath, options);
}, expectedError);
assert.throws(() => {
fs.readdir(testPath, options, common.mustNotCall());
}, expectedError);
assert.throws(() => {
fs.readdirSync(testPath, options);
}, expectedError);
assert.throws(() => {
fs.readlink(testPath, options, common.mustNotCall());
}, expectedError);
assert.throws(() => {
fs.readlinkSync(testPath, options);
}, expectedError);
assert.throws(() => {
fs.writeFile(testPath, 'data', options, common.mustNotCall());
}, expectedError);
assert.throws(() => {
fs.writeFileSync(testPath, 'data', options);
}, expectedError);
assert.throws(() => {
fs.appendFile(testPath, 'data', options, common.mustNotCall());
}, expectedError);
assert.throws(() => {
fs.appendFileSync(testPath, 'data', options);
}, expectedError);
assert.throws(() => {
fs.watch(testPath, options, common.mustNotCall());
}, expectedError);
assert.throws(() => {
fs.realpath(testPath, options, common.mustNotCall());
}, expectedError);
assert.throws(() => {
fs.realpathSync(testPath, options);
}, expectedError);
assert.throws(() => {
fs.mkdtemp(testPath, options, common.mustNotCall());
}, expectedError);
assert.throws(() => {
fs.mkdtempSync(testPath, options);
}, expectedError);
assert.throws(() => {
fs.ReadStream(testPath, options);
}, expectedError);
assert.throws(() => {
fs.WriteStream(testPath, options);
}, expectedError);

View File

@@ -0,0 +1,43 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
fs.access(Buffer.from(tmpdir.path), common.mustSucceed());
const buf = Buffer.from(tmpdir.resolve('a.txt'));
fs.open(buf, 'w+', common.mustSucceed((fd) => {
assert(fd);
fs.close(fd, common.mustSucceed());
}));
assert.throws(
() => {
fs.accessSync(true);
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "path" argument must be of type string or an instance of ' +
'Buffer or URL. Received type boolean (true)'
}
);
const dir = Buffer.from(fixtures.fixturesDir);
fs.readdir(dir, 'hex', common.mustSucceed((hexList) => {
fs.readdir(dir, common.mustSucceed((stringList) => {
stringList.forEach((val, idx) => {
const fromHexList = Buffer.from(hexList[idx], 'hex').toString();
assert.strictEqual(
fromHexList,
val,
`expected ${val}, got ${fromHexList} by hex decoding ${hexList[idx]}`
);
});
}));
}));

View File

@@ -0,0 +1,89 @@
'use strict';
// This tests that the lower bits of mode > 0o777 still works in fs APIs.
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
let mode;
// On Windows chmod is only able to manipulate write permission
if (common.isWindows) {
mode = 0o444; // read-only
} else {
mode = 0o777;
}
const maskToIgnore = 0o10000;
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
function test(mode, asString) {
const suffix = asString ? 'str' : 'num';
const input = asString ?
(mode | maskToIgnore).toString(8) : (mode | maskToIgnore);
{
const file = tmpdir.resolve(`chmod-async-${suffix}.txt`);
fs.writeFileSync(file, 'test', 'utf-8');
fs.chmod(file, input, common.mustSucceed(() => {
assert.strictEqual(fs.statSync(file).mode & 0o777, mode);
}));
}
{
const file = tmpdir.resolve(`chmodSync-${suffix}.txt`);
fs.writeFileSync(file, 'test', 'utf-8');
fs.chmodSync(file, input);
assert.strictEqual(fs.statSync(file).mode & 0o777, mode);
}
{
const file = tmpdir.resolve(`fchmod-async-${suffix}.txt`);
fs.writeFileSync(file, 'test', 'utf-8');
fs.open(file, 'w', common.mustSucceed((fd) => {
fs.fchmod(fd, input, common.mustSucceed(() => {
assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode);
fs.close(fd, assert.ifError);
}));
}));
}
{
const file = tmpdir.resolve(`fchmodSync-${suffix}.txt`);
fs.writeFileSync(file, 'test', 'utf-8');
const fd = fs.openSync(file, 'w');
fs.fchmodSync(fd, input);
assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode);
fs.close(fd, assert.ifError);
}
if (fs.lchmod) {
const link = tmpdir.resolve(`lchmod-src-${suffix}`);
const file = tmpdir.resolve(`lchmod-dest-${suffix}`);
fs.writeFileSync(file, 'test', 'utf-8');
fs.symlinkSync(file, link);
fs.lchmod(link, input, common.mustSucceed(() => {
assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode);
}));
}
if (fs.lchmodSync) {
const link = tmpdir.resolve(`lchmodSync-src-${suffix}`);
const file = tmpdir.resolve(`lchmodSync-dest-${suffix}`);
fs.writeFileSync(file, 'test', 'utf-8');
fs.symlinkSync(file, link);
fs.lchmodSync(link, input);
assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode);
}
}
test(mode, true);
test(mode, false);

View File

@@ -0,0 +1,152 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
let mode_async;
let mode_sync;
// Need to hijack fs.open/close to make sure that things
// get closed once they're opened.
fs._open = fs.open;
fs._openSync = fs.openSync;
fs.open = open;
fs.openSync = openSync;
fs._close = fs.close;
fs._closeSync = fs.closeSync;
fs.close = close;
fs.closeSync = closeSync;
let openCount = 0;
function open() {
openCount++;
return fs._open.apply(fs, arguments);
}
function openSync() {
openCount++;
return fs._openSync.apply(fs, arguments);
}
function close() {
openCount--;
return fs._close.apply(fs, arguments);
}
function closeSync() {
openCount--;
return fs._closeSync.apply(fs, arguments);
}
// On Windows chmod is only able to manipulate write permission
if (common.isWindows) {
mode_async = 0o400; // read-only
mode_sync = 0o600; // read-write
} else {
mode_async = 0o777;
mode_sync = 0o644;
}
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const file1 = tmpdir.resolve('a.js');
const file2 = tmpdir.resolve('a1.js');
// Create file1.
fs.closeSync(fs.openSync(file1, 'w'));
fs.chmod(file1, mode_async.toString(8), common.mustSucceed(() => {
if (common.isWindows) {
assert.ok((fs.statSync(file1).mode & 0o777) & mode_async);
} else {
assert.strictEqual(fs.statSync(file1).mode & 0o777, mode_async);
}
fs.chmodSync(file1, mode_sync);
if (common.isWindows) {
assert.ok((fs.statSync(file1).mode & 0o777) & mode_sync);
} else {
assert.strictEqual(fs.statSync(file1).mode & 0o777, mode_sync);
}
}));
fs.open(file2, 'w', common.mustSucceed((fd) => {
fs.fchmod(fd, mode_async.toString(8), common.mustSucceed(() => {
if (common.isWindows) {
assert.ok((fs.fstatSync(fd).mode & 0o777) & mode_async);
} else {
assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode_async);
}
assert.throws(
() => fs.fchmod(fd, {}),
{
code: 'ERR_INVALID_ARG_TYPE',
}
);
fs.fchmodSync(fd, mode_sync);
if (common.isWindows) {
assert.ok((fs.fstatSync(fd).mode & 0o777) & mode_sync);
} else {
assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode_sync);
}
fs.close(fd, assert.ifError);
}));
}));
// lchmod
if (fs.lchmod) {
const link = tmpdir.resolve('symbolic-link');
fs.symlinkSync(file2, link);
fs.lchmod(link, mode_async, common.mustSucceed(() => {
assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode_async);
fs.lchmodSync(link, mode_sync);
assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode_sync);
}));
}
[false, 1, {}, [], null, undefined].forEach((input) => {
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "path" argument must be of type string or an instance ' +
'of Buffer or URL.' +
common.invalidArgTypeHelper(input)
};
assert.throws(() => fs.chmod(input, 1, common.mustNotCall()), errObj);
assert.throws(() => fs.chmodSync(input, 1), errObj);
});
process.on('exit', function() {
assert.strictEqual(openCount, 0);
});

View File

@@ -0,0 +1,35 @@
'use strict';
// This tests that the errors thrown from fs.close and fs.closeSync
// include the desired properties
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
['', false, null, undefined, {}, []].forEach((input) => {
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "fd" argument must be of type number.' +
common.invalidArgTypeHelper(input)
};
assert.throws(() => fs.close(input), errObj);
assert.throws(() => fs.closeSync(input), errObj);
});
{
// Test error when cb is not a function
const fd = fs.openSync(__filename, 'r');
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
};
['', false, null, {}, []].forEach((input) => {
assert.throws(() => fs.close(fd, input), errObj);
});
fs.closeSync(fd);
}

View File

@@ -0,0 +1,12 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const fd = fs.openSync(__filename, 'r');
fs.close(fd, common.mustCall(function(...args) {
assert.deepStrictEqual(args, [null]);
}));

View File

@@ -0,0 +1,166 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const { internalBinding } = require('internal/test/binding');
const {
UV_ENOENT,
UV_EEXIST
} = internalBinding('uv');
const src = fixtures.path('a.js');
const dest = tmpdir.resolve('copyfile.out');
const {
COPYFILE_EXCL,
COPYFILE_FICLONE,
COPYFILE_FICLONE_FORCE,
UV_FS_COPYFILE_EXCL,
UV_FS_COPYFILE_FICLONE,
UV_FS_COPYFILE_FICLONE_FORCE
} = fs.constants;
function verify(src, dest) {
const srcData = fs.readFileSync(src, 'utf8');
const srcStat = fs.statSync(src);
const destData = fs.readFileSync(dest, 'utf8');
const destStat = fs.statSync(dest);
assert.strictEqual(srcData, destData);
assert.strictEqual(srcStat.mode, destStat.mode);
assert.strictEqual(srcStat.size, destStat.size);
}
tmpdir.refresh();
// Verify that flags are defined.
assert.strictEqual(typeof COPYFILE_EXCL, 'number');
assert.strictEqual(typeof COPYFILE_FICLONE, 'number');
assert.strictEqual(typeof COPYFILE_FICLONE_FORCE, 'number');
assert.strictEqual(typeof UV_FS_COPYFILE_EXCL, 'number');
assert.strictEqual(typeof UV_FS_COPYFILE_FICLONE, 'number');
assert.strictEqual(typeof UV_FS_COPYFILE_FICLONE_FORCE, 'number');
assert.strictEqual(COPYFILE_EXCL, UV_FS_COPYFILE_EXCL);
assert.strictEqual(COPYFILE_FICLONE, UV_FS_COPYFILE_FICLONE);
assert.strictEqual(COPYFILE_FICLONE_FORCE, UV_FS_COPYFILE_FICLONE_FORCE);
// Verify that files are overwritten when no flags are provided.
fs.writeFileSync(dest, '', 'utf8');
const result = fs.copyFileSync(src, dest);
assert.strictEqual(result, undefined);
verify(src, dest);
// Verify that files are overwritten with default flags.
fs.copyFileSync(src, dest, 0);
verify(src, dest);
// Verify that UV_FS_COPYFILE_FICLONE can be used.
fs.unlinkSync(dest);
fs.copyFileSync(src, dest, UV_FS_COPYFILE_FICLONE);
verify(src, dest);
// Verify that COPYFILE_FICLONE_FORCE can be used.
try {
fs.unlinkSync(dest);
fs.copyFileSync(src, dest, COPYFILE_FICLONE_FORCE);
verify(src, dest);
} catch (err) {
assert.strictEqual(err.syscall, 'copyfile');
assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' ||
err.code === 'ENOSYS' || err.code === 'EXDEV');
assert.strictEqual(err.path, src);
assert.strictEqual(err.dest, dest);
}
// Copies asynchronously.
tmpdir.refresh(); // Don't use unlinkSync() since the last test may fail.
fs.copyFile(src, dest, common.mustSucceed(() => {
verify(src, dest);
// Copy asynchronously with flags.
fs.copyFile(src, dest, COPYFILE_EXCL, common.mustCall((err) => {
if (err.code === 'ENOENT') { // Could be ENOENT or EEXIST
assert.strictEqual(err.message,
'ENOENT: no such file or directory, copyfile ' +
`'${src}' -> '${dest}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'copyfile');
} else {
assert.strictEqual(err.message,
'EEXIST: file already exists, copyfile ' +
`'${src}' -> '${dest}'`);
assert.strictEqual(err.errno, UV_EEXIST);
assert.strictEqual(err.code, 'EEXIST');
assert.strictEqual(err.syscall, 'copyfile');
}
}));
}));
// Throws if callback is not a function.
assert.throws(() => {
fs.copyFile(src, dest, 0, 0);
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
// Throws if the source path is not a string.
[false, 1, {}, [], null, undefined].forEach((i) => {
assert.throws(
() => fs.copyFile(i, dest, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /src/
}
);
assert.throws(
() => fs.copyFile(src, i, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /dest/
}
);
assert.throws(
() => fs.copyFileSync(i, dest),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /src/
}
);
assert.throws(
() => fs.copyFileSync(src, i),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /dest/
}
);
});
assert.throws(() => {
fs.copyFileSync(src, dest, 'r');
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /mode/
});
assert.throws(() => {
fs.copyFileSync(src, dest, 8);
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
});
assert.throws(() => {
fs.copyFile(src, dest, 'r', common.mustNotCall());
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /mode/
});

View File

@@ -0,0 +1,850 @@
// Flags: --expose-internals
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
tmpdir.refresh();
const nonexistentFile = tmpdir.resolve('non-existent');
const nonexistentDir = tmpdir.resolve('non-existent', 'foo', 'bar');
const existingFile = tmpdir.resolve('existingFile.js');
const existingFile2 = tmpdir.resolve('existingFile2.js');
const existingDir = tmpdir.resolve('dir');
const existingDir2 = fixtures.path('keys');
fs.mkdirSync(existingDir);
fs.writeFileSync(existingFile, 'test', 'utf-8');
fs.writeFileSync(existingFile2, 'test', 'utf-8');
const { COPYFILE_EXCL } = fs.constants;
const { internalBinding } = require('internal/test/binding');
const {
UV_EBADF,
UV_EEXIST,
UV_EINVAL,
UV_ENOENT,
UV_ENOTDIR,
UV_ENOTEMPTY,
UV_EPERM
} = internalBinding('uv');
// Template tag function for escaping special characters in strings so that:
// new RegExp(re`${str}`).test(str) === true
function re(literals, ...values) {
const escapeRE = /[\\^$.*+?()[\]{}|=!<>:-]/g;
let result = literals[0].replace(escapeRE, '\\$&');
for (const [i, value] of values.entries()) {
result += value.replace(escapeRE, '\\$&');
result += literals[i + 1].replace(escapeRE, '\\$&');
}
return result;
}
// stat
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, stat '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'stat');
return true;
};
fs.stat(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.statSync(nonexistentFile),
validateError
);
}
// lstat
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, lstat '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'lstat');
return true;
};
fs.lstat(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.lstatSync(nonexistentFile),
validateError
);
}
// fstat
{
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, fstat');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'fstat');
return true;
};
common.runWithInvalidFD((fd) => {
fs.fstat(fd, common.mustCall(validateError));
assert.throws(
() => fs.fstatSync(fd),
validateError
);
});
}
// realpath
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, lstat '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'lstat');
return true;
};
fs.realpath(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.realpathSync(nonexistentFile),
validateError
);
}
// native realpath
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, realpath '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'realpath');
return true;
};
fs.realpath.native(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.realpathSync.native(nonexistentFile),
validateError
);
}
// readlink
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, readlink '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'readlink');
return true;
};
fs.readlink(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.readlinkSync(nonexistentFile),
validateError
);
}
// Link nonexistent file
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
// Could be resolved to an absolute path
assert.ok(err.dest.endsWith('foo'),
`expect ${err.dest} to end with 'foo'`);
const regexp = new RegExp('^ENOENT: no such file or directory, link ' +
re`'${nonexistentFile}' -> ` + '\'.*foo\'');
assert.match(err.message, regexp);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'link');
return true;
};
fs.link(nonexistentFile, 'foo', common.mustCall(validateError));
assert.throws(
() => fs.linkSync(nonexistentFile, 'foo'),
validateError
);
}
// link existing file
{
const validateError = (err) => {
assert.strictEqual(existingFile, err.path);
assert.strictEqual(existingFile2, err.dest);
assert.strictEqual(
err.message,
`EEXIST: file already exists, link '${existingFile}' -> ` +
`'${existingFile2}'`);
assert.strictEqual(err.errno, UV_EEXIST);
assert.strictEqual(err.code, 'EEXIST');
assert.strictEqual(err.syscall, 'link');
return true;
};
fs.link(existingFile, existingFile2, common.mustCall(validateError));
assert.throws(
() => fs.linkSync(existingFile, existingFile2),
validateError
);
}
// symlink
{
const validateError = (err) => {
assert.strictEqual(existingFile, err.path);
assert.strictEqual(existingFile2, err.dest);
assert.strictEqual(
err.message,
`EEXIST: file already exists, symlink '${existingFile}' -> ` +
`'${existingFile2}'`);
assert.strictEqual(err.errno, UV_EEXIST);
assert.strictEqual(err.code, 'EEXIST');
assert.strictEqual(err.syscall, 'symlink');
return true;
};
fs.symlink(existingFile, existingFile2, common.mustCall(validateError));
assert.throws(
() => fs.symlinkSync(existingFile, existingFile2),
validateError
);
}
// unlink
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, unlink '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'unlink');
return true;
};
fs.unlink(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.unlinkSync(nonexistentFile),
validateError
);
}
// rename
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
// Could be resolved to an absolute path
assert.ok(err.dest.endsWith('foo'),
`expect ${err.dest} to end with 'foo'`);
const regexp = new RegExp('ENOENT: no such file or directory, rename ' +
re`'${nonexistentFile}' -> ` + '\'.*foo\'');
assert.match(err.message, regexp);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'rename');
return true;
};
const destFile = tmpdir.resolve('foo');
fs.rename(nonexistentFile, destFile, common.mustCall(validateError));
assert.throws(
() => fs.renameSync(nonexistentFile, destFile),
validateError
);
}
// Rename non-empty directory
{
const validateError = (err) => {
assert.strictEqual(existingDir, err.path);
assert.strictEqual(existingDir2, err.dest);
assert.strictEqual(err.syscall, 'rename');
// Could be ENOTEMPTY, EEXIST, or EPERM, depending on the platform
if (err.code === 'ENOTEMPTY') {
assert.strictEqual(
err.message,
`ENOTEMPTY: directory not empty, rename '${existingDir}' -> ` +
`'${existingDir2}'`);
assert.strictEqual(err.errno, UV_ENOTEMPTY);
} else if (err.code === 'EXDEV') { // Not on the same mounted filesystem
assert.strictEqual(
err.message,
`EXDEV: cross-device link not permitted, rename '${existingDir}' -> ` +
`'${existingDir2}'`);
} else if (err.code === 'EEXIST') { // smartos and aix
assert.strictEqual(
err.message,
`EEXIST: file already exists, rename '${existingDir}' -> ` +
`'${existingDir2}'`);
assert.strictEqual(err.errno, UV_EEXIST);
} else { // windows
assert.strictEqual(
err.message,
`EPERM: operation not permitted, rename '${existingDir}' -> ` +
`'${existingDir2}'`);
assert.strictEqual(err.errno, UV_EPERM);
assert.strictEqual(err.code, 'EPERM');
}
return true;
};
fs.rename(existingDir, existingDir2, common.mustCall(validateError));
assert.throws(
() => fs.renameSync(existingDir, existingDir2),
validateError
);
}
// rmdir
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, rmdir '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'rmdir');
return true;
};
fs.rmdir(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.rmdirSync(nonexistentFile),
validateError
);
}
// rmdir a file
{
const validateError = (err) => {
assert.strictEqual(existingFile, err.path);
assert.strictEqual(err.syscall, 'rmdir');
if (err.code === 'ENOTDIR') {
assert.strictEqual(
err.message,
`ENOTDIR: not a directory, rmdir '${existingFile}'`);
assert.strictEqual(err.errno, UV_ENOTDIR);
} else { // windows
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, rmdir '${existingFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
}
return true;
};
fs.rmdir(existingFile, common.mustCall(validateError));
assert.throws(
() => fs.rmdirSync(existingFile),
validateError
);
}
// mkdir
{
const validateError = (err) => {
assert.strictEqual(existingFile, err.path);
assert.strictEqual(
err.message,
`EEXIST: file already exists, mkdir '${existingFile}'`);
assert.strictEqual(err.errno, UV_EEXIST);
assert.strictEqual(err.code, 'EEXIST');
assert.strictEqual(err.syscall, 'mkdir');
return true;
};
fs.mkdir(existingFile, 0o666, common.mustCall(validateError));
assert.throws(
() => fs.mkdirSync(existingFile, 0o666),
validateError
);
}
// chmod
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, chmod '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'chmod');
return true;
};
fs.chmod(nonexistentFile, 0o666, common.mustCall(validateError));
assert.throws(
() => fs.chmodSync(nonexistentFile, 0o666),
validateError
);
}
// open
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, open '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'open');
return true;
};
fs.open(nonexistentFile, 'r', 0o666, common.mustCall(validateError));
assert.throws(
() => fs.openSync(nonexistentFile, 'r', 0o666),
validateError
);
}
// close
{
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, close');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'close');
return true;
};
common.runWithInvalidFD((fd) => {
fs.close(fd, common.mustCall(validateError));
assert.throws(
() => fs.closeSync(fd),
validateError
);
});
}
// readFile
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, open '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'open');
return true;
};
fs.readFile(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.readFileSync(nonexistentFile),
validateError
);
}
// readdir
{
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, scandir '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'scandir');
return true;
};
fs.readdir(nonexistentFile, common.mustCall(validateError));
assert.throws(
() => fs.readdirSync(nonexistentFile),
validateError
);
}
// ftruncate
{
const validateError = (err) => {
assert.strictEqual(err.syscall, 'ftruncate');
// Could be EBADF or EINVAL, depending on the platform
if (err.code === 'EBADF') {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, ftruncate');
assert.strictEqual(err.errno, UV_EBADF);
} else {
assert.strictEqual(err.message, 'EINVAL: invalid argument, ftruncate');
assert.strictEqual(err.errno, UV_EINVAL);
assert.strictEqual(err.code, 'EINVAL');
}
return true;
};
common.runWithInvalidFD((fd) => {
fs.ftruncate(fd, 4, common.mustCall(validateError));
assert.throws(
() => fs.ftruncateSync(fd, 4),
validateError
);
});
}
// fdatasync
{
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, fdatasync');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'fdatasync');
return true;
};
common.runWithInvalidFD((fd) => {
fs.fdatasync(fd, common.mustCall(validateError));
assert.throws(
() => fs.fdatasyncSync(fd),
validateError
);
});
}
// fsync
{
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, fsync');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'fsync');
return true;
};
common.runWithInvalidFD((fd) => {
fs.fsync(fd, common.mustCall(validateError));
assert.throws(
() => fs.fsyncSync(fd),
validateError
);
});
}
// chown
if (!common.isWindows) {
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, chown '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'chown');
return true;
};
fs.chown(nonexistentFile, process.getuid(), process.getgid(),
common.mustCall(validateError));
assert.throws(
() => fs.chownSync(nonexistentFile,
process.getuid(), process.getgid()),
validateError
);
}
// utimes
if (!common.isAIX) {
const validateError = (err) => {
assert.strictEqual(nonexistentFile, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, utime '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'utime');
return true;
};
fs.utimes(nonexistentFile, new Date(), new Date(),
common.mustCall(validateError));
assert.throws(
() => fs.utimesSync(nonexistentFile, new Date(), new Date()),
validateError
);
}
// mkdtemp
{
const validateError = (err) => {
const pathPrefix = new RegExp('^' + re`${nonexistentDir}`);
assert.match(err.path, pathPrefix);
const prefix = new RegExp('^ENOENT: no such file or directory, mkdtemp ' +
re`'${nonexistentDir}`);
assert.match(err.message, prefix);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'mkdtemp');
return true;
};
fs.mkdtemp(nonexistentDir, common.mustCall(validateError));
assert.throws(
() => fs.mkdtempSync(nonexistentDir),
validateError
);
}
// Check copyFile with invalid modes.
{
const validateError = {
code: 'ERR_OUT_OF_RANGE',
};
assert.throws(
() => fs.copyFile(existingFile, nonexistentFile, -1, () => {}),
validateError
);
assert.throws(
() => fs.copyFileSync(existingFile, nonexistentFile, -1),
validateError
);
}
// copyFile: destination exists but the COPYFILE_EXCL flag is provided.
{
const validateError = (err) => {
if (err.code === 'ENOENT') { // Could be ENOENT or EEXIST
assert.strictEqual(err.message,
'ENOENT: no such file or directory, copyfile ' +
`'${existingFile}' -> '${existingFile2}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'copyfile');
} else {
assert.strictEqual(err.message,
'EEXIST: file already exists, copyfile ' +
`'${existingFile}' -> '${existingFile2}'`);
assert.strictEqual(err.errno, UV_EEXIST);
assert.strictEqual(err.code, 'EEXIST');
assert.strictEqual(err.syscall, 'copyfile');
}
return true;
};
fs.copyFile(existingFile, existingFile2, COPYFILE_EXCL,
common.mustCall(validateError));
assert.throws(
() => fs.copyFileSync(existingFile, existingFile2, COPYFILE_EXCL),
validateError
);
}
// copyFile: the source does not exist.
{
const validateError = (err) => {
assert.strictEqual(err.message,
'ENOENT: no such file or directory, copyfile ' +
`'${nonexistentFile}' -> '${existingFile2}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'copyfile');
return true;
};
fs.copyFile(nonexistentFile, existingFile2, COPYFILE_EXCL,
common.mustCall(validateError));
assert.throws(
() => fs.copyFileSync(nonexistentFile, existingFile2, COPYFILE_EXCL),
validateError
);
}
// read
{
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, read');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'read');
return true;
};
common.runWithInvalidFD((fd) => {
const buf = Buffer.alloc(5);
fs.read(fd, buf, 0, 1, 1, common.mustCall(validateError));
assert.throws(
() => fs.readSync(fd, buf, 0, 1, 1),
validateError
);
});
}
// fchmod
{
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, fchmod');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'fchmod');
return true;
};
common.runWithInvalidFD((fd) => {
fs.fchmod(fd, 0o666, common.mustCall(validateError));
assert.throws(
() => fs.fchmodSync(fd, 0o666),
validateError
);
});
}
// fchown
if (!common.isWindows) {
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, fchown');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'fchown');
return true;
};
common.runWithInvalidFD((fd) => {
fs.fchown(fd, process.getuid(), process.getgid(),
common.mustCall(validateError));
assert.throws(
() => fs.fchownSync(fd, process.getuid(), process.getgid()),
validateError
);
});
}
// write buffer
{
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, write');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'write');
return true;
};
common.runWithInvalidFD((fd) => {
const buf = Buffer.alloc(5);
fs.write(fd, buf, 0, 1, 1, common.mustCall(validateError));
assert.throws(
() => fs.writeSync(fd, buf, 0, 1, 1),
validateError
);
});
}
// write string
{
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, write');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'write');
return true;
};
common.runWithInvalidFD((fd) => {
fs.write(fd, 'test', 1, common.mustCall(validateError));
assert.throws(
() => fs.writeSync(fd, 'test', 1),
validateError
);
});
}
// futimes
if (!common.isAIX) {
const validateError = (err) => {
assert.strictEqual(err.message, 'EBADF: bad file descriptor, futime');
assert.strictEqual(err.errno, UV_EBADF);
assert.strictEqual(err.code, 'EBADF');
assert.strictEqual(err.syscall, 'futime');
return true;
};
common.runWithInvalidFD((fd) => {
fs.futimes(fd, new Date(), new Date(), common.mustCall(validateError));
assert.throws(
() => fs.futimesSync(fd, new Date(), new Date()),
validateError
);
});
}

View File

@@ -0,0 +1,83 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
// This test ensures that input for fchmod is valid, testing for valid
// inputs for fd and mode
// Check input type
[false, null, undefined, {}, [], ''].forEach((input) => {
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "fd" argument must be of type number.' +
common.invalidArgTypeHelper(input)
};
assert.throws(() => fs.fchmod(input, 0o666, () => {}), errObj);
assert.throws(() => fs.fchmodSync(input, 0o666), errObj);
});
[false, null, {}, []].forEach((input) => {
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
};
assert.throws(() => fs.fchmod(1, input), errObj);
assert.throws(() => fs.fchmodSync(1, input), errObj);
});
assert.throws(() => fs.fchmod(1, '123x'), {
code: 'ERR_INVALID_ARG_VALUE'
});
[-1, 2 ** 32].forEach((input) => {
const errObj = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "fd" is out of range. It must be >= 0 && <= ' +
`2147483647. Received ${input}`
};
assert.throws(() => fs.fchmod(input, 0o666, () => {}), errObj);
assert.throws(() => fs.fchmodSync(input, 0o666), errObj);
});
[-1, 2 ** 32].forEach((input) => {
const errObj = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "mode" is out of range. It must be >= 0 && <= ' +
`4294967295. Received ${input}`
};
assert.throws(() => fs.fchmod(1, input, () => {}), errObj);
assert.throws(() => fs.fchmodSync(1, input), errObj);
});
[NaN, Infinity].forEach((input) => {
const errObj = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "fd" is out of range. It must be an integer. ' +
`Received ${input}`
};
assert.throws(() => fs.fchmod(input, 0o666, () => {}), errObj);
assert.throws(() => fs.fchmodSync(input, 0o666), errObj);
errObj.message = errObj.message.replace('fd', 'mode');
assert.throws(() => fs.fchmod(1, input, () => {}), errObj);
assert.throws(() => fs.fchmodSync(1, input), errObj);
});
[1.5].forEach((input) => {
const errObj = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "fd" is out of range. It must be an integer. ' +
`Received ${input}`
};
assert.throws(() => fs.fchmod(input, 0o666, () => {}), errObj);
assert.throws(() => fs.fchmodSync(input, 0o666), errObj);
errObj.message = errObj.message.replace('fd', 'mode');
assert.throws(() => fs.fchmod(1, input, () => {}), errObj);
assert.throws(() => fs.fchmodSync(1, input), errObj);
});

View File

@@ -0,0 +1,60 @@
'use strict';
require('../common');
const assert = require('assert');
const fs = require('fs');
function testFd(input, errObj) {
assert.throws(() => fs.fchown(input, 0, 0, () => {}), errObj);
assert.throws(() => fs.fchownSync(input, 0, 0), errObj);
}
function testUid(input, errObj) {
assert.throws(() => fs.fchown(1, input), errObj);
assert.throws(() => fs.fchownSync(1, input), errObj);
}
function testGid(input, errObj) {
assert.throws(() => fs.fchown(1, 1, input), errObj);
assert.throws(() => fs.fchownSync(1, 1, input), errObj);
}
['', false, null, undefined, {}, []].forEach((input) => {
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /fd|uid|gid/
};
testFd(input, errObj);
testUid(input, errObj);
testGid(input, errObj);
});
[Infinity, NaN].forEach((input) => {
const errObj = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "fd" is out of range. It must be an integer. ' +
`Received ${input}`
};
testFd(input, errObj);
errObj.message = errObj.message.replace('fd', 'uid');
testUid(input, errObj);
errObj.message = errObj.message.replace('uid', 'gid');
testGid(input, errObj);
});
[-2, 2 ** 32].forEach((input) => {
const errObj = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "fd" is out of range. It must be ' +
`>= 0 && <= 2147483647. Received ${input}`
};
testFd(input, errObj);
errObj.message = 'The value of "uid" is out of range. It must be >= -1 && ' +
`<= 4294967295. Received ${input}`;
testUid(input, errObj);
errObj.message = errObj.message.replace('uid', 'gid');
testGid(input, errObj);
});

View File

@@ -0,0 +1,25 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs').promises;
(async () => {
const filehandle = await fs.open(__filename);
assert.notStrictEqual(filehandle.fd, -1);
await filehandle.close();
assert.strictEqual(filehandle.fd, -1);
// Open another file handle first. This would typically receive the fd
// that `filehandle` previously used. In earlier versions of Node.js, the
// .stat() call would then succeed because it still used the original fd;
// See https://github.com/nodejs/node/issues/31361 for more details.
const otherFilehandle = await fs.open(process.execPath);
await assert.rejects(() => filehandle.stat(), {
code: 'EBADF',
syscall: 'fstat'
});
await otherFilehandle.close();
})().then(common.mustCall());

View File

@@ -0,0 +1,40 @@
// Flags: --expose-gc --no-warnings --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const path = require('path');
const { internalBinding } = require('internal/test/binding');
const fs = internalBinding('fs');
const { stringToFlags } = require('internal/fs/utils');
// Verifies that the FileHandle object is garbage collected and that a
// warning is emitted if it is not closed.
let fdnum;
{
const ctx = {};
fdnum = fs.openFileHandle(path.toNamespacedPath(__filename),
stringToFlags('r'), 0o666, undefined, ctx).fd;
assert.strictEqual(ctx.errno, undefined);
}
const deprecationWarning =
'Closing a FileHandle object on garbage collection is deprecated. ' +
'Please close FileHandle objects explicitly using ' +
'FileHandle.prototype.close(). In the future, an error will be ' +
'thrown if a file descriptor is closed during garbage collection.';
common.expectWarning({
'internal/test/binding': [
'These APIs are for internal testing only. Do not use them.',
],
'Warning': [
`Closing file descriptor ${fdnum} on garbage collection`,
],
'DeprecationWarning': [[deprecationWarning, 'DEP0137']]
});
global.gc();
setTimeout(() => {}, 10);

View File

@@ -0,0 +1,66 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const { promises } = fs;
const f = __filename;
// This test ensures that input for lchmod is valid, testing for valid
// inputs for path, mode and callback
if (!common.isMacOS) {
common.skip('lchmod is only available on macOS');
}
// Check callback
assert.throws(() => fs.lchmod(f), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => fs.lchmod(), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => fs.lchmod(f, {}), { code: 'ERR_INVALID_ARG_TYPE' });
// Check path
[false, 1, {}, [], null, undefined].forEach((i) => {
assert.throws(
() => fs.lchmod(i, 0o777, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
assert.throws(
() => fs.lchmodSync(i),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
});
// Check mode
[false, null, {}, []].forEach((input) => {
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
};
assert.rejects(promises.lchmod(f, input, () => {}), errObj).then(common.mustCall());
assert.throws(() => fs.lchmodSync(f, input), errObj);
});
assert.throws(() => fs.lchmod(f, '123x', common.mustNotCall()), {
code: 'ERR_INVALID_ARG_VALUE'
});
assert.throws(() => fs.lchmodSync(f, '123x'), {
code: 'ERR_INVALID_ARG_VALUE'
});
[-1, 2 ** 32].forEach((input) => {
const errObj = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "mode" is out of range. It must be >= 0 && <= ' +
`4294967295. Received ${input}`
};
assert.rejects(promises.lchmod(f, input, () => {}), errObj).then(common.mustCall());
assert.throws(() => fs.lchmodSync(f, input), errObj);
});

View File

@@ -0,0 +1,64 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { promises } = fs;
// Validate the path argument.
[false, 1, {}, [], null, undefined].forEach((i) => {
const err = { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' };
assert.throws(() => fs.lchown(i, 1, 1, common.mustNotCall()), err);
assert.throws(() => fs.lchownSync(i, 1, 1), err);
promises.lchown(false, 1, 1)
.then(common.mustNotCall())
.catch(common.expectsError(err));
});
// Validate the uid and gid arguments.
[false, 'test', {}, [], null, undefined].forEach((i) => {
const err = { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE' };
assert.throws(
() => fs.lchown('not_a_file_that_exists', i, 1, common.mustNotCall()),
err
);
assert.throws(
() => fs.lchown('not_a_file_that_exists', 1, i, common.mustNotCall()),
err
);
assert.throws(() => fs.lchownSync('not_a_file_that_exists', i, 1), err);
assert.throws(() => fs.lchownSync('not_a_file_that_exists', 1, i), err);
promises.lchown('not_a_file_that_exists', i, 1)
.then(common.mustNotCall())
.catch(common.expectsError(err));
promises.lchown('not_a_file_that_exists', 1, i)
.then(common.mustNotCall())
.catch(common.expectsError(err));
});
// Validate the callback argument.
[false, 1, 'test', {}, [], null, undefined].forEach((i) => {
assert.throws(() => fs.lchown('not_a_file_that_exists', 1, 1, i), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
});
});
if (!common.isWindows) {
const testFile = tmpdir.resolve(path.basename(__filename));
const uid = process.geteuid();
const gid = process.getegid();
tmpdir.refresh();
fs.copyFileSync(__filename, testFile);
fs.lchownSync(testFile, uid, gid);
fs.lchown(testFile, uid, gid, common.mustSucceed(async (err) => {
await promises.lchown(testFile, uid, gid);
}));
}

View File

@@ -0,0 +1,40 @@
'use strict';
// This tests that the lower bits of mode > 0o777 still works in fs.mkdir().
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
if (common.isWindows) {
common.skip('mode is not supported in mkdir on Windows');
return;
}
const mode = 0o644;
const maskToIgnore = 0o10000;
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
function test(mode, asString) {
const suffix = asString ? 'str' : 'num';
const input = asString ?
(mode | maskToIgnore).toString(8) : (mode | maskToIgnore);
{
const dir = tmpdir.resolve(`mkdirSync-${suffix}`);
fs.mkdirSync(dir, input);
assert.strictEqual(fs.statSync(dir).mode & 0o777, mode);
}
{
const dir = tmpdir.resolve(`mkdir-${suffix}`);
fs.mkdir(dir, input, common.mustSucceed(() => {
assert.strictEqual(fs.statSync(dir).mode & 0o777, mode);
}));
}
}
test(mode, true);
test(mode, false);

View File

@@ -0,0 +1,37 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const d = tmpdir.resolve('dir');
tmpdir.refresh();
// Make sure the directory does not exist
assert(!fs.existsSync(d));
// Create the directory now
fs.mkdirSync(d);
// Make sure the directory exists
assert(fs.existsSync(d));
// Try creating again, it should fail with EEXIST
assert.throws(function() {
fs.mkdirSync(d);
}, /EEXIST: file already exists, mkdir/);
// Remove the directory now
fs.rmdirSync(d);
// Make sure the directory does not exist
assert(!fs.existsSync(d));
// Similarly test the Async version
fs.mkdir(d, 0o666, common.mustSucceed(() => {
fs.mkdir(d, 0o666, common.mustCall(function(err) {
assert.strictEqual(this, undefined);
assert.ok(err, 'got no error');
assert.match(err.message, /^EEXIST/);
assert.strictEqual(err.code, 'EEXIST');
assert.strictEqual(err.path, d);
fs.rmdir(d, assert.ifError);
}));
}));

View File

@@ -0,0 +1,363 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
let dirc = 0;
function nextdir() {
return `test${++dirc}`;
}
// fs.mkdir creates directory using assigned path
{
const pathname = tmpdir.resolve(nextdir());
fs.mkdir(pathname, common.mustCall(function(err) {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(pathname), true);
}));
}
// fs.mkdir creates directory with assigned mode value
{
const pathname = tmpdir.resolve(nextdir());
fs.mkdir(pathname, 0o777, common.mustCall(function(err) {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(pathname), true);
}));
}
// fs.mkdir creates directory with mode passed as an options object
{
const pathname = tmpdir.resolve(nextdir());
fs.mkdir(pathname, common.mustNotMutateObjectDeep({ mode: 0o777 }), common.mustCall(function(err) {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(pathname), true);
}));
}
// fs.mkdirSync creates directory with mode passed as an options object
{
const pathname = tmpdir.resolve(nextdir());
fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ mode: 0o777 }));
assert.strictEqual(fs.existsSync(pathname), true);
}
// mkdirSync successfully creates directory from given path
{
const pathname = tmpdir.resolve(nextdir());
fs.mkdirSync(pathname);
const exists = fs.existsSync(pathname);
assert.strictEqual(exists, true);
}
// mkdirSync and mkdir require path to be a string, buffer or url.
// Anything else generates an error.
[false, 1, {}, [], null, undefined].forEach((i) => {
assert.throws(
() => fs.mkdir(i, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
assert.throws(
() => fs.mkdirSync(i),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
});
// mkdirpSync when both top-level, and sub-folders do not exist.
{
const pathname = tmpdir.resolve(nextdir(), nextdir());
fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true }));
const exists = fs.existsSync(pathname);
assert.strictEqual(exists, true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
}
// mkdirpSync when folder already exists.
{
const pathname = tmpdir.resolve(nextdir(), nextdir());
fs.mkdirSync(pathname, { recursive: true });
// Should not cause an error.
fs.mkdirSync(pathname, { recursive: true });
const exists = fs.existsSync(pathname);
assert.strictEqual(exists, true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
}
// mkdirpSync ../
{
const pathname = `${tmpdir.path}/${nextdir()}/../${nextdir()}/${nextdir()}`;
fs.mkdirSync(pathname, { recursive: true });
const exists = fs.existsSync(pathname);
assert.strictEqual(exists, true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
}
// mkdirpSync when path is a file.
{
const pathname = tmpdir.resolve(nextdir(), nextdir());
fs.mkdirSync(path.dirname(pathname));
fs.writeFileSync(pathname, '', 'utf8');
assert.throws(
() => { fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true })); },
{
code: 'EEXIST',
message: /EEXIST: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
}
);
}
// mkdirpSync when part of the path is a file.
{
const filename = tmpdir.resolve(nextdir(), nextdir());
const pathname = path.join(filename, nextdir(), nextdir());
fs.mkdirSync(path.dirname(filename));
fs.writeFileSync(filename, '', 'utf8');
assert.throws(
() => { fs.mkdirSync(pathname, { recursive: true }); },
{
code: 'ENOTDIR',
message: /ENOTDIR: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
path: pathname // See: https://github.com/nodejs/node/issues/28015
}
);
}
// `mkdirp` when folder does not yet exist.
{
const pathname = tmpdir.resolve(nextdir(), nextdir());
fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall(function(err) {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(pathname), true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
}));
}
// `mkdirp` when path is a file.
{
const pathname = tmpdir.resolve(nextdir(), nextdir());
fs.mkdirSync(path.dirname(pathname));
fs.writeFileSync(pathname, '', 'utf8');
fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => {
assert.strictEqual(err.code, 'EEXIST');
assert.strictEqual(err.syscall, 'mkdir');
assert.strictEqual(fs.statSync(pathname).isDirectory(), false);
}));
}
// `mkdirp` when part of the path is a file.
{
const filename = tmpdir.resolve(nextdir(), nextdir());
const pathname = path.join(filename, nextdir(), nextdir());
fs.mkdirSync(path.dirname(filename));
fs.writeFileSync(filename, '', 'utf8');
fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOTDIR');
assert.strictEqual(err.syscall, 'mkdir');
assert.strictEqual(fs.existsSync(pathname), false);
// See: https://github.com/nodejs/node/issues/28015
// The path field varies slightly in Windows errors, vs., other platforms
// see: https://github.com/libuv/libuv/issues/2661, for this reason we
// use startsWith() rather than comparing to the full "pathname".
assert(err.path.startsWith(filename));
}));
}
// mkdirpSync dirname loop
// XXX: windows and smartos have issues removing a directory that you're in.
if (common.isMainThread && (common.isLinux || common.isMacOS)) {
const pathname = tmpdir.resolve(nextdir());
fs.mkdirSync(pathname);
process.chdir(pathname);
fs.rmdirSync(pathname);
assert.throws(
() => { fs.mkdirSync('X', common.mustNotMutateObjectDeep({ recursive: true })); },
{
code: 'ENOENT',
message: /ENOENT: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
}
);
fs.mkdir('X', common.mustNotMutateObjectDeep({ recursive: true }), (err) => {
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'mkdir');
});
}
// mkdirSync and mkdir require options.recursive to be a boolean.
// Anything else generates an error.
{
const pathname = tmpdir.resolve(nextdir());
['', 1, {}, [], null, Symbol('test'), () => {}].forEach((recursive) => {
const received = common.invalidArgTypeHelper(recursive);
assert.throws(
() => fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive }), common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.recursive" property must be of type boolean.' +
received
}
);
assert.throws(
() => fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive })),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.recursive" property must be of type boolean.' +
received
}
);
});
}
// `mkdirp` returns first folder created, when all folders are new.
{
const dir1 = nextdir();
const dir2 = nextdir();
const firstPathCreated = tmpdir.resolve(dir1);
const pathname = tmpdir.resolve(dir1, dir2);
fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall(function(err, result) {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(pathname), true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
assert.strictEqual(result, path.toNamespacedPath(firstPathCreated));
}));
}
// `mkdirp` returns first folder created, when last folder is new.
{
const dir1 = nextdir();
const dir2 = nextdir();
const pathname = tmpdir.resolve(dir1, dir2);
fs.mkdirSync(tmpdir.resolve(dir1));
fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall(function(err, result) {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(pathname), true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
assert.strictEqual(result, path.toNamespacedPath(pathname));
}));
}
// `mkdirp` returns undefined, when no new folders are created.
{
const dir1 = nextdir();
const dir2 = nextdir();
const pathname = tmpdir.resolve(dir1, dir2);
fs.mkdirSync(tmpdir.resolve(dir1, dir2), common.mustNotMutateObjectDeep({ recursive: true }));
fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall(function(err, path) {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(pathname), true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
assert.strictEqual(path, undefined);
}));
}
// `mkdirp.sync` returns first folder created, when all folders are new.
{
const dir1 = nextdir();
const dir2 = nextdir();
const firstPathCreated = tmpdir.resolve(dir1);
const pathname = tmpdir.resolve(dir1, dir2);
const p = fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(pathname), true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
assert.strictEqual(p, path.toNamespacedPath(firstPathCreated));
}
// `mkdirp.sync` returns first folder created, when last folder is new.
{
const dir1 = nextdir();
const dir2 = nextdir();
const pathname = tmpdir.resolve(dir1, dir2);
fs.mkdirSync(tmpdir.resolve(dir1), common.mustNotMutateObjectDeep({ recursive: true }));
const p = fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(pathname), true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
assert.strictEqual(p, path.toNamespacedPath(pathname));
}
// `mkdirp.sync` returns undefined, when no new folders are created.
{
const dir1 = nextdir();
const dir2 = nextdir();
const pathname = tmpdir.resolve(dir1, dir2);
fs.mkdirSync(tmpdir.resolve(dir1, dir2), common.mustNotMutateObjectDeep({ recursive: true }));
const p = fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(pathname), true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
assert.strictEqual(p, undefined);
}
// `mkdirp.promises` returns first folder created, when all folders are new.
{
const dir1 = nextdir();
const dir2 = nextdir();
const firstPathCreated = tmpdir.resolve(dir1);
const pathname = tmpdir.resolve(dir1, dir2);
async function testCase() {
const p = await fs.promises.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(pathname), true);
assert.strictEqual(fs.statSync(pathname).isDirectory(), true);
assert.strictEqual(p, path.toNamespacedPath(firstPathCreated));
}
testCase();
}
// Keep the event loop alive so the async mkdir() requests
// have a chance to run (since they don't ref the event loop).
process.nextTick(() => {});

View File

@@ -0,0 +1,107 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
function handler(err, folder) {
assert.ifError(err);
assert(fs.existsSync(folder));
assert.strictEqual(this, undefined);
}
// Test with plain string
{
const tmpFolder = fs.mkdtempSync(tmpdir.resolve('foo.'));
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
assert(fs.existsSync(tmpFolder));
const utf8 = fs.mkdtempSync(tmpdir.resolve('\u0222abc.'));
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
Buffer.byteLength('\u0222abc.XXXXXX'));
assert(fs.existsSync(utf8));
fs.mkdtemp(tmpdir.resolve('bar.'), common.mustCall(handler));
// Same test as above, but making sure that passing an options object doesn't
// affect the way the callback function is handled.
fs.mkdtemp(tmpdir.resolve('bar.'), {}, common.mustCall(handler));
const warningMsg = 'mkdtemp() templates ending with X are not portable. ' +
'For details see: https://nodejs.org/api/fs.html';
common.expectWarning('Warning', warningMsg);
fs.mkdtemp(tmpdir.resolve('bar.X'), common.mustCall(handler));
}
// Test with URL object
{
const tmpFolder = fs.mkdtempSync(tmpdir.fileURL('foo.'));
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
assert(fs.existsSync(tmpFolder));
const utf8 = fs.mkdtempSync(tmpdir.fileURL('\u0222abc.'));
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
Buffer.byteLength('\u0222abc.XXXXXX'));
assert(fs.existsSync(utf8));
fs.mkdtemp(tmpdir.fileURL('bar.'), common.mustCall(handler));
// Same test as above, but making sure that passing an options object doesn't
// affect the way the callback function is handled.
fs.mkdtemp(tmpdir.fileURL('bar.'), {}, common.mustCall(handler));
// Warning fires only once
fs.mkdtemp(tmpdir.fileURL('bar.X'), common.mustCall(handler));
}
// Test with Buffer
{
const tmpFolder = fs.mkdtempSync(Buffer.from(tmpdir.resolve('foo.')));
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
assert(fs.existsSync(tmpFolder));
const utf8 = fs.mkdtempSync(Buffer.from(tmpdir.resolve('\u0222abc.')));
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
Buffer.byteLength('\u0222abc.XXXXXX'));
assert(fs.existsSync(utf8));
fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.')), common.mustCall(handler));
// Same test as above, but making sure that passing an options object doesn't
// affect the way the callback function is handled.
fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.')), {}, common.mustCall(handler));
// Warning fires only once
fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.X')), common.mustCall(handler));
}
// Test with Uint8Array
{
const encoder = new TextEncoder();
const tmpFolder = fs.mkdtempSync(encoder.encode(tmpdir.resolve('foo.')));
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
assert(fs.existsSync(tmpFolder));
const utf8 = fs.mkdtempSync(encoder.encode(tmpdir.resolve('\u0222abc.')));
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
Buffer.byteLength('\u0222abc.XXXXXX'));
assert(fs.existsSync(utf8));
fs.mkdtemp(encoder.encode(tmpdir.resolve('bar.')), common.mustCall(handler));
// Same test as above, but making sure that passing an options object doesn't
// affect the way the callback function is handled.
fs.mkdtemp(encoder.encode(tmpdir.resolve('bar.')), {}, common.mustCall(handler));
// Warning fires only once
fs.mkdtemp(encoder.encode(tmpdir.resolve('bar.X')), common.mustCall(handler));
}

View File

@@ -0,0 +1,158 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
function check(async, sync) {
const argsSync = Array.prototype.slice.call(arguments, 2);
const argsAsync = argsSync.concat(common.mustNotCall());
if (sync) {
assert.throws(
() => {
sync.apply(null, argsSync);
},
{
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError',
});
}
if (async) {
assert.throws(
() => {
async.apply(null, argsAsync);
},
{
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError'
});
}
}
check(fs.access, fs.accessSync, 'foo\u0000bar');
check(fs.access, fs.accessSync, 'foo\u0000bar', fs.constants.F_OK);
check(fs.appendFile, fs.appendFileSync, 'foo\u0000bar', 'abc');
check(fs.chmod, fs.chmodSync, 'foo\u0000bar', '0644');
check(fs.chown, fs.chownSync, 'foo\u0000bar', 12, 34);
check(fs.copyFile, fs.copyFileSync, 'foo\u0000bar', 'abc');
check(fs.copyFile, fs.copyFileSync, 'abc', 'foo\u0000bar');
check(fs.lchown, fs.lchownSync, 'foo\u0000bar', 12, 34);
check(fs.link, fs.linkSync, 'foo\u0000bar', 'foobar');
check(fs.link, fs.linkSync, 'foobar', 'foo\u0000bar');
check(fs.lstat, fs.lstatSync, 'foo\u0000bar');
check(fs.mkdir, fs.mkdirSync, 'foo\u0000bar', '0755');
check(fs.open, fs.openSync, 'foo\u0000bar', 'r');
check(fs.readFile, fs.readFileSync, 'foo\u0000bar');
check(fs.readdir, fs.readdirSync, 'foo\u0000bar');
check(fs.readdir, fs.readdirSync, 'foo\u0000bar', { recursive: true });
check(fs.readlink, fs.readlinkSync, 'foo\u0000bar');
check(fs.realpath, fs.realpathSync, 'foo\u0000bar');
check(fs.rename, fs.renameSync, 'foo\u0000bar', 'foobar');
check(fs.rename, fs.renameSync, 'foobar', 'foo\u0000bar');
check(fs.rmdir, fs.rmdirSync, 'foo\u0000bar');
check(fs.stat, fs.statSync, 'foo\u0000bar');
check(fs.symlink, fs.symlinkSync, 'foo\u0000bar', 'foobar');
check(fs.symlink, fs.symlinkSync, 'foobar', 'foo\u0000bar');
check(fs.truncate, fs.truncateSync, 'foo\u0000bar');
check(fs.unlink, fs.unlinkSync, 'foo\u0000bar');
check(null, fs.unwatchFile, 'foo\u0000bar', common.mustNotCall());
check(fs.utimes, fs.utimesSync, 'foo\u0000bar', 0, 0);
check(null, fs.watch, 'foo\u0000bar', common.mustNotCall());
check(null, fs.watchFile, 'foo\u0000bar', common.mustNotCall());
check(fs.writeFile, fs.writeFileSync, 'foo\u0000bar', 'abc');
const fileUrl = new URL('file:///C:/foo\u0000bar');
const fileUrl2 = new URL('file:///C:/foo%00bar');
check(fs.access, fs.accessSync, fileUrl);
check(fs.access, fs.accessSync, fileUrl, fs.constants.F_OK);
check(fs.appendFile, fs.appendFileSync, fileUrl, 'abc');
check(fs.chmod, fs.chmodSync, fileUrl, '0644');
check(fs.chown, fs.chownSync, fileUrl, 12, 34);
check(fs.copyFile, fs.copyFileSync, fileUrl, 'abc');
check(fs.copyFile, fs.copyFileSync, 'abc', fileUrl);
check(fs.lchown, fs.lchownSync, fileUrl, 12, 34);
check(fs.link, fs.linkSync, fileUrl, 'foobar');
check(fs.link, fs.linkSync, 'foobar', fileUrl);
check(fs.lstat, fs.lstatSync, fileUrl);
check(fs.mkdir, fs.mkdirSync, fileUrl, '0755');
check(fs.open, fs.openSync, fileUrl, 'r');
check(fs.readFile, fs.readFileSync, fileUrl);
check(fs.readdir, fs.readdirSync, fileUrl);
check(fs.readdir, fs.readdirSync, fileUrl, { recursive: true });
check(fs.readlink, fs.readlinkSync, fileUrl);
check(fs.realpath, fs.realpathSync, fileUrl);
check(fs.rename, fs.renameSync, fileUrl, 'foobar');
check(fs.rename, fs.renameSync, 'foobar', fileUrl);
check(fs.rmdir, fs.rmdirSync, fileUrl);
check(fs.stat, fs.statSync, fileUrl);
check(fs.symlink, fs.symlinkSync, fileUrl, 'foobar');
check(fs.symlink, fs.symlinkSync, 'foobar', fileUrl);
check(fs.truncate, fs.truncateSync, fileUrl);
check(fs.unlink, fs.unlinkSync, fileUrl);
check(null, fs.unwatchFile, fileUrl, assert.fail);
check(fs.utimes, fs.utimesSync, fileUrl, 0, 0);
check(null, fs.watch, fileUrl, assert.fail);
check(null, fs.watchFile, fileUrl, assert.fail);
check(fs.writeFile, fs.writeFileSync, fileUrl, 'abc');
check(fs.access, fs.accessSync, fileUrl2);
check(fs.access, fs.accessSync, fileUrl2, fs.constants.F_OK);
check(fs.appendFile, fs.appendFileSync, fileUrl2, 'abc');
check(fs.chmod, fs.chmodSync, fileUrl2, '0644');
check(fs.chown, fs.chownSync, fileUrl2, 12, 34);
check(fs.copyFile, fs.copyFileSync, fileUrl2, 'abc');
check(fs.copyFile, fs.copyFileSync, 'abc', fileUrl2);
check(fs.lchown, fs.lchownSync, fileUrl2, 12, 34);
check(fs.link, fs.linkSync, fileUrl2, 'foobar');
check(fs.link, fs.linkSync, 'foobar', fileUrl2);
check(fs.lstat, fs.lstatSync, fileUrl2);
check(fs.mkdir, fs.mkdirSync, fileUrl2, '0755');
check(fs.open, fs.openSync, fileUrl2, 'r');
check(fs.readFile, fs.readFileSync, fileUrl2);
check(fs.readdir, fs.readdirSync, fileUrl2);
check(fs.readdir, fs.readdirSync, fileUrl2, { recursive: true });
check(fs.readlink, fs.readlinkSync, fileUrl2);
check(fs.realpath, fs.realpathSync, fileUrl2);
check(fs.rename, fs.renameSync, fileUrl2, 'foobar');
check(fs.rename, fs.renameSync, 'foobar', fileUrl2);
check(fs.rmdir, fs.rmdirSync, fileUrl2);
check(fs.stat, fs.statSync, fileUrl2);
check(fs.symlink, fs.symlinkSync, fileUrl2, 'foobar');
check(fs.symlink, fs.symlinkSync, 'foobar', fileUrl2);
check(fs.truncate, fs.truncateSync, fileUrl2);
check(fs.unlink, fs.unlinkSync, fileUrl2);
check(null, fs.unwatchFile, fileUrl2, assert.fail);
check(fs.utimes, fs.utimesSync, fileUrl2, 0, 0);
check(null, fs.watch, fileUrl2, assert.fail);
check(null, fs.watchFile, fileUrl2, assert.fail);
check(fs.writeFile, fs.writeFileSync, fileUrl2, 'abc');
// An 'error' for exists means that it doesn't exist.
// One of many reasons why this file is the absolute worst.
fs.exists('foo\u0000bar', common.mustCall((exists) => {
assert(!exists);
}));
assert(!fs.existsSync('foo\u0000bar'));

View File

@@ -0,0 +1,93 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// Flags: --expose-internals
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const fs = require('fs');
// 0 if not found in fs.constants
const { O_APPEND = 0,
O_CREAT = 0,
O_EXCL = 0,
O_RDONLY = 0,
O_RDWR = 0,
O_SYNC = 0,
O_DSYNC = 0,
O_TRUNC = 0,
O_WRONLY = 0 } = fs.constants;
const { stringToFlags } = require('internal/fs/utils');
assert.strictEqual(stringToFlags('r'), O_RDONLY);
assert.strictEqual(stringToFlags('r+'), O_RDWR);
assert.strictEqual(stringToFlags('rs+'), O_RDWR | O_SYNC);
assert.strictEqual(stringToFlags('sr+'), O_RDWR | O_SYNC);
assert.strictEqual(stringToFlags('w'), O_TRUNC | O_CREAT | O_WRONLY);
assert.strictEqual(stringToFlags('w+'), O_TRUNC | O_CREAT | O_RDWR);
assert.strictEqual(stringToFlags('a'), O_APPEND | O_CREAT | O_WRONLY);
assert.strictEqual(stringToFlags('a+'), O_APPEND | O_CREAT | O_RDWR);
assert.strictEqual(stringToFlags('wx'), O_TRUNC | O_CREAT | O_WRONLY | O_EXCL);
assert.strictEqual(stringToFlags('xw'), O_TRUNC | O_CREAT | O_WRONLY | O_EXCL);
assert.strictEqual(stringToFlags('wx+'), O_TRUNC | O_CREAT | O_RDWR | O_EXCL);
assert.strictEqual(stringToFlags('xw+'), O_TRUNC | O_CREAT | O_RDWR | O_EXCL);
assert.strictEqual(stringToFlags('ax'), O_APPEND | O_CREAT | O_WRONLY | O_EXCL);
assert.strictEqual(stringToFlags('xa'), O_APPEND | O_CREAT | O_WRONLY | O_EXCL);
assert.strictEqual(stringToFlags('as'), O_APPEND | O_CREAT | O_WRONLY | O_SYNC);
assert.strictEqual(stringToFlags('sa'), O_APPEND | O_CREAT | O_WRONLY | O_SYNC);
assert.strictEqual(stringToFlags('ax+'), O_APPEND | O_CREAT | O_RDWR | O_EXCL);
assert.strictEqual(stringToFlags('xa+'), O_APPEND | O_CREAT | O_RDWR | O_EXCL);
assert.strictEqual(stringToFlags('as+'), O_APPEND | O_CREAT | O_RDWR | O_SYNC);
assert.strictEqual(stringToFlags('sa+'), O_APPEND | O_CREAT | O_RDWR | O_SYNC);
('+ +a +r +w rw wa war raw r++ a++ w++ x +x x+ rx rx+ wxx wax xwx xxx')
.split(' ')
.forEach(function(flags) {
assert.throws(
() => stringToFlags(flags),
{ code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError' }
);
});
assert.throws(
() => stringToFlags({}),
{ code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError' }
);
assert.throws(
() => stringToFlags(true),
{ code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError' }
);
if (common.isLinux || common.isMacOS) {
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const file = tmpdir.resolve('a.js');
fs.copyFileSync(fixtures.path('a.js'), file);
fs.open(file, O_DSYNC, common.mustSucceed((fd) => {
fs.closeSync(fd);
}));
}

View File

@@ -0,0 +1,289 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const tmpdir = require('../common/tmpdir');
const testDir = tmpdir.path;
const files = ['empty', 'files', 'for', 'just', 'testing'];
// Make sure tmp directory is clean
tmpdir.refresh();
// Create the necessary files
files.forEach(function(filename) {
fs.closeSync(fs.openSync(path.join(testDir, filename), 'w'));
});
function assertDirent(dirent) {
assert(dirent instanceof fs.Dirent);
assert.strictEqual(dirent.isFile(), true);
assert.strictEqual(dirent.isDirectory(), false);
assert.strictEqual(dirent.isSocket(), false);
assert.strictEqual(dirent.isBlockDevice(), false);
assert.strictEqual(dirent.isCharacterDevice(), false);
assert.strictEqual(dirent.isFIFO(), false);
assert.strictEqual(dirent.isSymbolicLink(), false);
}
const dirclosedError = {
code: 'ERR_DIR_CLOSED'
};
const dirconcurrentError = {
code: 'ERR_DIR_CONCURRENT_OPERATION'
};
const invalidCallbackObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
};
// Check the opendir Sync version
{
const dir = fs.opendirSync(testDir);
const entries = files.map(() => {
const dirent = dir.readSync();
assertDirent(dirent);
return { name: dirent.name, parentPath: dirent.parentPath, toString() { return dirent.name; } };
}).sort();
assert.deepStrictEqual(entries.map((d) => d.name), files);
assert.deepStrictEqual(entries.map((d) => d.parentPath), Array(entries.length).fill(testDir));
// dir.read should return null when no more entries exist
assert.strictEqual(dir.readSync(), null);
// check .path
assert.strictEqual(dir.path, testDir);
dir.closeSync();
assert.throws(() => dir.readSync(), dirclosedError);
assert.throws(() => dir.closeSync(), dirclosedError);
}
// Check the opendir async version
fs.opendir(testDir, common.mustSucceed((dir) => {
let sync = true;
dir.read(common.mustSucceed((dirent) => {
assert(!sync);
// Order is operating / file system dependent
assert(files.includes(dirent.name), `'files' should include ${dirent}`);
assertDirent(dirent);
let syncInner = true;
dir.read(common.mustSucceed((dirent) => {
assert(!syncInner);
dir.close(common.mustSucceed());
}));
syncInner = false;
}));
sync = false;
}));
// opendir() on file should throw ENOTDIR
assert.throws(function() {
fs.opendirSync(__filename);
}, /Error: ENOTDIR: not a directory/);
assert.throws(function() {
fs.opendir(__filename);
}, /TypeError \[ERR_INVALID_ARG_TYPE\]: The "callback" argument must be of type function/);
fs.opendir(__filename, common.mustCall(function(e) {
assert.strictEqual(e.code, 'ENOTDIR');
}));
[false, 1, [], {}, null, undefined].forEach((i) => {
assert.throws(
() => fs.opendir(i, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
assert.throws(
() => fs.opendirSync(i),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
});
// Promise-based tests
async function doPromiseTest() {
// Check the opendir Promise version
const dir = await fs.promises.opendir(testDir);
const entries = [];
let i = files.length;
while (i--) {
const dirent = await dir.read();
entries.push(dirent.name);
assertDirent(dirent);
}
assert.deepStrictEqual(files, entries.sort());
// dir.read should return null when no more entries exist
assert.strictEqual(await dir.read(), null);
await dir.close();
}
doPromiseTest().then(common.mustCall());
// Async iterator
async function doAsyncIterTest() {
const entries = [];
for await (const dirent of await fs.promises.opendir(testDir)) {
entries.push(dirent.name);
assertDirent(dirent);
}
assert.deepStrictEqual(files, entries.sort());
// Automatically closed during iterator
}
doAsyncIterTest().then(common.mustCall());
// Async iterators should do automatic cleanup
async function doAsyncIterBreakTest() {
const dir = await fs.promises.opendir(testDir);
for await (const dirent of dir) { // eslint-disable-line no-unused-vars
break;
}
await assert.rejects(async () => dir.read(), dirclosedError);
}
doAsyncIterBreakTest().then(common.mustCall());
async function doAsyncIterReturnTest() {
const dir = await fs.promises.opendir(testDir);
await (async function() {
for await (const dirent of dir) {
return;
}
})();
await assert.rejects(async () => dir.read(), dirclosedError);
}
doAsyncIterReturnTest().then(common.mustCall());
async function doAsyncIterThrowTest() {
const dir = await fs.promises.opendir(testDir);
try {
for await (const dirent of dir) { // eslint-disable-line no-unused-vars
throw new Error('oh no');
}
} catch (err) {
if (err.message !== 'oh no') {
throw err;
}
}
await assert.rejects(async () => dir.read(), dirclosedError);
}
doAsyncIterThrowTest().then(common.mustCall());
// Check error thrown on invalid values of bufferSize
for (const bufferSize of [-1, 0, 0.5, 1.5, Infinity, NaN]) {
assert.throws(
() => fs.opendirSync(testDir, common.mustNotMutateObjectDeep({ bufferSize })),
{
code: 'ERR_OUT_OF_RANGE'
});
}
for (const bufferSize of ['', '1', null]) {
assert.throws(
() => fs.opendirSync(testDir, common.mustNotMutateObjectDeep({ bufferSize })),
{
code: 'ERR_INVALID_ARG_TYPE'
});
}
// Check that passing a positive integer as bufferSize works
{
const dir = fs.opendirSync(testDir, common.mustNotMutateObjectDeep({ bufferSize: 1024 }));
assertDirent(dir.readSync());
dir.close();
}
// Check that when passing a string instead of function - throw an exception
async function doAsyncIterInvalidCallbackTest() {
const dir = await fs.promises.opendir(testDir);
assert.throws(() => dir.close('not function'), invalidCallbackObj);
}
doAsyncIterInvalidCallbackTest().then(common.mustCall());
// Check first call to close() - should not report an error.
async function doAsyncIterDirClosedTest() {
const dir = await fs.promises.opendir(testDir);
await dir.close();
await assert.rejects(() => dir.close(), dirclosedError);
}
doAsyncIterDirClosedTest().then(common.mustCall());
// Check that readSync() and closeSync() during read() throw exceptions
async function doConcurrentAsyncAndSyncOps() {
const dir = await fs.promises.opendir(testDir);
const promise = dir.read();
assert.throws(() => dir.closeSync(), dirconcurrentError);
assert.throws(() => dir.readSync(), dirconcurrentError);
await promise;
dir.closeSync();
}
doConcurrentAsyncAndSyncOps().then(common.mustCall());
// Check read throw exceptions on invalid callback
{
const dir = fs.opendirSync(testDir);
assert.throws(() => dir.read('INVALID_CALLBACK'), /ERR_INVALID_ARG_TYPE/);
}
// Check that concurrent read() operations don't do weird things.
async function doConcurrentAsyncOps() {
const dir = await fs.promises.opendir(testDir);
const promise1 = dir.read();
const promise2 = dir.read();
assertDirent(await promise1);
assertDirent(await promise2);
dir.closeSync();
}
doConcurrentAsyncOps().then(common.mustCall());
// Check that concurrent read() + close() operations don't do weird things.
async function doConcurrentAsyncMixedOps() {
const dir = await fs.promises.opendir(testDir);
const promise1 = dir.read();
const promise2 = dir.close();
assertDirent(await promise1);
await promise2;
}
doConcurrentAsyncMixedOps().then(common.mustCall());
// Check if directory already closed - the callback should pass an error.
{
const dir = fs.opendirSync(testDir);
dir.closeSync();
dir.close(common.mustCall((error) => {
assert.strictEqual(error.code, dirclosedError.code);
}));
}
// Check if directory already closed - throw an promise exception.
{
const dir = fs.opendirSync(testDir);
dir.closeSync();
assert.rejects(dir.close(), dirclosedError).then(common.mustCall());
}

View File

@@ -0,0 +1,31 @@
'use strict';
require('../common');
const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert');
const { describe, it } = require('node:test');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
describe('File operations with filenames containing surrogate pairs', () => {
it('should write, read, and delete a file with surrogate pairs in the filename', () => {
// Create a temporary directory
const tempdir = fs.mkdtempSync(tmpdir.resolve('emoji-fruit-🍇 🍈 🍉 🍊 🍋'));
assert.strictEqual(fs.existsSync(tempdir), true);
const filename = '🚀🔥🛸.txt';
const content = 'Test content';
// Write content to a file
fs.writeFileSync(path.join(tempdir, filename), content);
// Read content from the file
const readContent = fs.readFileSync(path.join(tempdir, filename), 'utf8');
// Check if the content matches
assert.strictEqual(readContent, content);
});
});

View File

@@ -0,0 +1,71 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
const tmpdir = require('../common/tmpdir');
// The following tests validate aggregate errors are thrown correctly
// when both an operation and close throw.
const {
readFile,
writeFile,
truncate,
lchmod,
} = require('fs/promises');
const {
FileHandle,
} = require('internal/fs/promises');
const assert = require('assert');
const originalFd = Object.getOwnPropertyDescriptor(FileHandle.prototype, 'fd');
let count = 0;
async function createFile() {
const filePath = tmpdir.resolve(`aggregate_errors_${++count}.txt`);
await writeFile(filePath, 'content');
return filePath;
}
async function checkAggregateError(op) {
try {
const filePath = await createFile();
Object.defineProperty(FileHandle.prototype, 'fd', {
get: function() {
// Close is set by using a setter,
// so it needs to be set on the instance.
const originalClose = this.close;
this.close = async () => {
// close the file
await originalClose.call(this);
const closeError = new Error('CLOSE_ERROR');
closeError.code = 456;
throw closeError;
};
const opError = new Error('INTERNAL_ERROR');
opError.code = 123;
throw opError;
}
});
await assert.rejects(op(filePath), common.mustCall((err) => {
assert.strictEqual(err.name, 'AggregateError');
assert.strictEqual(err.code, 123);
assert.strictEqual(err.errors.length, 2);
assert.strictEqual(err.errors[0].message, 'INTERNAL_ERROR');
assert.strictEqual(err.errors[1].message, 'CLOSE_ERROR');
return true;
}));
} finally {
Object.defineProperty(FileHandle.prototype, 'fd', originalFd);
}
}
(async function() {
tmpdir.refresh();
await checkAggregateError((filePath) => truncate(filePath));
await checkAggregateError((filePath) => readFile(filePath));
await checkAggregateError((filePath) => writeFile(filePath, '123'));
if (common.isMacOS) {
await checkAggregateError((filePath) => lchmod(filePath, 0o777));
}
})().then(common.mustCall());

View File

@@ -0,0 +1,66 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
const tmpdir = require('../common/tmpdir');
// The following tests validate aggregate errors are thrown correctly
// when both an operation and close throw.
const {
readFile,
writeFile,
truncate,
lchmod,
} = require('fs/promises');
const {
FileHandle,
} = require('internal/fs/promises');
const assert = require('assert');
const originalFd = Object.getOwnPropertyDescriptor(FileHandle.prototype, 'fd');
let count = 0;
async function createFile() {
const filePath = tmpdir.resolve(`close_errors_${++count}.txt`);
await writeFile(filePath, 'content');
return filePath;
}
async function checkCloseError(op) {
try {
const filePath = await createFile();
Object.defineProperty(FileHandle.prototype, 'fd', {
get: function() {
// Close is set by using a setter,
// so it needs to be set on the instance.
const originalClose = this.close;
this.close = async () => {
// close the file
await originalClose.call(this);
const closeError = new Error('CLOSE_ERROR');
closeError.code = 456;
throw closeError;
};
return originalFd.get.call(this);
}
});
await assert.rejects(op(filePath), {
name: 'Error',
message: 'CLOSE_ERROR',
code: 456,
});
} finally {
Object.defineProperty(FileHandle.prototype, 'fd', originalFd);
}
}
(async function() {
tmpdir.refresh();
await checkCloseError((filePath) => truncate(filePath));
await checkCloseError((filePath) => readFile(filePath));
await checkCloseError((filePath) => writeFile(filePath, '123'));
if (common.isMacOS) {
await checkCloseError((filePath) => lchmod(filePath, 0o777));
}
})().then(common.mustCall());

View File

@@ -0,0 +1,41 @@
// Flags: --expose-gc --no-warnings
'use strict';
// Test that a runtime warning is emitted when a FileHandle object
// is allowed to close on garbage collection. In the future, this
// test should verify that closing on garbage collection throws a
// process fatal exception.
const common = require('../common');
const assert = require('assert');
const { promises: fs } = require('fs');
const warning =
'Closing a FileHandle object on garbage collection is deprecated. ' +
'Please close FileHandle objects explicitly using ' +
'FileHandle.prototype.close(). In the future, an error will be ' +
'thrown if a file descriptor is closed during garbage collection.';
async function doOpen() {
const fh = await fs.open(__filename);
common.expectWarning({
Warning: [[`Closing file descriptor ${fh.fd} on garbage collection`]],
DeprecationWarning: [[warning, 'DEP0137']]
});
return fh;
}
doOpen().then(common.mustCall((fd) => {
assert.strictEqual(typeof fd, 'object');
})).then(common.mustCall(() => {
setImmediate(() => {
// The FileHandle should be out-of-scope and no longer accessed now.
global.gc();
// Wait an extra event loop turn, as the warning is emitted from the
// native layer in an unref()'ed setImmediate() callback.
setImmediate(common.mustCall());
});
}));

View File

@@ -0,0 +1,60 @@
'use strict';
// Flags: --expose-internals
const common = require('../common');
const tmpdir = require('../common/tmpdir');
// The following tests validate aggregate errors are thrown correctly
// when both an operation and close throw.
const {
readFile,
writeFile,
truncate,
lchmod,
} = require('fs/promises');
const {
FileHandle,
} = require('internal/fs/promises');
const assert = require('assert');
const originalFd = Object.getOwnPropertyDescriptor(FileHandle.prototype, 'fd');
let count = 0;
async function createFile() {
const filePath = tmpdir.resolve(`op_errors_${++count}.txt`);
await writeFile(filePath, 'content');
return filePath;
}
async function checkOperationError(op) {
try {
const filePath = await createFile();
Object.defineProperty(FileHandle.prototype, 'fd', {
get: function() {
// Verify that close is called when an error is thrown
this.close = common.mustCall(this.close);
const opError = new Error('INTERNAL_ERROR');
opError.code = 123;
throw opError;
}
});
await assert.rejects(op(filePath), {
name: 'Error',
message: 'INTERNAL_ERROR',
code: 123,
});
} finally {
Object.defineProperty(FileHandle.prototype, 'fd', originalFd);
}
}
(async function() {
tmpdir.refresh();
await checkOperationError((filePath) => truncate(filePath));
await checkOperationError((filePath) => readFile(filePath));
await checkOperationError((filePath) => writeFile(filePath, '123'));
if (common.isMacOS) {
await checkOperationError((filePath) => lchmod(filePath, 0o777));
}
})().then(common.mustCall());

View File

@@ -0,0 +1,129 @@
'use strict';
const common = require('../common');
// The following tests validate base functionality for the fs.promises
// FileHandle.read method.
const fs = require('fs');
const { open } = fs.promises;
const path = require('path');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const tmpDir = tmpdir.path;
async function read(fileHandle, buffer, offset, length, position, options) {
return options?.useConf ?
fileHandle.read({ buffer, offset, length, position }) :
fileHandle.read(buffer, offset, length, position);
}
async function validateRead(data, file, options) {
const filePath = path.resolve(tmpDir, file);
const buffer = Buffer.from(data, 'utf8');
const fd = fs.openSync(filePath, 'w+');
const fileHandle = await open(filePath, 'w+');
const streamFileHandle = await open(filePath, 'w+');
fs.writeSync(fd, buffer, 0, buffer.length);
fs.closeSync(fd);
fileHandle.on('close', common.mustCall());
const readAsyncHandle =
await read(fileHandle, Buffer.alloc(11), 0, 11, 0, options);
assert.deepStrictEqual(data.length, readAsyncHandle.bytesRead);
if (data.length)
assert.deepStrictEqual(buffer, readAsyncHandle.buffer);
await fileHandle.close();
const stream = fs.createReadStream(null, { fd: streamFileHandle });
let streamData = Buffer.alloc(0);
for await (const chunk of stream)
streamData = Buffer.from(chunk);
assert.deepStrictEqual(buffer, streamData);
if (data.length)
assert.deepStrictEqual(streamData, readAsyncHandle.buffer);
await streamFileHandle.close();
}
async function validateLargeRead(options) {
// Reading beyond file length (3 in this case) should return no data.
// This is a test for a bug where reads > uint32 would return data
// from the current position in the file.
const filePath = fixtures.path('x.txt');
const fileHandle = await open(filePath, 'r');
const pos = 0xffffffff + 1; // max-uint32 + 1
const readHandle =
await read(fileHandle, Buffer.alloc(1), 0, 1, pos, options);
assert.strictEqual(readHandle.bytesRead, 0);
}
async function validateReadNoParams() {
const filePath = fixtures.path('x.txt');
const fileHandle = await open(filePath, 'r');
// Should not throw
await fileHandle.read();
}
// Validates that the zero position is respected after the position has been
// moved. The test iterates over the xyz chars twice making sure that the values
// are read from the correct position.
async function validateReadWithPositionZero() {
const opts = { useConf: true };
const filePath = fixtures.path('x.txt');
const fileHandle = await open(filePath, 'r');
const expectedSequence = ['x', 'y', 'z'];
for (let i = 0; i < expectedSequence.length * 2; i++) {
const len = 1;
const pos = i % 3;
const buf = Buffer.alloc(len);
const { bytesRead } = await read(fileHandle, buf, 0, len, pos, opts);
assert.strictEqual(bytesRead, len);
assert.strictEqual(buf.toString(), expectedSequence[pos]);
}
}
async function validateReadLength(len) {
const buf = Buffer.alloc(4);
const opts = { useConf: true };
const filePath = fixtures.path('x.txt');
const fileHandle = await open(filePath, 'r');
const { bytesRead } = await read(fileHandle, buf, 0, len, 0, opts);
assert.strictEqual(bytesRead, len);
}
async function validateReadWithNoOptions(byte) {
const buf = Buffer.alloc(byte);
const filePath = fixtures.path('x.txt');
const fileHandle = await open(filePath, 'r');
let response = await fileHandle.read(buf);
assert.strictEqual(response.bytesRead, byte);
response = await read(fileHandle, buf, 0, undefined, 0);
assert.strictEqual(response.bytesRead, byte);
response = await read(fileHandle, buf, 0, null, 0);
assert.strictEqual(response.bytesRead, byte);
response = await read(fileHandle, buf, 0, undefined, 0, { useConf: true });
assert.strictEqual(response.bytesRead, byte);
response = await read(fileHandle, buf, 0, null, 0, { useConf: true });
assert.strictEqual(response.bytesRead, byte);
}
(async function() {
tmpdir.refresh();
await validateRead('Hello world', 'read-file', { useConf: false });
await validateRead('', 'read-empty-file', { useConf: false });
await validateRead('Hello world', 'read-file-conf', { useConf: true });
await validateRead('', 'read-empty-file-conf', { useConf: true });
await validateLargeRead({ useConf: false });
await validateLargeRead({ useConf: true });
await validateReadNoParams();
await validateReadWithPositionZero();
await validateReadLength(0);
await validateReadLength(1);
await validateReadWithNoOptions(0);
await validateReadWithNoOptions(1);
})().then(common.mustCall());

View File

@@ -0,0 +1,131 @@
'use strict';
const common = require('../common');
// The following tests validate base functionality for the fs.promises
// FileHandle.readFile method.
const fs = require('fs');
const {
open,
readFile,
writeFile,
truncate,
} = fs.promises;
const path = require('path');
const tmpdir = require('../common/tmpdir');
const tick = require('../common/tick');
const assert = require('assert');
const tmpDir = tmpdir.path;
tmpdir.refresh();
async function validateReadFile() {
const filePath = path.resolve(tmpDir, 'tmp-read-file.txt');
const fileHandle = await open(filePath, 'w+');
const buffer = Buffer.from('Hello world'.repeat(100), 'utf8');
const fd = fs.openSync(filePath, 'w+');
fs.writeSync(fd, buffer, 0, buffer.length);
fs.closeSync(fd);
const readFileData = await fileHandle.readFile();
assert.deepStrictEqual(buffer, readFileData);
await fileHandle.close();
}
async function validateReadFileProc() {
// Test to make sure reading a file under the /proc directory works. Adapted
// from test-fs-read-file-sync-hostname.js.
// Refs:
// - https://groups.google.com/forum/#!topic/nodejs-dev/rxZ_RoH1Gn0
// - https://github.com/nodejs/node/issues/21331
// Test is Linux-specific.
if (!common.isLinux)
return;
const fileHandle = await open('/proc/sys/kernel/hostname', 'r');
const hostname = await fileHandle.readFile();
assert.ok(hostname.length > 0);
}
async function doReadAndCancel() {
// Signal aborted from the start
{
const filePathForHandle = path.resolve(tmpDir, 'dogs-running.txt');
const fileHandle = await open(filePathForHandle, 'w+');
try {
const buffer = Buffer.from('Dogs running'.repeat(10000), 'utf8');
fs.writeFileSync(filePathForHandle, buffer);
const signal = AbortSignal.abort();
await assert.rejects(readFile(fileHandle, common.mustNotMutateObjectDeep({ signal })), {
name: 'AbortError'
});
} finally {
await fileHandle.close();
}
}
// Signal aborted on first tick
{
const filePathForHandle = path.resolve(tmpDir, 'dogs-running1.txt');
const fileHandle = await open(filePathForHandle, 'w+');
const buffer = Buffer.from('Dogs running'.repeat(10000), 'utf8');
fs.writeFileSync(filePathForHandle, buffer);
const controller = new AbortController();
const { signal } = controller;
process.nextTick(() => controller.abort());
await assert.rejects(readFile(fileHandle, common.mustNotMutateObjectDeep({ signal })), {
name: 'AbortError'
}, 'tick-0');
await fileHandle.close();
}
// Signal aborted right before buffer read
{
const newFile = path.resolve(tmpDir, 'dogs-running2.txt');
const buffer = Buffer.from('Dogs running'.repeat(1000), 'utf8');
fs.writeFileSync(newFile, buffer);
const fileHandle = await open(newFile, 'r');
const controller = new AbortController();
const { signal } = controller;
tick(1, () => controller.abort());
await assert.rejects(fileHandle.readFile(common.mustNotMutateObjectDeep({ signal, encoding: 'utf8' })), {
name: 'AbortError'
}, 'tick-1');
await fileHandle.close();
}
// Validate file size is within range for reading
{
// Variable taken from https://github.com/nodejs/node/blob/1377163f3351/lib/internal/fs/promises.js#L5
const kIoMaxLength = 2 ** 31 - 1;
if (!tmpdir.hasEnoughSpace(kIoMaxLength)) {
// truncate() will fail with ENOSPC if there is not enough space.
common.printSkipMessage(`Not enough space in ${tmpDir}`);
} else {
const newFile = path.resolve(tmpDir, 'dogs-running3.txt');
await writeFile(newFile, Buffer.from('0'));
await truncate(newFile, kIoMaxLength + 1);
const fileHandle = await open(newFile, 'r');
await assert.rejects(fileHandle.readFile(), {
name: 'RangeError',
code: 'ERR_FS_FILE_TOO_LARGE'
});
await fileHandle.close();
}
}
}
validateReadFile()
.then(validateReadFileProc)
.then(doReadAndCancel)
.then(common.mustCall());

View File

@@ -0,0 +1,48 @@
'use strict';
const common = require('../common');
// The following tests validate base functionality for the fs.promises
// FileHandle.write method.
const fs = require('fs');
const { open } = fs.promises;
const path = require('path');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const { finished } = require('stream/promises');
const { buffer } = require('stream/consumers');
const tmpDir = tmpdir.path;
tmpdir.refresh();
async function validateWrite() {
const filePathForHandle = path.resolve(tmpDir, 'tmp-write.txt');
const fileHandle = await open(filePathForHandle, 'w');
const buffer = Buffer.from('Hello world'.repeat(100), 'utf8');
const stream = fileHandle.createWriteStream();
stream.end(buffer);
await finished(stream);
const readFileData = fs.readFileSync(filePathForHandle);
assert.deepStrictEqual(buffer, readFileData);
}
async function validateRead() {
const filePathForHandle = path.resolve(tmpDir, 'tmp-read.txt');
const buf = Buffer.from('Hello world'.repeat(100), 'utf8');
fs.writeFileSync(filePathForHandle, buf);
const fileHandle = await open(filePathForHandle);
assert.deepStrictEqual(
await buffer(fileHandle.createReadStream()),
buf
);
}
Promise.all([
validateWrite(),
validateRead(),
]).then(common.mustCall());

View File

@@ -0,0 +1,35 @@
'use strict';
require('../common');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const { access, copyFile, open } = require('fs').promises;
async function validate() {
tmpdir.refresh();
const dest = tmpdir.resolve('baz.js');
await assert.rejects(
copyFile(fixtures.path('baz.js'), dest, 'r'),
{
code: 'ERR_INVALID_ARG_TYPE',
}
);
await copyFile(fixtures.path('baz.js'), dest);
await assert.rejects(
access(dest, 'r'),
{ code: 'ERR_INVALID_ARG_TYPE', message: /mode/ }
);
await access(dest);
const handle = await open(dest, 'r+');
await handle.datasync();
await handle.sync();
const buf = Buffer.from('hello world');
await handle.write(buf);
const ret = await handle.read(Buffer.alloc(11), 0, 11, 0);
assert.strictEqual(ret.bytesRead, 11);
assert.deepStrictEqual(ret.buffer, buf);
await handle.close();
}
validate();

View File

@@ -0,0 +1,200 @@
'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 path = require('path');
const { Readable } = require('stream');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const tmpDir = tmpdir.path;
tmpdir.refresh();
async function validateWriteFile() {
const filePathForHandle = path.resolve(tmpDir, 'tmp-write-file2.txt');
const fileHandle = await open(filePathForHandle, 'w+');
try {
const buffer = Buffer.from('Hello world'.repeat(100), 'utf8');
await fileHandle.writeFile(buffer);
const readFileData = fs.readFileSync(filePathForHandle);
assert.deepStrictEqual(buffer, readFileData);
} finally {
await fileHandle.close();
}
}
// Signal aborted while writing file
async function doWriteAndCancel() {
const filePathForHandle = path.resolve(tmpDir, 'dogs-running.txt');
const fileHandle = await open(filePathForHandle, 'w+');
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(writeFile(fileHandle, buffer, { signal }), {
name: 'AbortError'
});
} finally {
await fileHandle.close();
}
}
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());

View File

@@ -0,0 +1,90 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const { writeFile, readFile } = require('fs').promises;
const tmpdir = require('../common/tmpdir');
const { internalBinding } = require('internal/test/binding');
const fsBinding = internalBinding('fs');
tmpdir.refresh();
const fn = tmpdir.resolve('large-file');
// Creating large buffer with random content
const largeBuffer = Buffer.from(
Array.from({ length: 1024 ** 2 + 19 }, (_, index) => index)
);
async function createLargeFile() {
// Writing buffer to a file then try to read it
await writeFile(fn, largeBuffer);
}
async function validateReadFile() {
const readBuffer = await readFile(fn);
assert.strictEqual(readBuffer.equals(largeBuffer), true);
}
async function validateReadFileProc() {
// Test to make sure reading a file under the /proc directory works. Adapted
// from test-fs-read-file-sync-hostname.js.
// Refs:
// - https://groups.google.com/forum/#!topic/nodejs-dev/rxZ_RoH1Gn0
// - https://github.com/nodejs/node/issues/21331
// Test is Linux-specific.
if (!common.isLinux)
return;
const hostname = await readFile('/proc/sys/kernel/hostname');
assert.ok(hostname.length > 0);
}
function validateReadFileAbortLogicBefore() {
const signal = AbortSignal.abort();
assert.rejects(readFile(fn, { signal }), {
name: 'AbortError'
}).then(common.mustCall());
}
function validateReadFileAbortLogicDuring() {
const controller = new AbortController();
const signal = controller.signal;
process.nextTick(() => controller.abort());
assert.rejects(readFile(fn, { signal }), {
name: 'AbortError'
}).then(common.mustCall());
}
async function validateWrongSignalParam() {
// Verify that if something different than Abortcontroller.signal
// is passed, ERR_INVALID_ARG_TYPE is thrown
await assert.rejects(async () => {
const callback = common.mustNotCall();
await readFile(fn, { signal: 'hello' }, callback);
}, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' });
}
async function validateZeroByteLiar() {
const originalFStat = fsBinding.fstat;
fsBinding.fstat = common.mustCall(
async () => (/* stat fields */ [0, 1, 2, 3, 4, 5, 6, 7, 0 /* size */])
);
const readBuffer = await readFile(fn);
assert.strictEqual(readBuffer.toString(), largeBuffer.toString());
fsBinding.fstat = originalFStat;
}
(async () => {
await createLargeFile();
await validateReadFile();
await validateReadFileProc();
await validateReadFileAbortLogicBefore();
await validateReadFileAbortLogicDuring();
await validateWrongSignalParam();
await validateZeroByteLiar();
})().then(common.mustCall());

View File

@@ -0,0 +1,136 @@
'use strict';
const common = require('../common');
if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');
const { watch } = require('fs/promises');
const fs = require('fs');
const assert = require('assert');
const { join } = require('path');
const { setTimeout } = require('timers/promises');
const tmpdir = require('../common/tmpdir');
class WatchTestCase {
constructor(shouldInclude, dirName, fileName, field) {
this.dirName = dirName;
this.fileName = fileName;
this.field = field;
this.shouldSkip = !shouldInclude;
}
get dirPath() { return tmpdir.resolve(this.dirName); }
get filePath() { return join(this.dirPath, this.fileName); }
}
const kCases = [
// Watch on a directory should callback with a filename on supported systems
new WatchTestCase(
common.isLinux || common.isMacOS || common.isWindows || common.isAIX,
'watch1',
'foo',
'filePath'
),
// Watch on a file should callback with a filename on supported systems
new WatchTestCase(
common.isLinux || common.isMacOS || common.isWindows,
'watch2',
'bar',
'dirPath'
),
];
tmpdir.refresh();
for (const testCase of kCases) {
if (testCase.shouldSkip) continue;
fs.mkdirSync(testCase.dirPath);
// Long content so it's actually flushed.
const content1 = Date.now() + testCase.fileName.toLowerCase().repeat(1e4);
fs.writeFileSync(testCase.filePath, content1);
let interval;
async function test() {
if (common.isMacOS) {
// On macOS delay watcher start to avoid leaking previous events.
// Refs: https://github.com/libuv/libuv/pull/4503
await setTimeout(common.platformTimeout(100));
}
const watcher = watch(testCase[testCase.field]);
for await (const { eventType, filename } of watcher) {
clearInterval(interval);
assert.strictEqual(['rename', 'change'].includes(eventType), true);
assert.strictEqual(filename, testCase.fileName);
break;
}
// Waiting on it again is a non-op
// eslint-disable-next-line no-unused-vars
for await (const p of watcher) {
assert.fail('should not run');
}
}
// Long content so it's actually flushed. toUpperCase so there's real change.
const content2 = Date.now() + testCase.fileName.toUpperCase().repeat(1e4);
interval = setInterval(() => {
fs.writeFileSync(testCase.filePath, '');
fs.writeFileSync(testCase.filePath, content2);
}, 100);
test().then(common.mustCall());
}
assert.rejects(
async () => {
// eslint-disable-next-line no-unused-vars, no-empty
for await (const _ of watch(1)) { }
},
{ code: 'ERR_INVALID_ARG_TYPE' }).then(common.mustCall());
assert.rejects(
async () => {
// eslint-disable-next-line no-unused-vars, no-empty
for await (const _ of watch(__filename, 1)) { }
},
{ code: 'ERR_INVALID_ARG_TYPE' }).then(common.mustCall());
assert.rejects(
async () => {
// eslint-disable-next-line no-unused-vars, no-empty
for await (const _ of watch('', { persistent: 1 })) { }
},
{ code: 'ERR_INVALID_ARG_TYPE' }).then(common.mustCall());
assert.rejects(
async () => {
// eslint-disable-next-line no-unused-vars, no-empty
for await (const _ of watch('', { recursive: 1 })) { }
},
{ code: 'ERR_INVALID_ARG_TYPE' }).then(common.mustCall());
assert.rejects(
async () => {
// eslint-disable-next-line no-unused-vars, no-empty
for await (const _ of watch('', { encoding: 1 })) { }
},
{ code: 'ERR_INVALID_ARG_VALUE' }).then(common.mustCall());
assert.rejects(
async () => {
// eslint-disable-next-line no-unused-vars, no-empty
for await (const _ of watch('', { signal: 1 })) { }
},
{ code: 'ERR_INVALID_ARG_TYPE' }).then(common.mustCall());
(async () => {
const ac = new AbortController();
const { signal } = ac;
setImmediate(() => ac.abort());
try {
// eslint-disable-next-line no-unused-vars, no-empty
for await (const _ of watch(__filename, { signal })) { }
} catch (err) {
assert.strictEqual(err.name, 'AbortError');
}
})().then(common.mustCall());

View File

@@ -0,0 +1,110 @@
'use strict';
const common = require('../common');
// This test ensures that filehandle.write accepts "named parameters" object
// and doesn't interpret objects as strings
const assert = require('assert');
const fsPromises = require('fs').promises;
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const dest = tmpdir.resolve('tmp.txt');
const buffer = Buffer.from('zyx');
async function testInvalid(dest, expectedCode, ...params) {
if (params.length >= 2) {
params[1] = common.mustNotMutateObjectDeep(params[1]);
}
let fh;
try {
fh = await fsPromises.open(dest, 'w+');
await assert.rejects(
fh.write(...params),
{ code: expectedCode });
} finally {
await fh?.close();
}
}
async function testValid(dest, buffer, options) {
const length = options?.length;
const offset = options?.offset;
let fh, writeResult, writeBufCopy, readResult, readBufCopy;
try {
fh = await fsPromises.open(dest, 'w');
writeResult = await fh.write(buffer, options);
writeBufCopy = Uint8Array.prototype.slice.call(writeResult.buffer);
} finally {
await fh?.close();
}
try {
fh = await fsPromises.open(dest, 'r');
readResult = await fh.read(buffer, options);
readBufCopy = Uint8Array.prototype.slice.call(readResult.buffer);
} finally {
await fh?.close();
}
assert.ok(writeResult.bytesWritten >= readResult.bytesRead);
if (length !== undefined && length !== null) {
assert.strictEqual(writeResult.bytesWritten, length);
assert.strictEqual(readResult.bytesRead, length);
}
if (offset === undefined || offset === 0) {
assert.deepStrictEqual(writeBufCopy, readBufCopy);
}
assert.deepStrictEqual(writeResult.buffer, readResult.buffer);
}
(async () => {
// Test if first argument is not wrongly interpreted as ArrayBufferView|string
for (const badBuffer of [
undefined, null, true, 42, 42n, Symbol('42'), NaN, [], () => {},
common.mustNotCall(),
common.mustNotMutateObjectDeep({}),
Promise.resolve(new Uint8Array(1)),
{},
{ buffer: 'amNotParam' },
{ string: 'amNotParam' },
{ buffer: new Uint8Array(1).buffer },
new Date(),
new String('notPrimitive'),
{ toString() { return 'amObject'; } },
{ [Symbol.toPrimitive]: (hint) => 'amObject' },
]) {
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', common.mustNotMutateObjectDeep(badBuffer), {});
}
// First argument (buffer or string) is mandatory
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE');
// Various invalid options
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 5 });
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: 5 });
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 1, offset: 3 });
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: -1 });
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: -1 });
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', buffer, { offset: false });
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', buffer, { offset: true });
// Test compatibility with filehandle.read counterpart
for (const options of [
undefined,
null,
{},
{ length: 1 },
{ position: 5 },
{ length: 1, position: 5 },
{ length: 1, position: -1, offset: 2 },
{ length: null },
{ position: null },
{ offset: 1 },
]) {
await testValid(dest, buffer, common.mustNotMutateObjectDeep(options));
}
})().then(common.mustCall());

View File

@@ -0,0 +1,179 @@
'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 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 = {
expected: 'abc',
*[Symbol.iterator]() {
yield 'a';
yield 'b';
yield 'c';
}
};
const veryLargeBuffer = {
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';
}
};
async function doWrite() {
await fsPromises.writeFile(dest, buffer);
const data = fs.readFileSync(dest);
assert.deepStrictEqual(data, buffer);
}
async function doWriteStream() {
await fsPromises.writeFile(dest, stream);
const expected = 'abc';
const data = fs.readFileSync(dest, 'utf-8');
assert.deepStrictEqual(data, expected);
}
async function doWriteStreamWithCancel() {
const controller = new AbortController();
const { signal } = controller;
process.nextTick(() => controller.abort());
await assert.rejects(
fsPromises.writeFile(otherDest, stream, { signal }),
{ name: 'AbortError' }
);
}
async function doWriteIterable() {
await fsPromises.writeFile(dest, iterable);
const data = fs.readFileSync(dest, 'utf-8');
assert.deepStrictEqual(data, iterable.expected);
}
async function doWriteInvalidIterable() {
await Promise.all(
[42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) =>
assert.rejects(fsPromises.writeFile(dest, iterableWith(value)), {
code: 'ERR_INVALID_ARG_TYPE',
})
)
);
}
async function doWriteIterableWithEncoding() {
await fsPromises.writeFile(dest, stream2, 'latin1');
const expected = 'ümlaut sechzig';
const data = fs.readFileSync(dest, 'latin1');
assert.deepStrictEqual(data, expected);
}
async function doWriteBufferIterable() {
await fsPromises.writeFile(dest, bufferIterable);
const data = fs.readFileSync(dest, 'utf-8');
assert.deepStrictEqual(data, bufferIterable.expected);
}
async function doWriteAsyncIterable() {
await fsPromises.writeFile(dest, asyncIterable);
const data = fs.readFileSync(dest, 'utf-8');
assert.deepStrictEqual(data, asyncIterable.expected);
}
async function doWriteAsyncLargeIterable() {
await fsPromises.writeFile(dest, veryLargeBuffer);
const data = fs.readFileSync(dest, 'utf-8');
assert.deepStrictEqual(data, veryLargeBuffer.expected);
}
async function doWriteInvalidValues() {
await Promise.all(
[42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) =>
assert.rejects(fsPromises.writeFile(dest, value), {
code: 'ERR_INVALID_ARG_TYPE',
})
)
);
}
async function doWriteWithCancel() {
const controller = new AbortController();
const { signal } = controller;
process.nextTick(() => controller.abort());
await assert.rejects(
fsPromises.writeFile(otherDest, buffer, { signal }),
{ name: 'AbortError' }
);
}
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 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);
}
(async () => {
await doWrite();
await doWriteWithCancel();
await doAppend();
await doRead();
await doReadWithEncoding();
await doWriteStream();
await doWriteStreamWithCancel();
await doWriteIterable();
await doWriteInvalidIterable();
await doWriteIterableWithEncoding();
await doWriteBufferIterable();
await doWriteAsyncIterable();
await doWriteAsyncLargeIterable();
await doWriteInvalidValues();
})().then(common.mustCall());

View File

@@ -0,0 +1,512 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const path = require('path');
const fs = require('fs');
const fsPromises = fs.promises;
const {
access,
chmod,
chown,
copyFile,
lchown,
link,
lchmod,
lstat,
lutimes,
mkdir,
mkdtemp,
open,
readFile,
readdir,
readlink,
realpath,
rename,
rmdir,
stat,
statfs,
symlink,
truncate,
unlink,
utimes,
writeFile
} = fsPromises;
const tmpDir = tmpdir.path;
let dirc = 0;
function nextdir() {
return `test${++dirc}`;
}
// fs.promises should be enumerable.
assert.strictEqual(
Object.prototype.propertyIsEnumerable.call(fs, 'promises'),
true
);
{
access(__filename, 0)
.then(common.mustCall());
assert.rejects(
access('this file does not exist', 0),
{
code: 'ENOENT',
name: 'Error',
message: /^ENOENT: no such file or directory, access/,
stack: /at async Function\.rejects/
}
).then(common.mustCall());
assert.rejects(
access(__filename, 8),
{
code: 'ERR_OUT_OF_RANGE',
}
).then(common.mustCall());
assert.rejects(
access(__filename, { [Symbol.toPrimitive]() { return 5; } }),
{
code: 'ERR_INVALID_ARG_TYPE',
}
).then(common.mustCall());
}
function verifyStatObject(stat) {
assert.strictEqual(typeof stat, 'object');
assert.strictEqual(typeof stat.dev, 'number');
assert.strictEqual(typeof stat.mode, 'number');
}
function verifyStatFsObject(stat, isBigint = false) {
const valueType = isBigint ? 'bigint' : 'number';
assert.strictEqual(typeof stat, 'object');
assert.strictEqual(typeof stat.type, valueType);
assert.strictEqual(typeof stat.bsize, valueType);
assert.strictEqual(typeof stat.blocks, valueType);
assert.strictEqual(typeof stat.bfree, valueType);
assert.strictEqual(typeof stat.bavail, valueType);
assert.strictEqual(typeof stat.files, valueType);
assert.strictEqual(typeof stat.ffree, valueType);
}
async function getHandle(dest) {
await copyFile(fixtures.path('baz.js'), dest);
await access(dest);
return open(dest, 'r+');
}
async function executeOnHandle(dest, func) {
let handle;
try {
handle = await getHandle(dest);
await func(handle);
} finally {
if (handle) {
await handle.close();
}
}
}
{
async function doTest() {
tmpdir.refresh();
const dest = path.resolve(tmpDir, 'baz.js');
// handle is object
{
await executeOnHandle(dest, async (handle) => {
assert.strictEqual(typeof handle, 'object');
});
}
// file stats
{
await executeOnHandle(dest, async (handle) => {
let stats = await handle.stat();
verifyStatObject(stats);
assert.strictEqual(stats.size, 35);
await handle.truncate(1);
stats = await handle.stat();
verifyStatObject(stats);
assert.strictEqual(stats.size, 1);
stats = await stat(dest);
verifyStatObject(stats);
stats = await handle.stat();
verifyStatObject(stats);
await handle.datasync();
await handle.sync();
});
}
// File system stats
{
const statFs = await statfs(dest);
verifyStatFsObject(statFs);
}
// File system stats bigint
{
const statFs = await statfs(dest, { bigint: true });
verifyStatFsObject(statFs, true);
}
// Test fs.read promises when length to read is zero bytes
{
const dest = path.resolve(tmpDir, 'test1.js');
await executeOnHandle(dest, async (handle) => {
const buf = Buffer.from('DAWGS WIN');
const bufLen = buf.length;
await handle.write(buf);
const ret = await handle.read(Buffer.alloc(bufLen), 0, 0, 0);
assert.strictEqual(ret.bytesRead, 0);
await unlink(dest);
});
}
// Use fallback buffer allocation when first argument is null
{
await executeOnHandle(dest, async (handle) => {
const ret = await handle.read(null, 0, 0, 0);
assert.strictEqual(ret.buffer.length, 16384);
});
}
// TypeError if buffer is not ArrayBufferView or nullable object
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(
async () => handle.read(0, 0, 0, 0),
{ code: 'ERR_INVALID_ARG_TYPE' }
);
});
}
// Bytes written to file match buffer
{
await executeOnHandle(dest, async (handle) => {
const buf = Buffer.from('hello fsPromises');
const bufLen = buf.length;
await handle.write(buf);
const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0);
assert.strictEqual(ret.bytesRead, bufLen);
assert.deepStrictEqual(ret.buffer, buf);
});
}
// Truncate file to specified length
{
await executeOnHandle(dest, async (handle) => {
const buf = Buffer.from('hello FileHandle');
const bufLen = buf.length;
await handle.write(buf, 0, bufLen, 0);
const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0);
assert.strictEqual(ret.bytesRead, bufLen);
assert.deepStrictEqual(ret.buffer, buf);
await truncate(dest, 5);
assert.strictEqual((await readFile(dest)).toString(), 'hello');
});
}
// Invalid change of ownership
{
await executeOnHandle(dest, async (handle) => {
await chmod(dest, 0o666);
await handle.chmod(0o666);
await chmod(dest, (0o10777));
await handle.chmod(0o10777);
if (!common.isWindows) {
await chown(dest, process.getuid(), process.getgid());
await handle.chown(process.getuid(), process.getgid());
}
await assert.rejects(
async () => {
await chown(dest, 1, -2);
},
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "gid" is out of range. ' +
'It must be >= -1 && <= 4294967295. Received -2'
});
await assert.rejects(
async () => {
await handle.chown(1, -2);
},
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "gid" is out of range. ' +
'It must be >= -1 && <= 4294967295. Received -2'
});
});
}
// Set modification times
{
await executeOnHandle(dest, async (handle) => {
await utimes(dest, new Date(), new Date());
try {
await handle.utimes(new Date(), new Date());
} catch (err) {
// Some systems do not have futimes. If there is an error,
// expect it to be ENOSYS
common.expectsError({
code: 'ENOSYS',
name: 'Error'
})(err);
}
});
}
// Set modification times with lutimes
{
const a_time = new Date();
a_time.setMinutes(a_time.getMinutes() - 1);
const m_time = new Date();
m_time.setHours(m_time.getHours() - 1);
await lutimes(dest, a_time, m_time);
const stats = await stat(dest);
assert.strictEqual(a_time.toString(), stats.atime.toString());
assert.strictEqual(m_time.toString(), stats.mtime.toString());
}
// create symlink
{
const newPath = path.resolve(tmpDir, 'baz2.js');
await rename(dest, newPath);
let stats = await stat(newPath);
verifyStatObject(stats);
if (common.canCreateSymLink()) {
const newLink = path.resolve(tmpDir, 'baz3.js');
await symlink(newPath, newLink);
if (!common.isWindows) {
await lchown(newLink, process.getuid(), process.getgid());
}
stats = await lstat(newLink);
verifyStatObject(stats);
assert.strictEqual(newPath.toLowerCase(),
(await realpath(newLink)).toLowerCase());
assert.strictEqual(newPath.toLowerCase(),
(await readlink(newLink)).toLowerCase());
const newMode = 0o666;
if (common.isMacOS) {
// `lchmod` is only available on macOS.
await lchmod(newLink, newMode);
stats = await lstat(newLink);
assert.strictEqual(stats.mode & 0o777, newMode);
} else {
await Promise.all([
assert.rejects(
lchmod(newLink, newMode),
common.expectsError({
code: 'ERR_METHOD_NOT_IMPLEMENTED',
name: 'Error',
message: 'The lchmod() method is not implemented'
})
),
]);
}
await unlink(newLink);
}
}
// specify symlink type
{
const dir = path.join(tmpDir, nextdir());
await symlink(tmpDir, dir, 'dir');
const stats = await lstat(dir);
assert.strictEqual(stats.isSymbolicLink(), true);
await unlink(dir);
}
// create hard link
{
const newPath = path.resolve(tmpDir, 'baz2.js');
const newLink = path.resolve(tmpDir, 'baz4.js');
await link(newPath, newLink);
await unlink(newLink);
}
// Testing readdir lists both files and directories
{
const newDir = path.resolve(tmpDir, 'dir');
const newFile = path.resolve(tmpDir, 'foo.js');
await mkdir(newDir);
await writeFile(newFile, 'DAWGS WIN!', 'utf8');
const stats = await stat(newDir);
assert(stats.isDirectory());
const list = await readdir(tmpDir);
assert.notStrictEqual(list.indexOf('dir'), -1);
assert.notStrictEqual(list.indexOf('foo.js'), -1);
await rmdir(newDir);
await unlink(newFile);
}
// Use fallback encoding when input is null
{
const newFile = path.resolve(tmpDir, 'dogs_running.js');
await writeFile(newFile, 'dogs running', { encoding: null });
const fileExists = fs.existsSync(newFile);
assert.strictEqual(fileExists, true);
}
// `mkdir` when options is number.
{
const dir = path.join(tmpDir, nextdir());
await mkdir(dir, 777);
const stats = await stat(dir);
assert(stats.isDirectory());
}
// `mkdir` when options is string.
{
const dir = path.join(tmpDir, nextdir());
await mkdir(dir, '777');
const stats = await stat(dir);
assert(stats.isDirectory());
}
// `mkdirp` when folder does not yet exist.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
await mkdir(dir, { recursive: true });
const stats = await stat(dir);
assert(stats.isDirectory());
}
// `mkdirp` when path is a file.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
await mkdir(path.dirname(dir));
await writeFile(dir, '');
await assert.rejects(
mkdir(dir, { recursive: true }),
{
code: 'EEXIST',
message: /EEXIST: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
}
);
}
// `mkdirp` when part of the path is a file.
{
const file = path.join(tmpDir, nextdir(), nextdir());
const dir = path.join(file, nextdir(), nextdir());
await mkdir(path.dirname(file));
await writeFile(file, '');
await assert.rejects(
mkdir(dir, { recursive: true }),
{
code: 'ENOTDIR',
message: /ENOTDIR: .*mkdir/,
name: 'Error',
syscall: 'mkdir',
}
);
}
// mkdirp ./
{
const dir = path.resolve(tmpDir, `${nextdir()}/./${nextdir()}`);
await mkdir(dir, { recursive: true });
const stats = await stat(dir);
assert(stats.isDirectory());
}
// mkdirp ../
{
const dir = path.resolve(tmpDir, `${nextdir()}/../${nextdir()}`);
await mkdir(dir, { recursive: true });
const stats = await stat(dir);
assert(stats.isDirectory());
}
// fs.mkdirp requires the recursive option to be of type boolean.
// Everything else generates an error.
{
const dir = path.join(tmpDir, nextdir(), nextdir());
['', 1, {}, [], null, Symbol('test'), () => {}].forEach((recursive) => {
assert.rejects(
// mkdir() expects to get a boolean value for options.recursive.
async () => mkdir(dir, { recursive }),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
).then(common.mustCall());
});
}
// `mkdtemp` with invalid numeric prefix
{
await mkdtemp(path.resolve(tmpDir, 'FOO'));
await assert.rejects(
// mkdtemp() expects to get a string prefix.
async () => mkdtemp(1),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
}
// Regression test for https://github.com/nodejs/node/issues/38168
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(
async () => handle.write('abc', 0, 'hex'),
{
code: 'ERR_INVALID_ARG_VALUE',
message: /'encoding' is invalid for data of length 3/
}
);
const ret = await handle.write('abcd', 0, 'hex');
assert.strictEqual(ret.bytesWritten, 2);
});
}
// Test prototype methods calling with contexts other than FileHandle
{
await executeOnHandle(dest, async (handle) => {
await assert.rejects(() => handle.stat.call({}), {
code: 'ERR_INTERNAL_ASSERTION',
message: /handle must be an instance of FileHandle/
});
});
}
}
doTest().then(common.mustCall());
}

View File

@@ -0,0 +1,41 @@
'use strict';
require('../common');
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const fs = require('fs');
const filepath = fixtures.path('x.txt');
const fd = fs.openSync(filepath, 'r');
const fsPromises = fs.promises;
const buffer = new Uint8Array();
assert.throws(
() => fs.readSync(fd, buffer, 0, 10, 0),
{
code: 'ERR_INVALID_ARG_VALUE',
message: 'The argument \'buffer\' is empty and cannot be written. ' +
'Received Uint8Array(0) []'
}
);
assert.throws(
() => fs.read(fd, buffer, 0, 1, 0, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_VALUE',
message: 'The argument \'buffer\' is empty and cannot be written. ' +
'Received Uint8Array(0) []'
}
);
(async () => {
const filehandle = await fsPromises.open(filepath, 'r');
assert.rejects(
() => filehandle.read(buffer, 0, 1, 0),
{
code: 'ERR_INVALID_ARG_VALUE',
message: 'The argument \'buffer\' is empty and cannot be written. ' +
'Received Uint8Array(0) []'
}
).then(common.mustCall());
})().then(common.mustCall());

View File

@@ -0,0 +1,47 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const fs = require('fs');
// Test that concurrent file read streams dont interfere with each others
// contents, and that the chunks generated by the reads only retain a
// 'reasonable' amount of memory.
// Refs: https://github.com/nodejs/node/issues/21967
const filename = fixtures.path('loop.js'); // Some small non-homogeneous file.
const content = fs.readFileSync(filename);
const N = 2000;
let started = 0;
let done = 0;
const arrayBuffers = new Set();
function startRead() {
++started;
const chunks = [];
fs.createReadStream(filename)
.on('data', (chunk) => {
chunks.push(chunk);
arrayBuffers.add(chunk.buffer);
})
.on('end', common.mustCall(() => {
if (started < N)
startRead();
assert.deepStrictEqual(Buffer.concat(chunks), content);
if (++done === N) {
const retainedMemory =
[...arrayBuffers].map((ab) => ab.byteLength).reduce((a, b) => a + b);
assert(retainedMemory / (N * content.length) <= 3,
`Retaining ${retainedMemory} bytes in ABs for ${N} ` +
`chunks of size ${content.length}`);
}
}));
}
// Dont start the reads all at once that way we would have to allocate
// a large amount of memory upfront.
for (let i = 0; i < 6; ++i)
startRead();

View File

@@ -0,0 +1,63 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const stream = fs.createReadStream(__filename, {
bufferSize: 64
});
const err = new Error('BAM');
stream.on('error', common.mustCall((err_) => {
process.nextTick(common.mustCall(() => {
assert.strictEqual(stream.fd, null);
assert.strictEqual(err_, err);
}));
}));
fs.close = common.mustCall((fd_, cb) => {
assert.strictEqual(fd_, stream.fd);
process.nextTick(cb);
});
const read = fs.read;
fs.read = function() {
// First time is ok.
read.apply(fs, arguments);
// Then it breaks.
fs.read = common.mustCall(function() {
const cb = arguments[arguments.length - 1];
process.nextTick(() => {
cb(err);
});
// It should not be called again!
fs.read = () => {
throw new Error('BOOM!');
};
});
};
stream.on('data', (buf) => {
stream.on('data', common.mustNotCall("no more 'data' events should follow"));
});

View File

@@ -0,0 +1,154 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
const file = tmpdir.resolve('read_stream_filehandle_test.txt');
const input = 'hello world';
tmpdir.refresh();
fs.writeFileSync(file, input);
fs.promises.open(file, 'r').then((handle) => {
handle.on('close', common.mustCall());
const stream = fs.createReadStream(null, { fd: handle });
let output = '';
stream.on('data', common.mustCallAtLeast((data) => {
output += data;
}));
stream.on('end', common.mustCall(() => {
assert.strictEqual(output, input);
}));
stream.on('close', common.mustCall());
}).then(common.mustCall());
fs.promises.open(file, 'r').then((handle) => {
handle.on('close', common.mustCall());
const stream = fs.createReadStream(null, { fd: handle });
stream.on('data', common.mustNotCall());
stream.on('close', common.mustCall());
return handle.close();
}).then(common.mustCall());
fs.promises.open(file, 'r').then((handle) => {
handle.on('close', common.mustCall());
const stream = fs.createReadStream(null, { fd: handle });
stream.on('close', common.mustCall());
stream.on('data', common.mustCall(() => {
handle.close();
}));
}).then(common.mustCall());
fs.promises.open(file, 'r').then((handle) => {
handle.on('close', common.mustCall());
const stream = fs.createReadStream(null, { fd: handle });
stream.on('close', common.mustCall());
stream.close();
}).then(common.mustCall());
fs.promises.open(file, 'r').then((handle) => {
assert.throws(() => {
fs.createReadStream(null, { fd: handle, fs });
}, {
code: 'ERR_METHOD_NOT_IMPLEMENTED',
name: 'Error',
message: 'The FileHandle with fs method is not implemented'
});
return handle.close();
}).then(common.mustCall());
fs.promises.open(file, 'r').then((handle) => {
const { read: originalReadFunction } = handle;
handle.read = common.mustCallAtLeast(function read() {
return Reflect.apply(originalReadFunction, this, arguments);
});
const stream = fs.createReadStream(null, { fd: handle });
let output = '';
stream.on('data', common.mustCallAtLeast((data) => {
output += data;
}));
stream.on('end', common.mustCall(() => {
assert.strictEqual(output, input);
}));
}).then(common.mustCall());
// AbortSignal option test
fs.promises.open(file, 'r').then((handle) => {
const controller = new AbortController();
const { signal } = controller;
const stream = handle.createReadStream({ signal });
stream.on('data', common.mustNotCall());
stream.on('end', common.mustNotCall());
stream.on('error', common.mustCall((err) => {
assert.strictEqual(err.name, 'AbortError');
}));
stream.on('close', common.mustCall(() => {
handle.close();
}));
controller.abort();
}).then(common.mustCall());
// Already-aborted signal test
fs.promises.open(file, 'r').then((handle) => {
const signal = AbortSignal.abort();
const stream = handle.createReadStream({ signal });
stream.on('data', common.mustNotCall());
stream.on('end', common.mustNotCall());
stream.on('error', common.mustCall((err) => {
assert.strictEqual(err.name, 'AbortError');
}));
stream.on('close', common.mustCall(() => {
handle.close();
}));
}).then(common.mustCall());
// Invalid signal type test
fs.promises.open(file, 'r').then((handle) => {
for (const signal of [1, {}, [], '', null, NaN, 1n, () => {}, Symbol(), false, true]) {
assert.throws(() => {
handle.createReadStream({ signal });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
}
return handle.close();
}).then(common.mustCall());
// Custom abort reason test
fs.promises.open(file, 'r').then((handle) => {
const controller = new AbortController();
const { signal } = controller;
const reason = new Error('some silly abort reason');
const stream = handle.createReadStream({ signal });
stream.on('data', common.mustNotCall());
stream.on('end', common.mustNotCall());
stream.on('error', common.mustCall((err) => {
assert.strictEqual(err.name, 'AbortError');
assert.strictEqual(err.cause, reason);
}));
stream.on('close', common.mustCall(() => {
handle.close();
}));
controller.abort(reason);
}).then(common.mustCall());

View File

@@ -0,0 +1,205 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const fixtures = require('../common/fixtures');
const fn = fixtures.path('elipses.txt');
const rangeFile = fixtures.path('x.txt');
{
let paused = false;
const file = fs.ReadStream(fn);
file.on('open', common.mustCall(function(fd) {
file.length = 0;
assert.strictEqual(typeof fd, 'number');
assert.ok(file.readable);
// GH-535
file.pause();
file.resume();
file.pause();
file.resume();
}));
file.on('data', common.mustCallAtLeast(function(data) {
assert.ok(data instanceof Buffer);
assert.ok(!paused);
file.length += data.length;
paused = true;
file.pause();
setTimeout(function() {
paused = false;
file.resume();
}, 10);
}));
file.on('end', common.mustCall());
file.on('close', common.mustCall(function() {
assert.strictEqual(file.length, 30000);
}));
}
{
const file = fs.createReadStream(fn, { __proto__: { encoding: 'utf8' } });
file.length = 0;
file.on('data', function(data) {
assert.strictEqual(typeof data, 'string');
file.length += data.length;
for (let i = 0; i < data.length; i++) {
// http://www.fileformat.info/info/unicode/char/2026/index.htm
assert.strictEqual(data[i], '\u2026');
}
});
file.on('close', common.mustCall(function() {
assert.strictEqual(file.length, 10000);
}));
}
{
const options = { __proto__: { bufferSize: 1, start: 1, end: 2 } };
const file = fs.createReadStream(rangeFile, options);
assert.strictEqual(file.start, 1);
assert.strictEqual(file.end, 2);
let contentRead = '';
file.on('data', function(data) {
contentRead += data.toString('utf-8');
});
file.on('end', common.mustCall(function() {
assert.strictEqual(contentRead, 'yz');
}));
}
{
const options = { __proto__: { bufferSize: 1, start: 1 } };
const file = fs.createReadStream(rangeFile, options);
assert.strictEqual(file.start, 1);
file.data = '';
file.on('data', function(data) {
file.data += data.toString('utf-8');
});
file.on('end', common.mustCall(function() {
assert.strictEqual(file.data, 'yz\n');
}));
}
// https://github.com/joyent/node/issues/2320
{
const options = { __proto__: { bufferSize: 1.23, start: 1 } };
const file = fs.createReadStream(rangeFile, options);
assert.strictEqual(file.start, 1);
file.data = '';
file.on('data', function(data) {
file.data += data.toString('utf-8');
});
file.on('end', common.mustCall(function() {
assert.strictEqual(file.data, 'yz\n');
}));
}
{
const message =
'The value of "start" is out of range. It must be <= "end" (here: 2).' +
' Received 10';
assert.throws(
() => {
fs.createReadStream(rangeFile, { __proto__: { start: 10, end: 2 } });
},
{
code: 'ERR_OUT_OF_RANGE',
message,
name: 'RangeError'
});
}
{
const options = { __proto__: { start: 0, end: 0 } };
const stream = fs.createReadStream(rangeFile, options);
assert.strictEqual(stream.start, 0);
assert.strictEqual(stream.end, 0);
stream.data = '';
stream.on('data', function(chunk) {
stream.data += chunk;
});
stream.on('end', common.mustCall(function() {
assert.strictEqual(stream.data, 'x');
}));
}
// Pause and then resume immediately.
{
const pauseRes = fs.createReadStream(rangeFile);
pauseRes.pause();
pauseRes.resume();
}
{
let data = '';
let file =
fs.createReadStream(rangeFile, { __proto__: { autoClose: false } });
assert.strictEqual(file.autoClose, false);
file.on('data', (chunk) => { data += chunk; });
file.on('end', common.mustCall(function() {
process.nextTick(common.mustCall(function() {
assert(!file.closed);
assert(!file.destroyed);
assert.strictEqual(data, 'xyz\n');
fileNext();
}));
}));
function fileNext() {
// This will tell us if the fd is usable again or not.
file = fs.createReadStream(null, { __proto__: { fd: file.fd, start: 0 } });
file.data = '';
file.on('data', function(data) {
file.data += data;
});
file.on('end', common.mustCall(function() {
assert.strictEqual(file.data, 'xyz\n');
}));
}
process.on('exit', function() {
assert(file.closed);
assert(file.destroyed);
});
}
// Just to make sure autoClose won't close the stream because of error.
{
const options = { __proto__: { fd: 13337, autoClose: false } };
const file = fs.createReadStream(null, options);
file.on('data', common.mustNotCall());
file.on('error', common.mustCall());
process.on('exit', function() {
assert(!file.closed);
assert(!file.destroyed);
assert(file.fd);
});
}
// Make sure stream is destroyed when file does not exist.
{
const file = fs.createReadStream('/path/to/file/that/does/not/exist');
file.on('data', common.mustNotCall());
file.on('error', common.mustCall());
process.on('exit', function() {
assert(file.closed);
assert(file.destroyed);
});
}

View File

@@ -0,0 +1,17 @@
'use strict';
const common = require('../common');
const fs = require('fs');
common.expectWarning(
'DeprecationWarning',
'ReadStream.prototype.open() is deprecated', 'DEP0135');
const s = fs.createReadStream('asd')
// We don't care about errors in this test.
.on('error', () => {});
s.open();
process.nextTick(() => {
// Allow overriding open().
fs.ReadStream.prototype.open = common.mustCall();
fs.createReadStream('asd');
});

View File

@@ -0,0 +1,82 @@
'use strict';
// Refs: https://github.com/nodejs/node/issues/33940
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const fs = require('fs');
const assert = require('assert');
tmpdir.refresh();
const file = tmpdir.resolve('read_stream_pos_test.txt');
fs.writeFileSync(file, '');
let counter = 0;
const writeInterval = setInterval(() => {
counter = counter + 1;
const line = `hello at ${counter}\n`;
fs.writeFileSync(file, line, { flag: 'a' });
}, 1);
const hwm = 10;
let bufs = [];
let isLow = false;
let cur = 0;
let stream;
const readInterval = setInterval(() => {
if (stream) return;
stream = fs.createReadStream(file, {
highWaterMark: hwm,
start: cur
});
stream.on('data', common.mustCallAtLeast((chunk) => {
cur += chunk.length;
bufs.push(chunk);
if (isLow) {
const brokenLines = Buffer.concat(bufs).toString()
.split('\n')
.filter((line) => {
const s = 'hello at'.slice(0, line.length);
if (line && !line.startsWith(s)) {
return true;
}
return false;
});
assert.strictEqual(brokenLines.length, 0);
exitTest();
return;
}
if (chunk.length !== hwm) {
isLow = true;
}
}));
stream.on('end', () => {
stream = null;
isLow = false;
bufs = [];
});
}, 10);
// Time longer than 90 seconds to exit safely
const endTimer = setTimeout(() => {
exitTest();
}, 90000);
const exitTest = () => {
clearInterval(readInterval);
clearInterval(writeInterval);
clearTimeout(endTimer);
if (stream && !stream.destroyed) {
stream.on('close', () => {
process.exit();
});
stream.destroy();
} else {
process.exit();
}
};

View File

@@ -0,0 +1,77 @@
'use strict';
require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const fs = require('fs');
// This test ensures that appropriate TypeError is thrown by createReadStream
// when an argument with invalid type is passed
const example = fixtures.path('x.txt');
// Should not throw.
fs.createReadStream(example, undefined);
fs.createReadStream(example, null);
fs.createReadStream(example, 'utf8');
fs.createReadStream(example, { encoding: 'utf8' });
const createReadStreamErr = (path, opt, error) => {
assert.throws(() => {
fs.createReadStream(path, opt);
}, error);
};
const typeError = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
};
const rangeError = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError'
};
[123, 0, true, false].forEach((opts) =>
createReadStreamErr(example, opts, typeError)
);
// Case 0: Should not throw if either start or end is undefined
[{}, { start: 0 }, { end: Infinity }].forEach((opts) =>
fs.createReadStream(example, opts)
);
// Case 1: Should throw TypeError if either start or end is not of type 'number'
[
{ start: 'invalid' },
{ end: 'invalid' },
{ start: 'invalid', end: 'invalid' },
].forEach((opts) => createReadStreamErr(example, opts, typeError));
// Case 2: Should throw RangeError if either start or end is NaN
[{ start: NaN }, { end: NaN }, { start: NaN, end: NaN }].forEach((opts) =>
createReadStreamErr(example, opts, rangeError)
);
// Case 3: Should throw RangeError if either start or end is negative
[{ start: -1 }, { end: -1 }, { start: -1, end: -1 }].forEach((opts) =>
createReadStreamErr(example, opts, rangeError)
);
// Case 4: Should throw RangeError if either start or end is fractional
[{ start: 0.1 }, { end: 0.1 }, { start: 0.1, end: 0.1 }].forEach((opts) =>
createReadStreamErr(example, opts, rangeError)
);
// Case 5: Should not throw if both start and end are whole numbers
fs.createReadStream(example, { start: 1, end: 5 });
// Case 6: Should throw RangeError if start is greater than end
createReadStreamErr(example, { start: 5, end: 1 }, rangeError);
// Case 7: Should throw RangeError if start or end is not safe integer
const NOT_SAFE_INTEGER = 2 ** 53;
[
{ start: NOT_SAFE_INTEGER, end: Infinity },
{ start: 0, end: NOT_SAFE_INTEGER },
].forEach((opts) =>
createReadStreamErr(example, opts, rangeError)
);

View File

@@ -0,0 +1,277 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const child_process = require('child_process');
const assert = require('assert');
const fs = require('fs');
const fixtures = require('../common/fixtures');
const fn = fixtures.path('elipses.txt');
const rangeFile = fixtures.path('x.txt');
function test1(options) {
let paused = false;
let bytesRead = 0;
const file = fs.createReadStream(fn, options);
const fileSize = fs.statSync(fn).size;
assert.strictEqual(file.bytesRead, 0);
file.on('open', common.mustCall(function(fd) {
file.length = 0;
assert.strictEqual(typeof fd, 'number');
assert.strictEqual(file.bytesRead, 0);
assert.ok(file.readable);
// GH-535
file.pause();
file.resume();
file.pause();
file.resume();
}));
file.on('data', function(data) {
assert.ok(data instanceof Buffer);
assert.ok(data.byteOffset % 8 === 0);
assert.ok(!paused);
file.length += data.length;
bytesRead += data.length;
assert.strictEqual(file.bytesRead, bytesRead);
paused = true;
file.pause();
setTimeout(function() {
paused = false;
file.resume();
}, 10);
});
file.on('end', common.mustCall(function(chunk) {
assert.strictEqual(bytesRead, fileSize);
assert.strictEqual(file.bytesRead, fileSize);
}));
file.on('close', common.mustCall(function() {
assert.strictEqual(bytesRead, fileSize);
assert.strictEqual(file.bytesRead, fileSize);
}));
process.on('exit', function() {
assert.strictEqual(file.length, 30000);
});
}
test1({});
test1({
fs: {
open: common.mustCall(fs.open),
read: common.mustCallAtLeast(fs.read, 1),
close: common.mustCall(fs.close),
}
});
{
const file = fs.createReadStream(fn, common.mustNotMutateObjectDeep({ encoding: 'utf8' }));
file.length = 0;
file.on('data', function(data) {
assert.strictEqual(typeof data, 'string');
file.length += data.length;
for (let i = 0; i < data.length; i++) {
// http://www.fileformat.info/info/unicode/char/2026/index.htm
assert.strictEqual(data[i], '\u2026');
}
});
file.on('close', common.mustCall());
process.on('exit', function() {
assert.strictEqual(file.length, 10000);
});
}
{
const file =
fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ bufferSize: 1, start: 1, end: 2 }));
let contentRead = '';
file.on('data', function(data) {
contentRead += data.toString('utf-8');
});
file.on('end', common.mustCall(function(data) {
assert.strictEqual(contentRead, 'yz');
}));
}
{
const file = fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ bufferSize: 1, start: 1 }));
file.data = '';
file.on('data', function(data) {
file.data += data.toString('utf-8');
});
file.on('end', common.mustCall(function() {
assert.strictEqual(file.data, 'yz\n');
}));
}
{
// Ref: https://github.com/nodejs/node-v0.x-archive/issues/2320
const file = fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ bufferSize: 1.23, start: 1 }));
file.data = '';
file.on('data', function(data) {
file.data += data.toString('utf-8');
});
file.on('end', common.mustCall(function() {
assert.strictEqual(file.data, 'yz\n');
}));
}
assert.throws(
() => {
fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ start: 10, end: 2 }));
},
{
code: 'ERR_OUT_OF_RANGE',
message: 'The value of "start" is out of range. It must be <= "end"' +
' (here: 2). Received 10',
name: 'RangeError'
});
{
const stream = fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ start: 0, end: 0 }));
stream.data = '';
stream.on('data', function(chunk) {
stream.data += chunk;
});
stream.on('end', common.mustCall(function() {
assert.strictEqual(stream.data, 'x');
}));
}
{
// Verify that end works when start is not specified.
const stream = new fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ end: 1 }));
stream.data = '';
stream.on('data', function(chunk) {
stream.data += chunk;
});
stream.on('end', common.mustCall(function() {
assert.strictEqual(stream.data, 'xy');
}));
}
if (!common.isWindows) {
// Verify that end works when start is not specified, and we do not try to
// use positioned reads. This makes sure that this keeps working for
// non-seekable file descriptors.
tmpdir.refresh();
const filename = `${tmpdir.path}/foo.pipe`;
const mkfifoResult = child_process.spawnSync('mkfifo', [filename]);
if (!mkfifoResult.error) {
child_process.exec(...common.escapePOSIXShell`echo "xyz foobar" > "${filename}"`);
const stream = new fs.createReadStream(filename, common.mustNotMutateObjectDeep({ end: 1 }));
stream.data = '';
stream.on('data', function(chunk) {
stream.data += chunk;
});
stream.on('end', common.mustCall(function() {
assert.strictEqual(stream.data, 'xy');
fs.unlinkSync(filename);
}));
} else {
common.printSkipMessage('mkfifo not available');
}
}
{
// Pause and then resume immediately.
const pauseRes = fs.createReadStream(rangeFile);
pauseRes.pause();
pauseRes.resume();
}
{
let file = fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ autoClose: false }));
let data = '';
file.on('data', function(chunk) { data += chunk; });
file.on('end', common.mustCall(function() {
assert.strictEqual(data, 'xyz\n');
process.nextTick(function() {
assert(!file.closed);
assert(!file.destroyed);
fileNext();
});
}));
function fileNext() {
// This will tell us if the fd is usable again or not.
file = fs.createReadStream(null, common.mustNotMutateObjectDeep({ fd: file.fd, start: 0 }));
file.data = '';
file.on('data', function(data) {
file.data += data;
});
file.on('end', common.mustCall(function(err) {
assert.strictEqual(file.data, 'xyz\n');
}));
process.on('exit', function() {
assert(file.closed);
assert(file.destroyed);
});
}
}
{
// Just to make sure autoClose won't close the stream because of error.
const file = fs.createReadStream(null, common.mustNotMutateObjectDeep({ fd: 13337, autoClose: false }));
file.on('data', common.mustNotCall());
file.on('error', common.mustCall());
process.on('exit', function() {
assert(!file.closed);
assert(!file.destroyed);
assert(file.fd);
});
}
{
// Make sure stream is destroyed when file does not exist.
const file = fs.createReadStream('/path/to/file/that/does/not/exist');
file.on('data', common.mustNotCall());
file.on('error', common.mustCall());
process.on('exit', function() {
assert(file.closed);
assert(file.destroyed);
});
}

View File

@@ -0,0 +1,243 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const filepath = fixtures.path('x.txt');
const fd = fs.openSync(filepath, 'r');
const expected = 'xyz\n';
// Error must be thrown with string
assert.throws(
() => fs.read(fd, expected.length, 0, 'utf-8', common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "buffer" argument must be an instance of Buffer, ' +
'TypedArray, or DataView. Received type number (4)'
}
);
[true, null, undefined, () => {}, {}].forEach((value) => {
assert.throws(() => {
fs.read(value,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
0,
common.mustNotCall());
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
});
assert.throws(() => {
fs.read(fd,
Buffer.allocUnsafe(expected.length),
-1,
expected.length,
0,
common.mustNotCall());
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
});
assert.throws(() => {
fs.read(fd,
Buffer.allocUnsafe(expected.length),
NaN,
expected.length,
0,
common.mustNotCall());
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "offset" is out of range. It must be an integer. ' +
'Received NaN'
});
assert.throws(() => {
fs.read(fd,
Buffer.allocUnsafe(expected.length),
0,
-1,
0,
common.mustNotCall());
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "length" is out of range. ' +
'It must be >= 0. Received -1'
});
[true, () => {}, {}, ''].forEach((value) => {
assert.throws(() => {
fs.read(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
value,
common.mustNotCall());
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
});
[0.5, 2 ** 53, 2n ** 63n].forEach((value) => {
assert.throws(() => {
fs.read(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
value,
common.mustNotCall());
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError'
});
});
fs.read(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
0n,
common.mustSucceed());
fs.read(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
2n ** 53n - 1n,
common.mustCall((err) => {
if (err) {
if (common.isIBMi)
assert.strictEqual(err.code, 'EOVERFLOW');
else
assert.strictEqual(err.code, 'EFBIG');
}
}));
assert.throws(
() => fs.readSync(fd, expected.length, 0, 'utf-8'),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "buffer" argument must be an instance of Buffer, ' +
'TypedArray, or DataView. Received type number (4)'
}
);
[true, null, undefined, () => {}, {}].forEach((value) => {
assert.throws(() => {
fs.readSync(value,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
0);
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
});
assert.throws(() => {
fs.readSync(fd,
Buffer.allocUnsafe(expected.length),
-1,
expected.length,
0);
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
});
assert.throws(() => {
fs.readSync(fd,
Buffer.allocUnsafe(expected.length),
NaN,
expected.length,
0);
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "offset" is out of range. It must be an integer. ' +
'Received NaN'
});
assert.throws(() => {
fs.readSync(fd,
Buffer.allocUnsafe(expected.length),
0,
-1,
0);
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "length" is out of range. ' +
'It must be >= 0. Received -1'
});
assert.throws(() => {
fs.readSync(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length + 1,
0);
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "length" is out of range. ' +
'It must be <= 4. Received 5'
});
[true, () => {}, {}, ''].forEach((value) => {
assert.throws(() => {
fs.readSync(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
value);
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
});
[0.5, 2 ** 53, 2n ** 63n].forEach((value) => {
assert.throws(() => {
fs.readSync(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
value);
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError'
});
});
fs.readSync(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
0n);
try {
fs.readSync(fd,
Buffer.allocUnsafe(expected.length),
0,
expected.length,
2n ** 53n - 1n);
} catch (err) {
// On systems where max file size is below 2^53-1, we'd expect a EFBIG error.
// This is not using `assert.throws` because the above call should not raise
// any error on systems that allows file of that size.
if (err.code !== 'EFBIG' && !(common.isIBMi && err.code === 'EOVERFLOW'))
throw err;
}

View File

@@ -0,0 +1,102 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const fs = require('fs');
const filepath = fixtures.path('x.txt');
const fd = fs.openSync(filepath, 'r');
const expected = Buffer.from('xyz\n');
function test(bufferAsync, bufferSync, expected) {
fs.read(fd,
bufferAsync,
0,
expected.length,
0,
common.mustSucceed((bytesRead) => {
assert.strictEqual(bytesRead, expected.length);
assert.deepStrictEqual(bufferAsync, expected);
}));
const r = fs.readSync(fd, bufferSync, 0, expected.length, 0);
assert.deepStrictEqual(bufferSync, expected);
assert.strictEqual(r, expected.length);
}
test(Buffer.allocUnsafe(expected.length),
Buffer.allocUnsafe(expected.length),
expected);
test(new Uint8Array(expected.length),
new Uint8Array(expected.length),
Uint8Array.from(expected));
{
// Reading beyond file length (3 in this case) should return no data.
// This is a test for a bug where reads > uint32 would return data
// from the current position in the file.
const pos = 0xffffffff + 1; // max-uint32 + 1
const nRead = fs.readSync(fd, Buffer.alloc(1), 0, 1, pos);
assert.strictEqual(nRead, 0);
fs.read(fd, Buffer.alloc(1), 0, 1, pos, common.mustSucceed((nRead) => {
assert.strictEqual(nRead, 0);
}));
}
assert.throws(() => new fs.Dir(), {
code: 'ERR_MISSING_ARGS',
});
assert.throws(
() => fs.read(fd, Buffer.alloc(1), 0, 1, 0),
{
code: 'ERR_INVALID_ARG_TYPE',
}
);
assert.throws(
() => fs.read(fd, { buffer: null }, common.mustNotCall()),
{ code: 'ERR_INVALID_ARG_TYPE' },
'throws when options.buffer is null'
);
assert.throws(
() => fs.readSync(fd, { buffer: null }),
{
name: 'TypeError',
message: 'The "buffer" argument must be an instance of Buffer, ' +
'TypedArray, or DataView. Received an instance of Object',
},
'throws when options.buffer is null'
);
assert.throws(
() => fs.read(null, Buffer.alloc(1), 0, 1, 0),
{
message: 'The "fd" argument must be of type number. Received null',
code: 'ERR_INVALID_ARG_TYPE',
}
);

View File

@@ -0,0 +1,73 @@
'use strict';
const { mustNotMutateObjectDeep } = require('../common');
const fixtures = require('../common/fixtures');
const fs = require('fs');
const assert = require('assert');
const filepath = fixtures.path('x.txt');
const expected = Buffer.from('xyz\n');
function runTest(defaultBuffer, options, errorCode = false) {
let fd;
try {
fd = fs.openSync(filepath, 'r');
if (errorCode) {
assert.throws(
() => fs.readSync(fd, defaultBuffer, options),
{ code: errorCode }
);
} else {
const result = fs.readSync(fd, defaultBuffer, options);
assert.strictEqual(result, expected.length);
assert.deepStrictEqual(defaultBuffer, expected);
}
} finally {
if (fd != null) fs.closeSync(fd);
}
}
for (const options of [
// Test options object
{ offset: 0 },
{ length: expected.length },
{ position: 0 },
{ offset: 0, length: expected.length },
{ offset: 0, position: 0 },
{ length: expected.length, position: 0 },
{ offset: 0, length: expected.length, position: 0 },
{ position: null },
{ position: -1 },
{ position: 0n },
// Test default params
{},
null,
undefined,
// Test malicious corner case: it works as {length: 4} but not intentionally
new String('4444'),
]) {
runTest(Buffer.allocUnsafe(expected.length), options);
}
for (const options of [
// Test various invalid options
false,
true,
Infinity,
42n,
Symbol(),
'amString',
[],
() => {},
// Test if arbitrary entity with expected .length is not mistaken for options
'4'.repeat(expected.length),
[4, 4, 4, 4],
]) {
runTest(Buffer.allocUnsafe(expected.length), mustNotMutateObjectDeep(options), 'ERR_INVALID_ARG_TYPE');
}

View File

@@ -0,0 +1,21 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { readdir, readdirSync } = require('fs');
if (!common.isWindows) {
common.skip('This test is specific to Windows to test enumerate pipes');
}
// Ref: https://github.com/nodejs/node/issues/56002
// This test is specific to Windows.
const pipe = '\\\\.\\pipe\\';
const { length } = readdirSync(pipe);
assert.ok(length >= 0, `${length} is not greater or equal to 0`);
readdir(pipe, common.mustSucceed((files) => {
assert.ok(files.length >= 0, `${files.length} is not greater or equal to 0`);
}));

View File

@@ -0,0 +1,19 @@
'use strict';
require('../common');
const assert = require('assert');
const fs = require('fs');
function recurse() {
fs.readdirSync('.');
recurse();
}
assert.throws(
() => recurse(),
{
name: 'RangeError',
message: 'Maximum call stack size exceeded'
}
);

View File

@@ -0,0 +1,132 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const { internalBinding } = require('internal/test/binding');
const binding = internalBinding('fs');
const readdirDir = tmpdir.path;
const files = ['empty', 'files', 'for', 'just', 'testing'];
const constants = require('fs').constants;
const types = {
isDirectory: constants.UV_DIRENT_DIR,
isFile: constants.UV_DIRENT_FILE,
isBlockDevice: constants.UV_DIRENT_BLOCK,
isCharacterDevice: constants.UV_DIRENT_CHAR,
isSymbolicLink: constants.UV_DIRENT_LINK,
isFIFO: constants.UV_DIRENT_FIFO,
isSocket: constants.UV_DIRENT_SOCKET
};
const typeMethods = Object.keys(types);
// Make sure tmp directory is clean
tmpdir.refresh();
// Create the necessary files
files.forEach(function(currentFile) {
fs.writeFileSync(`${readdirDir}/${currentFile}`, '', 'utf8');
});
function assertDirents(dirents) {
assert.strictEqual(files.length, dirents.length);
for (const [i, dirent] of dirents.entries()) {
assert(dirent instanceof fs.Dirent);
assert.strictEqual(dirent.name, files[i]);
assert.strictEqual(dirent.isFile(), true);
assert.strictEqual(dirent.isDirectory(), false);
assert.strictEqual(dirent.isSocket(), false);
assert.strictEqual(dirent.isBlockDevice(), false);
assert.strictEqual(dirent.isCharacterDevice(), false);
assert.strictEqual(dirent.isFIFO(), false);
assert.strictEqual(dirent.isSymbolicLink(), false);
}
}
// Check the readdir Sync version
assertDirents(fs.readdirSync(readdirDir, { withFileTypes: true }));
fs.readdir(__filename, {
withFileTypes: true
}, common.mustCall((err) => {
assert.throws(
() => { throw err; },
{
code: 'ENOTDIR',
name: 'Error',
message: `ENOTDIR: not a directory, scandir '${__filename}'`
}
);
}));
// Check the readdir async version
fs.readdir(readdirDir, {
withFileTypes: true
}, common.mustSucceed((dirents) => {
assertDirents(dirents);
}));
(async () => {
const dirents = await fs.promises.readdir(readdirDir, {
withFileTypes: true
});
assertDirents(dirents);
})().then(common.mustCall());
// Check that mutating options doesn't affect results
(async () => {
const options = { withFileTypes: true };
const direntsPromise = fs.promises.readdir(readdirDir, options);
options.withFileTypes = false;
assertDirents(await direntsPromise);
})().then(common.mustCall());
{
const options = { recursive: true, withFileTypes: true };
fs.readdir(readdirDir, options, common.mustSucceed((dirents) => {
assertDirents(dirents);
}));
options.withFileTypes = false;
}
// Check for correct types when the binding returns unknowns
const UNKNOWN = constants.UV_DIRENT_UNKNOWN;
const oldReaddir = binding.readdir;
process.on('beforeExit', () => { binding.readdir = oldReaddir; });
binding.readdir = common.mustCall((path, encoding, types, req, ctx) => {
if (req) {
const oldCb = req.oncomplete;
req.oncomplete = (err, results) => {
if (err) {
oldCb(err);
return;
}
results[1] = results[1].map(() => UNKNOWN);
oldCb(null, results);
};
oldReaddir(path, encoding, types, req);
} else {
const results = oldReaddir(path, encoding, types);
results[1] = results[1].map(() => UNKNOWN);
return results;
}
}, 2);
assertDirents(fs.readdirSync(readdirDir, { withFileTypes: true }));
fs.readdir(readdirDir, {
withFileTypes: true
}, common.mustSucceed((dirents) => {
assertDirents(dirents);
}));
// Dirent types
for (const method of typeMethods) {
const dirent = new fs.Dirent('foo', types[method]);
for (const testMethod of typeMethods) {
assert.strictEqual(dirent[testMethod](), testMethod === method);
}
}

View File

@@ -0,0 +1,31 @@
'use strict';
const common = require('../common');
if (!common.isLinux)
common.skip('Test is linux specific.');
const path = require('path');
const fs = require('fs');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const filename = '\uD83D\uDC04';
const root = Buffer.from(`${tmpdir.path}${path.sep}`);
const filebuff = Buffer.from(filename, 'ucs2');
const fullpath = Buffer.concat([root, filebuff]);
try {
fs.closeSync(fs.openSync(fullpath, 'w+'));
} catch (e) {
if (e.code === 'EINVAL')
common.skip('test requires filesystem that supports UCS2');
throw e;
}
fs.readdir(tmpdir.path, 'ucs2', common.mustSucceed((list) => {
assert.strictEqual(list.length, 1);
const fn = list[0];
assert.deepStrictEqual(Buffer.from(fn, 'ucs2'), filebuff);
assert.strictEqual(fn, filename);
}));

View File

@@ -0,0 +1,53 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const readdirDir = tmpdir.path;
const files = ['empty', 'files', 'for', 'just', 'testing'];
// Make sure tmp directory is clean
tmpdir.refresh();
// Create the necessary files
files.forEach(function(currentFile) {
fs.closeSync(fs.openSync(`${readdirDir}/${currentFile}`, 'w'));
});
// Check the readdir Sync version
assert.deepStrictEqual(files, fs.readdirSync(readdirDir).sort());
// Check the readdir async version
fs.readdir(readdirDir, common.mustSucceed((f) => {
assert.deepStrictEqual(files, f.sort());
}));
// readdir() on file should throw ENOTDIR
// https://github.com/joyent/node/issues/1869
assert.throws(function() {
fs.readdirSync(__filename);
}, /Error: ENOTDIR: not a directory/);
fs.readdir(__filename, common.mustCall(function(e) {
assert.strictEqual(e.code, 'ENOTDIR');
}));
[false, 1, [], {}, null, undefined].forEach((i) => {
assert.throws(
() => fs.readdir(i, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
assert.throws(
() => fs.readdirSync(i),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
});

View File

@@ -0,0 +1,65 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const fs = require('fs');
// Test that fs.readFile fails correctly on a non-existent file.
// `fs.readFile('/')` does not fail on AIX and FreeBSD because you can open
// and read the directory there.
if (common.isAIX || common.isFreeBSD)
common.skip('platform not supported.');
const assert = require('assert');
const exec = require('child_process').exec;
const fixtures = require('../common/fixtures');
function test(env, cb) {
const filename = fixtures.path('test-fs-readfile-error.js');
exec(...common.escapePOSIXShell`"${process.execPath}" "${filename}"`, (err, stdout, stderr) => {
assert(err);
assert.strictEqual(stdout, '');
assert.notStrictEqual(stderr, '');
cb(String(stderr));
});
}
test({ NODE_DEBUG: '' }, common.mustCall((data) => {
assert.match(data, /EISDIR/);
assert.match(data, /test-fs-readfile-error/);
}));
test({ NODE_DEBUG: 'fs' }, common.mustCall((data) => {
assert.match(data, /EISDIR/);
assert.match(data, /test-fs-readfile-error/);
}));
assert.throws(
() => { fs.readFile(() => {}, common.mustNotCall()); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "path" argument must be of type string or an instance of ' +
'Buffer or URL. Received function ',
name: 'TypeError'
}
);

View File

@@ -0,0 +1,50 @@
'use strict';
// Test of fs.readFile with different flags.
const common = require('../common');
const fs = require('fs');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
{
const emptyFile = tmpdir.resolve('empty.txt');
fs.closeSync(fs.openSync(emptyFile, 'w'));
fs.readFile(
emptyFile,
// With `a+` the file is created if it does not exist
common.mustNotMutateObjectDeep({ encoding: 'utf8', flag: 'a+' }),
common.mustCall((err, data) => { assert.strictEqual(data, ''); })
);
fs.readFile(
emptyFile,
// Like `a+` but fails if the path exists.
common.mustNotMutateObjectDeep({ encoding: 'utf8', flag: 'ax+' }),
common.mustCall((err, data) => { assert.strictEqual(err.code, 'EEXIST'); })
);
}
{
const willBeCreated = tmpdir.resolve('will-be-created');
fs.readFile(
willBeCreated,
// With `a+` the file is created if it does not exist
common.mustNotMutateObjectDeep({ encoding: 'utf8', flag: 'a+' }),
common.mustCall((err, data) => { assert.strictEqual(data, ''); })
);
}
{
const willNotBeCreated = tmpdir.resolve('will-not-be-created');
fs.readFile(
willNotBeCreated,
// Default flag is `r`. An exception occurs if the file does not exist.
common.mustNotMutateObjectDeep({ encoding: 'utf8' }),
common.mustCall((err, data) => { assert.strictEqual(err.code, 'ENOENT'); })
);
}

View File

@@ -0,0 +1,100 @@
'use strict';
const common = require('../common');
// This test ensures that fs.readFile correctly returns the
// contents of varying-sized files.
const tmpdir = require('../../test/common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const prefix = `.removeme-fs-readfile-${process.pid}`;
tmpdir.refresh();
const fileInfo = [
{ name: tmpdir.resolve(`${prefix}-1K.txt`),
len: 1024 },
{ name: tmpdir.resolve(`${prefix}-64K.txt`),
len: 64 * 1024 },
{ name: tmpdir.resolve(`${prefix}-64KLessOne.txt`),
len: (64 * 1024) - 1 },
{ name: tmpdir.resolve(`${prefix}-1M.txt`),
len: 1 * 1024 * 1024 },
{ name: tmpdir.resolve(`${prefix}-1MPlusOne.txt`),
len: (1 * 1024 * 1024) + 1 },
];
// Populate each fileInfo (and file) with unique fill.
const sectorSize = 512;
for (const e of fileInfo) {
e.contents = Buffer.allocUnsafe(e.len);
// This accounts for anything unusual in Node's implementation of readFile.
// Using e.g. 'aa...aa' would miss bugs like Node re-reading
// the same section twice instead of two separate sections.
for (let offset = 0; offset < e.len; offset += sectorSize) {
const fillByte = 256 * Math.random();
const nBytesToFill = Math.min(sectorSize, e.len - offset);
e.contents.fill(fillByte, offset, offset + nBytesToFill);
}
fs.writeFileSync(e.name, e.contents);
}
// All files are now populated.
// Test readFile on each size.
for (const e of fileInfo) {
fs.readFile(e.name, common.mustCall((err, buf) => {
console.log(`Validating readFile on file ${e.name} of length ${e.len}`);
assert.ifError(err);
assert.deepStrictEqual(buf, e.contents);
}));
}
// readFile() and readFileSync() should fail if the file is too big.
{
const kIoMaxLength = 2 ** 31 - 1;
if (!tmpdir.hasEnoughSpace(kIoMaxLength)) {
// truncateSync() will fail with ENOSPC if there is not enough space.
common.printSkipMessage(`Not enough space in ${tmpdir.path}`);
} else {
const file = tmpdir.resolve(`${prefix}-too-large.txt`);
fs.writeFileSync(file, Buffer.from('0'));
fs.truncateSync(file, kIoMaxLength + 1);
fs.readFile(file, common.expectsError({
code: 'ERR_FS_FILE_TOO_LARGE',
name: 'RangeError',
}));
assert.throws(() => {
fs.readFileSync(file);
}, { code: 'ERR_FS_FILE_TOO_LARGE', name: 'RangeError' });
}
}
{
// Test cancellation, before
const signal = AbortSignal.abort();
fs.readFile(fileInfo[0].name, { signal }, common.mustCall((err, buf) => {
assert.strictEqual(err.name, 'AbortError');
}));
}
{
// Test cancellation, during read
const controller = new AbortController();
const signal = controller.signal;
fs.readFile(fileInfo[0].name, { signal }, common.mustCall((err, buf) => {
assert.strictEqual(err.name, 'AbortError');
}));
process.nextTick(() => controller.abort());
}
{
// Verify that if something different than Abortcontroller.signal
// is passed, ERR_INVALID_ARG_TYPE is thrown
assert.throws(() => {
const callback = common.mustNotCall();
fs.readFile(fileInfo[0].name, { signal: 'hello' }, callback);
}, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' });
}

View File

@@ -0,0 +1,18 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const fs = require('fs');
const readv = require('util').promisify(fs.readv);
const assert = require('assert');
const filepath = fixtures.path('x.txt');
const fd = fs.openSync(filepath, 'r');
const expected = [Buffer.from('xyz\n')];
readv(fd, expected)
.then(function({ bytesRead, buffers }) {
assert.deepStrictEqual(bytesRead, expected[0].length);
assert.deepStrictEqual(buffers, expected);
})
.then(common.mustCall());

View File

@@ -0,0 +1,20 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const readStream = fs.createReadStream(__filename);
assert.strictEqual(readStream.pending, true);
readStream.on('ready', common.mustCall(() => {
assert.strictEqual(readStream.pending, false);
}));
const writeFile = tmpdir.resolve('write-fsreadyevent.txt');
tmpdir.refresh();
const writeStream = fs.createWriteStream(writeFile, { autoClose: true });
assert.strictEqual(writeStream.pending, true);
writeStream.on('ready', common.mustCall(() => {
assert.strictEqual(writeStream.pending, false);
writeStream.end();
}));

View File

@@ -0,0 +1,90 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const fs = require('fs');
const string_dir = fs.realpathSync(fixtures.fixturesDir);
const buffer_dir = Buffer.from(string_dir);
const encodings = ['ascii', 'utf8', 'utf16le', 'ucs2',
'base64', 'binary', 'hex'];
const expected = {};
for (const encoding of encodings) {
expected[encoding] = buffer_dir.toString(encoding);
}
// test sync version
let encoding;
for (encoding in expected) {
const expected_value = expected[encoding];
let result;
result = fs.realpathSync(string_dir, { encoding });
assert.strictEqual(result, expected_value);
result = fs.realpathSync(string_dir, encoding);
assert.strictEqual(result, expected_value);
result = fs.realpathSync(buffer_dir, { encoding });
assert.strictEqual(result, expected_value);
result = fs.realpathSync(buffer_dir, encoding);
assert.strictEqual(result, expected_value);
}
let buffer_result;
buffer_result = fs.realpathSync(string_dir, { encoding: 'buffer' });
assert.deepStrictEqual(buffer_result, buffer_dir);
buffer_result = fs.realpathSync(string_dir, 'buffer');
assert.deepStrictEqual(buffer_result, buffer_dir);
buffer_result = fs.realpathSync(buffer_dir, { encoding: 'buffer' });
assert.deepStrictEqual(buffer_result, buffer_dir);
buffer_result = fs.realpathSync(buffer_dir, 'buffer');
assert.deepStrictEqual(buffer_result, buffer_dir);
// test async version
for (encoding in expected) {
const expected_value = expected[encoding];
fs.realpath(
string_dir,
{ encoding },
common.mustSucceed((res) => {
assert.strictEqual(res, expected_value);
})
);
fs.realpath(string_dir, encoding, common.mustSucceed((res) => {
assert.strictEqual(res, expected_value);
}));
fs.realpath(
buffer_dir,
{ encoding },
common.mustSucceed((res) => {
assert.strictEqual(res, expected_value);
})
);
fs.realpath(buffer_dir, encoding, common.mustSucceed((res) => {
assert.strictEqual(res, expected_value);
}));
}
fs.realpath(string_dir, { encoding: 'buffer' }, common.mustSucceed((res) => {
assert.deepStrictEqual(res, buffer_dir);
}));
fs.realpath(string_dir, 'buffer', common.mustSucceed((res) => {
assert.deepStrictEqual(res, buffer_dir);
}));
fs.realpath(buffer_dir, { encoding: 'buffer' }, common.mustSucceed((res) => {
assert.deepStrictEqual(res, buffer_dir);
}));
fs.realpath(buffer_dir, 'buffer', common.mustSucceed((res) => {
assert.deepStrictEqual(res, buffer_dir);
}));

View File

@@ -0,0 +1,18 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const filename = __filename.toLowerCase();
assert.strictEqual(
fs.realpathSync.native('./test/parallel/test-fs-realpath-native.js')
.toLowerCase(),
filename);
fs.realpath.native(
'./test/parallel/test-fs-realpath-native.js',
common.mustSucceed(function(res) {
assert.strictEqual(res.toLowerCase(), filename);
assert.strictEqual(this, undefined);
}));

View File

@@ -0,0 +1,618 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
if (!common.isMainThread)
common.skip('process.chdir is not available in Workers');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
let async_completed = 0;
let async_expected = 0;
const unlink = [];
const skipSymlinks = !common.canCreateSymLink();
const tmpDir = tmpdir.path;
tmpdir.refresh();
let root = '/';
let assertEqualPath = assert.strictEqual;
if (common.isWindows) {
// Something like "C:\\"
root = process.cwd().slice(0, 3);
assertEqualPath = function(path_left, path_right, message) {
assert
.strictEqual(path_left.toLowerCase(), path_right.toLowerCase(), message);
};
}
process.nextTick(runTest);
function tmp(p) {
return path.join(tmpDir, p);
}
const targetsAbsDir = path.join(tmpDir, 'targets');
const tmpAbsDir = tmpDir;
// Set up targetsAbsDir and expected subdirectories
fs.mkdirSync(targetsAbsDir);
fs.mkdirSync(path.join(targetsAbsDir, 'nested-index'));
fs.mkdirSync(path.join(targetsAbsDir, 'nested-index', 'one'));
fs.mkdirSync(path.join(targetsAbsDir, 'nested-index', 'two'));
function asynctest(testBlock, args, callback, assertBlock) {
async_expected++;
testBlock.apply(testBlock, args.concat(function(err) {
let ignoreError = false;
if (assertBlock) {
try {
ignoreError = assertBlock.apply(assertBlock, arguments);
} catch (e) {
err = e;
}
}
async_completed++;
callback(ignoreError ? null : err);
}));
}
// sub-tests:
function test_simple_error_callback(realpath, realpathSync, cb) {
realpath('/this/path/does/not/exist', common.mustCall(function(err, s) {
assert(err);
assert(!s);
cb();
}));
}
function test_simple_error_cb_with_null_options(realpath, realpathSync, cb) {
realpath('/this/path/does/not/exist', null, common.mustCall(function(err, s) {
assert(err);
assert(!s);
cb();
}));
}
function test_simple_relative_symlink(realpath, realpathSync, callback) {
console.log('test_simple_relative_symlink');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const entry = `${tmpDir}/symlink`;
const expected = `${tmpDir}/cycles/root.js`;
[
[entry, `../${path.basename(tmpDir)}/cycles/root.js`],
].forEach(function(t) {
try { fs.unlinkSync(t[0]); } catch {
// Continue regardless of error.
}
console.log('fs.symlinkSync(%j, %j, %j)', t[1], t[0], 'file');
fs.symlinkSync(t[1], t[0], 'file');
unlink.push(t[0]);
});
const result = realpathSync(entry);
assertEqualPath(result, path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
});
}
function test_simple_absolute_symlink(realpath, realpathSync, callback) {
console.log('test_simple_absolute_symlink');
// This one should still run, even if skipSymlinks is set,
// because it uses a junction.
const type = skipSymlinks ? 'junction' : 'dir';
console.log('using type=%s', type);
const entry = `${tmpAbsDir}/symlink`;
const expected = fixtures.path('nested-index', 'one');
[
[entry, expected],
].forEach(function(t) {
try { fs.unlinkSync(t[0]); } catch {
// Continue regardless of error.
}
console.error('fs.symlinkSync(%j, %j, %j)', t[1], t[0], type);
fs.symlinkSync(t[1], t[0], type);
unlink.push(t[0]);
});
const result = realpathSync(entry);
assertEqualPath(result, path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
});
}
function test_deep_relative_file_symlink(realpath, realpathSync, callback) {
console.log('test_deep_relative_file_symlink');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const expected = fixtures.path('cycles', 'root.js');
const linkData1 = path
.relative(path.join(targetsAbsDir, 'nested-index', 'one'),
expected);
const linkPath1 = path.join(targetsAbsDir,
'nested-index', 'one', 'symlink1.js');
try { fs.unlinkSync(linkPath1); } catch {
// Continue regardless of error.
}
fs.symlinkSync(linkData1, linkPath1, 'file');
const linkData2 = '../one/symlink1.js';
const entry = path.join(targetsAbsDir,
'nested-index', 'two', 'symlink1-b.js');
try { fs.unlinkSync(entry); } catch {
// Continue regardless of error.
}
fs.symlinkSync(linkData2, entry, 'file');
unlink.push(linkPath1);
unlink.push(entry);
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
});
}
function test_deep_relative_dir_symlink(realpath, realpathSync, callback) {
console.log('test_deep_relative_dir_symlink');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const expected = fixtures.path('cycles', 'folder');
const path1b = path.join(targetsAbsDir, 'nested-index', 'one');
const linkPath1b = path.join(path1b, 'symlink1-dir');
const linkData1b = path.relative(path1b, expected);
try { fs.unlinkSync(linkPath1b); } catch {
// Continue regardless of error.
}
fs.symlinkSync(linkData1b, linkPath1b, 'dir');
const linkData2b = '../one/symlink1-dir';
const entry = path.join(targetsAbsDir,
'nested-index', 'two', 'symlink12-dir');
try { fs.unlinkSync(entry); } catch {
// Continue regardless of error.
}
fs.symlinkSync(linkData2b, entry, 'dir');
unlink.push(linkPath1b);
unlink.push(entry);
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
});
}
function test_cyclic_link_protection(realpath, realpathSync, callback) {
console.log('test_cyclic_link_protection');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const entry = path.join(tmpDir, '/cycles/realpath-3a');
[
[entry, '../cycles/realpath-3b'],
[path.join(tmpDir, '/cycles/realpath-3b'), '../cycles/realpath-3c'],
[path.join(tmpDir, '/cycles/realpath-3c'), '../cycles/realpath-3a'],
].forEach(function(t) {
try { fs.unlinkSync(t[0]); } catch {
// Continue regardless of error.
}
fs.symlinkSync(t[1], t[0], 'dir');
unlink.push(t[0]);
});
assert.throws(() => {
realpathSync(entry);
}, { code: 'ELOOP', name: 'Error' });
asynctest(
realpath, [entry], callback, common.mustCall(function(err, result) {
assert.strictEqual(err.path, entry);
assert.strictEqual(result, undefined);
return true;
}));
}
function test_cyclic_link_overprotection(realpath, realpathSync, callback) {
console.log('test_cyclic_link_overprotection');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
const cycles = `${tmpDir}/cycles`;
const expected = realpathSync(cycles);
const folder = `${cycles}/folder`;
const link = `${folder}/cycles`;
let testPath = cycles;
testPath += '/folder/cycles'.repeat(10);
try { fs.unlinkSync(link); } catch {
// Continue regardless of error.
}
fs.symlinkSync(cycles, link, 'dir');
unlink.push(link);
assertEqualPath(realpathSync(testPath), path.resolve(expected));
asynctest(realpath, [testPath], callback, function(er, res) {
assertEqualPath(res, path.resolve(expected));
});
}
function test_relative_input_cwd(realpath, realpathSync, callback) {
console.log('test_relative_input_cwd');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return callback();
}
// We need to calculate the relative path to the tmp dir from cwd
const entrydir = process.cwd();
const entry = path.relative(entrydir,
path.join(`${tmpDir}/cycles/realpath-3a`));
const expected = `${tmpDir}/cycles/root.js`;
[
[entry, '../cycles/realpath-3b'],
[`${tmpDir}/cycles/realpath-3b`, '../cycles/realpath-3c'],
[`${tmpDir}/cycles/realpath-3c`, 'root.js'],
].forEach(function(t) {
const fn = t[0];
console.error('fn=%j', fn);
try { fs.unlinkSync(fn); } catch {
// Continue regardless of error.
}
const b = path.basename(t[1]);
const type = (b === 'root.js' ? 'file' : 'dir');
console.log('fs.symlinkSync(%j, %j, %j)', t[1], fn, type);
fs.symlinkSync(t[1], fn, 'file');
unlink.push(fn);
});
const origcwd = process.cwd();
process.chdir(entrydir);
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
process.chdir(origcwd);
assertEqualPath(result, path.resolve(expected));
return true;
});
}
function test_deep_symlink_mix(realpath, realpathSync, callback) {
console.log('test_deep_symlink_mix');
if (common.isWindows) {
// This one is a mix of files and directories, and it's quite tricky
// to get the file/dir links sorted out correctly.
common.printSkipMessage('symlink test (no privs)');
return callback();
}
// /tmp/node-test-realpath-f1 -> $tmpDir/node-test-realpath-d1/foo
// /tmp/node-test-realpath-d1 -> $tmpDir/node-test-realpath-d2
// /tmp/node-test-realpath-d2/foo -> $tmpDir/node-test-realpath-f2
// /tmp/node-test-realpath-f2
// -> $tmpDir/targets/nested-index/one/realpath-c
// $tmpDir/targets/nested-index/one/realpath-c
// -> $tmpDir/targets/nested-index/two/realpath-c
// $tmpDir/targets/nested-index/two/realpath-c -> $tmpDir/cycles/root.js
// $tmpDir/targets/cycles/root.js (hard)
const entry = tmp('node-test-realpath-f1');
try { fs.unlinkSync(tmp('node-test-realpath-d2/foo')); } catch {
// Continue regardless of error.
}
try { fs.rmdirSync(tmp('node-test-realpath-d2')); } catch {
// Continue regardless of error.
}
fs.mkdirSync(tmp('node-test-realpath-d2'), 0o700);
try {
[
[entry, `${tmpDir}/node-test-realpath-d1/foo`],
[tmp('node-test-realpath-d1'),
`${tmpDir}/node-test-realpath-d2`],
[tmp('node-test-realpath-d2/foo'), '../node-test-realpath-f2'],
[tmp('node-test-realpath-f2'),
`${targetsAbsDir}/nested-index/one/realpath-c`],
[`${targetsAbsDir}/nested-index/one/realpath-c`,
`${targetsAbsDir}/nested-index/two/realpath-c`],
[`${targetsAbsDir}/nested-index/two/realpath-c`,
`${tmpDir}/cycles/root.js`],
].forEach(function(t) {
try { fs.unlinkSync(t[0]); } catch {
// Continue regardless of error.
}
fs.symlinkSync(t[1], t[0]);
unlink.push(t[0]);
});
} finally {
unlink.push(tmp('node-test-realpath-d2'));
}
const expected = `${tmpAbsDir}/cycles/root.js`;
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
assertEqualPath(result, path.resolve(expected));
return true;
});
}
function test_non_symlinks(realpath, realpathSync, callback) {
console.log('test_non_symlinks');
const entrydir = path.dirname(tmpAbsDir);
const entry = `${tmpAbsDir.slice(entrydir.length + 1)}/cycles/root.js`;
const expected = `${tmpAbsDir}/cycles/root.js`;
const origcwd = process.cwd();
process.chdir(entrydir);
assertEqualPath(realpathSync(entry), path.resolve(expected));
asynctest(realpath, [entry], callback, function(err, result) {
process.chdir(origcwd);
assertEqualPath(result, path.resolve(expected));
return true;
});
}
const upone = path.join(process.cwd(), '..');
function test_escape_cwd(realpath, realpathSync, cb) {
console.log('test_escape_cwd');
asynctest(realpath, ['..'], cb, function(er, uponeActual) {
assertEqualPath(
upone, uponeActual,
`realpath("..") expected: ${path.resolve(upone)} actual:${uponeActual}`);
});
}
function test_upone_actual(realpath, realpathSync, cb) {
console.log('test_upone_actual');
const uponeActual = realpathSync('..');
assertEqualPath(upone, uponeActual);
cb();
}
// Going up with .. multiple times
// .
// `-- a/
// |-- b/
// | `-- e -> ..
// `-- d -> ..
// realpath(a/b/e/d/a/b/e/d/a) ==> a
function test_up_multiple(realpath, realpathSync, cb) {
console.error('test_up_multiple');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return cb();
}
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
fs.mkdirSync(tmp('a'), 0o755);
fs.mkdirSync(tmp('a/b'), 0o755);
fs.symlinkSync('..', tmp('a/d'), 'dir');
unlink.push(tmp('a/d'));
fs.symlinkSync('..', tmp('a/b/e'), 'dir');
unlink.push(tmp('a/b/e'));
const abedabed = tmp('abedabed'.split('').join('/'));
const abedabed_real = tmp('');
const abedabeda = tmp('abedabeda'.split('').join('/'));
const abedabeda_real = tmp('a');
assertEqualPath(realpathSync(abedabeda), abedabeda_real);
assertEqualPath(realpathSync(abedabed), abedabed_real);
realpath(abedabeda, function(er, real) {
assert.ifError(er);
assertEqualPath(abedabeda_real, real);
realpath(abedabed, function(er, real) {
assert.ifError(er);
assertEqualPath(abedabed_real, real);
cb();
});
});
}
// Going up with .. multiple times with options = null
// .
// `-- a/
// |-- b/
// | `-- e -> ..
// `-- d -> ..
// realpath(a/b/e/d/a/b/e/d/a) ==> a
function test_up_multiple_with_null_options(realpath, realpathSync, cb) {
console.error('test_up_multiple');
if (skipSymlinks) {
common.printSkipMessage('symlink test (no privs)');
return cb();
}
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
fs.mkdirSync(tmp('a'), 0o755);
fs.mkdirSync(tmp('a/b'), 0o755);
fs.symlinkSync('..', tmp('a/d'), 'dir');
unlink.push(tmp('a/d'));
fs.symlinkSync('..', tmp('a/b/e'), 'dir');
unlink.push(tmp('a/b/e'));
const abedabed = tmp('abedabed'.split('').join('/'));
const abedabed_real = tmp('');
const abedabeda = tmp('abedabeda'.split('').join('/'));
const abedabeda_real = tmp('a');
assertEqualPath(realpathSync(abedabeda), abedabeda_real);
assertEqualPath(realpathSync(abedabed), abedabed_real);
realpath(abedabeda, null, function(er, real) {
assert.ifError(er);
assertEqualPath(abedabeda_real, real);
realpath(abedabed, null, function(er, real) {
assert.ifError(er);
assertEqualPath(abedabed_real, real);
cb();
});
});
}
// Absolute symlinks with children.
// .
// `-- a/
// |-- b/
// | `-- c/
// | `-- x.txt
// `-- link -> /tmp/node-test-realpath-abs-kids/a/b/
// realpath(root+'/a/link/c/x.txt') ==> root+'/a/b/c/x.txt'
function test_abs_with_kids(realpath, realpathSync, cb) {
console.log('test_abs_with_kids');
// This one should still run, even if skipSymlinks is set,
// because it uses a junction.
const type = skipSymlinks ? 'junction' : 'dir';
console.log('using type=%s', type);
const root = `${tmpAbsDir}/node-test-realpath-abs-kids`;
function cleanup() {
['/a/b/c/x.txt',
'/a/link',
].forEach(function(file) {
try { fs.unlinkSync(root + file); } catch {
// Continue regardless of error.
}
});
['/a/b/c',
'/a/b',
'/a',
'',
].forEach(function(folder) {
try { fs.rmdirSync(root + folder); } catch {
// Continue regardless of error.
}
});
}
function setup() {
cleanup();
['',
'/a',
'/a/b',
'/a/b/c',
].forEach(function(folder) {
console.log(`mkdir ${root}${folder}`);
fs.mkdirSync(root + folder, 0o700);
});
fs.writeFileSync(`${root}/a/b/c/x.txt`, 'foo');
fs.symlinkSync(`${root}/a/b`, `${root}/a/link`, type);
}
setup();
const linkPath = `${root}/a/link/c/x.txt`;
const expectPath = `${root}/a/b/c/x.txt`;
const actual = realpathSync(linkPath);
// console.log({link:linkPath,expect:expectPath,actual:actual},'sync');
assertEqualPath(actual, path.resolve(expectPath));
asynctest(realpath, [linkPath], cb, function(er, actual) {
// console.log({link:linkPath,expect:expectPath,actual:actual},'async');
assertEqualPath(actual, path.resolve(expectPath));
cleanup();
});
}
function test_root(realpath, realpathSync, cb) {
assertEqualPath(root, realpathSync('/'));
realpath('/', function(err, result) {
assert.ifError(err);
assertEqualPath(root, result);
cb();
});
}
function test_root_with_null_options(realpath, realpathSync, cb) {
realpath('/', null, function(err, result) {
assert.ifError(err);
assertEqualPath(root, result);
cb();
});
}
// ----------------------------------------------------------------------------
const tests = [
test_simple_error_callback,
test_simple_error_cb_with_null_options,
test_simple_relative_symlink,
test_simple_absolute_symlink,
test_deep_relative_file_symlink,
test_deep_relative_dir_symlink,
test_cyclic_link_protection,
test_cyclic_link_overprotection,
test_relative_input_cwd,
test_deep_symlink_mix,
test_non_symlinks,
test_escape_cwd,
test_upone_actual,
test_abs_with_kids,
test_up_multiple,
test_up_multiple_with_null_options,
test_root,
test_root_with_null_options,
];
const numtests = tests.length;
let testsRun = 0;
function runNextTest(err) {
assert.ifError(err);
const test = tests.shift();
if (!test) {
return console.log(`${numtests} subtests completed OK for fs.realpath`);
}
testsRun++;
test(fs.realpath, fs.realpathSync, common.mustSucceed(() => {
testsRun++;
test(fs.realpath.native,
fs.realpathSync.native,
common.mustCall(runNextTest));
}));
}
function runTest() {
const tmpDirs = ['cycles', 'cycles/folder'];
tmpDirs.forEach(function(t) {
t = tmp(t);
fs.mkdirSync(t, 0o700);
});
fs.writeFileSync(tmp('cycles/root.js'), "console.error('roooot!');");
console.error('start tests');
runNextTest();
}
process.on('exit', function() {
assert.strictEqual(2 * numtests, testsRun);
assert.strictEqual(async_completed, async_expected);
});

View File

@@ -0,0 +1,42 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
[false, 1, [], {}, null, undefined].forEach((input) => {
const type = 'of type string or an instance of Buffer or URL.' +
common.invalidArgTypeHelper(input);
assert.throws(
() => fs.rename(input, 'does-not-exist', common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: `The "oldPath" argument must be ${type}`
}
);
assert.throws(
() => fs.rename('does-not-exist', input, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: `The "newPath" argument must be ${type}`
}
);
assert.throws(
() => fs.renameSync(input, 'does-not-exist'),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: `The "oldPath" argument must be ${type}`
}
);
assert.throws(
() => fs.renameSync('does-not-exist', input),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: `The "newPath" argument must be ${type}`
}
);
});

View File

@@ -0,0 +1,555 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { pathToFileURL } = require('url');
const { execSync } = require('child_process');
const { validateRmOptionsSync } = require('internal/fs/utils');
tmpdir.refresh();
let count = 0;
const nextDirPath = (name = 'rm') =>
tmpdir.resolve(`${name}-${count++}`);
const isGitPresent = (() => {
try { execSync('git --version'); return true; } catch { return false; }
})();
function gitInit(gitDirectory) {
fs.mkdirSync(gitDirectory);
execSync('git init', common.mustNotMutateObjectDeep({ cwd: gitDirectory }));
}
function makeNonEmptyDirectory(depth, files, folders, dirname, createSymLinks) {
fs.mkdirSync(dirname, common.mustNotMutateObjectDeep({ recursive: true }));
fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8');
const options = common.mustNotMutateObjectDeep({ flag: 'wx' });
for (let f = files; f > 0; f--) {
fs.writeFileSync(path.join(dirname, `f-${depth}-${f}`), '', options);
}
if (createSymLinks) {
// Valid symlink
fs.symlinkSync(
`f-${depth}-1`,
path.join(dirname, `link-${depth}-good`),
'file'
);
// Invalid symlink
fs.symlinkSync(
'does-not-exist',
path.join(dirname, `link-${depth}-bad`),
'file'
);
// Symlinks that form a loop
[['a', 'b'], ['b', 'a']].forEach(([x, y]) => {
fs.symlinkSync(
`link-${depth}-loop-${x}`,
path.join(dirname, `link-${depth}-loop-${y}`),
'file'
);
});
}
// File with a name that looks like a glob
fs.writeFileSync(path.join(dirname, '[a-z0-9].txt'), '', options);
depth--;
if (depth <= 0) {
return;
}
for (let f = folders; f > 0; f--) {
fs.mkdirSync(
path.join(dirname, `folder-${depth}-${f}`),
{ recursive: true }
);
makeNonEmptyDirectory(
depth,
files,
folders,
path.join(dirname, `d-${depth}-${f}`),
createSymLinks
);
}
}
function removeAsync(dir) {
// Removal should fail without the recursive option.
fs.rm(dir, common.mustCall((err) => {
assert.strictEqual(err.syscall, 'rm');
// Removal should fail without the recursive option set to true.
fs.rm(dir, common.mustNotMutateObjectDeep({ recursive: false }), common.mustCall((err) => {
assert.strictEqual(err.syscall, 'rm');
// Recursive removal should succeed.
fs.rm(dir, common.mustNotMutateObjectDeep({ recursive: true }), common.mustSucceed(() => {
// Attempted removal should fail now because the directory is gone.
fs.rm(dir, common.mustCall((err) => {
assert.strictEqual(err.syscall, 'lstat');
}));
}));
}));
}));
}
// Test the asynchronous version
{
// Create a 4-level folder hierarchy including symlinks
let dir = nextDirPath();
makeNonEmptyDirectory(4, 10, 2, dir, true);
removeAsync(dir);
// Create a 2-level folder hierarchy without symlinks
dir = nextDirPath();
makeNonEmptyDirectory(2, 10, 2, dir, false);
removeAsync(dir);
// Same test using URL instead of a path
dir = nextDirPath();
makeNonEmptyDirectory(2, 10, 2, dir, false);
removeAsync(pathToFileURL(dir));
// Create a flat folder including symlinks
dir = nextDirPath();
makeNonEmptyDirectory(1, 10, 2, dir, true);
removeAsync(dir);
// Should fail if target does not exist
fs.rm(
tmpdir.resolve('noexist.txt'),
common.mustNotMutateObjectDeep({ recursive: true }),
common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOENT');
})
);
// Should delete a file
const filePath = tmpdir.resolve('rm-async-file.txt');
fs.writeFileSync(filePath, '');
fs.rm(filePath, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => {
try {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(filePath), false);
} finally {
fs.rmSync(filePath, common.mustNotMutateObjectDeep({ force: true }));
}
}));
// Should delete a valid symlink
const linkTarget = tmpdir.resolve('link-target-async.txt');
fs.writeFileSync(linkTarget, '');
const validLink = tmpdir.resolve('valid-link-async');
fs.symlinkSync(linkTarget, validLink);
fs.rm(validLink, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => {
try {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(validLink), false);
} finally {
fs.rmSync(linkTarget, common.mustNotMutateObjectDeep({ force: true }));
fs.rmSync(validLink, common.mustNotMutateObjectDeep({ force: true }));
}
}));
// Should delete an invalid symlink
const invalidLink = tmpdir.resolve('invalid-link-async');
fs.symlinkSync('definitely-does-not-exist-async', invalidLink);
fs.rm(invalidLink, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => {
try {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(invalidLink), false);
} finally {
fs.rmSync(invalidLink, common.mustNotMutateObjectDeep({ force: true }));
}
}));
// Should delete a symlink that is part of a loop
const loopLinkA = tmpdir.resolve('loop-link-async-a');
const loopLinkB = tmpdir.resolve('loop-link-async-b');
fs.symlinkSync(loopLinkA, loopLinkB);
fs.symlinkSync(loopLinkB, loopLinkA);
fs.rm(loopLinkA, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => {
try {
assert.strictEqual(err, null);
assert.strictEqual(fs.existsSync(loopLinkA), false);
} finally {
fs.rmSync(loopLinkA, common.mustNotMutateObjectDeep({ force: true }));
fs.rmSync(loopLinkB, common.mustNotMutateObjectDeep({ force: true }));
}
}));
}
// Removing a .git directory should not throw an EPERM.
// Refs: https://github.com/isaacs/rimraf/issues/21.
if (isGitPresent) {
const gitDirectory = nextDirPath();
gitInit(gitDirectory);
fs.rm(gitDirectory, common.mustNotMutateObjectDeep({ recursive: true }), common.mustSucceed(() => {
assert.strictEqual(fs.existsSync(gitDirectory), false);
}));
}
// Test the synchronous version.
{
const dir = nextDirPath();
makeNonEmptyDirectory(4, 10, 2, dir, true);
// Removal should fail without the recursive option set to true.
assert.throws(() => {
fs.rmSync(dir);
}, { syscall: 'rm' });
assert.throws(() => {
fs.rmSync(dir, common.mustNotMutateObjectDeep({ recursive: false }));
}, { syscall: 'rm' });
// Should fail if target does not exist
assert.throws(() => {
fs.rmSync(tmpdir.resolve('noexist.txt'), common.mustNotMutateObjectDeep({ recursive: true }));
}, {
code: 'ENOENT',
name: 'Error',
message: /^ENOENT: no such file or directory, lstat/
});
// Should delete a file
const filePath = tmpdir.resolve('rm-file.txt');
fs.writeFileSync(filePath, '');
try {
fs.rmSync(filePath, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(filePath), false);
} finally {
fs.rmSync(filePath, common.mustNotMutateObjectDeep({ force: true }));
}
// Should delete a valid symlink
const linkTarget = tmpdir.resolve('link-target.txt');
fs.writeFileSync(linkTarget, '');
const validLink = tmpdir.resolve('valid-link');
fs.symlinkSync(linkTarget, validLink);
try {
fs.rmSync(validLink);
assert.strictEqual(fs.existsSync(validLink), false);
} finally {
fs.rmSync(linkTarget, common.mustNotMutateObjectDeep({ force: true }));
fs.rmSync(validLink, common.mustNotMutateObjectDeep({ force: true }));
}
// Should delete an invalid symlink
const invalidLink = tmpdir.resolve('invalid-link');
fs.symlinkSync('definitely-does-not-exist', invalidLink);
try {
fs.rmSync(invalidLink);
assert.strictEqual(fs.existsSync(invalidLink), false);
} finally {
fs.rmSync(invalidLink, common.mustNotMutateObjectDeep({ force: true }));
}
// Should delete a symlink that is part of a loop
const loopLinkA = tmpdir.resolve('loop-link-a');
const loopLinkB = tmpdir.resolve('loop-link-b');
fs.symlinkSync(loopLinkA, loopLinkB);
fs.symlinkSync(loopLinkB, loopLinkA);
try {
fs.rmSync(loopLinkA);
assert.strictEqual(fs.existsSync(loopLinkA), false);
} finally {
fs.rmSync(loopLinkA, common.mustNotMutateObjectDeep({ force: true }));
fs.rmSync(loopLinkB, common.mustNotMutateObjectDeep({ force: true }));
}
// Should accept URL
const fileURL = tmpdir.fileURL('rm-file.txt');
fs.writeFileSync(fileURL, '');
try {
fs.rmSync(fileURL, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(fileURL), false);
} finally {
fs.rmSync(fileURL, common.mustNotMutateObjectDeep({ force: true }));
}
// Recursive removal should succeed.
fs.rmSync(dir, { recursive: true });
assert.strictEqual(fs.existsSync(dir), false);
// Attempted removal should fail now because the directory is gone.
assert.throws(() => fs.rmSync(dir), { syscall: 'lstat' });
}
// Removing a .git directory should not throw an EPERM.
// Refs: https://github.com/isaacs/rimraf/issues/21.
if (isGitPresent) {
const gitDirectory = nextDirPath();
gitInit(gitDirectory);
fs.rmSync(gitDirectory, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(gitDirectory), false);
}
// Test the Promises based version.
(async () => {
const dir = nextDirPath();
makeNonEmptyDirectory(4, 10, 2, dir, true);
// Removal should fail without the recursive option set to true.
await assert.rejects(fs.promises.rm(dir), { syscall: 'rm' });
await assert.rejects(fs.promises.rm(dir, common.mustNotMutateObjectDeep({ recursive: false })), {
syscall: 'rm'
});
// Recursive removal should succeed.
await fs.promises.rm(dir, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(dir), false);
// Attempted removal should fail now because the directory is gone.
await assert.rejects(fs.promises.rm(dir), { syscall: 'lstat' });
// Should fail if target does not exist
await assert.rejects(fs.promises.rm(
tmpdir.resolve('noexist.txt'),
{ recursive: true }
), {
code: 'ENOENT',
name: 'Error',
message: /^ENOENT: no such file or directory, lstat/
});
// Should not fail if target does not exist and force option is true
await fs.promises.rm(tmpdir.resolve('noexist.txt'), common.mustNotMutateObjectDeep({ force: true }));
// Should delete file
const filePath = tmpdir.resolve('rm-promises-file.txt');
fs.writeFileSync(filePath, '');
try {
await fs.promises.rm(filePath, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(filePath), false);
} finally {
fs.rmSync(filePath, common.mustNotMutateObjectDeep({ force: true }));
}
// Should delete a valid symlink
const linkTarget = tmpdir.resolve('link-target-prom.txt');
fs.writeFileSync(linkTarget, '');
const validLink = tmpdir.resolve('valid-link-prom');
fs.symlinkSync(linkTarget, validLink);
try {
await fs.promises.rm(validLink);
assert.strictEqual(fs.existsSync(validLink), false);
} finally {
fs.rmSync(linkTarget, common.mustNotMutateObjectDeep({ force: true }));
fs.rmSync(validLink, common.mustNotMutateObjectDeep({ force: true }));
}
// Should delete an invalid symlink
const invalidLink = tmpdir.resolve('invalid-link-prom');
fs.symlinkSync('definitely-does-not-exist-prom', invalidLink);
try {
await fs.promises.rm(invalidLink);
assert.strictEqual(fs.existsSync(invalidLink), false);
} finally {
fs.rmSync(invalidLink, common.mustNotMutateObjectDeep({ force: true }));
}
// Should delete a symlink that is part of a loop
const loopLinkA = tmpdir.resolve('loop-link-prom-a');
const loopLinkB = tmpdir.resolve('loop-link-prom-b');
fs.symlinkSync(loopLinkA, loopLinkB);
fs.symlinkSync(loopLinkB, loopLinkA);
try {
await fs.promises.rm(loopLinkA);
assert.strictEqual(fs.existsSync(loopLinkA), false);
} finally {
fs.rmSync(loopLinkA, common.mustNotMutateObjectDeep({ force: true }));
fs.rmSync(loopLinkB, common.mustNotMutateObjectDeep({ force: true }));
}
// Should accept URL
const fileURL = tmpdir.fileURL('rm-promises-file.txt');
fs.writeFileSync(fileURL, '');
try {
await fs.promises.rm(fileURL, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(fileURL), false);
} finally {
fs.rmSync(fileURL, common.mustNotMutateObjectDeep({ force: true }));
}
})().then(common.mustCall());
// Removing a .git directory should not throw an EPERM.
// Refs: https://github.com/isaacs/rimraf/issues/21.
if (isGitPresent) {
(async () => {
const gitDirectory = nextDirPath();
gitInit(gitDirectory);
await fs.promises.rm(gitDirectory, common.mustNotMutateObjectDeep({ recursive: true }));
assert.strictEqual(fs.existsSync(gitDirectory), false);
})().then(common.mustCall());
}
// Test input validation.
{
const dir = nextDirPath();
makeNonEmptyDirectory(4, 10, 2, dir, true);
const filePath = (tmpdir.resolve('rm-args-file.txt'));
fs.writeFileSync(filePath, '');
const defaults = {
retryDelay: 100,
maxRetries: 0,
recursive: false,
force: false
};
const modified = {
retryDelay: 953,
maxRetries: 5,
recursive: true,
force: false
};
assert.deepStrictEqual(validateRmOptionsSync(filePath), defaults);
assert.deepStrictEqual(validateRmOptionsSync(filePath, {}), defaults);
assert.deepStrictEqual(validateRmOptionsSync(filePath, modified), modified);
assert.deepStrictEqual(validateRmOptionsSync(filePath, {
maxRetries: 99
}), {
retryDelay: 100,
maxRetries: 99,
recursive: false,
force: false
});
[null, 'foo', 5, NaN].forEach((bad) => {
assert.throws(() => {
validateRmOptionsSync(filePath, bad);
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /^The "options" argument must be of type object\./
});
});
[undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
assert.throws(() => {
validateRmOptionsSync(filePath, { recursive: bad });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /^The "options\.recursive" property must be of type boolean\./
});
});
[undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
assert.throws(() => {
validateRmOptionsSync(filePath, { force: bad });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /^The "options\.force" property must be of type boolean\./
});
});
assert.throws(() => {
validateRmOptionsSync(filePath, { retryDelay: -1 });
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: /^The value of "options\.retryDelay" is out of range\./
});
assert.throws(() => {
validateRmOptionsSync(filePath, { maxRetries: -1 });
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: /^The value of "options\.maxRetries" is out of range\./
});
}
{
// IBMi has a different access permission mechanism
// This test should not be run as `root`
if (!common.isIBMi && (common.isWindows || process.getuid() !== 0)) {
function makeDirectoryReadOnly(dir, mode) {
let accessErrorCode = 'EACCES';
if (common.isWindows) {
accessErrorCode = 'EPERM';
execSync(`icacls ${dir} /deny "everyone:(OI)(CI)(DE,DC)"`);
} else {
fs.chmodSync(dir, mode);
}
return accessErrorCode;
}
function makeDirectoryWritable(dir) {
if (fs.existsSync(dir)) {
if (common.isWindows) {
execSync(`icacls ${dir} /remove:d "everyone"`);
} else {
fs.chmodSync(dir, 0o777);
}
}
}
{
// Check that deleting a file that cannot be accessed using rmsync throws
// https://github.com/nodejs/node/issues/38683
const dirname = nextDirPath();
const filePath = path.join(dirname, 'text.txt');
try {
fs.mkdirSync(dirname, common.mustNotMutateObjectDeep({ recursive: true }));
fs.writeFileSync(filePath, 'hello');
const code = makeDirectoryReadOnly(dirname, 0o444);
assert.throws(() => {
fs.rmSync(filePath, common.mustNotMutateObjectDeep({ force: true }));
}, {
code,
name: 'Error',
});
} finally {
makeDirectoryWritable(dirname);
}
}
{
// Check endless recursion.
// https://github.com/nodejs/node/issues/34580
const dirname = nextDirPath();
fs.mkdirSync(dirname, common.mustNotMutateObjectDeep({ recursive: true }));
const root = fs.mkdtempSync(path.join(dirname, 'fs-'));
const middle = path.join(root, 'middle');
fs.mkdirSync(middle);
fs.mkdirSync(path.join(middle, 'leaf')); // Make `middle` non-empty
try {
const code = makeDirectoryReadOnly(middle, 0o555);
try {
assert.throws(() => {
fs.rmSync(root, common.mustNotMutateObjectDeep({ recursive: true }));
}, {
code,
name: 'Error',
});
} catch (err) {
// Only fail the test if the folder was not deleted.
// as in some cases rmSync successfully deletes read-only folders.
if (fs.existsSync(root)) {
throw err;
}
}
} finally {
makeDirectoryWritable(middle);
}
}
}
}

View File

@@ -0,0 +1,22 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
tmpdir.refresh();
{
// Should warn when trying to delete a nonexistent path
common.expectWarning(
'DeprecationWarning',
'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
'will be removed. Use fs.rm(path, { recursive: true }) instead',
'DEP0147'
);
assert.throws(
() => fs.rmdirSync(tmpdir.resolve('noexist.txt'),
{ recursive: true }),
{ code: 'ENOENT' }
);
}

View File

@@ -0,0 +1,22 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
tmpdir.refresh();
{
common.expectWarning(
'DeprecationWarning',
'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
'will be removed. Use fs.rm(path, { recursive: true }) instead',
'DEP0147'
);
const filePath = tmpdir.resolve('rmdir-recursive.txt');
fs.writeFileSync(filePath, '');
assert.throws(
() => fs.rmdirSync(filePath, { recursive: true }),
{ code: common.isWindows ? 'ENOENT' : 'ENOTDIR' }
);
}

View File

@@ -0,0 +1,35 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
tmpdir.refresh();
{
assert.throws(
() =>
fs.rmdirSync(tmpdir.resolve('noexist.txt'), { recursive: true }),
{
code: 'ENOENT',
}
);
}
{
fs.rmdir(
tmpdir.resolve('noexist.txt'),
{ recursive: true },
common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOENT');
})
);
}
{
assert.rejects(
() => fs.promises.rmdir(tmpdir.resolve('noexist.txt'),
{ recursive: true }),
{
code: 'ENOENT',
}
).then(common.mustCall());
}

View File

@@ -0,0 +1,28 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
tmpdir.refresh();
const code = common.isWindows ? 'ENOENT' : 'ENOTDIR';
{
const filePath = tmpdir.resolve('rmdir-recursive.txt');
fs.writeFileSync(filePath, '');
assert.throws(() => fs.rmdirSync(filePath, { recursive: true }), { code });
}
{
const filePath = tmpdir.resolve('rmdir-recursive.txt');
fs.writeFileSync(filePath, '');
fs.rmdir(filePath, { recursive: true }, common.mustCall((err) => {
assert.strictEqual(err.code, code);
}));
}
{
const filePath = tmpdir.resolve('rmdir-recursive.txt');
fs.writeFileSync(filePath, '');
assert.rejects(() => fs.promises.rmdir(filePath, { recursive: true }),
{ code }).then(common.mustCall());
}

View File

@@ -0,0 +1,21 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const fs = require('fs');
tmpdir.refresh();
{
// Should warn when trying to delete a nonexistent path
common.expectWarning(
'DeprecationWarning',
'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
'will be removed. Use fs.rm(path, { recursive: true }) instead',
'DEP0147'
);
fs.rmdir(
tmpdir.resolve('noexist.txt'),
{ recursive: true },
common.mustCall()
);
}

View File

@@ -0,0 +1,21 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
tmpdir.refresh();
{
common.expectWarning(
'DeprecationWarning',
'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
'will be removed. Use fs.rm(path, { recursive: true }) instead',
'DEP0147'
);
const filePath = tmpdir.resolve('rmdir-recursive.txt');
fs.writeFileSync(filePath, '');
fs.rmdir(filePath, { recursive: true }, common.mustCall((err) => {
assert.strictEqual(err.code, common.isWindows ? 'ENOENT' : 'ENOTDIR');
}));
}

View File

@@ -0,0 +1,219 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { validateRmdirOptions } = require('internal/fs/utils');
common.expectWarning(
'DeprecationWarning',
'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
'will be removed. Use fs.rm(path, { recursive: true }) instead',
'DEP0147'
);
tmpdir.refresh();
let count = 0;
const nextDirPath = (name = 'rmdir-recursive') =>
tmpdir.resolve(`${name}-${count++}`);
function makeNonEmptyDirectory(depth, files, folders, dirname, createSymLinks) {
fs.mkdirSync(dirname, { recursive: true });
fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8');
const options = { flag: 'wx' };
for (let f = files; f > 0; f--) {
fs.writeFileSync(path.join(dirname, `f-${depth}-${f}`), '', options);
}
if (createSymLinks) {
// Valid symlink
fs.symlinkSync(
`f-${depth}-1`,
path.join(dirname, `link-${depth}-good`),
'file'
);
// Invalid symlink
fs.symlinkSync(
'does-not-exist',
path.join(dirname, `link-${depth}-bad`),
'file'
);
}
// File with a name that looks like a glob
fs.writeFileSync(path.join(dirname, '[a-z0-9].txt'), '', options);
depth--;
if (depth <= 0) {
return;
}
for (let f = folders; f > 0; f--) {
fs.mkdirSync(
path.join(dirname, `folder-${depth}-${f}`),
{ recursive: true }
);
makeNonEmptyDirectory(
depth,
files,
folders,
path.join(dirname, `d-${depth}-${f}`),
createSymLinks
);
}
}
function removeAsync(dir) {
// Removal should fail without the recursive option.
fs.rmdir(dir, common.mustCall((err) => {
assert.strictEqual(err.syscall, 'rmdir');
// Removal should fail without the recursive option set to true.
fs.rmdir(dir, { recursive: false }, common.mustCall((err) => {
assert.strictEqual(err.syscall, 'rmdir');
// Recursive removal should succeed.
fs.rmdir(dir, { recursive: true }, common.mustSucceed(() => {
// An error should occur if recursive and the directory does not exist.
fs.rmdir(dir, { recursive: true }, common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOENT');
// Attempted removal should fail now because the directory is gone.
fs.rmdir(dir, common.mustCall((err) => {
assert.strictEqual(err.syscall, 'rmdir');
}));
}));
}));
}));
}));
}
// Test the asynchronous version
{
// Create a 4-level folder hierarchy including symlinks
let dir = nextDirPath();
makeNonEmptyDirectory(4, 10, 2, dir, true);
removeAsync(dir);
// Create a 2-level folder hierarchy without symlinks
dir = nextDirPath();
makeNonEmptyDirectory(2, 10, 2, dir, false);
removeAsync(dir);
// Create a flat folder including symlinks
dir = nextDirPath();
makeNonEmptyDirectory(1, 10, 2, dir, true);
removeAsync(dir);
}
// Test the synchronous version.
{
const dir = nextDirPath();
makeNonEmptyDirectory(4, 10, 2, dir, true);
// Removal should fail without the recursive option set to true.
assert.throws(() => {
fs.rmdirSync(dir);
}, { syscall: 'rmdir' });
assert.throws(() => {
fs.rmdirSync(dir, { recursive: false });
}, { syscall: 'rmdir' });
// Recursive removal should succeed.
fs.rmdirSync(dir, { recursive: true });
// An error should occur if recursive and the directory does not exist.
assert.throws(() => fs.rmdirSync(dir, { recursive: true }),
{ code: 'ENOENT' });
// Attempted removal should fail now because the directory is gone.
assert.throws(() => fs.rmdirSync(dir), { syscall: 'rmdir' });
}
// Test the Promises based version.
(async () => {
const dir = nextDirPath();
makeNonEmptyDirectory(4, 10, 2, dir, true);
// Removal should fail without the recursive option set to true.
await assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' });
await assert.rejects(fs.promises.rmdir(dir, { recursive: false }), {
syscall: 'rmdir'
});
// Recursive removal should succeed.
await fs.promises.rmdir(dir, { recursive: true });
// An error should occur if recursive and the directory does not exist.
await assert.rejects(fs.promises.rmdir(dir, { recursive: true }),
{ code: 'ENOENT' });
// Attempted removal should fail now because the directory is gone.
await assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' });
})().then(common.mustCall());
// Test input validation.
{
const defaults = {
retryDelay: 100,
maxRetries: 0,
recursive: false
};
const modified = {
retryDelay: 953,
maxRetries: 5,
recursive: true
};
assert.deepStrictEqual(validateRmdirOptions(), defaults);
assert.deepStrictEqual(validateRmdirOptions({}), defaults);
assert.deepStrictEqual(validateRmdirOptions(modified), modified);
assert.deepStrictEqual(validateRmdirOptions({
maxRetries: 99
}), {
retryDelay: 100,
maxRetries: 99,
recursive: false
});
[null, 'foo', 5, NaN].forEach((bad) => {
assert.throws(() => {
validateRmdirOptions(bad);
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /^The "options" argument must be of type object\./
});
});
[undefined, null, 'foo', Infinity, function() {}].forEach((bad) => {
assert.throws(() => {
validateRmdirOptions({ recursive: bad });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /^The "options\.recursive" property must be of type boolean\./
});
});
assert.throws(() => {
validateRmdirOptions({ retryDelay: -1 });
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: /^The value of "options\.retryDelay" is out of range\./
});
assert.throws(() => {
validateRmdirOptions({ maxRetries: -1 });
}, {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: /^The value of "options\.maxRetries" is out of range\./
});
}

View File

@@ -0,0 +1,223 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
fs.stat('.', common.mustSucceed(function(stats) {
assert.ok(stats.mtime instanceof Date);
assert.ok(Object.hasOwn(stats, 'blksize'));
assert.ok(Object.hasOwn(stats, 'blocks'));
// Confirm that we are not running in the context of the internal binding
// layer.
// Ref: https://github.com/nodejs/node/commit/463d6bac8b349acc462d345a6e298a76f7d06fb1
assert.strictEqual(this, undefined);
}));
fs.lstat('.', common.mustSucceed(function(stats) {
assert.ok(stats.mtime instanceof Date);
// Confirm that we are not running in the context of the internal binding
// layer.
// Ref: https://github.com/nodejs/node/commit/463d6bac8b349acc462d345a6e298a76f7d06fb1
assert.strictEqual(this, undefined);
}));
// fstat
fs.open('.', 'r', undefined, common.mustSucceed(function(fd) {
assert.ok(fd);
fs.fstat(-0, common.mustSucceed());
fs.fstat(fd, common.mustSucceed(function(stats) {
assert.ok(stats.mtime instanceof Date);
fs.close(fd, assert.ifError);
// Confirm that we are not running in the context of the internal binding
// layer.
// Ref: https://github.com/nodejs/node/commit/463d6bac8b349acc462d345a6e298a76f7d06fb1
assert.strictEqual(this, undefined);
}));
// Confirm that we are not running in the context of the internal binding
// layer.
// Ref: https://github.com/nodejs/node/commit/463d6bac8b349acc462d345a6e298a76f7d06fb1
assert.strictEqual(this, undefined);
}));
// fstatSync
fs.open('.', 'r', undefined, common.mustCall(function(err, fd) {
const stats = fs.fstatSync(fd);
assert.ok(stats.mtime instanceof Date);
fs.close(fd, common.mustSucceed());
}));
fs.stat(__filename, common.mustSucceed((s) => {
assert.strictEqual(s.isDirectory(), false);
assert.strictEqual(s.isFile(), true);
assert.strictEqual(s.isSocket(), false);
assert.strictEqual(s.isBlockDevice(), false);
assert.strictEqual(s.isCharacterDevice(), false);
assert.strictEqual(s.isFIFO(), false);
assert.strictEqual(s.isSymbolicLink(), false);
[
'dev', 'mode', 'nlink', 'uid',
'gid', 'rdev', 'blksize', 'ino', 'size', 'blocks',
'atime', 'mtime', 'ctime', 'birthtime',
'atimeMs', 'mtimeMs', 'ctimeMs', 'birthtimeMs',
].forEach(function(k) {
assert.ok(k in s, `${k} should be in Stats`);
assert.notStrictEqual(s[k], undefined, `${k} should not be undefined`);
assert.notStrictEqual(s[k], null, `${k} should not be null`);
});
[
'dev', 'mode', 'nlink', 'uid', 'gid', 'rdev', 'blksize', 'ino', 'size',
'blocks', 'atimeMs', 'mtimeMs', 'ctimeMs', 'birthtimeMs',
].forEach((k) => {
assert.strictEqual(typeof s[k], 'number', `${k} should be a number`);
});
['atime', 'mtime', 'ctime', 'birthtime'].forEach((k) => {
assert.ok(s[k] instanceof Date, `${k} should be a Date`);
});
}));
['', false, null, undefined, {}, []].forEach((input) => {
['fstat', 'fstatSync'].forEach((fnName) => {
assert.throws(
() => fs[fnName](input),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
});
});
[false, 1, {}, [], null, undefined].forEach((input) => {
assert.throws(
() => fs.lstat(input, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
assert.throws(
() => fs.lstatSync(input),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
assert.throws(
() => fs.stat(input, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
assert.throws(
() => fs.statSync(input),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
});
// Should not throw an error
fs.stat(__filename, undefined, common.mustCall());
fs.open(__filename, 'r', undefined, common.mustCall((err, fd) => {
// Should not throw an error
fs.fstat(fd, undefined, common.mustCall());
}));
// Should not throw an error
fs.lstat(__filename, undefined, common.mustCall());
{
fs.Stats(
0, // dev
0, // mode
0, // nlink
0, // uid
0, // gid
0, // rdev
0, // blksize
0, // ino
0, // size
0, // blocks
Date.UTC(1970, 0, 1, 0, 0, 0), // atime
Date.UTC(1970, 0, 1, 0, 0, 0), // mtime
Date.UTC(1970, 0, 1, 0, 0, 0), // ctime
Date.UTC(1970, 0, 1, 0, 0, 0) // birthtime
);
common.expectWarning({
DeprecationWarning: [
['fs.Stats constructor is deprecated.',
'DEP0180'],
]
});
}
{
// These two tests have an equivalent in ./test-fs-stat-bigint.js
// Stats Date properties can be set before reading them
fs.stat(__filename, common.mustSucceed((s) => {
s.atime = 2;
s.mtime = 3;
s.ctime = 4;
s.birthtime = 5;
assert.strictEqual(s.atime, 2);
assert.strictEqual(s.mtime, 3);
assert.strictEqual(s.ctime, 4);
assert.strictEqual(s.birthtime, 5);
}));
// Stats Date properties can be set after reading them
fs.stat(__filename, common.mustSucceed((s) => {
// eslint-disable-next-line no-unused-expressions
s.atime, s.mtime, s.ctime, s.birthtime;
s.atime = 2;
s.mtime = 3;
s.ctime = 4;
s.birthtime = 5;
assert.strictEqual(s.atime, 2);
assert.strictEqual(s.mtime, 3);
assert.strictEqual(s.ctime, 4);
assert.strictEqual(s.birthtime, 5);
}));
}
{
assert.throws(
() => fs.fstat(Symbol('test'), () => {}),
{
code: 'ERR_INVALID_ARG_TYPE',
},
);
}

View File

@@ -0,0 +1,59 @@
'use strict';
const common = require('../common');
const assert = require('node:assert');
const fs = require('node:fs');
function verifyStatFsObject(statfs, isBigint = false) {
const valueType = isBigint ? 'bigint' : 'number';
[
'type', 'bsize', 'blocks', 'bfree', 'bavail', 'files', 'ffree',
].forEach((k) => {
assert.ok(Object.hasOwn(statfs, k));
assert.strictEqual(typeof statfs[k], valueType,
`${k} should be a ${valueType}`);
});
}
fs.statfs(__filename, common.mustSucceed(function(stats) {
verifyStatFsObject(stats);
assert.strictEqual(this, undefined);
}));
fs.statfs(__filename, { bigint: true }, function(err, stats) {
assert.ifError(err);
verifyStatFsObject(stats, true);
assert.strictEqual(this, undefined);
});
// Synchronous
{
const statFsObj = fs.statfsSync(__filename);
verifyStatFsObject(statFsObj);
}
// Synchronous Bigint
{
const statFsBigIntObj = fs.statfsSync(__filename, { bigint: true });
verifyStatFsObject(statFsBigIntObj, true);
}
[false, 1, {}, [], null, undefined].forEach((input) => {
assert.throws(
() => fs.statfs(input, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
assert.throws(
() => fs.statfsSync(input),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
}
);
});
// Should not throw an error
fs.statfs(__filename, undefined, common.mustCall());

View File

@@ -0,0 +1,32 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
{
// Compat error.
function ReadStream(...args) {
fs.ReadStream.call(this, ...args);
}
Object.setPrototypeOf(ReadStream.prototype, fs.ReadStream.prototype);
Object.setPrototypeOf(ReadStream, fs.ReadStream);
ReadStream.prototype.open = common.mustCall(function ReadStream$open() {
const that = this;
fs.open(that.path, that.flags, that.mode, (err, fd) => {
that.emit('error', err);
});
});
const r = new ReadStream('/doesnotexist', { emitClose: true })
.on('error', common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(r.destroyed, true);
r.on('close', common.mustCall());
}));
}

View File

@@ -0,0 +1,50 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const assert = require('assert');
const debuglog = (arg) => {
console.log(new Date().toLocaleString(), arg);
};
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
{
// Compat error.
debuglog('start test');
function WriteStream(...args) {
debuglog('WriteStream constructor');
fs.WriteStream.call(this, ...args);
}
Object.setPrototypeOf(WriteStream.prototype, fs.WriteStream.prototype);
Object.setPrototypeOf(WriteStream, fs.WriteStream);
WriteStream.prototype.open = common.mustCall(function WriteStream$open() {
debuglog('WriteStream open() callback');
const that = this;
fs.open(that.path, that.flags, that.mode, (err, fd) => {
debuglog('inner fs open() callback');
that.emit('error', err);
});
});
fs.open(`${tmpdir.path}/dummy`, 'wx+', common.mustCall((err, fd) => {
debuglog('fs open() callback');
assert.ifError(err);
fs.close(fd, () => { debuglog(`closed ${fd}`); });
const w = new WriteStream(`${tmpdir.path}/dummy`,
{ flags: 'wx+', emitClose: true })
.on('error', common.mustCall((err) => {
debuglog('error event callback');
assert.strictEqual(err.code, 'EEXIST');
w.destroy();
w.on('close', common.mustCall(() => {
debuglog('close event callback');
}));
}));
}));
debuglog('waiting for callbacks');
}

View File

@@ -0,0 +1,70 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
{
// Compat with graceful-fs.
function ReadStream(...args) {
fs.ReadStream.call(this, ...args);
}
Object.setPrototypeOf(ReadStream.prototype, fs.ReadStream.prototype);
Object.setPrototypeOf(ReadStream, fs.ReadStream);
ReadStream.prototype.open = common.mustCall(function ReadStream$open() {
const that = this;
fs.open(that.path, that.flags, that.mode, (err, fd) => {
if (err) {
if (that.autoClose)
that.destroy();
that.emit('error', err);
} else {
that.fd = fd;
that.emit('open', fd);
that.read();
}
});
});
const r = new ReadStream(fixtures.path('x.txt'))
.on('open', common.mustCall((fd) => {
assert.strictEqual(fd, r.fd);
r.destroy();
}));
}
{
// Compat with graceful-fs.
function WriteStream(...args) {
fs.WriteStream.call(this, ...args);
}
Object.setPrototypeOf(WriteStream.prototype, fs.WriteStream.prototype);
Object.setPrototypeOf(WriteStream, fs.WriteStream);
WriteStream.prototype.open = common.mustCall(function WriteStream$open() {
const that = this;
fs.open(that.path, that.flags, that.mode, function(err, fd) {
if (err) {
that.destroy();
that.emit('error', err);
} else {
that.fd = fd;
that.emit('open', fd);
}
});
});
const w = new WriteStream(`${tmpdir.path}/dummy`)
.on('open', common.mustCall((fd) => {
assert.strictEqual(fd, w.fd);
w.destroy();
}));
}

View File

@@ -0,0 +1,97 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
{
// Compat with old node.
function ReadStream(...args) {
fs.ReadStream.call(this, ...args);
}
Object.setPrototypeOf(ReadStream.prototype, fs.ReadStream.prototype);
Object.setPrototypeOf(ReadStream, fs.ReadStream);
ReadStream.prototype.open = common.mustCall(function() {
fs.open(this.path, this.flags, this.mode, (er, fd) => {
if (er) {
if (this.autoClose) {
this.destroy();
}
this.emit('error', er);
return;
}
this.fd = fd;
this.emit('open', fd);
this.emit('ready');
});
});
let readyCalled = false;
let ticked = false;
const r = new ReadStream(fixtures.path('x.txt'))
.on('ready', common.mustCall(() => {
readyCalled = true;
// Make sure 'ready' is emitted in same tick as 'open'.
assert.strictEqual(ticked, false);
}))
.on('error', common.mustNotCall())
.on('open', common.mustCall((fd) => {
process.nextTick(() => {
ticked = true;
r.destroy();
});
assert.strictEqual(readyCalled, false);
assert.strictEqual(fd, r.fd);
}));
}
{
// Compat with old node.
function WriteStream(...args) {
fs.WriteStream.call(this, ...args);
}
Object.setPrototypeOf(WriteStream.prototype, fs.WriteStream.prototype);
Object.setPrototypeOf(WriteStream, fs.WriteStream);
WriteStream.prototype.open = common.mustCall(function() {
fs.open(this.path, this.flags, this.mode, (er, fd) => {
if (er) {
if (this.autoClose) {
this.destroy();
}
this.emit('error', er);
return;
}
this.fd = fd;
this.emit('open', fd);
this.emit('ready');
});
});
let readyCalled = false;
let ticked = false;
const w = new WriteStream(`${tmpdir.path}/dummy`)
.on('ready', common.mustCall(() => {
readyCalled = true;
// Make sure 'ready' is emitted in same tick as 'open'.
assert.strictEqual(ticked, false);
}))
.on('error', common.mustNotCall())
.on('open', common.mustCall((fd) => {
process.nextTick(() => {
ticked = true;
w.destroy();
});
assert.strictEqual(readyCalled, false);
assert.strictEqual(fd, w.fd);
}));
}

View File

@@ -0,0 +1,43 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
{
const stream = fs.createReadStream(__filename);
stream.on('close', common.mustCall());
test(stream);
}
{
const stream = fs.createWriteStream(`${tmpdir.path}/dummy`);
stream.on('close', common.mustCall());
test(stream);
}
{
const stream = fs.createReadStream(__filename, { emitClose: true });
stream.on('close', common.mustCall());
test(stream);
}
{
const stream = fs.createWriteStream(`${tmpdir.path}/dummy2`,
{ emitClose: true });
stream.on('close', common.mustCall());
test(stream);
}
function test(stream) {
const err = new Error('DESTROYED');
stream.on('open', function() {
stream.destroy(err);
});
stream.on('error', common.mustCall(function(err_) {
assert.strictEqual(err_, err);
}));
}

View File

@@ -0,0 +1,72 @@
'use strict';
require('../common');
const fixtures = require('../common/fixtures');
const fs = require('fs');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const streamOpts = ['open', 'close'];
const writeStreamOptions = [...streamOpts, 'write'];
const readStreamOptions = [...streamOpts, 'read'];
const originalFs = { fs };
{
const file = tmpdir.resolve('write-end-test0.txt');
writeStreamOptions.forEach((fn) => {
const overrideFs = Object.assign({}, originalFs.fs, { [fn]: null });
if (fn === 'write') overrideFs.writev = null;
const opts = {
fs: overrideFs
};
assert.throws(
() => fs.createWriteStream(file, opts), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: `The "options.fs.${fn}" property must be of type function. ` +
'Received null'
},
`createWriteStream options.fs.${fn} should throw if isn't a function`
);
});
}
{
const file = tmpdir.resolve('write-end-test0.txt');
const overrideFs = Object.assign({}, originalFs.fs, { writev: 'not a fn' });
const opts = {
fs: overrideFs
};
assert.throws(
() => fs.createWriteStream(file, opts), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.fs.writev" property must be of type function. ' +
'Received type string (\'not a fn\')'
},
'createWriteStream options.fs.writev should throw if isn\'t a function'
);
}
{
const file = fixtures.path('x.txt');
readStreamOptions.forEach((fn) => {
const overrideFs = Object.assign({}, originalFs.fs, { [fn]: null });
const opts = {
fs: overrideFs
};
assert.throws(
() => fs.createReadStream(file, opts), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: `The "options.fs.${fn}" property must be of type function. ` +
'Received null'
},
`createReadStream options.fs.${fn} should throw if isn't a function`
);
});
}

View File

@@ -0,0 +1,49 @@
'use strict';
const { mustNotMutateObjectDeep } = require('../common');
const assert = require('assert');
const fs = require('fs');
{
const fd = 'k';
assert.throws(
() => {
fs.createReadStream(null, mustNotMutateObjectDeep({ fd }));
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
assert.throws(
() => {
fs.createWriteStream(null, mustNotMutateObjectDeep({ fd }));
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
}
{
const path = 46;
assert.throws(
() => {
fs.createReadStream(path);
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
assert.throws(
() => {
fs.createWriteStream(path);
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
}

View File

@@ -0,0 +1,102 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.canCreateSymLink())
common.skip('insufficient privileges');
const assert = require('assert');
const fs = require('fs');
let linkTime;
let fileTime;
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
// Test creating and reading symbolic link
const linkData = fixtures.path('/cycles/root.js');
const linkPath = tmpdir.resolve('symlink1.js');
fs.symlink(linkData, linkPath, common.mustSucceed(() => {
fs.lstat(linkPath, common.mustSucceed((stats) => {
linkTime = stats.mtime.getTime();
}));
fs.stat(linkPath, common.mustSucceed((stats) => {
fileTime = stats.mtime.getTime();
}));
fs.readlink(linkPath, common.mustSucceed((destination) => {
assert.strictEqual(destination, linkData);
}));
}));
// Test invalid symlink
{
const linkData = fixtures.path('/not/exists/file');
const linkPath = tmpdir.resolve('symlink2.js');
fs.symlink(linkData, linkPath, common.mustSucceed(() => {
assert(!fs.existsSync(linkPath));
}));
}
[false, 1, {}, [], null, undefined].forEach((input) => {
const errObj = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /target|path/
};
assert.throws(() => fs.symlink(input, '', common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync(input, ''), errObj);
assert.throws(() => fs.symlink('', input, common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', input), errObj);
});
const errObj = {
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError',
};
assert.throws(() => fs.symlink('', '', '🍏', common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', '', '🍏'), errObj);
assert.throws(() => fs.symlink('', '', 'nonExistentType', common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', '', 'nonExistentType'), errObj);
assert.rejects(() => fs.promises.symlink('', '', 'nonExistentType'), errObj)
.then(common.mustCall());
assert.throws(() => fs.symlink('', '', false, common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', '', false), errObj);
assert.rejects(() => fs.promises.symlink('', '', false), errObj)
.then(common.mustCall());
assert.throws(() => fs.symlink('', '', {}, common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', '', {}), errObj);
assert.rejects(() => fs.promises.symlink('', '', {}), errObj)
.then(common.mustCall());
process.on('exit', () => {
assert.notStrictEqual(linkTime, fileTime);
});

View File

@@ -0,0 +1,86 @@
// Flags: --expose-internals
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
require('../common');
const assert = require('assert');
const fs = require('fs');
const { internalBinding } = require('internal/test/binding');
// Ensure that (read|write|append)FileSync() closes the file descriptor
fs.openSync = function() {
return 42;
};
fs.closeSync = function(fd) {
assert.strictEqual(fd, 42);
close_called++;
};
fs.readSync = function() {
throw new Error('BAM');
};
fs.writeSync = function() {
throw new Error('BAM');
};
// Internal fast paths are pure C++, can't error inside write
internalBinding('fs').writeFileUtf8 = function() {
// Fake close
close_called++;
throw new Error('BAM');
};
internalBinding('fs').fstat = function() {
throw new Error('EBADF: bad file descriptor, fstat');
};
let close_called = 0;
ensureThrows(function() {
// Fast path: writeFileSync utf8
fs.writeFileSync('dummy', 'xxx');
}, 'BAM');
ensureThrows(function() {
// Non-fast path
fs.writeFileSync('dummy', 'xxx', { encoding: 'base64' });
}, 'BAM');
ensureThrows(function() {
// Fast path: writeFileSync utf8
fs.appendFileSync('dummy', 'xxx');
}, 'BAM');
ensureThrows(function() {
// Non-fast path
fs.appendFileSync('dummy', 'xxx', { encoding: 'base64' });
}, 'BAM');
function ensureThrows(cb, message) {
let got_exception = false;
close_called = 0;
try {
cb();
} catch (e) {
assert.strictEqual(e.message, message);
got_exception = true;
}
assert.strictEqual(close_called, 1);
assert.strictEqual(got_exception, true);
}

View File

@@ -0,0 +1,40 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const spawn = require('child_process').spawn;
const stream = require('stream');
const fs = require('fs');
// require('internal/fs/utils').SyncWriteStream is used as a stdio
// implementation when stdout/stderr point to files.
if (process.argv[2] === 'child') {
// Note: Calling console.log() is part of this test as it exercises the
// SyncWriteStream#_write() code path.
console.log(JSON.stringify([process.stdout, process.stderr].map((stdio) => ({
instance: stdio instanceof stream.Writable,
readable: stdio.readable,
writable: stdio.writable,
}))));
return;
}
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const filename = tmpdir.resolve('stdout');
const stdoutFd = fs.openSync(filename, 'w');
const proc = spawn(process.execPath, [__filename, 'child'], {
stdio: ['inherit', stdoutFd, stdoutFd ]
});
proc.on('close', common.mustCall(() => {
fs.closeSync(stdoutFd);
assert.deepStrictEqual(JSON.parse(fs.readFileSync(filename, 'utf8')), [
{ instance: true, readable: false, writable: true },
{ instance: true, readable: false, writable: true },
]);
}));

View File

@@ -0,0 +1,29 @@
'use strict';
require('../common');
const assert = require('assert');
const fs = require('fs');
for (const input of [Infinity, -Infinity, NaN]) {
assert.throws(
() => {
fs._toUnixTimestamp(input);
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
}
assert.throws(
() => {
fs._toUnixTimestamp({});
},
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
const okInputs = [1, -1, '1', '-1', Date.now()];
for (const input of okInputs) {
fs._toUnixTimestamp(input);
}

View File

@@ -0,0 +1,27 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const tmp = tmpdir.path;
tmpdir.refresh();
const filename = path.resolve(tmp, 'truncate-file.txt');
fs.writeFileSync(filename, 'hello world', 'utf8');
const fd = fs.openSync(filename, 'r+');
const msg = 'Using fs.truncate with a file descriptor is deprecated.' +
' Please use fs.ftruncate with a file descriptor instead.';
common.expectWarning('DeprecationWarning', msg, 'DEP0081');
fs.truncate(fd, 5, common.mustSucceed(() => {
assert.strictEqual(fs.readFileSync(filename, 'utf8'), 'hello');
}));
process.once('beforeExit', () => {
fs.closeSync(fd);
fs.unlinkSync(filename);
console.log('ok');
});

View File

@@ -0,0 +1,298 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const tmp = tmpdir.path;
const filename = path.resolve(tmp, 'truncate-file.txt');
const data = Buffer.alloc(1024 * 16, 'x');
tmpdir.refresh();
let stat;
const msg = 'Using fs.truncate with a file descriptor is deprecated.' +
' Please use fs.ftruncate with a file descriptor instead.';
// Check truncateSync
fs.writeFileSync(filename, data);
stat = fs.statSync(filename);
assert.strictEqual(stat.size, 1024 * 16);
fs.truncateSync(filename, 1024);
stat = fs.statSync(filename);
assert.strictEqual(stat.size, 1024);
fs.truncateSync(filename);
stat = fs.statSync(filename);
assert.strictEqual(stat.size, 0);
// Check ftruncateSync
fs.writeFileSync(filename, data);
const fd = fs.openSync(filename, 'r+');
stat = fs.statSync(filename);
assert.strictEqual(stat.size, 1024 * 16);
fs.ftruncateSync(fd, 1024);
stat = fs.statSync(filename);
assert.strictEqual(stat.size, 1024);
fs.ftruncateSync(fd);
stat = fs.statSync(filename);
assert.strictEqual(stat.size, 0);
// truncateSync
common.expectWarning('DeprecationWarning', msg, 'DEP0081');
fs.truncateSync(fd);
fs.closeSync(fd);
// Async tests
testTruncate(common.mustSucceed(() => {
testFtruncate(common.mustSucceed());
}));
function testTruncate(cb) {
fs.writeFile(filename, data, function(er) {
if (er) return cb(er);
fs.stat(filename, function(er, stat) {
if (er) return cb(er);
assert.strictEqual(stat.size, 1024 * 16);
fs.truncate(filename, 1024, function(er) {
if (er) return cb(er);
fs.stat(filename, function(er, stat) {
if (er) return cb(er);
assert.strictEqual(stat.size, 1024);
fs.truncate(filename, function(er) {
if (er) return cb(er);
fs.stat(filename, function(er, stat) {
if (er) return cb(er);
assert.strictEqual(stat.size, 0);
cb();
});
});
});
});
});
});
}
function testFtruncate(cb) {
fs.writeFile(filename, data, function(er) {
if (er) return cb(er);
fs.stat(filename, function(er, stat) {
if (er) return cb(er);
assert.strictEqual(stat.size, 1024 * 16);
fs.open(filename, 'w', function(er, fd) {
if (er) return cb(er);
fs.ftruncate(fd, 1024, function(er) {
if (er) return cb(er);
fs.stat(filename, function(er, stat) {
if (er) return cb(er);
assert.strictEqual(stat.size, 1024);
fs.ftruncate(fd, function(er) {
if (er) return cb(er);
fs.stat(filename, function(er, stat) {
if (er) return cb(er);
assert.strictEqual(stat.size, 0);
fs.close(fd, cb);
});
});
});
});
});
});
});
}
// Make sure if the size of the file is smaller than the length then it is
// filled with zeroes.
{
const file1 = path.resolve(tmp, 'truncate-file-1.txt');
fs.writeFileSync(file1, 'Hi');
fs.truncateSync(file1, 4);
assert(fs.readFileSync(file1).equals(Buffer.from('Hi\u0000\u0000')));
}
{
const file2 = path.resolve(tmp, 'truncate-file-2.txt');
fs.writeFileSync(file2, 'Hi');
const fd = fs.openSync(file2, 'r+');
process.on('beforeExit', () => fs.closeSync(fd));
fs.ftruncateSync(fd, 4);
assert(fs.readFileSync(file2).equals(Buffer.from('Hi\u0000\u0000')));
}
{
const file3 = path.resolve(tmp, 'truncate-file-3.txt');
fs.writeFileSync(file3, 'Hi');
fs.truncate(file3, 4, common.mustSucceed(() => {
assert(fs.readFileSync(file3).equals(Buffer.from('Hi\u0000\u0000')));
}));
}
{
const file4 = path.resolve(tmp, 'truncate-file-4.txt');
fs.writeFileSync(file4, 'Hi');
const fd = fs.openSync(file4, 'r+');
process.on('beforeExit', () => fs.closeSync(fd));
fs.ftruncate(fd, 4, common.mustSucceed(() => {
assert(fs.readFileSync(file4).equals(Buffer.from('Hi\u0000\u0000')));
}));
}
{
const file5 = path.resolve(tmp, 'truncate-file-5.txt');
fs.writeFileSync(file5, 'Hi');
const fd = fs.openSync(file5, 'r+');
process.on('beforeExit', () => fs.closeSync(fd));
['', false, null, {}, []].forEach((input) => {
const received = common.invalidArgTypeHelper(input);
assert.throws(
() => fs.truncate(file5, input, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: `The "len" argument must be of type number.${received}`
}
);
assert.throws(
() => fs.ftruncate(fd, input),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: `The "len" argument must be of type number.${received}`
}
);
});
[-1.5, 1.5].forEach((input) => {
assert.throws(
() => fs.truncate(file5, input),
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "len" is out of range. It must be ' +
`an integer. Received ${input}`
}
);
assert.throws(
() => fs.ftruncate(fd, input),
{
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "len" is out of range. It must be ' +
`an integer. Received ${input}`
}
);
});
fs.ftruncate(fd, undefined, common.mustSucceed(() => {
assert(fs.readFileSync(file5).equals(Buffer.from('')));
}));
}
{
const file6 = path.resolve(tmp, 'truncate-file-6.txt');
fs.writeFileSync(file6, 'Hi');
const fd = fs.openSync(file6, 'r+');
process.on('beforeExit', () => fs.closeSync(fd));
fs.ftruncate(fd, -1, common.mustSucceed(() => {
assert(fs.readFileSync(file6).equals(Buffer.from('')));
}));
}
{
const file7 = path.resolve(tmp, 'truncate-file-7.txt');
fs.writeFileSync(file7, 'Hi');
fs.truncate(file7, undefined, common.mustSucceed(() => {
assert(fs.readFileSync(file7).equals(Buffer.from('')));
}));
}
{
const file8 = path.resolve(tmp, 'non-existent-truncate-file.txt');
const validateError = (err) => {
assert.strictEqual(file8, err.path);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, open '${file8}'`);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'open');
return true;
};
fs.truncate(file8, 0, common.mustCall(validateError));
}
['', false, null, {}, []].forEach((input) => {
assert.throws(
() => fs.truncate('/foo/bar', input),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "len" argument must be of type number.' +
common.invalidArgTypeHelper(input)
}
);
});
['', false, null, undefined, {}, []].forEach((input) => {
['ftruncate', 'ftruncateSync'].forEach((fnName) => {
assert.throws(
() => fs[fnName](input, 1, () => {}),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "fd" argument must be of type number.' +
common.invalidArgTypeHelper(input)
}
);
});
});
{
const file1 = path.resolve(tmp, 'truncate-file-1.txt');
fs.writeFileSync(file1, 'Hi');
fs.truncateSync(file1, -1); // Negative coerced to 0, No error.
assert(fs.readFileSync(file1).equals(Buffer.alloc(0)));
}
{
const file1 = path.resolve(tmp, 'truncate-file-2.txt');
fs.writeFileSync(file1, 'Hi');
// Negative coerced to 0, No error.
fs.truncate(file1, -1, common.mustSucceed(() => {
assert(fs.readFileSync(file1).equals(Buffer.alloc(0)));
}));
}

View File

@@ -0,0 +1,87 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const {
validateOffsetLengthRead,
validateOffsetLengthWrite,
} = require('internal/fs/utils');
{
const offset = -1;
assert.throws(
() => validateOffsetLengthRead(offset, 0, 0),
common.expectsError({
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "offset" is out of range. ' +
`It must be >= 0. Received ${offset}`
})
);
}
{
const length = -1;
assert.throws(
() => validateOffsetLengthRead(0, length, 0),
common.expectsError({
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "length" is out of range. ' +
`It must be >= 0. Received ${length}`
})
);
}
{
const offset = 1;
const length = 1;
const byteLength = offset + length - 1;
assert.throws(
() => validateOffsetLengthRead(offset, length, byteLength),
common.expectsError({
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "length" is out of range. ' +
`It must be <= ${byteLength - offset}. Received ${length}`
})
);
}
// Most platforms don't allow reads or writes >= 2 GiB.
// See https://github.com/libuv/libuv/pull/1501.
const kIoMaxLength = 2 ** 31 - 1;
// RangeError when offset > byteLength
{
const offset = 100;
const length = 100;
const byteLength = 50;
assert.throws(
() => validateOffsetLengthWrite(offset, length, byteLength),
common.expectsError({
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "offset" is out of range. ' +
`It must be <= ${byteLength}. Received ${offset}`
})
);
}
// RangeError when byteLength < kIoMaxLength, and length > byteLength - offset.
{
const offset = kIoMaxLength - 150;
const length = 200;
const byteLength = kIoMaxLength - 100;
assert.throws(
() => validateOffsetLengthWrite(offset, length, byteLength),
common.expectsError({
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "length" is out of range. ' +
`It must be <= ${byteLength - offset}. Received ${length}`
})
);
}

View File

@@ -0,0 +1,138 @@
// Flags: --expose-internals
'use strict';
const common = require('../common');
const { getDirents, getDirent } = require('internal/fs/utils');
const assert = require('assert');
const { internalBinding } = require('internal/test/binding');
const { UV_DIRENT_UNKNOWN } = internalBinding('constants').fs;
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const filename = 'foo';
{
// setup
tmpdir.refresh();
fs.writeFileSync(tmpdir.resolve(filename), '');
}
// getDirents
{
// string + string
getDirents(
tmpdir.path,
[[filename], [UV_DIRENT_UNKNOWN]],
common.mustCall((err, names) => {
assert.strictEqual(err, null);
assert.strictEqual(names.length, 1);
},
));
}
{
// string + Buffer
getDirents(
tmpdir.path,
[[Buffer.from(filename)], [UV_DIRENT_UNKNOWN]],
common.mustCall((err, names) => {
assert.strictEqual(err, null);
assert.strictEqual(names.length, 1);
},
));
}
{
// Buffer + Buffer
getDirents(
Buffer.from(tmpdir.path),
[[Buffer.from(filename)], [UV_DIRENT_UNKNOWN]],
common.mustCall((err, names) => {
assert.strictEqual(err, null);
assert.strictEqual(names.length, 1);
},
));
}
{
// wrong combination
getDirents(
42,
[[Buffer.from(filename)], [UV_DIRENT_UNKNOWN]],
common.mustCall((err) => {
assert.strictEqual(
err.message,
[
'The "path" argument must be of type string or an ' +
'instance of Buffer. Received type number (42)',
].join(''));
},
));
}
// getDirent
{
// string + string
getDirent(
tmpdir.path,
filename,
UV_DIRENT_UNKNOWN,
common.mustCall((err, dirent) => {
assert.strictEqual(err, null);
assert.strictEqual(dirent.name, filename);
assert.strictEqual(dirent.parentPath, tmpdir.path);
},
));
}
{
// Reassigning `.path` property should not trigger a warning
const dirent = getDirent(
tmpdir.path,
filename,
UV_DIRENT_UNKNOWN,
);
assert.strictEqual(dirent.name, filename);
dirent.path = 'some other value';
assert.strictEqual(dirent.parentPath, tmpdir.path);
assert.strictEqual(dirent.path, 'some other value');
}
{
// string + Buffer
const filenameBuffer = Buffer.from(filename);
getDirent(
tmpdir.path,
filenameBuffer,
UV_DIRENT_UNKNOWN,
common.mustCall((err, dirent) => {
assert.strictEqual(err, null);
assert.strictEqual(dirent.name, filenameBuffer);
assert.strictEqual(dirent.parentPath, tmpdir.path);
},
));
}
{
// Buffer + Buffer
const filenameBuffer = Buffer.from(filename);
const dirnameBuffer = Buffer.from(tmpdir.path);
getDirent(
dirnameBuffer,
filenameBuffer,
UV_DIRENT_UNKNOWN,
common.mustCall((err, dirent) => {
assert.strictEqual(err, null);
assert.strictEqual(dirent.name, filenameBuffer);
assert.deepStrictEqual(dirent.parentPath, dirnameBuffer);
},
));
}
{
// wrong combination
getDirent(
42,
Buffer.from(filename),
UV_DIRENT_UNKNOWN,
common.mustCall((err) => {
assert.strictEqual(
err.message,
[
'The "path" argument must be of type string or an ' +
'instance of Buffer. Received type number (42)',
].join(''));
},
));
}

View File

@@ -0,0 +1,211 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const util = require('util');
const fs = require('fs');
const url = require('url');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const lpath = `${tmpdir.path}/symlink`;
fs.symlinkSync('unoent-entry', lpath);
function stat_resource(resource, statSync = fs.statSync) {
if (typeof resource === 'string') {
return statSync(resource);
}
const stats = fs.fstatSync(resource);
// Ensure mtime has been written to disk
// except for directories on AIX where it cannot be synced
if ((common.isAIX || common.isIBMi) && stats.isDirectory())
return stats;
fs.fsyncSync(resource);
return fs.fstatSync(resource);
}
function check_mtime(resource, mtime, statSync) {
mtime = fs._toUnixTimestamp(mtime);
const stats = stat_resource(resource, statSync);
const real_mtime = fs._toUnixTimestamp(stats.mtime);
return mtime - real_mtime;
}
function expect_errno(syscall, resource, err, errno) {
assert(
err && (err.code === errno || err.code === 'ENOSYS'),
`FAILED: expect_errno ${util.inspect(arguments)}`
);
}
function expect_ok(syscall, resource, err, atime, mtime, statSync) {
const mtime_diff = check_mtime(resource, mtime, statSync);
assert(
// Check up to single-second precision.
// Sub-second precision is OS and fs dependant.
!err && (mtime_diff < 2) || err && err.code === 'ENOSYS',
`FAILED: expect_ok ${util.inspect(arguments)}
check_mtime: ${mtime_diff}`
);
}
const stats = fs.statSync(tmpdir.path);
const asPath = (path) => path;
const asUrl = (path) => url.pathToFileURL(path);
const cases = [
[asPath, new Date('1982-09-10 13:37')],
[asPath, new Date()],
[asPath, 123456.789],
[asPath, stats.mtime],
[asPath, '123456', -1],
[asPath, new Date('2017-04-08T17:59:38.008Z')],
[asUrl, new Date()],
];
runTests(cases.values());
function runTests(iter) {
const { value, done } = iter.next();
if (done) return;
// Support easy setting same or different atime / mtime values.
const [pathType, atime, mtime = atime] = value;
let fd;
//
// test async code paths
//
fs.utimes(pathType(tmpdir.path), atime, mtime, common.mustCall((err) => {
expect_ok('utimes', tmpdir.path, err, atime, mtime);
fs.lutimes(pathType(lpath), atime, mtime, common.mustCall((err) => {
expect_ok('lutimes', lpath, err, atime, mtime, fs.lstatSync);
fs.utimes(pathType('foobarbaz'), atime, mtime, common.mustCall((err) => {
expect_errno('utimes', 'foobarbaz', err, 'ENOENT');
// don't close this fd
if (common.isWindows) {
fd = fs.openSync(tmpdir.path, 'r+');
} else {
fd = fs.openSync(tmpdir.path, 'r');
}
fs.futimes(fd, atime, mtime, common.mustCall((err) => {
expect_ok('futimes', fd, err, atime, mtime);
syncTests();
setImmediate(common.mustCall(runTests), iter);
}));
}));
}));
}));
//
// test synchronized code paths, these functions throw on failure
//
function syncTests() {
fs.utimesSync(pathType(tmpdir.path), atime, mtime);
expect_ok('utimesSync', tmpdir.path, undefined, atime, mtime);
fs.lutimesSync(pathType(lpath), atime, mtime);
expect_ok('lutimesSync', lpath, undefined, atime, mtime, fs.lstatSync);
// Some systems don't have futimes
// if there's an error, it should be ENOSYS
try {
fs.futimesSync(fd, atime, mtime);
expect_ok('futimesSync', fd, undefined, atime, mtime);
} catch (ex) {
expect_errno('futimesSync', fd, ex, 'ENOSYS');
}
let err;
try {
fs.utimesSync(pathType('foobarbaz'), atime, mtime);
} catch (ex) {
err = ex;
}
expect_errno('utimesSync', 'foobarbaz', err, 'ENOENT');
err = undefined;
}
}
const expectTypeError = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
};
// utimes-only error cases
{
assert.throws(
() => fs.utimes(0, new Date(), new Date(), common.mustNotCall()),
expectTypeError
);
assert.throws(
() => fs.utimesSync(0, new Date(), new Date()),
expectTypeError
);
}
// shared error cases
[false, {}, [], null, undefined].forEach((i) => {
assert.throws(
() => fs.utimes(i, new Date(), new Date(), common.mustNotCall()),
expectTypeError
);
assert.throws(
() => fs.utimesSync(i, new Date(), new Date()),
expectTypeError
);
assert.throws(
() => fs.futimes(i, new Date(), new Date(), common.mustNotCall()),
expectTypeError
);
assert.throws(
() => fs.futimesSync(i, new Date(), new Date()),
expectTypeError
);
});
const expectRangeError = {
code: 'ERR_OUT_OF_RANGE',
name: 'RangeError',
message: 'The value of "fd" is out of range. ' +
'It must be >= 0 && <= 2147483647. Received -1'
};
// futimes-only error cases
{
assert.throws(
() => fs.futimes(-1, new Date(), new Date(), common.mustNotCall()),
expectRangeError
);
assert.throws(
() => fs.futimesSync(-1, new Date(), new Date()),
expectRangeError
);
}

View File

@@ -0,0 +1,72 @@
// Flags: --expose-internals
'use strict';
// This verifies the error thrown by fs.watch.
const common = require('../common');
if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');
const assert = require('assert');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const nonexistentFile = tmpdir.resolve('non-existent');
const { internalBinding } = require('internal/test/binding');
const {
UV_ENODEV,
UV_ENOENT
} = internalBinding('uv');
tmpdir.refresh();
{
const validateError = (err) => {
assert.strictEqual(err.path, nonexistentFile);
assert.strictEqual(err.filename, nonexistentFile);
assert.ok(err.syscall === 'watch' || err.syscall === 'stat');
if (err.code === 'ENOENT') {
assert.ok(err.message.startsWith('ENOENT: no such file or directory'));
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
} else { // AIX
assert.strictEqual(
err.message,
`ENODEV: no such device, watch '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENODEV);
assert.strictEqual(err.code, 'ENODEV');
}
return true;
};
assert.throws(
() => fs.watch(nonexistentFile, common.mustNotCall()),
validateError
);
}
{
if (common.isMacOS || common.isWindows) {
const file = tmpdir.resolve('file-to-watch');
fs.writeFileSync(file, 'test');
const watcher = fs.watch(file, common.mustNotCall());
const validateError = (err) => {
assert.strictEqual(err.path, nonexistentFile);
assert.strictEqual(err.filename, nonexistentFile);
assert.strictEqual(
err.message,
`ENOENT: no such file or directory, watch '${nonexistentFile}'`);
assert.strictEqual(err.errno, UV_ENOENT);
assert.strictEqual(err.code, 'ENOENT');
assert.strictEqual(err.syscall, 'watch');
fs.unlinkSync(file);
return true;
};
watcher.on('error', common.mustCall(validateError));
// Simulate the invocation from the binding
watcher._handle.onchange(UV_ENOENT, 'ENOENT', nonexistentFile);
}
}

View File

@@ -0,0 +1,47 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
// Make sure the deletion event gets reported in the following scenario:
// 1. Watch a file.
// 2. The initial stat() goes okay.
// 3. Something deletes the watched file.
// 4. The second stat() fails with ENOENT.
// The second stat() translates into the first 'change' event but a logic error
// stopped it from getting emitted.
// https://github.com/nodejs/node-v0.x-archive/issues/4027
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const filename = tmpdir.resolve('watched');
fs.writeFileSync(filename, 'quis custodiet ipsos custodes');
fs.watchFile(filename, { interval: 50 }, common.mustCall(function(curr, prev) {
fs.unwatchFile(filename);
}));
fs.unlinkSync(filename);

View File

@@ -40,9 +40,8 @@ const relativePath = path.join(file, path.basename(subfolderPath), childrenFile)
const watcher = fs.watch(testDirectory, { recursive: true });
let watcherClosed = false;
watcher.on('change', function(event, filename) {
assert.strictEqual(event, 'rename');
if (filename === relativePath) {
assert.strictEqual(event, 'rename');
watcher.close();
watcherClosed = true;
}

View File

@@ -0,0 +1,53 @@
'use strict';
const common = require('../common');
if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');
// fs-watch on folders have limited capability in AIX.
// The testcase makes use of folder watching, and causes
// hang. This behavior is documented. Skip this for AIX.
if (common.isAIX)
common.skip('folder watch capability is limited in AIX.');
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const tmpdir = require('../common/tmpdir');
const testDir = tmpdir.path;
tmpdir.refresh();
// Add a file to newly created folder to already watching folder
const rootDirectory = fs.mkdtempSync(testDir + path.sep);
const testDirectory = path.join(rootDirectory, 'test-3');
fs.mkdirSync(testDirectory);
const filePath = path.join(testDirectory, 'folder-3');
const childrenFile = 'file-4.txt';
const childrenAbsolutePath = path.join(filePath, childrenFile);
const childrenRelativePath = path.join(path.basename(filePath), childrenFile);
let watcherClosed = false;
const watcher = fs.watch(testDirectory, { recursive: true });
watcher.on('change', function(event, filename) {
if (filename === childrenRelativePath) {
assert.strictEqual(event, 'rename');
watcher.close();
watcherClosed = true;
}
});
// Do the write with a delay to ensure that the OS is ready to notify us.
setTimeout(() => {
fs.mkdirSync(filePath);
fs.writeFileSync(childrenAbsolutePath, 'world');
}, common.platformTimeout(200));
process.once('exit', function() {
assert(watcherClosed, 'watcher Object was not closed');
});

View File

@@ -0,0 +1,94 @@
'use strict';
const common = require('../common');
if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');
// fs-watch on folders have limited capability in AIX.
// The testcase makes use of folder watching, and causes
// hang. This behavior is documented. Skip this for AIX.
if (common.isAIX)
common.skip('folder watch capability is limited in AIX.');
const assert = require('assert');
const path = require('path');
const fs = require('fs/promises');
const fsSync = require('fs');
const tmpdir = require('../common/tmpdir');
const testDir = tmpdir.path;
tmpdir.refresh();
(async function run() {
// Add a file to already watching folder
const testsubdir = await fs.mkdtemp(testDir + path.sep);
const file = '1.txt';
const filePath = path.join(testsubdir, file);
const watcher = fs.watch(testsubdir, { recursive: true });
let interval;
process.on('exit', function() {
assert.ok(interval === null, 'watcher Object was not closed');
});
process.nextTick(common.mustCall(() => {
interval = setInterval(() => {
fsSync.writeFileSync(filePath, 'world');
}, 500);
}));
for await (const payload of watcher) {
const { eventType, filename } = payload;
assert.ok(eventType === 'change' || eventType === 'rename');
if (filename === file) {
break;
}
}
clearInterval(interval);
interval = null;
})().then(common.mustCall());
(async function() {
// Test that aborted AbortSignal are reported.
const testsubdir = await fs.mkdtemp(testDir + path.sep);
const error = new Error();
const watcher = fs.watch(testsubdir, { recursive: true, signal: AbortSignal.abort(error) });
await assert.rejects(async () => {
// eslint-disable-next-line no-unused-vars
for await (const _ of watcher);
}, { code: 'ABORT_ERR', cause: error });
})().then(common.mustCall());
(async function() {
// Test that with AbortController.
const testsubdir = await fs.mkdtemp(testDir + path.sep);
const file = '2.txt';
const filePath = path.join(testsubdir, file);
const error = new Error();
const ac = new AbortController();
const watcher = fs.watch(testsubdir, { recursive: true, signal: ac.signal });
let interval;
process.on('exit', function() {
assert.ok(interval === null, 'watcher Object was not closed');
});
process.nextTick(common.mustCall(() => {
interval = setInterval(() => {
fsSync.writeFileSync(filePath, 'world');
}, 50);
ac.abort(error);
}));
await assert.rejects(async () => {
for await (const { eventType } of watcher) {
assert.ok(eventType === 'change' || eventType === 'rename');
}
}, { code: 'ABORT_ERR', cause: error });
clearInterval(interval);
interval = null;
})().then(common.mustCall());

Some files were not shown because too many files have changed in this diff Show More