writable-consumable-stream
Version:
An async stream which can be iterated over using a for-await-of loop.
1,614 lines (1,320 loc) • 47.3 kB
JavaScript
const WritableConsumableStream = require('../index');
const assert = require('assert');
let pendingTimeoutSet = new Set();
function wait(duration) {
return new Promise((resolve) => {
let timeout = setTimeout(() => {
pendingTimeoutSet.clear(timeout);
resolve();
}, duration);
pendingTimeoutSet.add(timeout);
});
}
function cancelAllPendingWaits() {
for (let timeout of pendingTimeoutSet) {
clearTimeout(timeout);
}
}
describe('WritableConsumableStream', () => {
let stream;
describe('for-await-of loop', () => {
beforeEach(async () => {
stream = new WritableConsumableStream();
});
afterEach(async () => {
cancelAllPendingWaits();
stream.close();
});
it('should receive packets asynchronously', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(10);
stream.write('hello' + i);
}
stream.close();
})();
let receivedPackets = [];
for await (let packet of stream) {
receivedPackets.push(packet);
}
assert.equal(receivedPackets.length, 10);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should receive packets asynchronously if multiple packets are written sequentially', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(10);
stream.write('a' + i);
stream.write('b' + i);
stream.write('c' + i);
}
stream.close();
})();
let receivedPackets = [];
for await (let packet of stream) {
receivedPackets.push(packet);
}
assert.equal(receivedPackets.length, 30);
assert.equal(receivedPackets[0], 'a0');
assert.equal(receivedPackets[1], 'b0');
assert.equal(receivedPackets[2], 'c0');
assert.equal(receivedPackets[3], 'a1');
assert.equal(receivedPackets[4], 'b1');
assert.equal(receivedPackets[5], 'c1');
assert.equal(receivedPackets[29], 'c9');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should receive packets if stream is written to from inside a consuming for-await-of loop', async () => {
(async () => {
for (let i = 0; i < 3; i++) {
await wait(10);
stream.write('a' + i);
}
})();
let count = 0;
let receivedPackets = [];
for await (let packet of stream) {
receivedPackets.push(packet);
stream.write('nested' + count);
if (++count > 10) {
break;
}
}
assert.equal(receivedPackets[0], 'a0');
assert.equal(receivedPackets.some(message => message === 'nested0'), true);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should only consume messages which were written after the consumer was created', async () => {
stream.write('one');
stream.write('two');
let receivedPackets = [];
let doneConsumingPromise = (async () => {
for await (let packet of stream) {
receivedPackets.push(packet);
}
})();
stream.write('three');
stream.write('four');
stream.write('five');
stream.close();
await doneConsumingPromise;
assert.equal(receivedPackets.length, 3);
assert.equal(receivedPackets[0], 'three');
assert.equal(receivedPackets[1], 'four');
assert.equal(receivedPackets[2], 'five');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should not miss packets if it awaits inside a for-await-of loop', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(2);
stream.write('a' + i);
}
stream.close();
})();
let receivedPackets = [];
for await (let packet of stream) {
receivedPackets.push(packet);
await wait(50);
}
assert.equal(receivedPackets.length, 10);
for (let i = 0; i < 10; i++) {
assert.equal(receivedPackets[i], 'a' + i);
}
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should not miss packets if it awaits inside two concurrent for-await-of loops', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(10);
stream.write('a' + i);
}
stream.close();
})();
let receivedPacketsA = [];
let receivedPacketsB = [];
await Promise.all([
(async () => {
for await (let packet of stream) {
receivedPacketsA.push(packet);
await wait(5);
}
})(),
(async () => {
for await (let packet of stream) {
receivedPacketsB.push(packet);
await wait(50);
}
})()
]);
assert.equal(receivedPacketsA.length, 10);
for (let i = 0; i < 10; i++) {
assert.equal(receivedPacketsA[i], 'a' + i);
}
assert.equal(receivedPacketsB.length, 10);
for (let i = 0; i < 10; i++) {
assert.equal(receivedPacketsB[i], 'a' + i);
}
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to resume consumption after the stream has been closed', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(10);
stream.write('a' + i);
}
stream.close();
})();
let receivedPacketsA = [];
for await (let packet of stream) {
receivedPacketsA.push(packet);
}
assert.equal(receivedPacketsA.length, 10);
(async () => {
for (let i = 0; i < 10; i++) {
await wait(10);
stream.write('b' + i);
}
stream.close();
})();
let receivedPacketsB = [];
for await (let packet of stream) {
receivedPacketsB.push(packet);
}
assert.equal(receivedPacketsB.length, 10);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to resume consumption of messages written within the same stack frame after the stream has been closed', async () => {
stream.write('one');
stream.write('two');
let receivedPackets = [];
let doneConsumingPromiseA = (async () => {
for await (let packet of stream) {
receivedPackets.push(packet);
}
})();
stream.write('three');
stream.write('four');
stream.write('five');
stream.close();
await doneConsumingPromiseA;
let doneConsumingPromiseB = (async () => {
for await (let packet of stream) {
receivedPackets.push(packet);
}
})();
stream.write('six');
stream.write('seven');
stream.close();
await doneConsumingPromiseB;
assert.equal(receivedPackets.length, 5);
assert.equal(receivedPackets[0], 'three');
assert.equal(receivedPackets[1], 'four');
assert.equal(receivedPackets[2], 'five');
assert.equal(receivedPackets[3], 'six');
assert.equal(receivedPackets[4], 'seven');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to optionally timeout the consumer iterator when write delay is consistent', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(30);
stream.write('hello' + i);
}
stream.close();
})();
let receivedPackets = [];
let consumable = stream.createConsumer(20);
let error;
try {
for await (let packet of consumable) {
receivedPackets.push(packet);
}
} catch (err) {
error = err;
}
let consumerStatsList = stream.getConsumerStatsList();
assert.equal(consumerStatsList.length, 0);
assert.notEqual(error, null);
assert.equal(error.name, 'TimeoutError');
assert.equal(receivedPackets.length, 0);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to optionally timeout the consumer iterator when write delay is inconsistent', async () => {
(async () => {
await wait(10);
stream.write('hello0');
await wait(10);
stream.write('hello1');
await wait(10);
stream.write('hello2');
await wait(30);
stream.write('hello3');
stream.close();
})();
let receivedPackets = [];
let consumable = stream.createConsumer(20);
let error;
try {
for await (let packet of consumable) {
receivedPackets.push(packet);
}
} catch (err) {
error = err;
}
let consumerStatsList = stream.getConsumerStatsList();
assert.equal(consumerStatsList.length, 0);
assert.notEqual(error, null);
assert.equal(error.name, 'TimeoutError');
assert.equal(receivedPackets.length, 3);
assert.equal(receivedPackets[0], 'hello0');
assert.equal(receivedPackets[2], 'hello2');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to optionally timeout the consumer iterator even if steam is not explicitly closed', async () => {
(async () => {
await wait(10);
stream.write('hello0');
await wait(10);
stream.write('hello1');
await wait(30);
stream.write('hello2');
})();
let receivedPackets = [];
let consumable = stream.createConsumer(20);
let error;
try {
for await (let packet of consumable) {
receivedPackets.push(packet);
}
} catch (err) {
error = err;
}
let consumerStatsList = stream.getConsumerStatsList();
assert.equal(consumerStatsList.length, 0);
assert.notEqual(error, null);
assert.equal(error.name, 'TimeoutError');
assert.equal(receivedPackets.length, 2);
assert.equal(receivedPackets[0], 'hello0');
assert.equal(receivedPackets[1], 'hello1');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to resume consumption immediately after stream is closed unless a condition is met', async () => {
let resume = true;
(async () => {
for (let i = 0; i < 5; i++) {
await wait(10);
stream.write('hello' + i);
}
// Consumer should be able to resume without missing any messages.
stream.close();
stream.write('world0');
for (let i = 1; i < 5; i++) {
await wait(10);
stream.write('world' + i);
}
resume = false;
stream.close();
})();
let receivedPackets = [];
let consumable = stream.createConsumer();
while (true) {
for await (let data of consumable) {
receivedPackets.push(data);
}
if (!resume) break;
}
assert.equal(receivedPackets.length, 10);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to close stream with custom data', async () => {
(async () => {
for (let i = 0; i < 5; i++) {
await wait(10);
stream.write('hello' + i);
}
stream.close('done123');
})();
let receivedPackets = [];
let receivedEndPacket = null;
let consumer = stream.createConsumer();
while (true) {
let packet = await consumer.next();
if (packet.done) {
receivedEndPacket = packet.value;
break;
}
receivedPackets.push(packet.value);
}
assert.equal(receivedPackets.length, 5);
assert.equal(receivedEndPacket, 'done123');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
});
describe('kill', () => {
beforeEach(async () => {
stream = new WritableConsumableStream();
});
afterEach(async () => {
cancelAllPendingWaits();
stream.close();
});
it('should stop consumer immediately when stream is killed', async () => {
let backpressureBeforeKill;
let backpressureAfterKill;
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
backpressureBeforeKill = stream.getBackpressure();
stream.kill();
backpressureAfterKill = stream.getBackpressure();
})();
let receivedPackets = [];
for await (let packet of stream) {
await wait(50);
receivedPackets.push(packet);
}
let backpressureAfterConsume = stream.getBackpressure();
assert.equal(backpressureBeforeKill, 10);
assert.equal(backpressureAfterKill, 0);
assert.equal(backpressureAfterConsume, 0);
assert.equal(receivedPackets.length, 0);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should throw a timeout error early when stream is killed', async () => {
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
stream.kill();
})();
let error;
try {
await Promise.race([
stream.once(200), // This should throw an error early.
wait(100)
]);
} catch (err) {
error = err;
}
assert.equal(error.name, 'TimeoutError');
let backpressure = stream.getBackpressure();
assert.equal(backpressure, 0);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to restart a killed stream', async () => {
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
await wait(10);
stream.kill();
await wait(70);
for (let i = 0; i < 10; i++) {
stream.write('world' + i);
}
await wait(10);
stream.close();
})();
let receivedPackets = [];
for await (let packet of stream) {
await wait(50);
receivedPackets.push(packet);
}
for await (let packet of stream) {
await wait(50);
receivedPackets.push(packet);
}
let backpressure = stream.getBackpressure();
assert.equal(backpressure, 0);
assert.equal(receivedPackets.length, 11);
assert.equal(receivedPackets[0], 'hello0');
assert.equal(receivedPackets[1], 'world0');
assert.equal(receivedPackets[10], 'world9');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to start writing to a killed stream immediately', async () => {
(async () => {
await wait(10);
stream.kill();
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
stream.close();
})();
let consumer = stream.createConsumer();
let receivedPackets = [];
while (true) {
let packet = await consumer.next();
if (packet.done) break;
receivedPackets.push(packet);
}
while (true) {
let packet = await consumer.next();
if (packet.done) break;
receivedPackets.push(packet);
}
assert.equal(receivedPackets.length, 10);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should set consumer.isAlive to false if stream is killed', async () => { // TODO 22
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
stream.close();
})();
let consumer = stream.createConsumer();
assert.equal(consumer.isAlive, true);
stream.kill();
assert.equal(consumer.isAlive, false);
let receivedPackets = [];
while (true) {
let packet = await consumer.next();
if (packet.done) break;
receivedPackets.push(packet);
}
assert.equal(receivedPackets.length, 0);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should pass kill data to consumer when stream is killed if using consumer', async () => {
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
stream.kill(12345);
})();
let consumer = stream.createConsumer();
let receivedPackets = [];
while (true) {
let packet = await consumer.next();
await wait(50);
receivedPackets.push(packet);
if (packet.done) {
break;
}
}
assert.equal(receivedPackets.length, 1);
assert.equal(receivedPackets[0].value, 12345);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should stop consumer at the end of the current iteration when stream is killed and iteration has already started', async () => {
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
await wait(10);
stream.kill();
})();
let receivedPackets = [];
for await (let packet of stream) {
await wait(50);
receivedPackets.push(packet);
}
assert.equal(receivedPackets.length, 1);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should stop all consumers immediately', async () => {
let isWriting = true;
(async () => {
for (let i = 0; i < 10; i++) {
await wait(40);
if (!isWriting) return;
stream.write('a' + i);
}
})();
(async () => {
await wait(220);
stream.kill();
isWriting = false;
})();
let receivedPacketsA = [];
let receivedPacketsB = [];
await Promise.all([
(async () => {
for await (let packet of stream) {
receivedPacketsA.push(packet);
}
})(),
(async () => {
for await (let packet of stream) {
receivedPacketsB.push(packet);
await wait(300);
}
})()
]);
assert.equal(receivedPacketsA.length, 5);
assert.equal(receivedPacketsB.length, 1);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should stop consumers which have not started iterating', async () => {
let consumer = stream.createConsumer();
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
stream.kill('end');
await wait(10);
assert.equal(consumer.getBackpressure(), 0);
});
});
describe('backpressure', () => {
beforeEach(async () => {
stream = new WritableConsumableStream();
});
afterEach(async () => {
cancelAllPendingWaits();
stream.close();
});
it('should track backpressure correctly when consuming stream', async () => {
await Promise.all([
(async () => {
let consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 0);
await wait(10);
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 0);
assert.equal(consumerStats[0].id, 1);
assert.equal(stream.hasConsumer(1), true);
assert.equal(stream.hasConsumer(2), false);
stream.write('a0');
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 1);
await wait(10);
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 0);
stream.write('a1');
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 1);
await wait(10);
stream.write('a2');
await wait(10);
stream.write('a3');
await wait(10);
stream.write('a4');
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 4);
stream.close();
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 5);
})(),
(async () => {
let expectedPressure = 6;
for await (let data of stream) {
expectedPressure--;
await wait(70);
let consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, expectedPressure);
}
let consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 0);
})()
]);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should track backpressure correctly when consuming stream with a consumer', async () => {
await Promise.all([
(async () => {
for (let i = 0; i < 10; i++) {
await wait(10);
stream.write('a' + i);
let consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, i + 1);
}
stream.close();
})(),
(async () => {
let iter = stream.createConsumer();
assert.equal(iter.id, 1);
await wait(20);
let expectedPressure = 11;
while (true) {
expectedPressure--;
await wait(120);
let data = await iter.next();
let consumerStats = stream.getConsumerStatsList();
if (data.done) {
assert.equal(consumerStats.length, 0);
break;
}
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, expectedPressure);
}
})()
]);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should track backpressure correctly when writing to and consuming stream intermittently with multiple consumers', async () => {
let iterA = stream.createConsumer();
assert.equal(iterA.id, 1);
let consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 0);
await wait(10);
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 0);
stream.write('a0');
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 1);
stream.write('a1');
await wait(10);
stream.write('a2');
await wait(10);
let iterB = stream.createConsumer();
assert.equal(iterB.id, 2);
stream.write('a3');
await wait(10);
stream.write('a4');
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 2);
assert.equal(consumerStats[0].backpressure, 5);
assert.equal(stream.getBackpressure(), 5);
assert.equal(iterA.getBackpressure(), 5);
assert.equal(stream.getConsumerBackpressure(1), 5);
assert.equal(iterB.getBackpressure(), 2);
assert.equal(stream.getConsumerBackpressure(2), 2);
await iterA.next();
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 2);
assert.equal(consumerStats[0].backpressure, 4);
await iterA.next();
await iterA.next();
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 2);
assert.equal(consumerStats[0].backpressure, 2);
stream.write('a5');
stream.write('a6');
stream.write('a7');
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 2);
assert.equal(consumerStats[0].backpressure, 5);
assert.equal(stream.getConsumerBackpressure(2), 5);
stream.close();
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 2);
assert.equal(consumerStats[0].backpressure, 6);
assert.equal(stream.getBackpressure(), 6);
await iterA.next();
await iterA.next();
await wait(10);
await iterA.next();
await iterA.next();
await iterA.next();
assert.equal(stream.getConsumerBackpressure(2), 6);
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 2);
assert.equal(consumerStats[0].backpressure, 1);
await iterB.next();
await iterB.next();
await iterB.next();
await iterB.next();
await iterB.next();
assert.equal(stream.getConsumerBackpressure(2), 1);
let iterBData = await iterB.next();
assert.equal(stream.getConsumerBackpressure(2), 0);
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 1);
assert.equal(consumerStats[0].backpressure, 1);
assert.equal(stream.getBackpressure(), 1);
let iterAData = await iterA.next();
consumerStats = stream.getConsumerStatsList();
assert.equal(consumerStats.length, 0);
assert.equal(iterAData.done, true);
assert.equal(iterBData.done, true);
assert.equal(iterA.getBackpressure(), 0);
assert.equal(iterB.getBackpressure(), 0);
assert.equal(stream.getBackpressure(), 0);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should reset backpressure after invoking consumer.return()', async () => {
let consumer = stream.createConsumer();
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
stream.close('end');
await wait(10);
consumer.return();
assert.equal(stream.getConsumerStatsList().length, 0);
assert.equal(consumer.getBackpressure(), 0);
for (let i = 0; i < 10; i++) {
stream.write('hi' + i);
}
stream.close('end');
await wait(10);
assert.equal(stream.getConsumerStatsList().length, 0);
assert.equal(consumer.getBackpressure(), 0);
consumer.return();
assert.equal(stream.getConsumerStatsList().length, 0);
assert.equal(consumer.getBackpressure(), 0);
});
it('should be able to calculate correct backpressure after invoking consumer.return()', async () => {
let consumer = stream.createConsumer();
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
stream.close('end');
await wait(10);
consumer.return();
assert.equal(stream.getConsumerStatsList().length, 0);
assert.equal(consumer.getBackpressure(), 0);
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hi' + i);
}
stream.close('end');
})();
let receivedPackets = [];
let expectedPressure = 10;
while (true) {
let packet = await consumer.next();
assert.equal(consumer.getBackpressure(), expectedPressure);
let consumerStatsList = stream.getConsumerStatsList();
if (expectedPressure > 0) {
assert.equal(consumerStatsList.length, 1);
assert.equal(consumerStatsList[0].backpressure, expectedPressure);
} else {
assert.equal(consumerStatsList.length, 0);
}
expectedPressure--;
receivedPackets.push(packet);
if (packet.done) break;
}
assert.equal(receivedPackets.length, 11);
assert.equal(receivedPackets[0].value, 'hi0');
assert.equal(receivedPackets[9].value, 'hi9');
assert.equal(receivedPackets[10].done, true);
assert.equal(receivedPackets[10].value, 'end');
assert.equal(stream.getConsumerStatsList().length, 0);
assert.equal(consumer.getBackpressure(), 0);
});
});
describe('await once', () => {
beforeEach(async () => {
stream = new WritableConsumableStream();
});
afterEach(async () => {
cancelAllPendingWaits();
stream.close();
});
it('should receive next packet asynchronously when once() method is used', async () => {
(async () => {
for (let i = 0; i < 3; i++) {
await wait(10);
stream.write('a' + i);
}
})();
let nextPacket = await stream.once();
assert.equal(nextPacket, 'a0');
nextPacket = await stream.once();
assert.equal(nextPacket, 'a1');
nextPacket = await stream.once();
assert.equal(nextPacket, 'a2');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should throw an error if a number is passed to the once() method and it times out', async () => {
(async () => {
for (let i = 0; i < 3; i++) {
await wait(20);
stream.write('a' + i);
}
})();
let nextPacket = await stream.once(30);
assert.equal(nextPacket, 'a0');
let error;
nextPacket = null;
try {
nextPacket = await stream.once(10);
} catch (err) {
error = err;
}
assert.equal(nextPacket, null);
assert.notEqual(error, null);
assert.equal(error.name, 'TimeoutError');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should not resolve once() call when stream.close() is called', async () => {
(async () => {
await wait(10);
stream.close();
})();
let receivedPackets = [];
(async () => {
let nextPacket = await stream.once();
receivedPackets.push(nextPacket);
})();
await wait(100);
assert.equal(receivedPackets.length, 0);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should not resolve previous once() call after stream.close() is called', async () => {
(async () => {
await wait(10);
stream.close();
await wait(10);
stream.write('foo');
})();
let receivedPackets = [];
(async () => {
let nextPacket = await stream.once();
receivedPackets.push(nextPacket);
})();
await wait(100);
assert.equal(receivedPackets.length, 0);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should resolve once() if it is called after stream.close() is called and then a new packet is written', async () => {
(async () => {
await wait(10);
stream.close();
await wait(10);
stream.write('foo');
})();
let receivedPackets = [];
(async () => {
let nextPacket = await stream.once();
receivedPackets.push(nextPacket);
})();
await wait(100);
assert.equal(receivedPackets.length, 0);
(async () => {
await wait(10);
stream.write('bar');
})();
let packet = await stream.once();
assert.equal(packet, 'bar');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
});
describe('while loop with await inside', () => {
beforeEach(async () => {
stream = new WritableConsumableStream();
});
afterEach(async () => {
cancelAllPendingWaits();
stream.close();
});
it('should receive packets asynchronously', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(10);
stream.write('hello' + i);
}
stream.close();
})();
let receivedPackets = [];
let asyncIterator = stream.createConsumer();
while (true) {
let packet = await asyncIterator.next();
if (packet.done) break;
receivedPackets.push(packet.value);
}
assert.equal(receivedPackets.length, 10);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should receive packets asynchronously if multiple packets are written sequentially', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(10);
stream.write('a' + i);
stream.write('b' + i);
stream.write('c' + i);
}
stream.close();
})();
let receivedPackets = [];
let asyncIterator = stream.createConsumer();
while (true) {
let packet = await asyncIterator.next();
if (packet.done) break;
receivedPackets.push(packet.value);
}
assert.equal(receivedPackets.length, 30);
assert.equal(receivedPackets[0], 'a0');
assert.equal(receivedPackets[1], 'b0');
assert.equal(receivedPackets[2], 'c0');
assert.equal(receivedPackets[3], 'a1');
assert.equal(receivedPackets[4], 'b1');
assert.equal(receivedPackets[5], 'c1');
assert.equal(receivedPackets[29], 'c9');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to timeout the consumer if the stream is idle for too long', async () => {
(async () => {
for (let i = 0; i < 10; i++) {
await wait(30);
stream.write('hello' + i);
}
stream.close();
})();
let receivedPackets = [];
let asyncIterator = stream.createConsumer(20);
let error;
try {
while (true) {
let packet = await asyncIterator.next();
if (packet.done) break;
receivedPackets.push(packet.value);
}
} catch (err) {
error = err;
}
assert.equal(receivedPackets.length, 0);
assert.notEqual(error, null);
assert.equal(error.name, 'TimeoutError');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to continue iterating if a single iteration times out', async () => {
(async () => {
await wait(20);
stream.write('hello0');
await wait(20);
stream.write('hello1');
await wait(40);
stream.write('hello2');
await wait(20);
stream.write('hello3');
await wait(20);
stream.write('hello4');
stream.close();
})();
let receivedPackets = [];
let asyncIterator = stream.createConsumer(30);
let errors = [];
while (true) {
let packet;
try {
packet = await asyncIterator.next();
} catch (err) {
errors.push(err);
continue;
}
if (packet.done) break;
receivedPackets.push(packet.value);
}
assert.equal(receivedPackets.length, 5);
assert.equal(errors.length, 1);
assert.notEqual(errors[0], null);
assert.equal(errors[0].name, 'TimeoutError');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
});
describe('actions on an individual consumer', () => {
beforeEach(async () => {
stream = new WritableConsumableStream();
});
afterEach(async () => {
cancelAllPendingWaits();
stream.close();
});
it('should stop a specific consumer immediately when that consumer is killed', async () => {
let backpressureBeforeKill;
let backpressureAfterKill;
let consumerA = stream.createConsumer();
let consumerB = stream.createConsumer();
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
backpressureBeforeKill = stream.getBackpressure();
stream.killConsumer(consumerA.id, 'custom kill data');
backpressureAfterKill = stream.getBackpressure();
stream.close();
})();
let receivedPacketsA = [];
let receivedPacketsB = [];
await Promise.all([
(async () => {
while (true) {
let packet = await consumerA.next();
await wait(50);
receivedPacketsA.push(packet);
if (packet.done) break;
}
})(),
(async () => {
while (true) {
let packet = await consumerB.next();
await wait(50);
receivedPacketsB.push(packet);
if (packet.done) break;
}
})()
]);
let backpressureAfterConsume = stream.getBackpressure();
assert.equal(backpressureBeforeKill, 10);
assert.equal(backpressureAfterKill, 10); // consumerB was still running.
assert.equal(backpressureAfterConsume, 0);
assert.equal(receivedPacketsA.length, 1);
assert.equal(receivedPacketsA[0].done, true);
assert.equal(receivedPacketsA[0].value, 'custom kill data');
assert.equal(receivedPacketsB.length, 11);
assert.equal(receivedPacketsB[0].value, 'hello0');
assert.equal(receivedPacketsB[9].value, 'hello9');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should stop a specific consumer when that consumer is closed', async () => {
let maxBackpressureBeforeClose;
let maxBackpressureAfterClose;
let backpressureBeforeCloseA;
let backpressureBeforeCloseB;
let consumerA = stream.createConsumer();
let consumerB = stream.createConsumer();
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
maxBackpressureBeforeClose = stream.getBackpressure();
stream.closeConsumer(consumerA.id, 'custom close data');
maxBackpressureAfterClose = stream.getBackpressure();
stream.write('foo');
backpressureBeforeCloseA = stream.getConsumerBackpressure(consumerA.id);
backpressureBeforeCloseB = stream.getConsumerBackpressure(consumerB.id);
stream.close('close others');
})();
let receivedPacketsA = [];
let receivedPacketsB = [];
await Promise.all([
(async () => {
while (true) {
let packet = await consumerA.next();
await wait(50);
receivedPacketsA.push(packet);
if (packet.done) break;
}
})(),
(async () => {
while (true) {
let packet = await consumerB.next();
await wait(50);
receivedPacketsB.push(packet);
if (packet.done) break;
}
})()
]);
let maxBackpressureAfterConsume = stream.getBackpressure();
assert.equal(backpressureBeforeCloseA, 12);
assert.equal(backpressureBeforeCloseB, 12);
assert.equal(maxBackpressureBeforeClose, 10);
assert.equal(maxBackpressureAfterClose, 11);
assert.equal(maxBackpressureAfterConsume, 0);
assert.equal(receivedPacketsA.length, 11);
assert.equal(receivedPacketsA[0].value, 'hello0');
assert.equal(receivedPacketsA[9].value, 'hello9');
assert.equal(receivedPacketsA[10].done, true);
assert.equal(receivedPacketsA[10].value, 'custom close data');
assert.equal(receivedPacketsB.length, 12);
assert.equal(receivedPacketsB[0].value, 'hello0');
assert.equal(receivedPacketsB[9].value, 'hello9');
assert.equal(receivedPacketsB[10].value, 'foo');
assert.equal(receivedPacketsB[11].done, true);
assert.equal(receivedPacketsB[11].value, 'close others');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should support closing only one of multiple consumers', async () => {
let backpressureBeforeClose;
let backpressureAfterClose;
let consumerA = stream.createConsumer();
let consumerB = stream.createConsumer();
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
backpressureBeforeClose = stream.getBackpressure();
stream.closeConsumer(consumerA.id, 'custom close data');
backpressureAfterClose = stream.getBackpressure();
})();
let receivedPacketsA = [];
let receivedPacketsB = [];
await Promise.race([
(async () => {
while (true) {
let packet = await consumerA.next();
await wait(50);
receivedPacketsA.push(packet);
if (packet.done) break;
}
})(),
(async () => {
while (true) {
let packet = await consumerB.next();
await wait(50);
receivedPacketsB.push(packet);
if (packet.done) break;
}
})()
]);
let backpressureAfterConsume = stream.getBackpressure();
assert.equal(backpressureBeforeClose, 10);
assert.equal(backpressureAfterClose, 11);
assert.equal(backpressureAfterConsume, 0);
assert.equal(receivedPacketsA.length, 11);
assert.equal(receivedPacketsA[10].done, true);
assert.equal(receivedPacketsA[10].value, 'custom close data');
assert.equal(receivedPacketsB.length, 10);
assert.equal(receivedPacketsB[0].value, 'hello0');
assert.equal(receivedPacketsB[9].value, 'hello9');
stream.close(consumerB.id);
await wait(10);
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
it('should be able to write to a specific consumer', async () => {
let consumerA = stream.createConsumer();
let consumerB = stream.createConsumer();
(async () => {
await wait(10);
for (let i = 0; i < 10; i++) {
stream.write('hello' + i);
}
for (let i = 0; i < 3; i++) {
stream.writeToConsumer(consumerA.id, 'hi' + i);
}
stream.close('close all');
})();
let receivedPacketsA = [];
let receivedPacketsB = [];
await Promise.all([
(async () => {
while (true) {
let packet = await consumerA.next();
await wait(50);
receivedPacketsA.push(packet);
if (packet.done) break;
}
})(),
(async () => {
while (true) {
let packet = await consumerB.next();
await wait(50);
receivedPacketsB.push(packet);
if (packet.done) break;
}
})()
]);
let backpressureAfterConsume = stream.getBackpressure();
assert.equal(backpressureAfterConsume, 0);
assert.equal(receivedPacketsA.length, 14);
assert.equal(receivedPacketsA[0].value, 'hello0');
assert.equal(receivedPacketsA[9].value, 'hello9');
assert.equal(receivedPacketsA[10].value, 'hi0');
assert.equal(receivedPacketsA[12].value, 'hi2');
assert.equal(receivedPacketsA[13].done, true);
assert.equal(receivedPacketsA[13].value, 'close all');
assert.equal(receivedPacketsB.length, 11);
assert.equal(receivedPacketsB[0].value, 'hello0');
assert.equal(receivedPacketsB[9].value, 'hello9');
assert.equal(receivedPacketsB[10].done, true);
assert.equal(receivedPacketsB[10].value, 'close all');
assert.equal(stream.getConsumerCount(), 0); // Check internal cleanup.
});
});
describe('consumer count', () => {
beforeEach(async () => {
stream = new WritableConsumableStream();
});
afterEach(async () => {
cancelAllPendingWaits();
stream.close();
});
it('should return the number of consumers as 1 after stream.createConsumer() is used once', async () => {
(async () => {
for await (let message of stream.createConsumer()) {}
})();
assert.equal(stream.getConsumerCount(), 1);
});
it('should return the number of consumers as 2 after stream.createConsumer() is used twice', async () => {
(async () => {
for await (let message of stream.createConsumer()) {}
})();
(async () => {
for await (let message of stream.createConsumer()) {}
})();
assert.equal(stream.getConsumerCount(), 2);
});
it('should return the number of consumers as 1 after stream is consumed directly by for-await-of loop once', async () => {
(async () => {
for await (let message of stream) {}
})();
assert.equal(stream.getConsumerCount(), 1);
});
it('should return the number of consumers as 2 after stream is consumed directly by for-await-of loop twice', async () => {
(async () => {
for await (let message of stream) {}
})();
(async () => {
for await (let message of stream) {}
})();
assert.equal(stream.getConsumerCount(), 2);
});
it('should return the number of consumers as 0 after stream is killed', async () => {
(async () => {
for await (let message of stream.createConsumer()) {}
})();
(async () => {
for await (let message of stream.createConsumer()) {}
})();
stream.kill();
assert.equal(stream.getConsumerCount(), 0);
});
it('should return the number of consumers as 1 after a specific consumer is killed', async () => {
let consumerA;
(async () => {
consumerA = stream.createConsumer();
for await (let message of consumerA) {}
})();
(async () => {
for await (let message of stream.createConsumer()) {}
})();
stream.killConsumer(consumerA.id);
assert.equal(stream.getConsumerCount(), 1);
});
it('should return the number of consumers as 0 after stream is close after last message has been consumed', async () => {
(async () => {
for await (let message of stream.createConsumer()) {}
})();
(async () => {
for await (let message of stream.createConsumer()) {}
})();
(async () => {
for await (let message of stream.createConsumer()) {}
})();
stream.close();
assert.equal(stream.getConsumerCount(), 3);
await wait(1000);
assert.equal(stream.getConsumerCount(), 0);
});
it('should return the number of consumers as 1 after a specific consumer is closed after last message has been consumed', async () => {
let consumerA;
(async () => {
consumerA = stream.createConsumer();
for await (let message of consumerA) {}
})();
(async () => {
for await (let message of stream.createConsumer()) {}
})();
stream.closeConsumer(consumerA.id);
assert.equal(stream.getConsumerCount(), 2);
await wait(1000);
assert.equal(stream.getConsumerCount(), 1);
});
});
});