mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
Node sets a test's concurrency to Infinity for `true` whenever the test has a parent, and to os.availableParallelism() - 1 only for the root test. Every test and suite a program declares has a parent, so `true` now runs all of its subtests at once instead of capping them at the CPU count minus one. The concurrency fixture adds a suite of 64 concurrent tests that checks all of them run at once.
198 lines
5.1 KiB
TypeScript
198 lines
5.1 KiB
TypeScript
// @exit: 1
|
|
// The concurrency option on suites and tests. Subtests of a parent with a
|
|
// concurrency above 1 run at the same time, up to that many at once, and
|
|
// start in declaration order as slots free up; nested suites and tests
|
|
// inherit the parent's value. Results are reported in declaration order.
|
|
// t.test and test() calls inside a concurrent test start the subtest at
|
|
// once. A concurrency that is not a positive integer up to 4294967295
|
|
// throws Node's ERR_OUT_OF_RANGE when the test or suite is created.
|
|
import { after, beforeEach, describe, it, test } from "node:test";
|
|
|
|
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms * 3));
|
|
const log: string[] = [];
|
|
const expected: string[] = [];
|
|
|
|
describe("unlimited", { concurrency: true }, () => {
|
|
it("slow", async () => {
|
|
log.push("slow start");
|
|
await delay(60);
|
|
log.push("slow end");
|
|
});
|
|
it("fast", async () => {
|
|
log.push("fast start");
|
|
await delay(10);
|
|
log.push("fast end");
|
|
});
|
|
it("sync", () => {
|
|
log.push("sync");
|
|
});
|
|
after(() => {
|
|
expected.push("slow start", "fast start", "sync", "fast end", "slow end");
|
|
});
|
|
});
|
|
|
|
describe("limited", { concurrency: 2 }, () => {
|
|
it("a", async () => {
|
|
log.push("a start");
|
|
await delay(40);
|
|
log.push("a end");
|
|
});
|
|
it("b", async () => {
|
|
log.push("b start");
|
|
await delay(10);
|
|
log.push("b end");
|
|
});
|
|
it("c", async () => {
|
|
log.push("c start");
|
|
await delay(10);
|
|
log.push("c end");
|
|
});
|
|
after(() => {
|
|
expected.push("a start", "b start", "b end", "c start", "c end", "a end");
|
|
});
|
|
});
|
|
|
|
describe("in order", { concurrency: false }, () => {
|
|
it("x", async () => {
|
|
log.push("x start");
|
|
await delay(20);
|
|
log.push("x end");
|
|
});
|
|
it("y", () => {
|
|
log.push("y");
|
|
});
|
|
after(() => {
|
|
expected.push("x start", "x end", "y");
|
|
});
|
|
});
|
|
|
|
describe("turns", { concurrency: true }, () => {
|
|
it("ticker", async () => {
|
|
for (let i = 0; i < 12; i++) {
|
|
log.push(`t${i}`);
|
|
await null;
|
|
}
|
|
});
|
|
describe("hooked", () => {
|
|
beforeEach(() => {
|
|
log.push("hook");
|
|
});
|
|
it("h", () => {
|
|
log.push("h");
|
|
});
|
|
});
|
|
describe("plain", () => {
|
|
it("p", () => {
|
|
log.push("p");
|
|
});
|
|
});
|
|
it("s", () => {
|
|
log.push("s");
|
|
});
|
|
after(() => {
|
|
expected.push("t0", "s", "t1", "t2", "hook", "p", "t3", "t4", "t5", "t6", "t7", "t8");
|
|
expected.push("t9", "t10", "h", "t11");
|
|
});
|
|
});
|
|
|
|
// `true` runs every subtest at once, whatever the CPU count.
|
|
let running = 0;
|
|
let most = 0;
|
|
describe("all at once", { concurrency: true }, () => {
|
|
for (let i = 0; i < 64; i++) {
|
|
it(`n${i}`, async () => {
|
|
running++;
|
|
most = Math.max(most, running);
|
|
await delay(5);
|
|
running--;
|
|
});
|
|
}
|
|
after(() => {
|
|
log.push(`most ${most}`);
|
|
expected.push("most 64");
|
|
});
|
|
});
|
|
|
|
test("subtests", { concurrency: 2 }, async (t) => {
|
|
void t.test("u1", async () => {
|
|
log.push("u1 start");
|
|
await delay(30);
|
|
log.push("u1 end");
|
|
});
|
|
void t.test("u2", async () => {
|
|
log.push("u2 start");
|
|
await delay(5);
|
|
log.push("u2 end");
|
|
});
|
|
void t.test("u3", async () => {
|
|
log.push("u3 start");
|
|
await delay(5);
|
|
log.push("u3 end");
|
|
});
|
|
log.push("body end");
|
|
expected.push("u1 start", "u2 start", "body end", "u2 end", "u3 start", "u3 end", "u1 end");
|
|
});
|
|
|
|
test("awaited together", { concurrency: true }, async (t) => {
|
|
await Promise.all([
|
|
t.test("w1", async () => {
|
|
log.push("w1 start");
|
|
await delay(20);
|
|
log.push("w1 end");
|
|
}),
|
|
t.test("w2", async () => {
|
|
log.push("w2 start");
|
|
await delay(5);
|
|
log.push("w2 end");
|
|
}),
|
|
]);
|
|
log.push("joined");
|
|
expected.push("w1 start", "w2 start", "w2 end", "w1 end", "joined");
|
|
});
|
|
|
|
test("nested test() calls", { concurrency: true }, async () => {
|
|
test("v1", async () => {
|
|
log.push("v1 start");
|
|
await delay(20);
|
|
log.push("v1 end");
|
|
});
|
|
test("v2", async () => {
|
|
log.push("v2 start");
|
|
await delay(5);
|
|
log.push("v2 end");
|
|
});
|
|
log.push("nested body end");
|
|
expected.push("v1 start", "v2 start", "nested body end", "v2 end", "v1 end");
|
|
});
|
|
|
|
test("failures report in order", { concurrency: true }, async (t) => {
|
|
void t.test("f1", async () => {
|
|
await delay(20);
|
|
throw new Error("f1");
|
|
});
|
|
void t.test("f2", async () => {
|
|
await delay(5);
|
|
throw new Error("f2");
|
|
});
|
|
});
|
|
|
|
const rangeErrors: string[] = [];
|
|
for (const concurrency of [0, 1.5, 2 ** 32]) {
|
|
try {
|
|
describe("invalid", { concurrency }, () => {});
|
|
} catch (error) {
|
|
rangeErrors.push(`${(error as Error).name} ${(error as Error).message}`);
|
|
}
|
|
}
|
|
|
|
test("ran concurrently", () => {
|
|
if (log.join(",") !== expected.join(",")) throw new Error(`${log.join(",")}`);
|
|
const range = 'The value of "options.concurrency" is out of range.';
|
|
const want = [
|
|
`RangeError ${range} It must be >= 1 && <= 4294967295. Received 0`,
|
|
`RangeError ${range} It must be an integer. Received 1.5`,
|
|
`RangeError ${range} It must be >= 1 && <= 4294967295. Received 4294967296`,
|
|
];
|
|
if (rangeErrors.join("\n") !== want.join("\n")) throw new Error(rangeErrors.join("\n"));
|
|
});
|