From 127aa803362cace4bed5a81a5a1af20a69af4423 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Sun, 25 May 2025 15:33:05 +0300 Subject: [PATCH 1/3] Modernize code --- lib/lazystream.js | 74 +++++++++++++++++++++++-------------------- package.json | 4 +-- test/fs_test.js | 23 +++++++------- test/helper.js | 55 +++++++++++++++----------------- test/pipe_test.js | 17 +++++----- test/readable_test.js | 29 ++++++++--------- test/writable_test.js | 27 +++++++--------- 7 files changed, 112 insertions(+), 117 deletions(-) diff --git a/lib/lazystream.js b/lib/lazystream.js index d9f6170..7f54243 100644 --- a/lib/lazystream.js +++ b/lib/lazystream.js @@ -1,54 +1,58 @@ -var util = require('util'); -var PassThrough = require('readable-stream/passthrough'); - -module.exports = { - Readable: Readable, - Writable: Writable -}; - -util.inherits(Readable, PassThrough); -util.inherits(Writable, PassThrough); +const { PassThrough } = require('readable-stream'); // Patch the given method of instance so that the callback // is executed once, before the actual method is called the // first time. function beforeFirstCall(instance, method, callback) { - instance[method] = function() { + instance[method] = function(...args) { delete instance[method]; - callback.apply(this, arguments); - return this[method].apply(this, arguments); + callback.apply(this, args); + return this[method].apply(this, args); }; } -function Readable(fn, options) { - if (!(this instanceof Readable)) - return new Readable(fn, options); +class Readable extends PassThrough { + constructor(fn, options) { + super(options); - PassThrough.call(this, options); + // Support calling without new + if (!(this instanceof Readable)) { + return new Readable(fn, options); + } - beforeFirstCall(this, '_read', function() { - var source = fn.call(this, options); - var emit = this.emit.bind(this, 'error'); - source.on('error', emit); - source.pipe(this); - }); + beforeFirstCall(this, '_read', function() { + const source = fn.call(this, options); + const emit = this.emit.bind(this, 'error'); + source.on('error', emit); + source.pipe(this); + }); - this.emit('readable'); + this.emit('readable'); + } } -function Writable(fn, options) { - if (!(this instanceof Writable)) - return new Writable(fn, options); +class Writable extends PassThrough { + constructor(fn, options) { + super(options); - PassThrough.call(this, options); + // Support calling without new + if (!(this instanceof Writable)) { + return new Writable(fn, options); + } - beforeFirstCall(this, '_write', function() { - var destination = fn.call(this, options); - var emit = this.emit.bind(this, 'error'); - destination.on('error', emit); - this.pipe(destination); - }); + beforeFirstCall(this, '_write', function() { + const destination = fn.call(this, options); + const emit = this.emit.bind(this, 'error'); + destination.on('error', emit); + this.pipe(destination); + }); - this.emit('writable'); + this.emit('writable'); + } } +module.exports = { + Readable, + Writable +}; + diff --git a/package.json b/package.json index 94565e4..9eeae49 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,10 @@ "test/*.md" ], "dependencies": { - "readable-stream": "^2.0.5" + "readable-stream": "^4.7.0" }, "devDependencies": { - "nodeunit": "^0.9.1" + "nodeunit": "^0.11.3" }, "keywords": [ "emfile", diff --git a/test/fs_test.js b/test/fs_test.js index 149b1c4..567d51a 100644 --- a/test/fs_test.js +++ b/test/fs_test.js @@ -1,15 +1,14 @@ - -var stream = require('../lib/lazystream'); -var fs = require('fs'); -var tmpDir = 'test/tmp/'; -var readFile = 'test/data.md'; -var writeFile = tmpDir + 'data.md'; +const stream = require('../lib/lazystream'); +const fs = require('fs'); +const tmpDir = 'test/tmp/'; +const readFile = 'test/data.md'; +const writeFile = tmpDir + 'data.md'; exports.fs = { readwrite: function(test) { - var readfd, writefd; + let readfd, writefd; - var readable = new stream.Readable(function() { + const readable = new stream.Readable(function() { return fs.createReadStream(readFile) .on('open', function(fd) { readfd = fd; @@ -20,7 +19,7 @@ exports.fs = { }); }); - var writable = new stream.Writable(function() { + const writable = new stream.Writable(function() { return fs.createWriteStream(writeFile) .on('open', function(fd) { writefd = fd; @@ -46,12 +45,12 @@ exports.fs = { readable.on('end', function() { step(); }); writable.on('end', function() { step(); }); - var steps = 0; + let steps = 0; function step() { steps += 1; if (steps == 4) { - var input = fs.readFileSync(readFile); - var output = fs.readFileSync(writeFile); + const input = fs.readFileSync(readFile); + const output = fs.readFileSync(writeFile); test.ok(input >= output && input <= output, 'Should be equal'); diff --git a/test/helper.js b/test/helper.js index 9d41191..f658668 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,39 +1,36 @@ +const { Readable, Writable } = require('readable-stream'); -var _Readable = require('readable-stream/readable'); -var _Writable = require('readable-stream/writable'); -var util = require('util'); - -module.exports = { - DummyReadable: DummyReadable, - DummyWritable: DummyWritable -}; +class DummyReadable extends Readable { + constructor(strings) { + super(); + this.strings = strings; + this.emit('readable'); + } -function DummyReadable(strings) { - _Readable.call(this); - this.strings = strings; - this.emit('readable'); + _read(n) { + if (this.strings.length) { + this.push(new Buffer(this.strings.shift())); + } else { + this.push(null); + } + } } -util.inherits(DummyReadable, _Readable); - -DummyReadable.prototype._read = function _read(n) { - if (this.strings.length) { - this.push(new Buffer(this.strings.shift())); - } else { - this.push(null); +class DummyWritable extends Writable { + constructor(strings) { + super(); + this.strings = strings; + this.emit('writable'); } -}; -function DummyWritable(strings) { - _Writable.call(this); - this.strings = strings; - this.emit('writable'); + _write(chunk, encoding, callback) { + this.strings.push(chunk.toString()); + if (callback) callback(); + } } -util.inherits(DummyWritable, _Writable); - -DummyWritable.prototype._write = function _write(chunk, encoding, callback) { - this.strings.push(chunk.toString()); - if (callback) callback(); +module.exports = { + DummyReadable, + DummyWritable }; diff --git a/test/pipe_test.js b/test/pipe_test.js index 7129e35..fed78c3 100644 --- a/test/pipe_test.js +++ b/test/pipe_test.js @@ -1,22 +1,21 @@ - -var stream = require('../lib/lazystream'); -var helper = require('./helper'); +const stream = require('../lib/lazystream'); +const helper = require('./helper'); exports.pipe = { readwrite: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - var readableInstantiated = false; - var writableInstantiated = false; + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; + let readableInstantiated = false; + let writableInstantiated = false; test.expect(3); - var readable = new stream.Readable(function() { + const readable = new stream.Readable(function() { readableInstantiated = true; return new helper.DummyReadable([].concat(expected)); }); - var writable = new stream.Writable(function() { + const writable = new stream.Writable(function() { writableInstantiated = true; return new helper.DummyWritable(actual); }); diff --git a/test/readable_test.js b/test/readable_test.js index 9ae0636..979662b 100644 --- a/test/readable_test.js +++ b/test/readable_test.js @@ -1,11 +1,10 @@ - -var Readable = require('../lib/lazystream').Readable; -var DummyReadable = require('./helper').DummyReadable; +const { Readable } = require('../lib/lazystream'); +const { DummyReadable } = require('./helper'); exports.readable = { dummy: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; test.expect(1); @@ -21,7 +20,7 @@ exports.readable = { options: function(test) { test.expect(3); - var readable = new Readable(function(options) { + const readable = new Readable(function(options) { test.ok(this instanceof Readable, "Readable should bind itself to callback's this"); test.equal(options.encoding, "utf-8", "Readable should make options accessible to callback"); this.ok = true; @@ -35,13 +34,13 @@ exports.readable = { test.done(); }, streams2: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - var instantiated = false; + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; + let instantiated = false; test.expect(2); - var readable = new Readable(function() { + const readable = new Readable(function() { instantiated = true; return new DummyReadable([].concat(expected)); }); @@ -49,7 +48,7 @@ exports.readable = { test.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); readable.on('readable', function() { - var chunk; + let chunk; while ((chunk = readable.read())) { actual.push(chunk.toString()); } @@ -62,13 +61,13 @@ exports.readable = { readable.read(0); }, resume: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - var instantiated = false; + const expected = [ 'line1\n', 'line2\n' ]; + let actual = []; + let instantiated = false; test.expect(2); - var readable = new Readable(function() { + const readable = new Readable(function() { instantiated = true; return new DummyReadable([].concat(expected)); }); diff --git a/test/writable_test.js b/test/writable_test.js index a663845..d217d52 100644 --- a/test/writable_test.js +++ b/test/writable_test.js @@ -1,12 +1,11 @@ - -var Writable = require('../lib/lazystream').Writable; -var DummyWritable = require('./helper').DummyWritable; +const { Writable } = require('../lib/lazystream'); +const { DummyWritable } = require('./helper'); exports.writable = { options: function(test) { test.expect(3); - var writable = new Writable(function(options) { + const writable = new Writable(function(options) { test.ok(this instanceof Writable, "Writable should bind itself to callback's this"); test.equal(options.encoding, "utf-8", "Writable should make options accessible to callback"); this.ok = true; @@ -20,26 +19,25 @@ exports.writable = { test.done(); }, dummy: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; test.expect(0); - var dummy = new DummyWritable(actual); + const dummy = new DummyWritable(actual); expected.forEach(function(item) { - dummy.write(new Buffer(item)); + dummy.write(Buffer.from(item)); }); test.done(); }, streams2: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - var instantiated = false; + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; + let instantiated = false; test.expect(2); - var writable = new Writable(function() { + const writable = new Writable(function() { instantiated = true; return new DummyWritable(actual); }); @@ -50,9 +48,8 @@ exports.writable = { test.equal(actual.join(''), expected.join(''), 'Writable should not change the data of the underlying stream'); test.done(); }); - expected.forEach(function(item) { - writable.write(new Buffer(item)); + writable.write(Buffer.from(item)); }); writable.end(); } From ecab4b3d4a6eb9f619ef97e5abc5faaa2906194f Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Sun, 25 May 2025 15:57:55 +0300 Subject: [PATCH 2/3] Migrate tests to node:test --- package.json | 7 +-- test/fs_test.js | 78 +++++++++++++++------------------- test/helper.js | 3 +- test/pipe_test.js | 28 ++++++------ test/readable_test.js | 99 ++++++++++++++++++++++--------------------- test/writable_test.js | 53 +++++++++++------------ 6 files changed, 127 insertions(+), 141 deletions(-) diff --git a/package.json b/package.json index 9eeae49..bd8dc65 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,10 @@ "license": "MIT", "main": "lib/lazystream.js", "engines": { - "node": ">= 0.6.3" + "node": ">= 18.0.0" }, "scripts": { - "test": "nodeunit test/readable_test.js test/writable_test.js test/pipe_test.js test/fs_test.js" + "test": "node --test test/readable_test.js test/writable_test.js test/pipe_test.js test/fs_test.js" }, "files": [ "lib/lazystream.js", @@ -34,9 +34,6 @@ "dependencies": { "readable-stream": "^4.7.0" }, - "devDependencies": { - "nodeunit": "^0.11.3" - }, "keywords": [ "emfile", "lazy", diff --git a/test/fs_test.js b/test/fs_test.js index 567d51a..c393f66 100644 --- a/test/fs_test.js +++ b/test/fs_test.js @@ -1,21 +1,28 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); const stream = require('../lib/lazystream'); const fs = require('fs'); +const fsp = fs.promises; const tmpDir = 'test/tmp/'; const readFile = 'test/data.md'; const writeFile = tmpDir + 'data.md'; -exports.fs = { - readwrite: function(test) { +describe('fs', () => { + test('readwrite', async () => { let readfd, writefd; + // Clean up and prepare directories + await fsp.mkdir(tmpDir, { recursive: true }); + try { + await fsp.unlink(writeFile); + } catch (error) { + if (error.code !== 'ENOENT') throw error; + } + const readable = new stream.Readable(function() { return fs.createReadStream(readFile) .on('open', function(fd) { readfd = fd; - }) - .on('close', function() { - readfd = undefined; - step(); }); }); @@ -23,46 +30,29 @@ exports.fs = { return fs.createWriteStream(writeFile) .on('open', function(fd) { writefd = fd; - }) - .on('close', function() { - writefd = undefined; - step(); }); }); - test.expect(3); - - test.equal(readfd, undefined, 'Input file should not be opened until read'); - test.equal(writefd, undefined, 'Output file should not be opened until write'); - - if (!fs.existsSync(tmpDir)) { - fs.mkdirSync(tmpDir); - } - if (fs.existsSync(writeFile)) { - fs.unlinkSync(writeFile); - } - - readable.on('end', function() { step(); }); - writable.on('end', function() { step(); }); - - let steps = 0; - function step() { - steps += 1; - if (steps == 4) { - const input = fs.readFileSync(readFile); - const output = fs.readFileSync(writeFile); - - test.ok(input >= output && input <= output, 'Should be equal'); - - fs.unlinkSync(writeFile); - fs.rmdirSync(tmpDir); - - test.done(); - } - }; - - readable.pipe(writable); - } -}; + assert.equal(readfd, undefined, 'Input file should not be opened until read'); + assert.equal(writefd, undefined, 'Output file should not be opened until write'); + + // Pipe files and wait for completion + await new Promise((resolve, reject) => { + readable.pipe(writable); + writable.on('finish', async () => { + try { + const input = await fsp.readFile(readFile); + const output = await fsp.readFile(writeFile); + + assert.ok(Buffer.isBuffer(input) && Buffer.isBuffer(output), 'Both should be buffers'); + assert.deepEqual(input, output, 'Files should be equal'); + resolve(); + } catch (error) { + reject(error); + } + }); + }); + }); +}); diff --git a/test/helper.js b/test/helper.js index f658668..5ee2783 100644 --- a/test/helper.js +++ b/test/helper.js @@ -9,7 +9,7 @@ class DummyReadable extends Readable { _read(n) { if (this.strings.length) { - this.push(new Buffer(this.strings.shift())); + this.push(Buffer.from(this.strings.shift())); } else { this.push(null); } @@ -33,4 +33,3 @@ module.exports = { DummyReadable, DummyWritable }; - diff --git a/test/pipe_test.js b/test/pipe_test.js index fed78c3..5fa0e70 100644 --- a/test/pipe_test.js +++ b/test/pipe_test.js @@ -1,15 +1,15 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); const stream = require('../lib/lazystream'); const helper = require('./helper'); -exports.pipe = { - readwrite: function(test) { +describe('pipe', () => { + test('readwrite', async () => { const expected = [ 'line1\n', 'line2\n' ]; const actual = []; let readableInstantiated = false; let writableInstantiated = false; - test.expect(3); - const readable = new stream.Readable(function() { readableInstantiated = true; return new helper.DummyReadable([].concat(expected)); @@ -20,16 +20,18 @@ exports.pipe = { return new helper.DummyWritable(actual); }); - test.equal(readableInstantiated, false, 'DummyReadable should only be instantiated when it is needed'); - test.equal(writableInstantiated, false, 'DummyWritable should only be instantiated when it is needed'); + assert.equal(readableInstantiated, false, 'DummyReadable should only be instantiated when it is needed'); + assert.equal(writableInstantiated, false, 'DummyWritable should only be instantiated when it is needed'); + + return new Promise((resolve) => { + writable.on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'Piping on demand streams should keep data intact'); + resolve(); + }); - writable.on('end', function() { - test.equal(actual.join(''), expected.join(''), 'Piping on demand streams should keep data intact'); - test.done(); + readable.pipe(writable); }); - - readable.pipe(writable); - } -}; + }); +}); diff --git a/test/readable_test.js b/test/readable_test.js index 979662b..636b79b 100644 --- a/test/readable_test.js +++ b/test/readable_test.js @@ -1,72 +1,71 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); const { Readable } = require('../lib/lazystream'); const { DummyReadable } = require('./helper'); -exports.readable = { - dummy: function(test) { +describe('readable', () => { + test('dummy', async () => { const expected = [ 'line1\n', 'line2\n' ]; const actual = []; - test.expect(1); - - new DummyReadable([].concat(expected)) - .on('data', function(chunk) { - actual.push(chunk.toString()); - }) - .on('end', function() { - test.equal(actual.join(''), expected.join(''), 'DummyReadable should produce the data it was created with'); - test.done(); - }); - }, - options: function(test) { - test.expect(3); + return new Promise((resolve) => { + new DummyReadable([].concat(expected)) + .on('data', function(chunk) { + actual.push(chunk.toString()); + }) + .on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'DummyReadable should produce the data it was created with'); + resolve(); + }); + }); + }); + test('options', () => { const readable = new Readable(function(options) { - test.ok(this instanceof Readable, "Readable should bind itself to callback's this"); - test.equal(options.encoding, "utf-8", "Readable should make options accessible to callback"); + assert.ok(this instanceof Readable, "Readable should bind itself to callback's this"); + assert.equal(options.encoding, "utf-8", "Readable should make options accessible to callback"); this.ok = true; return new DummyReadable(["test"]); }, {encoding: "utf-8"}); readable.read(4); - test.ok(readable.ok); + assert.ok(readable.ok); + }); - test.done(); - }, - streams2: function(test) { + test('streams2', async () => { const expected = [ 'line1\n', 'line2\n' ]; const actual = []; let instantiated = false; - test.expect(2); - const readable = new Readable(function() { instantiated = true; return new DummyReadable([].concat(expected)); }); - test.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); + assert.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); - readable.on('readable', function() { - let chunk; - while ((chunk = readable.read())) { - actual.push(chunk.toString()); - } - }); - readable.on('end', function() { - test.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream'); - test.done(); + return new Promise((resolve) => { + readable.on('readable', function() { + let chunk; + while ((chunk = readable.read())) { + actual.push(chunk.toString()); + } + }); + readable.on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream'); + resolve(); + }); + + readable.read(0); }); + }); - readable.read(0); - }, - resume: function(test) { + test('resume', async () => { const expected = [ 'line1\n', 'line2\n' ]; let actual = []; let instantiated = false; - test.expect(2); - const readable = new Readable(function() { instantiated = true; return new DummyReadable([].concat(expected)); @@ -74,16 +73,18 @@ exports.readable = { readable.pause(); - readable.on('data', function(chunk) { - actual.push(chunk.toString()); - }); - readable.on('end', function() { - test.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream'); - test.done(); - }); + assert.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); - test.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); + return new Promise((resolve) => { + readable.on('data', function(chunk) { + actual.push(chunk.toString()); + }); + readable.on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream'); + resolve(); + }); - readable.resume(); - } -}; + readable.resume(); + }); + }); +}); diff --git a/test/writable_test.js b/test/writable_test.js index d217d52..b042236 100644 --- a/test/writable_test.js +++ b/test/writable_test.js @@ -1,56 +1,53 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); const { Writable } = require('../lib/lazystream'); const { DummyWritable } = require('./helper'); -exports.writable = { - options: function(test) { - test.expect(3); - +describe('writable', () => { + test('options', () => { const writable = new Writable(function(options) { - test.ok(this instanceof Writable, "Writable should bind itself to callback's this"); - test.equal(options.encoding, "utf-8", "Writable should make options accessible to callback"); + assert.ok(this instanceof Writable, "Writable should bind itself to callback's this"); + assert.equal(options.encoding, "utf-8", "Writable should make options accessible to callback"); this.ok = true; return new DummyWritable([]); }, {encoding: "utf-8"}); writable.write("test"); - test.ok(writable.ok); + assert.ok(writable.ok); + }); - test.done(); - }, - dummy: function(test) { + test('dummy', () => { const expected = [ 'line1\n', 'line2\n' ]; const actual = []; - test.expect(0); - const dummy = new DummyWritable(actual); expected.forEach(function(item) { dummy.write(Buffer.from(item)); }); - test.done(); - }, - streams2: function(test) { + }); + + test('streams2', async () => { const expected = [ 'line1\n', 'line2\n' ]; const actual = []; let instantiated = false; - test.expect(2); - const writable = new Writable(function() { instantiated = true; return new DummyWritable(actual); }); - test.equal(instantiated, false, 'DummyWritable should only be instantiated when it is needed'); - - writable.on('end', function() { - test.equal(actual.join(''), expected.join(''), 'Writable should not change the data of the underlying stream'); - test.done(); - }); - expected.forEach(function(item) { - writable.write(Buffer.from(item)); + assert.equal(instantiated, false, 'DummyWritable should only be instantiated when it is needed'); + + return new Promise((resolve) => { + writable.on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'Writable should not change the data of the underlying stream'); + resolve(); + }); + expected.forEach(function(item) { + writable.write(Buffer.from(item)); + }); + writable.end(); }); - writable.end(); - } -}; + }); +}); From 2d24b09116ecb20e7852a04f7c20a8664dc41ebb Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Sun, 25 May 2025 17:16:42 +0300 Subject: [PATCH 3/3] Use built-in stream instead of readable-stream --- lib/lazystream.js | 2 +- package.json | 3 --- test/helper.js | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/lazystream.js b/lib/lazystream.js index 7f54243..4eaf964 100644 --- a/lib/lazystream.js +++ b/lib/lazystream.js @@ -1,4 +1,4 @@ -const { PassThrough } = require('readable-stream'); +const { PassThrough } = require('stream'); // Patch the given method of instance so that the callback // is executed once, before the actual method is called the diff --git a/package.json b/package.json index bd8dc65..9838b71 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,6 @@ "test/*.js", "test/*.md" ], - "dependencies": { - "readable-stream": "^4.7.0" - }, "keywords": [ "emfile", "lazy", diff --git a/test/helper.js b/test/helper.js index 5ee2783..3fa1daa 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,4 +1,4 @@ -const { Readable, Writable } = require('readable-stream'); +const { Readable, Writable } = require('stream'); class DummyReadable extends Readable { constructor(strings) {