test: give the 10k-iteration sort-key property test its own timeout #318
@ -26,48 +26,56 @@ describe('sort-key helpers (issue #45)', () => {
|
||||
* pattern — repeatedly drop the last page between the first two — must never
|
||||
* collide and never overflow the key length, because the caller rebalances
|
||||
* 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)', () => {
|
||||
// Start with five pages in a fixed order.
|
||||
let order = evenlySpacedKeys(5).map((key, i) => ({ id: `p${i}`, key }));
|
||||
let rebalances = 0;
|
||||
it(
|
||||
'10.000 adversarial reorders never collide or overflow (rebalance verified)',
|
||||
{ timeout: 30_000 },
|
||||
() => {
|
||||
// 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 keys = evenlySpacedKeys(order.length);
|
||||
order = order.map((page, i) => ({ ...page, key: keys[i]! }));
|
||||
rebalances += 1;
|
||||
};
|
||||
const rebalance = (): void => {
|
||||
const keys = evenlySpacedKeys(order.length);
|
||||
order = order.map((page, i) => ({ ...page, key: keys[i]! }));
|
||||
rebalances += 1;
|
||||
};
|
||||
|
||||
for (let i = 0; i < 10_000; i += 1) {
|
||||
// Move the last page to sit between the first and second — the tightest
|
||||
// possible gap, which is what grows key length fastest.
|
||||
const moved = order[order.length - 1]!;
|
||||
const rest = order.slice(0, -1);
|
||||
const afterKey = rest[0]!.key;
|
||||
const beforeKey = rest[1]!.key;
|
||||
for (let i = 0; i < 10_000; i += 1) {
|
||||
// Move the last page to sit between the first and second — the tightest
|
||||
// possible gap, which is what grows key length fastest.
|
||||
const moved = order[order.length - 1]!;
|
||||
const rest = order.slice(0, -1);
|
||||
const afterKey = rest[0]!.key;
|
||||
const beforeKey = rest[1]!.key;
|
||||
|
||||
const key = nextKeyOrRebalance(afterKey, beforeKey);
|
||||
if (key === null) {
|
||||
// Rebalance keeps the CURRENT order, then retry the move once.
|
||||
rebalance();
|
||||
const k2 = nextKeyOrRebalance(order[0]!.key, order[1]!.key);
|
||||
expect(k2).not.toBeNull();
|
||||
order = [order[0]!, { ...moved, key: k2! }, ...order.slice(1)];
|
||||
} else {
|
||||
order = [rest[0]!, { ...moved, key }, ...rest.slice(1)];
|
||||
const key = nextKeyOrRebalance(afterKey, beforeKey);
|
||||
if (key === null) {
|
||||
// Rebalance keeps the CURRENT order, then retry the move once.
|
||||
rebalance();
|
||||
const k2 = nextKeyOrRebalance(order[0]!.key, order[1]!.key);
|
||||
expect(k2).not.toBeNull();
|
||||
order = [order[0]!, { ...moved, key: k2! }, ...order.slice(1)];
|
||||
} else {
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
// The adversarial pattern must have forced at least one rebalance.
|
||||
expect(rebalances).toBeGreaterThan(0);
|
||||
});
|
||||
// The adversarial pattern must have forced at least one rebalance.
|
||||
expect(rebalances).toBeGreaterThan(0);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user