Skip to content

Commit e8e9e8a

Browse files
committed
vfs: address test review feedback
- Add VFS docs link in test.md mock-fs section - Update TODO precision in test-runner-mock-fs.js - Move async truncate test to test-vfs-promises.js - Expand promise tests with full method coverage - Remove trivial assertion and clean up comments in test-vfs-chdir.js - Add ESM cache explanation in test-vfs-import.mjs - Split overlay worker code into test/fixtures/vfs-overlay-worker.js - Reorganize test-vfs-provider-memory.js to provider-specific tests - Add ESM require tests to test-vfs-require.js
1 parent aa76003 commit e8e9e8a

File tree

10 files changed

+233
-159
lines changed

10 files changed

+233
-159
lines changed

doc/api/test.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2358,7 +2358,7 @@ added: REPLACEME
23582358
* Returns: {MockFSContext} An object that can be used to manage the mock file
23592359
system.
23602360

2361-
This function creates a mock file system using the Virtual File System (VFS).
2361+
This function creates a mock file system using the [Virtual File System (VFS)][].
23622362
The mock file system is automatically cleaned up when the test completes.
23632363

23642364
## Class: `MockFSContext`
@@ -4228,3 +4228,4 @@ Can be used to abort test subtasks when the test has been aborted.
42284228
[suite options]: #suitename-options-fn
42294229
[test reporters]: #test-reporters
42304230
[test runner execution model]: #test-runner-execution-model
4231+
[virtual file system (vfs)]: vfs.md
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
3+
const { workerData, parentPort } = require('worker_threads');
4+
const vfs = require('node:vfs');
5+
const fs = require('fs');
6+
const path = require('path');
7+
8+
const testDir = workerData.testDir;
9+
10+
// In worker: create overlay VFS that mocks specific files
11+
const overlayVfs = vfs.create(new vfs.MemoryProvider(), {
12+
overlay: true,
13+
virtualCwd: true,
14+
});
15+
16+
// Mock a specific config file
17+
overlayVfs.mkdirSync('/config');
18+
overlayVfs.writeFileSync('/config/app.json', '{"env": "test"}');
19+
20+
overlayVfs.mount(testDir);
21+
22+
// Read mocked file
23+
const mockedConfig = fs.readFileSync(path.join(testDir, 'config', 'app.json'), 'utf8');
24+
25+
// Read real file (not mocked) - should work in overlay mode
26+
const realFile = fs.readFileSync(path.join(testDir, 'real.txt'), 'utf8');
27+
28+
// Test chdir to VFS directory
29+
overlayVfs.chdir(path.join(testDir, 'config'));
30+
const cwd = overlayVfs.cwd();
31+
32+
overlayVfs.unmount();
33+
34+
parentPort.postMessage({
35+
mockedConfig,
36+
realFile,
37+
cwd,
38+
});

test/parallel/test-runner-mock-fs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ test('mock.fs() exposes vfs property', (t) => {
157157
});
158158

159159
// Test mock.fs() with dynamic file content
160-
// TODO(vfs): Dynamic file content (functions) not yet supported in provider-based VFS
160+
// TODO(vfs): Dynamic file content (functions) not yet exposed through mock.fs() API
161161
test('mock.fs() supports dynamic file content', { skip: true }, (t) => {
162162
let counter = 0;
163163
const mockFs = t.mock.fs({ prefix: '/dynamic-test' });

test/parallel/test-vfs-basic.js

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Flags: --expose-internals
22
'use strict';
33

4-
const common = require('../common');
4+
require('../common');
55
const assert = require('assert');
66
const vfs = require('node:vfs');
77

@@ -260,20 +260,6 @@ const vfs = require('node:vfs');
260260
assert.strictEqual(content.slice(0, 2).toString(), 'hi');
261261
}
262262

263-
// Test async truncate
264-
{
265-
const myVfs = vfs.create();
266-
myVfs.writeFileSync('/async-truncate.txt', 'async content');
267-
268-
const fd = myVfs.openSync('/async-truncate.txt', 'r+');
269-
const handle = require('internal/vfs/fd').getVirtualFd(fd);
270-
271-
handle.entry.truncate(5).then(common.mustCall(() => {
272-
myVfs.closeSync(fd);
273-
assert.strictEqual(myVfs.readFileSync('/async-truncate.txt', 'utf8'), 'async');
274-
}));
275-
}
276-
277263
// Test rename operation
278264
{
279265
const myVfs = vfs.create();
@@ -298,6 +284,41 @@ const vfs = require('node:vfs');
298284
assert.strictEqual(myVfs.readFileSync('/dest.txt', 'utf8'), 'copy me');
299285
}
300286

287+
// Test appendFileSync
288+
{
289+
const myVfs = vfs.create();
290+
myVfs.writeFileSync('/append.txt', 'hello');
291+
292+
myVfs.appendFileSync('/append.txt', ' world');
293+
assert.strictEqual(myVfs.readFileSync('/append.txt', 'utf8'), 'hello world');
294+
295+
myVfs.appendFileSync('/append.txt', '!');
296+
assert.strictEqual(myVfs.readFileSync('/append.txt', 'utf8'), 'hello world!');
297+
298+
// Append to non-existent file creates it
299+
myVfs.appendFileSync('/newfile.txt', 'new content');
300+
assert.strictEqual(myVfs.readFileSync('/newfile.txt', 'utf8'), 'new content');
301+
302+
// Append with Buffer
303+
myVfs.writeFileSync('/buf-append.txt', Buffer.from('start'));
304+
myVfs.appendFileSync('/buf-append.txt', Buffer.from('-buffer'));
305+
assert.strictEqual(myVfs.readFileSync('/buf-append.txt', 'utf8'), 'start-buffer');
306+
}
307+
308+
// Test accessSync
309+
{
310+
const myVfs = vfs.create();
311+
myVfs.writeFileSync('/access-test.txt', 'content');
312+
313+
// Should not throw for existing file
314+
myVfs.accessSync('/access-test.txt');
315+
316+
// Should throw for non-existent file
317+
assert.throws(() => {
318+
myVfs.accessSync('/nonexistent.txt');
319+
}, { code: 'ENOENT' });
320+
}
321+
301322
// Test Symbol.dispose unmounts the VFS
302323
{
303324
const myVfs = vfs.create();

test/parallel/test-vfs-chdir.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,6 @@ const vfs = require('node:vfs');
168168

169169
const originalCwd = process.cwd();
170170

171-
// Before chdir, process.cwd returns real cwd
172-
assert.strictEqual(process.cwd(), originalCwd);
173-
174171
// Set virtual cwd
175172
myVfs.chdir('/virtual/project');
176173
assert.strictEqual(process.cwd(), '/virtual/project');
@@ -208,9 +205,7 @@ const vfs = require('node:vfs');
208205

209206
myVfs.unmount();
210207

211-
// After unmount, cwd should throw (not enabled)
212-
// Actually, virtualCwdEnabled is still true, just unmounted
213-
// Let's remount and check cwd is reset
208+
// After remount, virtual cwd should be reset to null
214209
myVfs.mount('/virtual');
215210
assert.strictEqual(myVfs.cwd(), null);
216211

test/parallel/test-vfs-import.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import '../common/index.mjs';
22
import assert from 'assert';
33
import vfs from 'node:vfs';
44

5+
// NOTE: Each test uses a different mount path (/virtual, /virtual2, etc.)
6+
// because ESM imports are cached by URL - using the same mount path would
7+
// return cached modules from previous tests instead of fresh imports.
8+
59
// Test importing a simple virtual ES module
610
{
711
const myVfs = vfs.create();

test/parallel/test-vfs-overlay.js

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -185,48 +185,8 @@ fs.writeFileSync(path.join(testDir, 'subdir', 'nested.txt'), 'nested real');
185185
const { Worker, isMainThread } = require('worker_threads');
186186

187187
if (isMainThread) {
188-
// Main thread: create worker with overlay VFS test
189-
const workerCode = `
190-
const { workerData, parentPort } = require('worker_threads');
191-
const vfs = require('node:vfs');
192-
const fs = require('fs');
193-
const path = require('path');
194-
195-
const testDir = workerData.testDir;
196-
197-
// In worker: create overlay VFS that mocks specific files
198-
const overlayVfs = vfs.create(new vfs.MemoryProvider(), {
199-
overlay: true,
200-
virtualCwd: true,
201-
});
202-
203-
// Mock a specific config file
204-
overlayVfs.mkdirSync('/config');
205-
overlayVfs.writeFileSync('/config/app.json', '{"env": "test"}');
206-
207-
overlayVfs.mount(testDir);
208-
209-
// Read mocked file
210-
const mockedConfig = fs.readFileSync(path.join(testDir, 'config', 'app.json'), 'utf8');
211-
212-
// Read real file (not mocked) - should work in overlay mode
213-
const realFile = fs.readFileSync(path.join(testDir, 'real.txt'), 'utf8');
214-
215-
// Test chdir to VFS directory
216-
overlayVfs.chdir(path.join(testDir, 'config'));
217-
const cwd = overlayVfs.cwd();
218-
219-
overlayVfs.unmount();
220-
221-
parentPort.postMessage({
222-
mockedConfig,
223-
realFile,
224-
cwd,
225-
});
226-
`;
227-
228-
const worker = new Worker(workerCode, {
229-
eval: true,
188+
const workerPath = require.resolve('../fixtures/vfs-overlay-worker.js');
189+
const worker = new Worker(workerPath, {
230190
workerData: { testDir },
231191
});
232192

test/parallel/test-vfs-promises.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Flags: --expose-internals
12
'use strict';
23

34
const common = require('../common');
@@ -268,3 +269,122 @@ const vfs = require('node:vfs');
268269
{ code: 'ENOENT' }
269270
);
270271
})().then(common.mustCall());
272+
273+
// Test promises.writeFile
274+
(async () => {
275+
const myVfs = vfs.create();
276+
277+
await myVfs.promises.writeFile('/write-test.txt', 'async written');
278+
assert.strictEqual(myVfs.readFileSync('/write-test.txt', 'utf8'), 'async written');
279+
280+
// Overwrite existing file
281+
await myVfs.promises.writeFile('/write-test.txt', 'overwritten');
282+
assert.strictEqual(myVfs.readFileSync('/write-test.txt', 'utf8'), 'overwritten');
283+
284+
// Write with Buffer
285+
await myVfs.promises.writeFile('/buffer-write.txt', Buffer.from('buffer data'));
286+
assert.strictEqual(myVfs.readFileSync('/buffer-write.txt', 'utf8'), 'buffer data');
287+
})().then(common.mustCall());
288+
289+
// Test promises.appendFile
290+
(async () => {
291+
const myVfs = vfs.create();
292+
myVfs.writeFileSync('/append-test.txt', 'start');
293+
294+
await myVfs.promises.appendFile('/append-test.txt', '-end');
295+
assert.strictEqual(myVfs.readFileSync('/append-test.txt', 'utf8'), 'start-end');
296+
297+
// Append to non-existent file creates it
298+
await myVfs.promises.appendFile('/new-append.txt', 'new content');
299+
assert.strictEqual(myVfs.readFileSync('/new-append.txt', 'utf8'), 'new content');
300+
})().then(common.mustCall());
301+
302+
// Test promises.mkdir
303+
(async () => {
304+
const myVfs = vfs.create();
305+
306+
await myVfs.promises.mkdir('/async-dir');
307+
const stat = myVfs.statSync('/async-dir');
308+
assert.strictEqual(stat.isDirectory(), true);
309+
310+
// Recursive mkdir
311+
await myVfs.promises.mkdir('/async-dir/nested/deep', { recursive: true });
312+
assert.strictEqual(myVfs.statSync('/async-dir/nested/deep').isDirectory(), true);
313+
314+
// Mkdir on existing directory throws without recursive
315+
await assert.rejects(
316+
myVfs.promises.mkdir('/async-dir'),
317+
{ code: 'EEXIST' }
318+
);
319+
})().then(common.mustCall());
320+
321+
// Test promises.unlink
322+
(async () => {
323+
const myVfs = vfs.create();
324+
myVfs.writeFileSync('/unlink-test.txt', 'to delete');
325+
326+
await myVfs.promises.unlink('/unlink-test.txt');
327+
assert.strictEqual(myVfs.existsSync('/unlink-test.txt'), false);
328+
329+
await assert.rejects(
330+
myVfs.promises.unlink('/nonexistent.txt'),
331+
{ code: 'ENOENT' }
332+
);
333+
})().then(common.mustCall());
334+
335+
// Test promises.rmdir
336+
(async () => {
337+
const myVfs = vfs.create();
338+
myVfs.mkdirSync('/rmdir-test');
339+
340+
await myVfs.promises.rmdir('/rmdir-test');
341+
assert.strictEqual(myVfs.existsSync('/rmdir-test'), false);
342+
343+
// Rmdir on non-empty directory throws
344+
myVfs.mkdirSync('/nonempty');
345+
myVfs.writeFileSync('/nonempty/file.txt', 'content');
346+
await assert.rejects(
347+
myVfs.promises.rmdir('/nonempty'),
348+
{ code: 'ENOTEMPTY' }
349+
);
350+
})().then(common.mustCall());
351+
352+
// Test promises.rename
353+
(async () => {
354+
const myVfs = vfs.create();
355+
myVfs.writeFileSync('/rename-src.txt', 'rename me');
356+
357+
await myVfs.promises.rename('/rename-src.txt', '/rename-dest.txt');
358+
assert.strictEqual(myVfs.existsSync('/rename-src.txt'), false);
359+
assert.strictEqual(myVfs.readFileSync('/rename-dest.txt', 'utf8'), 'rename me');
360+
})().then(common.mustCall());
361+
362+
// Test promises.copyFile
363+
(async () => {
364+
const myVfs = vfs.create();
365+
myVfs.writeFileSync('/copy-src.txt', 'copy me');
366+
367+
await myVfs.promises.copyFile('/copy-src.txt', '/copy-dest.txt');
368+
assert.strictEqual(myVfs.readFileSync('/copy-dest.txt', 'utf8'), 'copy me');
369+
// Source still exists
370+
assert.strictEqual(myVfs.existsSync('/copy-src.txt'), true);
371+
372+
await assert.rejects(
373+
myVfs.promises.copyFile('/nonexistent.txt', '/fail.txt'),
374+
{ code: 'ENOENT' }
375+
);
376+
})().then(common.mustCall());
377+
378+
// Test async truncate (via file handle)
379+
(async () => {
380+
const myVfs = vfs.create();
381+
myVfs.writeFileSync('/truncate-test.txt', 'async content');
382+
383+
const fd = myVfs.openSync('/truncate-test.txt', 'r+');
384+
const { getVirtualFd } = require('internal/vfs/fd');
385+
const handle = getVirtualFd(fd);
386+
387+
await handle.entry.truncate(5);
388+
myVfs.closeSync(fd);
389+
assert.strictEqual(myVfs.readFileSync('/truncate-test.txt', 'utf8'), 'async');
390+
})().then(common.mustCall());

0 commit comments

Comments
 (0)