• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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