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