test: give the 10k-iteration sort-key property test its own timeout #318

Merged
fable-5 merged 1 commits from fix/sort-key-test-timeout into main 2026-08-02 14:30:05 +02:00
Showing only changes of commit 78258c4f9b - Show all commits

View File

@ -26,48 +26,56 @@ describe('sort-key helpers (issue #45)', () => {
* pattern repeatedly drop the last page between the first two must never * pattern repeatedly drop the last page between the first two must never
* collide and never overflow the key length, because the caller rebalances * collide and never overflow the key length, because the caller rebalances
* when {@link nextKeyOrRebalance} returns null. * when {@link nextKeyOrRebalance} returns null.
*
* Under parallel CI load the 10.000 iterations have repeatedly exceeded the
* default 5 s per-test timeout (runs 685, 699 same code passed on rerun),
* so this test carries its own budget.
*/ */
it('10.000 adversarial reorders never collide or overflow (rebalance verified)', () => { it(
// Start with five pages in a fixed order. '10.000 adversarial reorders never collide or overflow (rebalance verified)',
let order = evenlySpacedKeys(5).map((key, i) => ({ id: `p${i}`, key })); { timeout: 30_000 },
let rebalances = 0; () => {
// Start with five pages in a fixed order.
let order = evenlySpacedKeys(5).map((key, i) => ({ id: `p${i}`, key }));
let rebalances = 0;
const rebalance = (): void => { const rebalance = (): void => {
const keys = evenlySpacedKeys(order.length); const keys = evenlySpacedKeys(order.length);
order = order.map((page, i) => ({ ...page, key: keys[i]! })); order = order.map((page, i) => ({ ...page, key: keys[i]! }));
rebalances += 1; rebalances += 1;
}; };
for (let i = 0; i < 10_000; i += 1) { for (let i = 0; i < 10_000; i += 1) {
// Move the last page to sit between the first and second — the tightest // Move the last page to sit between the first and second — the tightest
// possible gap, which is what grows key length fastest. // possible gap, which is what grows key length fastest.
const moved = order[order.length - 1]!; const moved = order[order.length - 1]!;
const rest = order.slice(0, -1); const rest = order.slice(0, -1);
const afterKey = rest[0]!.key; const afterKey = rest[0]!.key;
const beforeKey = rest[1]!.key; const beforeKey = rest[1]!.key;
const key = nextKeyOrRebalance(afterKey, beforeKey); const key = nextKeyOrRebalance(afterKey, beforeKey);
if (key === null) { if (key === null) {
// Rebalance keeps the CURRENT order, then retry the move once. // Rebalance keeps the CURRENT order, then retry the move once.
rebalance(); rebalance();
const k2 = nextKeyOrRebalance(order[0]!.key, order[1]!.key); const k2 = nextKeyOrRebalance(order[0]!.key, order[1]!.key);
expect(k2).not.toBeNull(); expect(k2).not.toBeNull();
order = [order[0]!, { ...moved, key: k2! }, ...order.slice(1)]; order = [order[0]!, { ...moved, key: k2! }, ...order.slice(1)];
} else { } else {
order = [rest[0]!, { ...moved, key }, ...rest.slice(1)]; order = [rest[0]!, { ...moved, key }, ...rest.slice(1)];
}
// Invariants after every move: keys unique, bounded, and consistent with
// the intended array order.
const keys = order.map((p) => p.key);
expect(new Set(keys).size).toBe(keys.length);
expect(Math.max(...keys.map((k) => k.length))).toBeLessThanOrEqual(MAX_SORT_KEY_LENGTH);
for (let j = 1; j < keys.length; j += 1) {
expect(keys[j - 1]! < keys[j]!).toBe(true);
}
} }
// Invariants after every move: keys unique, bounded, and consistent with // The adversarial pattern must have forced at least one rebalance.
// the intended array order. expect(rebalances).toBeGreaterThan(0);
const keys = order.map((p) => p.key); },
expect(new Set(keys).size).toBe(keys.length); );
expect(Math.max(...keys.map((k) => k.length))).toBeLessThanOrEqual(MAX_SORT_KEY_LENGTH);
for (let j = 1; j < keys.length; j += 1) {
expect(keys[j - 1]! < keys[j]!).toBe(true);
}
}
// The adversarial pattern must have forced at least one rebalance.
expect(rebalances).toBeGreaterThan(0);
});
}); });