Skip to content
File

Blob: src/workerd/api/node/tests/fs-nodejs-test.js

javascript1279 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4import {
5 ifError,
6 ok,
7 rejects,
8 deepStrictEqual,
9 strictEqual,
10 notStrictEqual,
11 throws,
12 doesNotThrow,
13} from 'node:assert';
14import zlib from 'node:zlib';
15import { Readable } from 'node:stream';
16 
17import {
18 existsSync,
19 statSync,
20 openSync,
21 closeSync,
22 fstatSync,
23 ftruncateSync,
24 truncateSync,
25 writeSync,
26 writevSync,
27 readSync,
28 readvSync,
29 readFileSync,
30 writeFileSync,
31 appendFileSync,
32 copyFileSync,
33 renameSync,
34 close,
35 fstat,
36 ftruncate,
37 truncate,
38 unlinkSync,
39 write,
40 writev,
41 writeFile,
42 appendFile,
43 read,
44 readdir,
45 readlink,
46 realpath,
47 mkdtemp,
48 ReadStream,
49 WriteStream,
50 readdirSync,
51 readlinkSync,
52 readv,
53 readFile,
54 rename,
55 copyFile,
56 realpathSync,
57 mkdtempSync,
58 constants,
59 promises,
60 createWriteStream,
61} from 'node:fs';
62 
63import { join } from 'node:path';
64 
65const { COPYFILE_EXCL } = constants;
66 
67const kErrInvalidArgType = { code: 'ERR_INVALID_ARG_TYPE' };
68const kErrInvalidArgValue = { code: 'ERR_INVALID_ARG_VALUE' };
69const kErrEBadf = { code: 'EBADF' };
70const kErrEExist = { code: 'EEXIST' };
71const kErrOutOfRange = { code: 'ERR_OUT_OF_RANGE' };
72 
73export const openCloseTest = {
74 async test() {
75 throws(() => fstatSync(123), kErrEBadf);
76 throws(() => fstatSync(123, { bigint: 'yes' }), kErrInvalidArgType);
77 throws(() => fstatSync('abc'), kErrInvalidArgType);
78 
79 // Test that all the mode combinations work
80 const modes = [
81 'r',
82 'r+',
83 'w',
84 'w+',
85 'a',
86 'a+',
87 'rs',
88 'rs+',
89 'wx',
90 'wx+',
91 'ax',
92 'ax+',
93 ];
94 for (const mode of modes) {
95 // Open the file
96 const fd = openSync('/tmp/test.txt', mode);
97 ok(existsSync('/tmp/test.txt'));
98 const stat = fstatSync(fd, { bigint: true });
99 ok(stat);
100 unlinkSync('/tmp/test.txt');
101 }
102 
103 ok(!existsSync('/tmp/test.txt'));
104 const fd = openSync('/tmp/test.txt', 'w+');
105 ok(existsSync('/tmp/test.txt'));
106 
107 // Check the exclusive option fails when the file already exists
108 throws(() => openSync('/tmp/test.txt', 'wx'), kErrEExist);
109 throws(() => openSync('/tmp/test.txt', 'wx+'), kErrEExist);
110 throws(() => openSync('/tmp/test.txt', 'ax'), kErrEExist);
111 throws(() => openSync('/tmp/test.txt', 'ax+'), kErrEExist);
112 throws(() => openSync(Buffer.from('/tmp/test.txt'), 'wx'), kErrEExist);
113 throws(() => openSync(Buffer.from('/tmp/test.txt'), 'wx+'), kErrEExist);
114 throws(() => openSync(Buffer.from('/tmp/test.txt'), 'ax'), kErrEExist);
115 throws(() => openSync(Buffer.from('/tmp/test.txt'), 'ax+'), kErrEExist);
116 
117 const stat = fstatSync(fd, { bigint: true });
118 ok(stat);
119 ok(stat.isFile());
120 ok(!stat.isDirectory());
121 strictEqual(stat.size, 0n);
122 
123 {
124 const { promise, resolve, reject } = Promise.withResolvers();
125 fstat(fd, (err, stat) => {
126 try {
127 ifError(err);
128 ok(stat);
129 ok(stat.isFile());
130 ok(!stat.isDirectory());
131 resolve();
132 } catch (err) {
133 reject(err);
134 }
135 });
136 await promise;
137 }
138 
139 // Close the file
140 closeSync(fd);
141 // Can close multiple times
142 closeSync(fd);
143 // Can close non-existent file descriptors
144 closeSync(123);
145 
146 ['a', {}, null].forEach((i) => {
147 throws(() => closeSync(i), {
148 code: 'ERR_INVALID_ARG_TYPE',
149 });
150 throws(() => close(i), {
151 code: 'ERR_INVALID_ARG_TYPE',
152 });
153 throws(() => close(0, i), {
154 code: 'ERR_INVALID_ARG_TYPE',
155 });
156 });
157 
158 {
159 const { promise, resolve, reject } = Promise.withResolvers();
160 close(fd, (err) => {
161 if (err) return reject(err);
162 resolve();
163 });
164 await promise;
165 }
166 },
167};
168 
169export const ftruncateTest = {
170 async test() {
171 const fd = openSync('/tmp/test.txt', 'w+');
172 ok(existsSync('/tmp/test.txt'));
173 
174 throws(() => ftruncateSync('hello'), kErrInvalidArgType);
175 throws(() => ftruncateSync(123), kErrEBadf);
176 throws(() => ftruncateSync(123, 10), kErrEBadf);
177 throws(() => ftruncateSync(fd, 'hello'), kErrInvalidArgType);
178 
179 const stat = fstatSync(fd, { bigint: true });
180 strictEqual(stat.size, 0n);
181 
182 // Truncate to 10 bytes
183 ftruncateSync(fd, 10);
184 const stat2 = fstatSync(fd, { bigint: true });
185 strictEqual(stat2.size, 10n);
186 
187 // Truncate to 0 bytes
188 ftruncateSync(fd, 0);
189 const stat3 = fstatSync(fd, { bigint: true });
190 strictEqual(stat3.size, 0n);
191 
192 // Truncate to a negative size throws an error
193 throws(() => ftruncateSync(fd, -1), kErrOutOfRange);
194 
195 {
196 const { promise, resolve, reject } = Promise.withResolvers();
197 ftruncate(fd, 5, (err) => {
198 if (err) return reject(err);
199 const stat4 = fstatSync(fd, { bigint: true });
200 strictEqual(stat4.size, 5n);
201 resolve();
202 });
203 await promise;
204 }
205 
206 {
207 throws(() => ftruncate(fd, -1, () => {}), {
208 code: 'ERR_OUT_OF_RANGE',
209 });
210 }
211 
212 {
213 throws(() => ftruncateSync(fd, 0xffffffff), {
214 message: /File size limit exceeded/,
215 });
216 throws(() => ftruncateSync(fd, 0x08000000 + 1), {
217 message: /File size limit exceeded/,
218 });
219 // 0x08000000 is the maximum allowed file size.
220 ftruncateSync(fd, 0x08000000);
221 }
222 
223 closeSync(fd);
224 },
225};
226 
227export const truncateTest = {
228 async test() {
229 throws(() => truncateSync(123), kErrInvalidArgType);
230 throws(() => truncateSync('/', 'hello'), kErrInvalidArgType);
231 
232 ok(!existsSync('/tmp/test.txt'));
233 throws(() => truncateSync('/tmp/test.txt', 10), {
234 code: 'ENOENT',
235 path: '/tmp/test.txt',
236 });
237 
238 // Create the file
239 writeFileSync('/tmp/test.txt', 'Hello World');
240 ok(existsSync('/tmp/test.txt'));
241 let stat = statSync('/tmp/test.txt');
242 strictEqual(stat.size, 11);
243 
244 // Truncate to 10 bytes
245 truncateSync('/tmp/test.txt', 10);
246 stat = statSync('/tmp/test.txt');
247 strictEqual(stat.size, 10);
248 
249 // Truncate to 20 bytes
250 truncateSync('/tmp/test.txt', 20);
251 stat = statSync('/tmp/test.txt');
252 strictEqual(stat.size, 20);
253 
254 // Truncate to 0 bytes
255 truncateSync('/tmp/test.txt', 0);
256 stat = statSync('/tmp/test.txt');
257 strictEqual(stat.size, 0);
258 
259 // Truncate to a negative size throws an error
260 throws(() => truncateSync('/tmp/test.txt', -1), kErrOutOfRange);
261 
262 {
263 const { promise, resolve, reject } = Promise.withResolvers();
264 truncate('/tmp/test.txt', 5, (err) => {
265 if (err) return reject(err);
266 const stat2 = statSync('/tmp/test.txt');
267 strictEqual(stat2.size, 5);
268 resolve();
269 });
270 await promise;
271 }
272 
273 throws(() => truncate('/tmp/test.txt', -1), kErrOutOfRange);
274 },
275};
276 
277export const writeSyncTest = {
278 test() {
279 const fd = openSync('/tmp/test.txt', 'w+');
280 ok(existsSync('/tmp/test.txt'));
281 
282 const stat = fstatSync(fd, { bigint: true });
283 strictEqual(stat.size, 0n);
284 
285 throws(() => writeSync(''), kErrInvalidArgType);
286 throws(() => writeSync(fd, 'Hello World', ''), kErrInvalidArgType);
287 throws(
288 () => writeSync(fd, 'Hello World', { position: 'hello' }),
289 kErrInvalidArgType
290 );
291 throws(() => writeSync(123, 'Hello World'), kErrEBadf);
292 throws(() => writeSync(fd, Buffer.alloc(2), { offset: 5 }), {
293 code: 'ERR_BUFFER_OUT_OF_BOUNDS',
294 });
295 throws(() => writeSync(fd, Buffer.alloc(2), { length: 5 }), {
296 code: 'ERR_BUFFER_OUT_OF_BOUNDS',
297 });
298 throws(
299 () => writeSync(fd, Buffer.alloc(2), { offset: 'hello' }),
300 kErrInvalidArgType
301 );
302 
303 writeSync(fd, 'Hello World');
304 writeSync(fd, '!!!!');
305 const stat2 = fstatSync(fd, { bigint: true });
306 strictEqual(stat2.size, 15n);
307 
308 const dest = Buffer.alloc(Number(stat2.size));
309 
310 // When we don't specify a position, it reads from the current position,
311 // which currently is the end of the file... so we get nothing here.
312 strictEqual(readSync(fd, dest), 0);
313 
314 // But when we do specify a position, we can read from the beginning...
315 strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), dest.byteLength);
316 strictEqual(dest.toString(), 'Hello World!!!!');
317 
318 // Likewise, we can use an options object for the position
319 dest.fill(0);
320 strictEqual(dest.toString(), '\0'.repeat(dest.byteLength));
321 strictEqual(readSync(fd, dest, { position: 0 }), 15);
322 strictEqual(dest.toString(), 'Hello World!!!!');
323 
324 const dest2 = readFileSync('/tmp/test.txt');
325 const dest3 = readFileSync(fd);
326 const dest4 = readFileSync(fd, { encoding: 'utf8' });
327 strictEqual(dest2.toString(), 'Hello World!!!!');
328 strictEqual(dest3.toString(), 'Hello World!!!!');
329 strictEqual(dest4, 'Hello World!!!!');
330 
331 closeSync(fd);
332 },
333};
334 
335export const writeAsyncCallbackTest = {
336 async test() {
337 const fd = openSync('/tmp/test.txt', 'w+');
338 ok(existsSync('/tmp/test.txt'));
339 
340 const stat = fstatSync(fd, { bigint: true });
341 strictEqual(stat.size, 0n);
342 
343 function mustNotCall() {
344 throw new Error('This function must not be called');
345 }
346 
347 throws(() => write(fd, 'Hello World'), kErrInvalidArgType);
348 throws(() => write(fd, 'Hello World', '', mustNotCall), kErrInvalidArgType);
349 throws(
350 () => write(fd, 'Hello World', { position: 'hello' }, mustNotCall),
351 kErrInvalidArgType
352 );
353 throws(() => write(fd, Buffer.alloc(2), { offset: 5 }, mustNotCall), {
354 code: 'ERR_BUFFER_OUT_OF_BOUNDS',
355 });
356 throws(() => write(fd, Buffer.alloc(2), { length: 5 }, mustNotCall), {
357 code: 'ERR_BUFFER_OUT_OF_BOUNDS',
358 });
359 throws(
360 () => write(fd, Buffer.alloc(2), { offset: 'hello' }, mustNotCall),
361 kErrInvalidArgType
362 );
363 
364 await new Promise((resolve, reject) => {
365 write(fd, 'Hello World', (err, written) => {
366 if (err) return reject(err);
367 strictEqual(written, 11);
368 resolve();
369 });
370 });
371 
372 await new Promise((resolve, reject) => {
373 write(fd, '!!!!', (err, written) => {
374 if (err) return reject(err);
375 strictEqual(written, 4);
376 resolve();
377 });
378 });
379 
380 await rejects(
381 new Promise((resolve, reject) => {
382 write(123, 'Hello World', { position: 0 }, (err) => {
383 if (err) return reject(err);
384 resolve();
385 });
386 }),
387 kErrEBadf
388 );
389 
390 const stat2 = fstatSync(fd, { bigint: true });
391 strictEqual(stat2.size, 15n);
392 
393 const dest = Buffer.alloc(Number(stat2.size));
394 
395 // When we don't specify a position, it reads from the current position,
396 // which currently is the end of the file... so we get nothing here.
397 strictEqual(readSync(fd, dest), 0);
398 
399 // But when we do specify a position, we can read from the beginning...
400 strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), dest.byteLength);
401 strictEqual(dest.toString(), 'Hello World!!!!');
402 
403 closeSync(fd);
404 },
405};
406 
407export const writeSyncTest2 = {
408 test() {
409 const fd = openSync('/tmp/test.txt', 'w+');
410 ok(existsSync('/tmp/test.txt'));
411 
412 const stat = fstatSync(fd, { bigint: true });
413 strictEqual(stat.size, 0n);
414 
415 strictEqual(writeSync(fd, 'Hello World', 2n), 11);
416 
417 // Writing to a position beyond max uint32_t is not allowed.
418 throws(() => writeSync(fd, 'Hello World', 2n ** 32n), {
419 code: 'EINVAL',
420 });
421 throws(() => writeSync(fd, 'Hello World', 2 ** 32), {
422 code: 'ERR_OUT_OF_RANGE',
423 });
424 
425 strictEqual(writeSync(fd, 'aa', 0, 'ascii'), 2);
426 
427 const stat2 = fstatSync(fd);
428 strictEqual(stat2.size, 13);
429 
430 const dest = Buffer.alloc(stat2.size);
431 strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), 13);
432 strictEqual(dest.toString(), 'aaHello World');
433 
434 closeSync(fd);
435 },
436};
437 
438export const writeSyncTest3 = {
439 test() {
440 const fd = openSync('/tmp/test.txt', 'w+');
441 ok(existsSync('/tmp/test.txt'));
442 
443 const stat = fstatSync(fd, { bigint: true });
444 strictEqual(stat.size, 0n);
445 
446 writeSync(fd, Buffer.from('Hello World'));
447 const stat2 = fstatSync(fd, { bigint: true });
448 strictEqual(stat2.size, 11n);
449 
450 closeSync(fd);
451 },
452};
453 
454export const writeSyncTest4 = {
455 test() {
456 const fd = openSync('/tmp/test.txt', 'w+');
457 ok(existsSync('/tmp/test.txt'));
458 
459 const stat = fstatSync(fd, { bigint: true });
460 strictEqual(stat.size, 0n);
461 
462 // Writing a partial buffer works
463 writeSync(fd, Buffer.from('Hello World'), 1, 3, 1);
464 
465 // Specifying an offset or length beyond the buffer size is not allowed.
466 throws(() => writeSync(fd, Buffer.from('Hello World'), 100, 3), {
467 message: /outside of buffer bounds/,
468 });
469 // Specifying an offset or length beyond the buffer size is not allowed.
470 throws(() => writeSync(fd, Buffer.from('Hello World'), 0, 100), {
471 message: /outside of buffer bounds/,
472 });
473 
474 throws(() => writeSync(fd, Buffer.from('hello world'), 'a'), {
475 code: 'ERR_INVALID_ARG_TYPE',
476 });
477 
478 throws(() => writeSync(fd, Buffer.from('hello world'), 1n), {
479 code: 'ERR_INVALID_ARG_TYPE',
480 });
481 
482 throws(() => writeSync(fd, Buffer.from('hello world'), 0, 'a'), {
483 code: 'ERR_INVALID_ARG_TYPE',
484 });
485 
486 throws(() => writeSync(fd, Buffer.from('hello world'), 1, 1n), {
487 code: 'ERR_INVALID_ARG_TYPE',
488 });
489 
490 throws(() => writeSync(fd, 123), {
491 code: 'ERR_INVALID_ARG_TYPE',
492 });
493 
494 const stat2 = fstatSync(fd, { bigint: true });
495 strictEqual(stat2.size, 4n);
496 
497 closeSync(fd);
498 },
499};
500 
501export const writeSyncAppend = {
502 test() {
503 const fd = openSync('/tmp/test.txt', 'a');
504 ok(existsSync('/tmp/test.txt'));
505 
506 const stat = fstatSync(fd, { bigint: true });
507 strictEqual(stat.size, 0n);
508 
509 // In append mode, the position is ignored.
510 
511 writeSync(fd, 'Hello World', 1000);
512 const stat2 = fstatSync(fd, { bigint: true });
513 strictEqual(stat2.size, 11n);
514 
515 writeSync(fd, '!!!!', 2000);
516 const stat3 = fstatSync(fd, { bigint: true });
517 strictEqual(stat3.size, 15n);
518 
519 closeSync(fd);
520 },
521};
522 
523export const writevSyncTest = {
524 test() {
525 const fd = openSync('/tmp/test.txt', 'w+');
526 ok(existsSync('/tmp/test.txt'));
527 
528 const stat = fstatSync(fd, { bigint: true });
529 strictEqual(stat.size, 0n);
530 
531 writevSync(fd, [Buffer.from('Hello World'), Buffer.from('!!!!')]);
532 
533 throws(() => writevSync(fd, [1, 2]), {
534 code: /ERR_INVALID_ARG_TYPE/,
535 });
536 
537 throws(() => writevSync(100, [Buffer.from('')]), {
538 message: 'bad file descriptor',
539 code: 'EBADF',
540 });
541 
542 const stat2 = fstatSync(fd, { bigint: true });
543 strictEqual(stat2.size, 15n);
544 
545 const dest1 = Buffer.alloc(5);
546 const dest2 = Buffer.alloc(10);
547 const dest3 = Buffer.alloc(5);
548 let read = readvSync(fd, [dest1, dest2, dest3], 0);
549 strictEqual(read, 15);
550 let dest = Buffer.concat([dest1, dest2, dest3]);
551 strictEqual(dest.toString('utf8', 0, read), 'Hello World!!!!');
552 
553 dest1.fill(0);
554 dest2.fill(0);
555 dest3.fill(0);
556 read = readvSync(fd, [dest1, dest2, dest3], 1);
557 strictEqual(read, 14);
558 dest = Buffer.concat([dest1, dest2, dest3]);
559 strictEqual(dest.toString('utf8', 0, read), 'ello World!!!!');
560 
561 // Reading from a position beyond the end of the file returns nothing.
562 strictEqual(readvSync(fd, [dest1], 100), 0);
563 
564 closeSync(fd);
565 },
566};
567 
568export const writevAsyncCallbackTest = {
569 async test() {
570 const fd = openSync('/tmp/test.txt', 'w+');
571 ok(existsSync('/tmp/test.txt'));
572 
573 const stat = fstatSync(fd, { bigint: true });
574 strictEqual(stat.size, 0n);
575 
576 throws(() => writev('hello'), kErrInvalidArgType);
577 throws(() => writev(0, 123), kErrInvalidArgType);
578 throws(() => writev(fd, [Buffer.from('')], 123), kErrInvalidArgType);
579 
580 await new Promise((resolve, reject) => {
581 writev(
582 fd,
583 [Buffer.from('Hello World'), Buffer.from('!!!!')],
584 (err, written) => {
585 if (err) return reject(err);
586 strictEqual(written, 15);
587 resolve();
588 }
589 );
590 });
591 
592 throws(
593 () =>
594 writev(fd, [1, 2], (err) => {
595 if (err) throw err;
596 }),
597 {
598 code: /ERR_INVALID_ARG_TYPE/,
599 }
600 );
601 
602 await rejects(
603 new Promise((resolve, reject) => {
604 writev(100, [Buffer.from('')], (err) => {
605 if (err) return reject(err);
606 resolve();
607 });
608 }),
609 {
610 code: 'EBADF',
611 }
612 );
613 
614 const stat2 = fstatSync(fd, { bigint: true });
615 strictEqual(stat2.size, 15n);
616 
617 const dest1 = Buffer.alloc(5);
618 const dest2 = Buffer.alloc(10);
619 const dest3 = Buffer.alloc(5);
620 let read = readvSync(fd, [dest1, dest2, dest3], 0);
621 strictEqual(read, 15);
622 let dest = Buffer.concat([dest1, dest2, dest3]);
623 strictEqual(dest.toString('utf8', 0, read), 'Hello World!!!!');
624 
625 dest1.fill(0);
626 dest2.fill(0);
627 dest3.fill(0);
628 read = readvSync(fd, [dest1, dest2, dest3], 1);
629 strictEqual(read, 14);
630 dest = Buffer.concat([dest1, dest2, dest3]);
631 strictEqual(dest.toString('utf8', 0, read), 'ello World!!!!');
632 
633 // Reading from a position beyond the end of the file returns nothing.
634 strictEqual(readvSync(fd, [dest1], 100), 0);
635 
636 closeSync(fd);
637 },
638};
639 
640export const writeFileSyncTest = {
641 test() {
642 ok(!existsSync('/tmp/test.txt'));
643 strictEqual(writeFileSync('/tmp/test.txt', 'Hello World'), 11);
644 ok(existsSync('/tmp/test.txt'));
645 let stat = statSync('/tmp/test.txt');
646 strictEqual(stat.size, 11);
647 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World');
648 
649 strictEqual(appendFileSync('/tmp/test.txt', '!!!!'), 4);
650 stat = statSync('/tmp/test.txt');
651 strictEqual(stat.size, 15);
652 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World!!!!');
653 
654 // We can also use a file descriptor
655 const fd = openSync('/tmp/test.txt', 'a+');
656 writeFileSync(fd, '##');
657 strictEqual(readFileSync(fd).toString(), 'Hello World!!!!##');
658 closeSync(fd);
659 },
660};
661 
662export const appendFileSyncFlush = {
663 test() {
664 ok(!existsSync('/tmp/test.txt'));
665 
666 // The flush option really is not supported in any particular way in
667 // our implementation but let's verify it.
668 appendFileSync('/tmp/test.txt', 'hello world', { flush: true });
669 
670 ['no', {}, null, -1].forEach((i) => {
671 throws(
672 () => appendFileSync('/tmp/test.txt', 'hello world', { flush: 'no' }),
673 {
674 code: 'ERR_INVALID_ARG_TYPE',
675 }
676 );
677 });
678 
679 ok(existsSync('/tmp/test.txt'));
680 strictEqual(readFileSync('/tmp/test.txt').toString(), 'hello world');
681 },
682};
683 
684export const writeFileAsyncCallbackTest = {
685 async test() {
686 ok(!existsSync('/tmp/test.txt'));
687 
688 throws(() => writeFile({}), kErrInvalidArgType);
689 throws(() => writeFile('', 123), kErrInvalidArgType);
690 throws(() => writeFile('', '', 123), kErrInvalidArgType);
691 throws(() => writeFile('', '', {}), kErrInvalidArgType);
692 
693 await new Promise((resolve, reject) => {
694 writeFile('/tmp/test.txt', 'Hello World', (err) => {
695 if (err) return reject(err);
696 ok(existsSync('/tmp/test.txt'));
697 resolve();
698 });
699 });
700 
701 let stat = statSync('/tmp/test.txt');
702 strictEqual(stat.size, 11);
703 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World');
704 
705 await new Promise((resolve, reject) => {
706 appendFile('/tmp/test.txt', '!!!!', (err) => {
707 strictEqual(
708 readFileSync('/tmp/test.txt').toString(),
709 'Hello World!!!!'
710 );
711 if (err) return reject(err);
712 resolve();
713 });
714 });
715 
716 stat = statSync('/tmp/test.txt');
717 strictEqual(stat.size, 15);
718 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World!!!!');
719 
720 // We can also use a file descriptor
721 const fd = openSync('/tmp/test.txt', 'a+');
722 await new Promise((resolve, reject) => {
723 writeFile(fd, '##', 'utf8', (err) => {
724 if (err) return reject(err);
725 resolve();
726 });
727 });
728 strictEqual(readFileSync(fd).toString(), 'Hello World!!!!##');
729 closeSync(fd);
730 
731 // We can use the promise API as well.
732 await promises.appendFile('/tmp/test.txt', '!!!');
733 strictEqual(
734 readFileSync('/tmp/test.txt').toString(),
735 'Hello World!!!!##!!!'
736 );
737 },
738};
739 
740export const appendFileCases = {
741 async test() {
742 ok(!existsSync('/tmp/test.txt'));
743 // It accepts bufers
744 appendFileSync('/tmp/test.txt', Buffer.from('Hello World'));
745 ok(existsSync('/tmp/test.txt'));
746 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World');
747 
748 // With the callback API also
749 const { promise, resolve, reject } = Promise.withResolvers();
750 appendFile('/tmp/test.txt', Buffer.from('!!!!'), (err) => {
751 if (err) return reject(err);
752 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World!!!!');
753 resolve();
754 });
755 await promise;
756 
757 // And the promises API
758 await promises.appendFile('/tmp/test.txt', Buffer.from('!!!'));
759 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World!!!!!!!');
760 
761 // But invalid types throw errors
762 [123, {}, null, []].forEach((data) => {
763 throws(() => appendFileSync('/tmp/test.txt', data), {
764 code: 'ERR_INVALID_ARG_TYPE',
765 });
766 });
767 },
768};
769 
770export const readAsyncCallbackTest = {
771 async test() {
772 const fd = openSync('/tmp/test.txt', 'w+');
773 ok(existsSync('/tmp/test.txt'));
774 
775 throws(() => read('hello'), kErrInvalidArgType);
776 throws(() => read(0, 123), kErrInvalidArgType);
777 throws(() => read(0, Buffer.alloc(1), 123), kErrInvalidArgType);
778 throws(() => read(0, Buffer.alloc(1), {}), kErrInvalidArgType);
779 
780 const stat = fstatSync(fd, { bigint: true });
781 strictEqual(stat.size, 0n);
782 
783 writeSync(fd, 'Hello World');
784 writeSync(fd, '!!!!');
785 const stat2 = fstatSync(fd, { bigint: true });
786 strictEqual(stat2.size, 15n);
787 
788 const dest = Buffer.alloc(15);
789 let bytesRead = await new Promise((resolve, reject) => {
790 read(fd, dest, 0, dest.byteLength, 0, (err, bytesRead) => {
791 if (err) return reject(err);
792 resolve(bytesRead);
793 });
794 });
795 
796 strictEqual(bytesRead, 15);
797 strictEqual(dest.toString(), 'Hello World!!!!');
798 
799 closeSync(fd);
800 },
801};
802 
803export const readvAsyncCallbackTest = {
804 async test() {
805 const fd = openSync('/tmp/test.txt', 'w+');
806 ok(existsSync('/tmp/test.txt'));
807 
808 throws(() => readv('hello'), kErrInvalidArgType);
809 throws(() => readv(0, 123), kErrInvalidArgType);
810 throws(() => readv(0, [Buffer.alloc(1)], 123), kErrInvalidArgType);
811 throws(() => readv(0, [Buffer.alloc(1)], {}), kErrInvalidArgType);
812 
813 const stat = fstatSync(fd, { bigint: true });
814 strictEqual(stat.size, 0n);
815 
816 writeSync(fd, 'Hello World');
817 writeSync(fd, '!!!!');
818 const stat2 = fstatSync(fd, { bigint: true });
819 strictEqual(stat2.size, 15n);
820 
821 const dest1 = Buffer.alloc(5);
822 const dest2 = Buffer.alloc(10);
823 let bytesRead = await new Promise((resolve, reject) => {
824 readv(fd, [dest1, dest2], 0, (err, bytesRead) => {
825 if (err) return reject(err);
826 resolve(bytesRead);
827 });
828 });
829 
830 strictEqual(bytesRead, 15);
831 strictEqual(Buffer.concat([dest1, dest2]).toString(), 'Hello World!!!!');
832 
833 closeSync(fd);
834 },
835};
836 
837export const readFileAsyncCallbackTest = {
838 async test() {
839 const fd = openSync('/tmp/test.txt', 'w+');
840 ok(existsSync('/tmp/test.txt'));
841 
842 throws(() => readFile('hello'), kErrInvalidArgType);
843 throws(() => readFile(0, 123), kErrInvalidArgType);
844 throws(() => readFile(0, {}), kErrInvalidArgType);
845 
846 const stat = fstatSync(fd, { bigint: true });
847 strictEqual(stat.size, 0n);
848 
849 writeSync(fd, 'Hello World');
850 writeSync(fd, '!!!!');
851 const stat2 = fstatSync(fd, { bigint: true });
852 strictEqual(stat2.size, 15n);
853 
854 const content = await new Promise((resolve, reject) => {
855 readFile(fd, (err, content) => {
856 if (err) return reject(err);
857 resolve(content);
858 });
859 });
860 
861 strictEqual(content.toString(), 'Hello World!!!!');
862 
863 closeSync(fd);
864 },
865};
866 
867export const copyAndRenameSyncTest = {
868 test() {
869 ok(!existsSync('/tmp/test.txt'));
870 ok(!existsSync('/tmp/test2.txt'));
871 writeFileSync('/tmp/test.txt', 'Hello World');
872 ok(existsSync('/tmp/test.txt'));
873 ok(!existsSync('/tmp/test2.txt'));
874 
875 copyFileSync('/tmp/test.txt', '/tmp/test2.txt');
876 // Both files exist
877 ok(existsSync('/tmp/test.txt'));
878 ok(existsSync('/tmp/test2.txt'));
879 
880 strictEqual(
881 readFileSync('/tmp/test.txt').toString(),
882 readFileSync('/tmp/test2.txt').toString()
883 );
884 
885 copyFileSync('/tmp/test.txt', '/tmp/test4.txt', 0);
886 // Both files exist
887 ok(existsSync('/tmp/test.txt'));
888 ok(existsSync('/tmp/test4.txt'));
889 
890 strictEqual(
891 readFileSync('/tmp/test.txt').toString(),
892 readFileSync('/tmp/test4.txt').toString()
893 );
894 
895 throws(
896 () => copyFileSync('/tmp/test.txt', '/tmp/test4.txt', COPYFILE_EXCL),
897 {
898 code: 'EEXIST',
899 }
900 );
901 
902 throws(
903 () =>
904 copyFileSync(
905 '/tmp/test.txt',
906 '/tmp/nope.txt',
907 constants.COPYFILE_FICLONE_FORCE
908 ),
909 {
910 message: /unsupported/,
911 }
912 );
913 
914 copyFileSync('/tmp/test.txt', '/tmp/test5.txt', constants.COPYFILE_FICLONE);
915 // Both files exist
916 ok(existsSync('/tmp/test.txt'));
917 ok(existsSync('/tmp/test5.txt'));
918 
919 strictEqual(
920 readFileSync('/tmp/test.txt').toString(),
921 readFileSync('/tmp/test5.txt').toString()
922 );
923 
924 [false, 1, {}, [], null, undefined].forEach((i) => {
925 throws(() => copyFileSync(i, '/tmp/nope.txt'), {
926 code: 'ERR_INVALID_ARG_TYPE',
927 });
928 throws(() => copyFileSync('/tmp/test.txt', i), {
929 code: 'ERR_INVALID_ARG_TYPE',
930 });
931 });
932 [false, {}, [], null].forEach((i) => {
933 throws(() => copyFileSync('/tmp/test.txt', '/tmp/test2.txt', i), {
934 code: 'ERR_INVALID_ARG_VALUE',
935 });
936 });
937 
938 // We can modify one of the files and the other remains unchanged
939 writeFileSync('/tmp/test.txt', 'Hello World 2');
940 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World 2');
941 strictEqual(readFileSync('/tmp/test2.txt').toString(), 'Hello World');
942 
943 // Renaming the files work
944 renameSync('/tmp/test.txt', '/tmp/test3.txt');
945 ok(!existsSync('/tmp/test.txt'));
946 ok(existsSync('/tmp/test3.txt'));
947 strictEqual(readFileSync('/tmp/test3.txt').toString(), 'Hello World 2');
948 },
949};
950 
951export const copyAndRenameAsyncCallbackTest = {
952 async test() {
953 ok(!existsSync('/tmp/test.txt'));
954 ok(!existsSync('/tmp/test2.txt'));
955 writeFileSync('/tmp/test.txt', 'Hello World');
956 ok(existsSync('/tmp/test.txt'));
957 ok(!existsSync('/tmp/test2.txt'));
958 
959 throws(() => copyFile(123), kErrInvalidArgType);
960 throws(() => copyFile('/tmp/test.txt', 123), kErrInvalidArgType);
961 throws(
962 () => copyFile('/tmp/test.txt', '/tmp/test2.txt', 123),
963 kErrInvalidArgValue
964 );
965 throws(
966 () => copyFile('/tmp/test.txt', '/tmp/test2.txt', 0),
967 kErrInvalidArgType
968 );
969 
970 throws(() => rename(123), kErrInvalidArgType);
971 throws(() => rename('/tmp/test.txt', 123), kErrInvalidArgType);
972 throws(
973 () => rename('/tmp/test.txt', '/tmp/test2.txt', 0),
974 kErrInvalidArgType
975 );
976 
977 await new Promise((resolve, reject) => {
978 copyFile('/tmp/test.txt', '/tmp/test2.txt', (err) => {
979 if (err) return reject(err);
980 resolve();
981 });
982 });
983 
984 // Test the exclusive option fails when the file already exists
985 await rejects(
986 new Promise((resolve, reject) => {
987 copyFile('/tmp/test.txt', '/tmp/test2.txt', COPYFILE_EXCL, (err) => {
988 if (err) return reject(err);
989 resolve();
990 });
991 }),
992 kErrEExist
993 );
994 
995 // Both files exist
996 ok(existsSync('/tmp/test.txt'));
997 ok(existsSync('/tmp/test2.txt'));
998 
999 strictEqual(
1000 readFileSync('/tmp/test.txt').toString(),
1001 readFileSync('/tmp/test2.txt').toString()
1002 );
1003 
1004 // We can modify one of the files and the other remains unchanged
1005 writeFileSync('/tmp/test.txt', 'Hello World 2');
1006 strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World 2');
1007 strictEqual(readFileSync('/tmp/test2.txt').toString(), 'Hello World');
1008 
1009 // Renaming the files work
1010 await new Promise((resolve, reject) => {
1011 rename('/tmp/test.txt', '/tmp/test3.txt', (err) => {
1012 if (err) return reject(err);
1013 resolve();
1014 });
1015 });
1016 
1017 ok(!existsSync('/tmp/test.txt'));
1018 ok(existsSync('/tmp/test3.txt'));
1019 strictEqual(readFileSync('/tmp/test3.txt').toString(), 'Hello World 2');
1020 },
1021};
1022 
1023export const fsCwdTest = {
1024 test() {
1025 process.chdir('/bundle');
1026 
1027 throws(
1028 () => {
1029 writeFileSync('test-cwd.txt', 'Hello from original cwd');
1030 },
1031 { code: 'EPERM' }
1032 );
1033 
1034 process.chdir('/tmp');
1035 
1036 writeFileSync('test-cwd.txt', 'Hello from /tmp');
1037 ok(existsSync('test-cwd.txt'));
1038 ok(existsSync('/tmp/test-cwd.txt'));
1039 
1040 ok(existsSync('test-cwd.txt'));
1041 ok(!existsSync(`/bundle/test-cwd.txt`));
1042 
1043 strictEqual(readFileSync('test-cwd.txt').toString(), 'Hello from /tmp');
1044 strictEqual(
1045 readFileSync('/tmp/test-cwd.txt').toString(),
1046 'Hello from /tmp'
1047 );
1048 
1049 process.chdir('/bundle');
1050 
1051 ok(!existsSync('test-cwd.txt'));
1052 throws(
1053 () => {
1054 readFileSync('test-cwd.txt');
1055 },
1056 { code: 'ENOENT', path: '/bundle/test-cwd.txt' }
1057 );
1058 
1059 unlinkSync('/tmp/test-cwd.txt');
1060 },
1061};
1062 
1063export const readBadEncoding = {
1064 test() {
1065 const kErrorObj = {
1066 code: 'ERR_INVALID_ARG_VALUE',
1067 };
1068 throws(() => readFileSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);
1069 throws(
1070 () => appendFileSync('/tmp/test.txt', 'data', 'bad-encoding'),
1071 kErrorObj
1072 );
1073 throws(() => readdirSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);
1074 throws(() => readlinkSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);
1075 throws(
1076 () => writeFileSync('/tmp/test.txt', 'data', 'bad-encoding'),
1077 kErrorObj
1078 );
1079 throws(
1080 () => appendFileSync('/tmp/test.txt', 'data', 'bad-encoding'),
1081 kErrorObj
1082 );
1083 throws(() => realpathSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);
1084 throws(() => mkdtempSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);
1085 throws(() => ReadStream('/tmp/test.txt', 'bad-encoding'), kErrorObj);
1086 throws(() => WriteStream('/tmp/test.txt', 'bad-encoding'), kErrorObj);
1087 
1088 throws(
1089 () => writeFile('/tmp/test.txt', 'data', 'bad-encoding', mustNotCall),
1090 kErrorObj
1091 );
1092 throws(
1093 () => appendFile('/tmp/test.txt', 'data', 'bad-encoding', mustNotCall),
1094 kErrorObj
1095 );
1096 
1097 function mustNotCall() {
1098 throw new Error('This function must not be called');
1099 }
1100 
1101 throws(
1102 () => readFile('/tmp/test.txt', 'bad-encoding', mustNotCall),
1103 kErrorObj
1104 );
1105 throws(
1106 () => readdir('/tmp/test.txt', 'bad-encoding', mustNotCall),
1107 kErrorObj
1108 );
1109 throws(
1110 () => readlink('/tmp/test.txt', 'bad-encoding', mustNotCall),
1111 kErrorObj
1112 );
1113 throws(
1114 () => realpath('/tmp/test.txt', 'bad-encoding', mustNotCall),
1115 kErrorObj
1116 );
1117 throws(
1118 () => mkdtemp('/tmp/test.txt', 'bad-encoding', mustNotCall),
1119 kErrorObj
1120 );
1121 },
1122};
1123 
1124export const fsConstantsTest = {
1125 test() {
1126 // Check if the two constants accepted by chmod() on Windows are defined.
1127 notStrictEqual(constants.S_IRUSR, undefined);
1128 notStrictEqual(constants.S_IWUSR, undefined);
1129 
1130 // Check null prototype.
1131 strictEqual(Object.getPrototypeOf(constants), null);
1132 
1133 const knownFsConstantNames = [
1134 'UV_FS_SYMLINK_DIR',
1135 'UV_FS_SYMLINK_JUNCTION',
1136 'O_RDONLY',
1137 'O_WRONLY',
1138 'O_RDWR',
1139 'UV_DIRENT_UNKNOWN',
1140 'UV_DIRENT_FILE',
1141 'UV_DIRENT_DIR',
1142 'UV_DIRENT_LINK',
1143 'UV_DIRENT_FIFO',
1144 'UV_DIRENT_SOCKET',
1145 'UV_DIRENT_CHAR',
1146 'UV_DIRENT_BLOCK',
1147 'S_IFMT',
1148 'S_IFREG',
1149 'S_IFDIR',
1150 'S_IFCHR',
1151 'S_IFBLK',
1152 'S_IFIFO',
1153 'S_IFLNK',
1154 'S_IFSOCK',
1155 'O_CREAT',
1156 'O_EXCL',
1157 'UV_FS_O_FILEMAP',
1158 'O_NOCTTY',
1159 'O_TRUNC',
1160 'O_APPEND',
1161 'O_DIRECTORY',
1162 'O_EXCL',
1163 'O_NOATIME',
1164 'O_NOFOLLOW',
1165 'O_SYNC',
1166 'O_DSYNC',
1167 'O_SYMLINK',
1168 'O_DIRECT',
1169 'O_NONBLOCK',
1170 'S_IRWXU',
1171 'S_IRUSR',
1172 'S_IWUSR',
1173 'S_IXUSR',
1174 'S_IRWXG',
1175 'S_IRGRP',
1176 'S_IWGRP',
1177 'S_IXGRP',
1178 'S_IRWXO',
1179 'S_IROTH',
1180 'S_IWOTH',
1181 'S_IXOTH',
1182 'F_OK',
1183 'R_OK',
1184 'W_OK',
1185 'X_OK',
1186 'UV_FS_COPYFILE_EXCL',
1187 'COPYFILE_EXCL',
1188 'UV_FS_COPYFILE_FICLONE',
1189 'COPYFILE_FICLONE',
1190 'UV_FS_COPYFILE_FICLONE_FORCE',
1191 'COPYFILE_FICLONE_FORCE',
1192 'EXTENSIONLESS_FORMAT_JAVASCRIPT',
1193 'EXTENSIONLESS_FORMAT_WASM',
1194 ];
1195 
1196 const fsConstantNames = Object.keys(constants);
1197 const unknownFsConstantNames = fsConstantNames.filter((constant) => {
1198 return !knownFsConstantNames.includes(constant);
1199 });
1200 deepStrictEqual(
1201 unknownFsConstantNames,
1202 [],
1203 `Unknown fs.constants: ${unknownFsConstantNames.join(', ')}`
1204 );
1205 
1206 strictEqual(typeof constants.COPYFILE_EXCL, 'number');
1207 strictEqual(typeof constants.COPYFILE_FICLONE, 'number');
1208 strictEqual(typeof constants.COPYFILE_FICLONE_FORCE, 'number');
1209 strictEqual(typeof constants.UV_FS_COPYFILE_EXCL, 'number');
1210 strictEqual(typeof constants.UV_FS_COPYFILE_FICLONE, 'number');
1211 strictEqual(typeof constants.UV_FS_COPYFILE_FICLONE_FORCE, 'number');
1212 strictEqual(constants.COPYFILE_EXCL, constants.UV_FS_COPYFILE_EXCL);
1213 strictEqual(constants.COPYFILE_FICLONE, constants.UV_FS_COPYFILE_FICLONE);
1214 strictEqual(
1215 constants.COPYFILE_FICLONE_FORCE,
1216 constants.UV_FS_COPYFILE_FICLONE_FORCE
1217 );
1218 },
1219};
1220 
1221export const fmapIgnoredTest = {
1222 test() {
1223 const filename = '/tmp/foo';
1224 const text = 'Memory File Mapping Test';
1225 
1226 const mw =
1227 constants.UV_FS_O_FILEMAP |
1228 constants.O_TRUNC |
1229 constants.O_CREAT |
1230 constants.O_WRONLY;
1231 const mr = constants.UV_FS_O_FILEMAP | constants.O_RDONLY;
1232 
1233 writeFileSync(filename, text, { flag: mw });
1234 const r1 = readFileSync(filename, { encoding: 'utf8', flag: mr });
1235 strictEqual(r1, text);
1236 },
1237};
1238 
1239export const fileNamesWithNullBytesTest = {
1240 test() {
1241 const filename = '/tmp/test\0.txt';
1242 throws(() => writeFileSync(filename, 'Hello World'), {
1243 code: 'ERR_INVALID_ARG_VALUE',
1244 });
1245 },
1246};
1247 
1248export const fileNamesWithSurrogatePairsTest = {
1249 test() {
1250 const tempdir = mkdtempSync('/tmp/emoji-fruit-๐Ÿ‡ ๐Ÿˆ ๐Ÿ‰ ๐ŸŠ ๐Ÿ‹');
1251 ok(existsSync(tempdir));
1252 const filename = '๐Ÿš€๐Ÿ”ฅ๐Ÿ›ธ.txt';
1253 const content = 'Test content';
1254 writeFileSync(join(tempdir, filename), content);
1255 const readContent = readFileSync(join(tempdir, filename), 'utf8');
1256 strictEqual(readContent, content);
1257 },
1258};
1259 
1260export const testFsWithZlib = {
1261 async test() {
1262 const imgBase64 =
1263 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
1264 
1265 const imageStream = Readable.from(Buffer.from(imgBase64, 'base64'));
1266 await new Promise((resolve, reject) => {
1267 const outStream = createWriteStream(`/tmp/image-stream.png.gz`);
1268 outStream.on('close', resolve).on('error', reject);
1269 imageStream.pipe(zlib.createGzip()).pipe(outStream);
1270 });
1271 
1272 doesNotThrow(() => {
1273 zlib
1274 .gunzipSync(readFileSync('/tmp/image-stream.png.gz'))
1275 .toString('base64');
1276 });
1277 },
1278};