strom/tests/compose.spec.ts

278 lines
7.6 KiB
TypeScript
Raw Normal View History

2019-08-21 19:40:19 +00:00
const test = require("ava");
2019-08-22 18:52:39 +00:00
const { expect } = require("chai");
const { compose, composeDuplex, map, rate } = require("../src");
const { sleep } = require("../src/helpers");
import { performance } from "perf_hooks";
2019-08-21 19:40:19 +00:00
2019-08-22 18:52:39 +00:00
test.cb("compose() chains two streams together in the correct order", t => {
t.plan(3);
let i = 0;
const first = map((chunk: number) => chunk + 1);
const second = map((chunk: number) => chunk * 2);
2019-08-21 19:40:19 +00:00
const composed = compose(
[first, second],
{ objectMode: true },
);
2019-08-22 16:07:30 +00:00
composed.on("data", data => {
2019-08-22 18:52:39 +00:00
expect(data).to.equal(result[i]);
t.pass();
i++;
if (i === 3) {
t.end();
}
2019-08-22 16:07:30 +00:00
});
2019-08-22 18:52:39 +00:00
composed.on("error", err => {
t.end(err);
2019-08-22 16:07:30 +00:00
});
2019-08-22 18:52:39 +00:00
composed.on("end", () => {
t.end();
2019-08-22 16:07:30 +00:00
});
2019-08-21 19:40:19 +00:00
2019-08-22 18:52:39 +00:00
const input = [1, 2, 3];
const result = [4, 6, 8];
input.forEach(item => composed.write(item));
2019-08-22 16:07:30 +00:00
});
2019-08-22 18:52:39 +00:00
test.cb(
"compose() followed by pipe chains streams together in the correct order",
t => {
t.plan(3);
let i = 0;
const first = map((chunk: number) => chunk + 1);
const second = map((chunk: number) => chunk * 2);
2019-08-21 19:40:19 +00:00
2019-08-22 18:52:39 +00:00
const composed = compose(
[first, second],
{ objectMode: true },
);
const third = map((chunk: number) => chunk + 1);
composed.pipe(third).on("data", data => {
expect(data).to.equal(result[i]);
t.pass();
i++;
if (i === 3) {
t.end();
}
});
2019-08-22 16:07:30 +00:00
2019-08-22 18:52:39 +00:00
composed.on("error", err => {
t.end(err);
});
const input = [1, 2, 3];
const result = [5, 7, 9];
input.forEach(item => composed.write(item));
},
);
test.cb(
"compose() should emit drain event after 1 second when first stream is bottleneck",
t => {
2019-09-07 18:27:55 +00:00
t.plan(6);
2019-09-07 21:14:08 +00:00
let passedBottleneck = 0;
2019-09-07 18:27:55 +00:00
interface Chunk {
index: number;
mapped: string[];
}
const first = map(
2019-09-07 18:27:55 +00:00
async (chunk: Chunk) => {
await sleep(200);
2019-09-07 21:14:08 +00:00
passedBottleneck++;
2019-09-07 18:27:55 +00:00
chunk.mapped.push("first");
return chunk;
},
{
objectMode: true,
},
);
const second = map(
2019-09-07 18:27:55 +00:00
async (chunk: Chunk) => {
chunk.mapped.push("second");
return chunk;
},
{ objectMode: true },
);
const composed = compose(
[first, second],
2019-09-07 21:14:08 +00:00
{ objectMode: true, highWaterMark: 5 },
);
composed.on("error", err => {
t.end(err);
});
composed.on("drain", err => {
2019-09-07 21:14:08 +00:00
expect(composed._writableState.length).to.be.equal(0);
expect(performance.now() - start).to.be.greaterThan(1000);
t.pass();
});
2019-09-07 18:27:55 +00:00
composed.on("data", (chunk: Chunk) => {
2019-09-07 21:14:08 +00:00
// Since first is bottleneck, composed accumulates until cb is executed in first. Therefore buffer should contain 4, 3, 2, 1 then 0 elements
expect(composed._writableState.length).to.be.equal(
input.length - passedBottleneck,
);
2019-09-07 18:27:55 +00:00
expect(chunk.mapped.length).to.equal(2);
expect(chunk.mapped).to.deep.equal(["first", "second"]);
t.pass();
if (chunk.index === 5) {
t.end();
}
});
const input = [
2019-09-07 21:14:08 +00:00
{ index: 1, mapped: [] },
{ index: 2, mapped: [] },
{ index: 3, mapped: [] },
{ index: 4, mapped: [] },
{ index: 5, mapped: [] },
];
input.forEach(item => {
composed.write(item);
});
const start = performance.now();
},
);
test.cb(
"compose() should emit drain event immediately when second stream is bottleneck",
t => {
2019-09-07 18:27:55 +00:00
t.plan(6);
interface Chunk {
index: number;
mapped: string[];
}
const first = map(
2019-09-07 18:27:55 +00:00
async (chunk: Chunk) => {
chunk.mapped.push("first");
return chunk;
},
{
objectMode: true,
},
);
const second = map(
2019-09-07 18:27:55 +00:00
async (chunk: Chunk) => {
2019-09-07 21:14:08 +00:00
pendingReads--;
await sleep(500);
2019-09-07 21:14:08 +00:00
expect(first._readableState.length).to.equal(pendingReads);
2019-09-07 18:27:55 +00:00
chunk.mapped.push("second");
return chunk;
},
2019-09-07 21:14:08 +00:00
{ objectMode: true, highWaterMark: 1 },
);
const composed = compose(
[first, second],
2019-09-07 21:14:08 +00:00
{ objectMode: true, highWaterMark: 5 },
);
composed.on("error", err => {
t.end(err);
});
composed.on("drain", err => {
2019-09-07 21:14:08 +00:00
expect(composed._writableState.length).to.be.equal(0);
expect(performance.now() - start).to.be.lessThan(100);
t.pass();
});
2019-09-07 18:27:55 +00:00
composed.on("data", (chunk: Chunk) => {
2019-09-07 21:14:08 +00:00
// Since second is bottleneck, composed will write into first immediately. Buffer should be empty.
expect(composed._writableState.length).to.be.equal(0);
2019-09-07 18:27:55 +00:00
expect(chunk.mapped.length).to.equal(2);
expect(chunk.mapped).to.deep.equal(["first", "second"]);
t.pass();
if (chunk.index === 5) {
t.end();
}
});
const input = [
2019-09-07 18:27:55 +00:00
{ index: 1, mapped: [] },
{ index: 2, mapped: [] },
{ index: 3, mapped: [] },
{ index: 4, mapped: [] },
{ index: 5, mapped: [] },
];
2019-09-07 21:14:08 +00:00
let pendingReads = input.length;
input.forEach(item => {
composed.write(item);
});
const start = performance.now();
},
);
test.cb(
2019-09-07 18:27:55 +00:00
"compose() should emit drain event and first should contain up to highWaterMark items in readable state when second is bottleneck",
t => {
2019-09-07 18:27:55 +00:00
t.plan(6);
interface Chunk {
index: number;
mapped: string[];
}
const first = map(
2019-09-07 18:27:55 +00:00
async (chunk: Chunk) => {
expect(first._readableState.length).to.be.at.most(2);
2019-09-07 18:27:55 +00:00
chunk.mapped.push("first");
return chunk;
},
{
objectMode: true,
highWaterMark: 2,
},
);
const second = map(
2019-09-07 18:27:55 +00:00
async (chunk: Chunk) => {
expect(second._writableState.length).to.be.equal(1);
await sleep(100);
2019-09-07 18:27:55 +00:00
chunk.mapped.push("second");
return chunk;
},
{ objectMode: true, highWaterMark: 2 },
);
const composed = compose(
[first, second],
2019-09-07 21:14:08 +00:00
{ objectMode: true, highWaterMark: 5 },
);
composed.on("error", err => {
t.end(err);
});
2019-09-07 18:27:55 +00:00
composed.on("data", (chunk: Chunk) => {
expect(chunk.mapped.length).to.equal(2);
expect(chunk.mapped).to.deep.equal(["first", "second"]);
t.pass();
if (chunk.index === 5) {
t.end();
}
});
2019-09-07 18:27:55 +00:00
composed.on("drain", () => {
2019-09-07 21:14:08 +00:00
expect(composed._writableState.length).to.be.equal(0);
2019-09-07 18:27:55 +00:00
t.pass();
});
const input = [
2019-09-07 18:27:55 +00:00
{ index: 1, mapped: [] },
{ index: 2, mapped: [] },
{ index: 3, mapped: [] },
{ index: 4, mapped: [] },
{ index: 5, mapped: [] },
];
input.forEach(item => {
composed.write(item);
});
},
);