vfs: add minimal node:vfs subsystem#63115
Conversation
|
Review requested:
|
The docs in this PR claim that you can call |
30f5755 to
779fc37
Compare
Fixed, good spot. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #63115 +/- ##
==========================================
- Coverage 91.94% 90.21% -1.73%
==========================================
Files 362 726 +364
Lines 155999 231648 +75649
Branches 24057 43692 +19635
==========================================
+ Hits 143429 208989 +65560
- Misses 12295 14433 +2138
- Partials 275 8226 +7951
🚀 New features to boost your workflow:
|
|
Can you add a test for #63158? |
Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
The getter exposed the raw real-fs file descriptor of a virtual file handle, which is a leaky abstraction: it lets user code bypass the VFS layer. The only consumer was a test that closed the real fd to trigger the EBADF rejection paths in the async fd ops; those branches are defensive and unreachable from the public API, so the test block is dropped along with the getter. Assisted-by: Claude-Opus4.7 Signed-off-by: Matteo Collina <hello@matteocollina.com>
* file_handle: import SymbolDispose from primordials so the SymbolDispose prototype binding doesn't throw at load time. * providers/real: import getValidatedPath and setOwnProperty that the code-review rewrite of the rootPath validator referenced without importing. * test-vfs-real-provider: getValidatedPath rejects non-strings with ERR_INVALID_ARG_TYPE (not ERR_INVALID_ARG_VALUE) and now accepts the empty string, so the assertions are updated and the empty-string case dropped. * test-vfs-memory-provider-dynamic: the object-literal rewrite left a stray ');' instead of '}', which crashed the parser. Signed-off-by: Matteo Collina <hello@matteocollina.com>
Signed-off-by: Matteo Collina <hello@matteocollina.com>
Tracks the suggestion from review to delegate the async fd ops in RealFileHandle to FileHandle methods instead of manually wrapping fs.read/write/fstat/ftruncate/close in Promises. The refactor is blocked on a way to wrap an existing numeric fd in a FileHandle so a sync-opened handle can share one underlying handle for async ops. Signed-off-by: Matteo Collina <hello@matteocollina.com>
RealFSProvider.realpath fed the resolved path through StringPrototype startsWith against the stored rootPath plus a separator. On Windows fs.realpathSync is a JS implementation that preserves case while fs.promises.realpath calls the native binding, which canonicalizes the drive letter (and potentially other components). The async path then fails the startsWith check and rejects with EACCES, even for valid paths inside the root. Use path.relative to test containment instead, which treats the comparison as case-insensitive on Windows. Both realpathSync and realpath now share the same helper. Fixes failure in test-vfs-real-provider-symlinks on test-binary-windows-js-suites. Signed-off-by: Matteo Collina <hello@matteocollina.com>
| let data = ''; | ||
|
|
||
| stream.on('open', common.mustCall((fd) => { | ||
| assert.ok((fd & 0x40000000) !== 0); |
There was a problem hiding this comment.
| assert.ok((fd & 0x40000000) !== 0); | |
| assert.notStrictEqual(fd & 0x40000000, 0); |
| ws.on('error', common.mustCall((err) => { | ||
| assert.ok(err); | ||
| })); |
There was a problem hiding this comment.
| ws.on('error', common.mustCall((err) => { | |
| assert.ok(err); | |
| })); | |
| ws.on('error', common.expectsError()); |
| ws.on('error', common.mustCall((err) => { | ||
| assert.ok(err); | ||
| })); |
There was a problem hiding this comment.
| ws.on('error', common.mustCall((err) => { | |
| assert.ok(err); | |
| })); | |
| ws.on('error', common.expectsError()); |
| const fd = myVfs.openSync('/cl.txt'); | ||
| const rs = myVfs.createReadStream('/cl.txt', { fd, autoClose: true }); | ||
| myVfs.closeSync(fd); | ||
| rs.on('error', common.mustCall()); |
There was a problem hiding this comment.
| rs.on('error', common.mustCall()); | |
| rs.on('error', common.expectsError()); |
| rs.on('error', common.mustCall((err) => { | ||
| assert.strictEqual(err.code, 'EBADF'); | ||
| })); |
There was a problem hiding this comment.
| rs.on('error', common.mustCall((err) => { | |
| assert.strictEqual(err.code, 'EBADF'); | |
| })); | |
| rs.on('error', common.expectsError({ | |
| code: 'EBADF', | |
| })); |
| stream.on('error', common.mustCall((err) => { | ||
| assert.strictEqual(err.code, 'ENOENT'); | ||
| })); |
There was a problem hiding this comment.
| stream.on('error', common.mustCall((err) => { | |
| assert.strictEqual(err.code, 'ENOENT'); | |
| })); | |
| stream.on('error', common.expectsError({ | |
| code: 'ENOENT', | |
| })); |
Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com> Co-authored-by: Matteo Collina <matteo.collina@gmail.com>
| const fh = await myVfs.promises.open('/hello.txt', 'r'); | ||
| assert.ok(fh); | ||
| assert.ok(typeof fh === 'number' || typeof fh === 'object'); |
There was a problem hiding this comment.
That's a strangely loose assertion. Why? Do we expect this to change?
* test-vfs-stream-errors: drop the unused `assert` import. * test-vfs-streams: capitalize a lowercase comment to satisfy capitalized-comments. Signed-off-by: Matteo Collina <hello@matteocollina.com>
Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
spawnSyncAndAssert handles the expected stderr/status, so the top-level `assert` is no longer referenced. Signed-off-by: Matteo Collina <hello@matteocollina.com>
Adds an experimental
node:vfsbuiltin (gated behind--experimental-vfs) withVirtualFileSystem,VirtualProvider,MemoryProvider, andRealFSProvider. No integration withnode:fs, the module loader, or SEA those are intended to land in follow-up PRs.Extracted from: #61478
Approximate line counts: code ~4k / docs ~1k / tests ~5k — total ~10k lines, with tests being the largest share.