1'use strict'; 2 3const common = require('../common'); 4const assert = require('assert'); 5const tmpdir = require('../common/tmpdir'); 6const fixtures = require('../common/fixtures'); 7const path = require('path'); 8const fs = require('fs'); 9const fsPromises = fs.promises; 10const { 11 access, 12 chmod, 13 chown, 14 copyFile, 15 lchown, 16 link, 17 lchmod, 18 lstat, 19 lutimes, 20 mkdir, 21 mkdtemp, 22 open, 23 readFile, 24 readdir, 25 readlink, 26 realpath, 27 rename, 28 rmdir, 29 stat, 30 statfs, 31 symlink, 32 truncate, 33 unlink, 34 utimes, 35 writeFile 36} = fsPromises; 37 38const tmpDir = tmpdir.path; 39 40let dirc = 0; 41function nextdir() { 42 return `test${++dirc}`; 43} 44 45// fs.promises should be enumerable. 46assert.strictEqual( 47 Object.prototype.propertyIsEnumerable.call(fs, 'promises'), 48 true 49); 50 51{ 52 access(__filename, 0) 53 .then(common.mustCall()); 54 55 assert.rejects( 56 access('this file does not exist', 0), 57 { 58 code: 'ENOENT', 59 name: 'Error', 60 message: /^ENOENT: no such file or directory, access/ 61 } 62 ); 63 64 assert.rejects( 65 access(__filename, 8), 66 { 67 code: 'ERR_OUT_OF_RANGE', 68 } 69 ); 70 71 assert.rejects( 72 access(__filename, { [Symbol.toPrimitive]() { return 5; } }), 73 { 74 code: 'ERR_INVALID_ARG_TYPE', 75 } 76 ); 77} 78 79function verifyStatObject(stat) { 80 assert.strictEqual(typeof stat, 'object'); 81 assert.strictEqual(typeof stat.dev, 'number'); 82 assert.strictEqual(typeof stat.mode, 'number'); 83} 84 85function verifyStatFsObject(stat, isBigint = false) { 86 const valueType = isBigint ? 'bigint' : 'number'; 87 88 assert.strictEqual(typeof stat, 'object'); 89 assert.strictEqual(typeof stat.type, valueType); 90 assert.strictEqual(typeof stat.bsize, valueType); 91 assert.strictEqual(typeof stat.blocks, valueType); 92 assert.strictEqual(typeof stat.bfree, valueType); 93 assert.strictEqual(typeof stat.bavail, valueType); 94 assert.strictEqual(typeof stat.files, valueType); 95 assert.strictEqual(typeof stat.ffree, valueType); 96} 97 98async function getHandle(dest) { 99 await copyFile(fixtures.path('baz.js'), dest); 100 await access(dest); 101 102 return open(dest, 'r+'); 103} 104 105async function executeOnHandle(dest, func) { 106 let handle; 107 try { 108 handle = await getHandle(dest); 109 await func(handle); 110 } finally { 111 if (handle) { 112 await handle.close(); 113 } 114 } 115} 116 117{ 118 async function doTest() { 119 tmpdir.refresh(); 120 121 const dest = path.resolve(tmpDir, 'baz.js'); 122 123 // handle is object 124 { 125 await executeOnHandle(dest, async (handle) => { 126 assert.strictEqual(typeof handle, 'object'); 127 }); 128 } 129 130 // file stats 131 { 132 await executeOnHandle(dest, async (handle) => { 133 let stats = await handle.stat(); 134 verifyStatObject(stats); 135 assert.strictEqual(stats.size, 35); 136 137 await handle.truncate(1); 138 139 stats = await handle.stat(); 140 verifyStatObject(stats); 141 assert.strictEqual(stats.size, 1); 142 143 stats = await stat(dest); 144 verifyStatObject(stats); 145 146 stats = await handle.stat(); 147 verifyStatObject(stats); 148 149 await handle.datasync(); 150 await handle.sync(); 151 }); 152 } 153 154 // File system stats 155 { 156 const statFs = await statfs(dest); 157 verifyStatFsObject(statFs); 158 } 159 160 // File system stats bigint 161 { 162 const statFs = await statfs(dest, { bigint: true }); 163 verifyStatFsObject(statFs, true); 164 } 165 166 // Test fs.read promises when length to read is zero bytes 167 { 168 const dest = path.resolve(tmpDir, 'test1.js'); 169 await executeOnHandle(dest, async (handle) => { 170 const buf = Buffer.from('DAWGS WIN'); 171 const bufLen = buf.length; 172 await handle.write(buf); 173 const ret = await handle.read(Buffer.alloc(bufLen), 0, 0, 0); 174 assert.strictEqual(ret.bytesRead, 0); 175 176 await unlink(dest); 177 }); 178 } 179 180 // Use fallback buffer allocation when input not buffer 181 { 182 await executeOnHandle(dest, async (handle) => { 183 const ret = await handle.read(0, 0, 0, 0); 184 assert.strictEqual(ret.buffer.length, 16384); 185 }); 186 } 187 188 // Bytes written to file match buffer 189 { 190 await executeOnHandle(dest, async (handle) => { 191 const buf = Buffer.from('hello fsPromises'); 192 const bufLen = buf.length; 193 await handle.write(buf); 194 const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0); 195 assert.strictEqual(ret.bytesRead, bufLen); 196 assert.deepStrictEqual(ret.buffer, buf); 197 }); 198 } 199 200 // Truncate file to specified length 201 { 202 await executeOnHandle(dest, async (handle) => { 203 const buf = Buffer.from('hello FileHandle'); 204 const bufLen = buf.length; 205 await handle.write(buf, 0, bufLen, 0); 206 const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0); 207 assert.strictEqual(ret.bytesRead, bufLen); 208 assert.deepStrictEqual(ret.buffer, buf); 209 await truncate(dest, 5); 210 assert.strictEqual((await readFile(dest)).toString(), 'hello'); 211 }); 212 } 213 214 // Invalid change of ownership 215 { 216 await executeOnHandle(dest, async (handle) => { 217 await chmod(dest, 0o666); 218 await handle.chmod(0o666); 219 220 await chmod(dest, (0o10777)); 221 await handle.chmod(0o10777); 222 223 if (!common.isWindows) { 224 await chown(dest, process.getuid(), process.getgid()); 225 await handle.chown(process.getuid(), process.getgid()); 226 } 227 228 await assert.rejects( 229 async () => { 230 await chown(dest, 1, -2); 231 }, 232 { 233 code: 'ERR_OUT_OF_RANGE', 234 name: 'RangeError', 235 message: 'The value of "gid" is out of range. ' + 236 'It must be >= -1 && <= 4294967295. Received -2' 237 }); 238 239 await assert.rejects( 240 async () => { 241 await handle.chown(1, -2); 242 }, 243 { 244 code: 'ERR_OUT_OF_RANGE', 245 name: 'RangeError', 246 message: 'The value of "gid" is out of range. ' + 247 'It must be >= -1 && <= 4294967295. Received -2' 248 }); 249 }); 250 } 251 252 // Set modification times 253 { 254 await executeOnHandle(dest, async (handle) => { 255 256 await utimes(dest, new Date(), new Date()); 257 258 try { 259 await handle.utimes(new Date(), new Date()); 260 } catch (err) { 261 // Some systems do not have futimes. If there is an error, 262 // expect it to be ENOSYS 263 common.expectsError({ 264 code: 'ENOSYS', 265 name: 'Error' 266 })(err); 267 } 268 }); 269 } 270 271 // Set modification times with lutimes 272 { 273 const a_time = new Date(); 274 a_time.setMinutes(a_time.getMinutes() - 1); 275 const m_time = new Date(); 276 m_time.setHours(m_time.getHours() - 1); 277 await lutimes(dest, a_time, m_time); 278 const stats = await stat(dest); 279 280 assert.strictEqual(a_time.toString(), stats.atime.toString()); 281 assert.strictEqual(m_time.toString(), stats.mtime.toString()); 282 } 283 284 // create symlink 285 { 286 const newPath = path.resolve(tmpDir, 'baz2.js'); 287 await rename(dest, newPath); 288 let stats = await stat(newPath); 289 verifyStatObject(stats); 290 291 if (common.canCreateSymLink()) { 292 const newLink = path.resolve(tmpDir, 'baz3.js'); 293 await symlink(newPath, newLink); 294 if (!common.isWindows) { 295 await lchown(newLink, process.getuid(), process.getgid()); 296 } 297 stats = await lstat(newLink); 298 verifyStatObject(stats); 299 300 assert.strictEqual(newPath.toLowerCase(), 301 (await realpath(newLink)).toLowerCase()); 302 assert.strictEqual(newPath.toLowerCase(), 303 (await readlink(newLink)).toLowerCase()); 304 305 const newMode = 0o666; 306 if (common.isOSX) { 307 // `lchmod` is only available on macOS. 308 await lchmod(newLink, newMode); 309 stats = await lstat(newLink); 310 assert.strictEqual(stats.mode & 0o777, newMode); 311 } else { 312 await Promise.all([ 313 assert.rejects( 314 lchmod(newLink, newMode), 315 common.expectsError({ 316 code: 'ERR_METHOD_NOT_IMPLEMENTED', 317 name: 'Error', 318 message: 'The lchmod() method is not implemented' 319 }) 320 ), 321 ]); 322 } 323 324 await unlink(newLink); 325 } 326 } 327 328 // specify symlink type 329 { 330 const dir = path.join(tmpDir, nextdir()); 331 await symlink(tmpDir, dir, 'dir'); 332 const stats = await lstat(dir); 333 assert.strictEqual(stats.isSymbolicLink(), true); 334 await unlink(dir); 335 } 336 337 // create hard link 338 { 339 const newPath = path.resolve(tmpDir, 'baz2.js'); 340 const newLink = path.resolve(tmpDir, 'baz4.js'); 341 await link(newPath, newLink); 342 343 await unlink(newLink); 344 } 345 346 // Testing readdir lists both files and directories 347 { 348 const newDir = path.resolve(tmpDir, 'dir'); 349 const newFile = path.resolve(tmpDir, 'foo.js'); 350 351 await mkdir(newDir); 352 await writeFile(newFile, 'DAWGS WIN!', 'utf8'); 353 354 const stats = await stat(newDir); 355 assert(stats.isDirectory()); 356 const list = await readdir(tmpDir); 357 assert.notStrictEqual(list.indexOf('dir'), -1); 358 assert.notStrictEqual(list.indexOf('foo.js'), -1); 359 await rmdir(newDir); 360 await unlink(newFile); 361 } 362 363 // Use fallback encoding when input is null 364 { 365 const newFile = path.resolve(tmpDir, 'dogs_running.js'); 366 await writeFile(newFile, 'dogs running', { encoding: null }); 367 const fileExists = fs.existsSync(newFile); 368 assert.strictEqual(fileExists, true); 369 } 370 371 // `mkdir` when options is number. 372 { 373 const dir = path.join(tmpDir, nextdir()); 374 await mkdir(dir, 777); 375 const stats = await stat(dir); 376 assert(stats.isDirectory()); 377 } 378 379 // `mkdir` when options is string. 380 { 381 const dir = path.join(tmpDir, nextdir()); 382 await mkdir(dir, '777'); 383 const stats = await stat(dir); 384 assert(stats.isDirectory()); 385 } 386 387 // `mkdirp` when folder does not yet exist. 388 { 389 const dir = path.join(tmpDir, nextdir(), nextdir()); 390 await mkdir(dir, { recursive: true }); 391 const stats = await stat(dir); 392 assert(stats.isDirectory()); 393 } 394 395 // `mkdirp` when path is a file. 396 { 397 const dir = path.join(tmpDir, nextdir(), nextdir()); 398 await mkdir(path.dirname(dir)); 399 await writeFile(dir, ''); 400 assert.rejects( 401 mkdir(dir, { recursive: true }), 402 { 403 code: 'EEXIST', 404 message: /EEXIST: .*mkdir/, 405 name: 'Error', 406 syscall: 'mkdir', 407 } 408 ); 409 } 410 411 // `mkdirp` when part of the path is a file. 412 { 413 const file = path.join(tmpDir, nextdir(), nextdir()); 414 const dir = path.join(file, nextdir(), nextdir()); 415 await mkdir(path.dirname(file)); 416 await writeFile(file, ''); 417 assert.rejects( 418 mkdir(dir, { recursive: true }), 419 { 420 code: 'ENOTDIR', 421 message: /ENOTDIR: .*mkdir/, 422 name: 'Error', 423 syscall: 'mkdir', 424 } 425 ); 426 } 427 428 // mkdirp ./ 429 { 430 const dir = path.resolve(tmpDir, `${nextdir()}/./${nextdir()}`); 431 await mkdir(dir, { recursive: true }); 432 const stats = await stat(dir); 433 assert(stats.isDirectory()); 434 } 435 436 // mkdirp ../ 437 { 438 const dir = path.resolve(tmpDir, `${nextdir()}/../${nextdir()}`); 439 await mkdir(dir, { recursive: true }); 440 const stats = await stat(dir); 441 assert(stats.isDirectory()); 442 } 443 444 // fs.mkdirp requires the recursive option to be of type boolean. 445 // Everything else generates an error. 446 { 447 const dir = path.join(tmpDir, nextdir(), nextdir()); 448 ['', 1, {}, [], null, Symbol('test'), () => {}].forEach((recursive) => { 449 assert.rejects( 450 // mkdir() expects to get a boolean value for options.recursive. 451 async () => mkdir(dir, { recursive }), 452 { 453 code: 'ERR_INVALID_ARG_TYPE', 454 name: 'TypeError' 455 } 456 ); 457 }); 458 } 459 460 // `mkdtemp` with invalid numeric prefix 461 { 462 await mkdtemp(path.resolve(tmpDir, 'FOO')); 463 assert.rejects( 464 // mkdtemp() expects to get a string prefix. 465 async () => mkdtemp(1), 466 { 467 code: 'ERR_INVALID_ARG_TYPE', 468 name: 'TypeError' 469 } 470 ); 471 } 472 473 // Regression test for https://github.com/nodejs/node/issues/38168 474 { 475 await executeOnHandle(dest, async (handle) => { 476 await assert.rejects( 477 async () => handle.write('abc', 0, 'hex'), 478 { 479 code: 'ERR_INVALID_ARG_VALUE', 480 message: /'encoding' is invalid for data of length 3/ 481 } 482 ); 483 484 const ret = await handle.write('abcd', 0, 'hex'); 485 assert.strictEqual(ret.bytesWritten, 2); 486 }); 487 } 488 489 // Test prototype methods calling with contexts other than FileHandle 490 { 491 await executeOnHandle(dest, async (handle) => { 492 await assert.rejects(() => handle.stat.call({}), { 493 code: 'ERR_INTERNAL_ASSERTION', 494 message: /handle must be an instance of FileHandle/ 495 }); 496 }); 497 } 498 } 499 500 doTest().then(common.mustCall()); 501} 502