• 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  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