'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()); }