node/test/parallel/test-vfs-callback-api.js
Matteo Collina c9562ddb82
vfs: add minimal node:vfs subsystem
Adds the node:vfs builtin module with VirtualFileSystem and
provider classes. No integration with fs, modules, or SEA.

Assisted-by: Claude-Opus4.7
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/63115
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
2026-05-23 18:43:31 +00:00

154 lines
4.6 KiB
JavaScript

// Flags: --experimental-vfs
'use strict';
// Exercise the VFS callback-style async API on every method.
const common = require('../common');
const assert = require('assert');
const vfs = require('node:vfs');
const myVfs = vfs.create();
myVfs.mkdirSync('/dir/sub', { recursive: true });
myVfs.writeFileSync('/dir/file.txt', 'hello');
myVfs.writeFileSync('/dir/other.txt', 'other');
// readFile (no options)
myVfs.readFile('/dir/file.txt', common.mustSucceed((data) => {
assert.ok(Buffer.isBuffer(data));
}));
// writeFile + appendFile (no options) -> readFile
myVfs.writeFile('/cb-write.txt', 'a', common.mustSucceed(() => {
myVfs.readFile('/cb-write.txt', 'utf8', common.mustSucceed((data) => {
assert.strictEqual(data, 'a');
}));
}));
// stat / lstat (with and without options)
myVfs.stat('/dir/file.txt', common.mustSucceed((st) => {
assert.strictEqual(st.size, 5);
}));
myVfs.stat('/dir/file.txt', { bigint: true }, common.mustSucceed((st) => {
assert.strictEqual(typeof st.size, 'bigint');
}));
myVfs.lstat('/dir/file.txt', common.mustSucceed((st) => {
assert.ok(st.isFile());
}));
// readdir
myVfs.readdir('/dir', common.mustSucceed((names) => {
assert.ok(names.includes('file.txt'));
}));
// realpath
myVfs.realpath('/dir/file.txt', common.mustSucceed((p) => {
assert.strictEqual(p, '/dir/file.txt');
}));
// access (with and without mode)
myVfs.access('/dir/file.txt', common.mustSucceed());
myVfs.access('/dir/file.txt', 0, common.mustSucceed());
myVfs.access('/missing.txt', common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOENT');
}));
// open / read / write / close cb chain
myVfs.open('/dir/file.txt', common.mustSucceed((fd) => {
const buf = Buffer.alloc(5);
myVfs.read(fd, buf, 0, 5, 0, common.mustSucceed((bytesRead) => {
assert.strictEqual(bytesRead, 5);
assert.strictEqual(buf.toString(), 'hello');
myVfs.close(fd, common.mustSucceed());
}));
}));
// Open with explicit flags / mode
myVfs.open('/dir/new1.txt', 'w', common.mustSucceed((fd) => {
const buf = Buffer.from('xyz');
myVfs.write(fd, buf, 0, 3, 0, common.mustSucceed((bytesWritten) => {
assert.strictEqual(bytesWritten, 3);
myVfs.fstat(fd, common.mustSucceed((st) => {
assert.strictEqual(st.size, 3);
myVfs.ftruncate(fd, 1, common.mustSucceed(() => {
myVfs.close(fd, common.mustCall());
}));
}));
}));
}));
// Open with explicit flags, no mode arg form
myVfs.open('/dir/new2.txt', 'w', 0o644, common.mustSucceed((fd) => {
myVfs.close(fd, common.mustCall());
}));
// rm callback (file)
myVfs.writeFileSync('/cb-rm.txt', 'x');
myVfs.rm('/cb-rm.txt', common.mustSucceed(() => {
assert.strictEqual(myVfs.existsSync('/cb-rm.txt'), false);
}));
// Rm callback with options (recursive)
myVfs.mkdirSync('/cb-rm-dir/sub', { recursive: true });
myVfs.writeFileSync('/cb-rm-dir/sub/f.txt', 'x');
myVfs.rm('/cb-rm-dir', { recursive: true }, common.mustSucceed());
// Rm callback failure path
myVfs.rm('/missing-rm', common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOENT');
}));
// truncate / ftruncate cb
myVfs.writeFileSync('/cb-tr.txt', 'abcdef');
myVfs.truncate('/cb-tr.txt', 3, common.mustSucceed(() => {
assert.strictEqual(myVfs.readFileSync('/cb-tr.txt', 'utf8'), 'abc');
}));
myVfs.truncate('/missing-tr.txt', common.mustCall((err) => {
assert.ok(err);
}));
myVfs.ftruncate(0xFFFFFFF /* invalid fd */, common.mustCall((err) => {
assert.strictEqual(err.code, 'EBADF');
}));
// link cb
myVfs.writeFileSync('/cb-link-src.txt', 'x');
myVfs.link('/cb-link-src.txt', '/cb-link-dst.txt', common.mustSucceed());
myVfs.link('/missing-src.txt', '/cb-bad-link.txt', common.mustCall((err) => {
assert.ok(err);
}));
// mkdtemp cb
myVfs.mkdtemp('/tmp-', common.mustSucceed((p) => {
assert.ok(p.startsWith('/tmp-'));
}));
myVfs.mkdtemp('/tmp-', {}, common.mustSucceed((p) => {
assert.ok(p.startsWith('/tmp-'));
}));
// opendir cb
myVfs.opendir('/dir', common.mustSucceed((dir) => {
assert.strictEqual(dir.path, '/dir');
dir.closeSync();
}));
myVfs.opendir('/missing-dir', common.mustCall((err) => {
assert.ok(err);
}));
// EBADF callback paths
myVfs.read(0xFFFFFFF, Buffer.alloc(1), 0, 1, 0, common.mustCall((err) => {
assert.strictEqual(err.code, 'EBADF');
}));
myVfs.write(0xFFFFFFF, Buffer.alloc(1), 0, 1, 0, common.mustCall((err) => {
assert.strictEqual(err.code, 'EBADF');
}));
myVfs.close(0xFFFFFFF, common.mustCall((err) => {
assert.strictEqual(err.code, 'EBADF');
}));
myVfs.fstat(0xFFFFFFF, common.mustCall((err) => {
assert.strictEqual(err.code, 'EBADF');
}));
// readlink cb
myVfs.symlinkSync('/dir/file.txt', '/cb-link');
myVfs.readlink('/cb-link', common.mustSucceed((target) => {
assert.strictEqual(target, '/dir/file.txt');
}));