Files
node/test/parallel/test-vfs-streams.js
T
Matteo Collina 49ad95045a vfs: unflag
Enable node:vfs by default. --no-experimental-vfs disables node:vfs
exposure and --vfs-load.

Assisted-by: pi
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/66318
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
2026-10-15 07:53:03 +00:00

326 lines
8.1 KiB
JavaScript

// Flags: --disable-warning=ExperimentalWarning
'use strict';
const common = require('../common');
const assert = require('assert');
const vfs = require('node:vfs');
// Test basic createReadStream
{
const myVfs = vfs.create();
myVfs.writeFileSync('/file.txt', 'hello world');
const stream = myVfs.createReadStream('/file.txt');
let data = '';
stream.on('open', common.mustCall((fd) => {
assert.notStrictEqual(fd & 0x40000000, 0);
}));
stream.on('ready', common.mustCall());
stream.on('data', (chunk) => {
data += chunk;
});
stream.on('end', common.mustCall(() => {
assert.strictEqual(data, 'hello world');
}));
stream.on('close', common.mustCall());
}
// Test createReadStream with encoding
{
const myVfs = vfs.create();
myVfs.writeFileSync('/encoded.txt', 'encoded content');
const stream = myVfs.createReadStream('/encoded.txt', { encoding: 'utf8' });
let data = '';
let receivedString = false;
stream.on('data', (chunk) => {
if (typeof chunk === 'string') {
receivedString = true;
}
data += chunk;
});
stream.on('end', common.mustCall(() => {
assert.strictEqual(receivedString, true);
assert.strictEqual(data, 'encoded content');
}));
}
// Test createReadStream with start and end
{
const myVfs = vfs.create();
myVfs.writeFileSync('/range.txt', '0123456789');
const stream = myVfs.createReadStream('/range.txt', {
start: 2,
end: 5,
});
let data = '';
stream.on('data', (chunk) => {
data += chunk;
});
stream.on('end', common.mustCall(() => {
// End is inclusive, so positions 2, 3, 4, 5 = "2345" (4 chars)
assert.strictEqual(data, '2345');
}));
}
// Test createReadStream with start only
{
const myVfs = vfs.create();
myVfs.writeFileSync('/start.txt', 'abcdefghij');
const stream = myVfs.createReadStream('/start.txt', { start: 5 });
let data = '';
stream.on('data', (chunk) => {
data += chunk;
});
stream.on('end', common.mustCall(() => {
assert.strictEqual(data, 'fghij');
}));
}
// Test createReadStream with non-existent file
{
const myVfs = vfs.create();
const stream = myVfs.createReadStream('/nonexistent.txt');
stream.on('error', common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOENT');
}));
}
// Test createReadStream path property
{
const myVfs = vfs.create();
myVfs.writeFileSync('/path-test.txt', 'test');
const stream = myVfs.createReadStream('/path-test.txt');
assert.strictEqual(stream.path, '/path-test.txt');
stream.on('data', () => {}); // Consume data
stream.on('end', common.mustCall());
}
// Test createReadStream with small highWaterMark
{
const myVfs = vfs.create();
myVfs.writeFileSync('/small-hwm.txt', 'AAAABBBBCCCCDDDD');
const stream = myVfs.createReadStream('/small-hwm.txt', {
highWaterMark: 4,
});
const chunks = [];
stream.on('data', (chunk) => {
chunks.push(chunk.toString());
});
stream.on('end', common.mustCall(() => {
// With highWaterMark of 4, we should get multiple chunks
assert.ok(chunks.length >= 1);
assert.strictEqual(chunks.join(''), 'AAAABBBBCCCCDDDD');
}));
}
// Test createReadStream destroy
{
const myVfs = vfs.create();
myVfs.writeFileSync('/destroy.txt', 'content to destroy');
const stream = myVfs.createReadStream('/destroy.txt');
stream.on('open', common.mustCall(() => {
stream.destroy();
}));
stream.on('close', common.mustCall());
}
// Test createReadStream with large file
{
const myVfs = vfs.create();
const largeContent = 'X'.repeat(100000);
myVfs.writeFileSync('/large.txt', largeContent);
const stream = myVfs.createReadStream('/large.txt');
let receivedLength = 0;
stream.on('data', (chunk) => {
receivedLength += chunk.length;
});
stream.on('end', common.mustCall(() => {
assert.strictEqual(receivedLength, 100000);
}));
}
// Test createReadStream pipe to another stream
{
const myVfs = vfs.create();
const { Writable } = require('stream');
myVfs.writeFileSync('/pipe-source.txt', 'pipe this content');
const stream = myVfs.createReadStream('/pipe-source.txt');
let collected = '';
const writable = new Writable({
write(chunk, encoding, callback) {
collected += chunk;
callback();
},
});
stream.pipe(writable);
writable.on('finish', common.mustCall(() => {
assert.strictEqual(collected, 'pipe this content');
}));
}
// Test autoClose: false
{
const myVfs = vfs.create();
myVfs.writeFileSync('/no-auto-close.txt', 'content');
const stream = myVfs.createReadStream('/no-auto-close.txt', {
autoClose: false,
});
stream.on('close', common.mustCall());
// Start flowing the stream
stream.resume();
stream.on('end', common.mustCall(() => {
// With autoClose: false, close should not be emitted automatically
// We need to manually destroy the stream
setImmediate(() => {
stream.destroy();
});
}));
}
// ==================== Additional coverage ====================
const { Readable } = require('stream');
const { pipeline } = require('stream/promises');
// Slicing read stream with start/end
{
const myVfs = vfs.create();
myVfs.writeFileSync('/slice.txt', 'hello world');
const rs = myVfs.createReadStream('/slice.txt', { start: 6, end: 10 });
const chunks = [];
rs.on('data', (c) => chunks.push(c));
rs.on('end', common.mustCall(() => {
assert.strictEqual(Buffer.concat(chunks).toString(), 'world');
}));
}
// createReadStream via for-await iteration
(async () => {
const myVfs = vfs.create();
myVfs.writeFileSync('/iter.txt', 'abc');
const rs = myVfs.createReadStream('/iter.txt', { encoding: 'utf8' });
const chunks = [];
for await (const chunk of rs) {
chunks.push(chunk);
}
assert.strictEqual(chunks.join(''), 'abc');
})().then(common.mustCall());
// start: beyond file size → empty stream
{
const myVfs = vfs.create();
myVfs.writeFileSync('/sm.txt', 'abc');
const rs = myVfs.createReadStream('/sm.txt', { start: 10 });
rs.on('data', common.mustNotCall('no data expected'));
rs.on('end', common.mustCall());
}
// Empty file → end immediately
{
const myVfs = vfs.create();
myVfs.writeFileSync('/empty.txt', '');
const rs = myVfs.createReadStream('/empty.txt');
rs.on('data', common.mustNotCall('no data expected'));
rs.on('end', common.mustCall());
}
// Pipeline write
(async () => {
const myVfs = vfs.create();
await pipeline(
Readable.from([Buffer.from('hello'), Buffer.from(' world')]),
myVfs.createWriteStream('/out.txt'),
);
assert.strictEqual(myVfs.readFileSync('/out.txt', 'utf8'), 'hello world');
})().then(common.mustCall());
// Pipeline read
(async () => {
const myVfs = vfs.create();
myVfs.writeFileSync('/in.txt', 'hello world');
await pipeline(
myVfs.createReadStream('/in.txt'),
myVfs.createWriteStream('/copied.txt'),
);
assert.strictEqual(myVfs.readFileSync('/copied.txt', 'utf8'), 'hello world');
})().then(common.mustCall());
// Pipeline write with start position
(async () => {
const myVfs = vfs.create();
myVfs.writeFileSync('/pad.txt', 'AAAAAAAAAA');
await pipeline(
Readable.from([Buffer.from('XX')]),
myVfs.createWriteStream('/pad.txt', { start: 3, flags: 'r+' }),
);
assert.strictEqual(myVfs.readFileSync('/pad.txt', 'utf8'), 'AAAXXAAAAA');
})().then(common.mustCall());
// Write string chunk + encoding callback
(async () => {
const myVfs = vfs.create();
const stream = myVfs.createWriteStream('/str.txt');
await new Promise((resolve, reject) => {
stream.write('hello', 'utf8', (err) => (err ? reject(err) : resolve()));
});
await new Promise((resolve) => stream.end(resolve));
assert.strictEqual(myVfs.readFileSync('/str.txt', 'utf8'), 'hello');
})().then(common.mustCall());
// path getter
{
const myVfs = vfs.create();
myVfs.writeFileSync('/p.txt', 'x');
const rs = myVfs.createReadStream('/p.txt');
assert.strictEqual(rs.path, '/p.txt');
rs.destroy();
const ws = myVfs.createWriteStream('/p2.txt');
assert.strictEqual(ws.path, '/p2.txt');
ws.destroy();
}
// destroy() before any data triggers _destroy + close cleanup
{
const myVfs = vfs.create();
myVfs.writeFileSync('/d.txt', 'data');
const rs = myVfs.createReadStream('/d.txt');
rs.on('error', () => {});
rs.destroy();
}