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