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