thu-maic--openmaic
123 行
3.5 KiB
TypeScript
123 行
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { lazyBoundedMap, mapWithConcurrency } from '@/lib/utils/concurrency';
|
|
|
|
const tick = (ms = 5) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
describe('mapWithConcurrency', () => {
|
|
it('returns results in input order regardless of completion order', async () => {
|
|
// Later items resolve first, but results must stay aligned with input.
|
|
const out = await mapWithConcurrency([30, 10, 20], 3, async (ms, i) => {
|
|
await tick(ms);
|
|
return i;
|
|
});
|
|
expect(out).toEqual([0, 1, 2]);
|
|
});
|
|
|
|
it('never runs more than `limit` workers at once', async () => {
|
|
let active = 0;
|
|
let peak = 0;
|
|
await mapWithConcurrency(
|
|
Array.from({ length: 9 }, (_, i) => i),
|
|
3,
|
|
async (n) => {
|
|
active += 1;
|
|
peak = Math.max(peak, active);
|
|
await tick();
|
|
active -= 1;
|
|
return n;
|
|
},
|
|
);
|
|
expect(peak).toBeLessThanOrEqual(3); // never exceeds the pool
|
|
});
|
|
|
|
it('clamps the limit to the item count (no over-spawn)', async () => {
|
|
let active = 0;
|
|
let peak = 0;
|
|
const out = await mapWithConcurrency([1, 2], 100, async (n) => {
|
|
active += 1;
|
|
peak = Math.max(peak, active);
|
|
await tick();
|
|
active -= 1;
|
|
return n;
|
|
});
|
|
expect(peak).toBeLessThanOrEqual(2);
|
|
expect(out).toEqual([1, 2]);
|
|
});
|
|
|
|
it('stops pulling new items once shouldContinue() turns false', async () => {
|
|
const processed: number[] = [];
|
|
let done = 0;
|
|
await mapWithConcurrency(
|
|
[1, 2, 3, 4, 5, 6],
|
|
1,
|
|
async (n) => {
|
|
processed.push(n);
|
|
done += 1;
|
|
return n;
|
|
},
|
|
{ shouldContinue: () => done < 3 },
|
|
);
|
|
// limit 1 + stop after 3 ⇒ items 4–6 are never started.
|
|
expect(processed).toEqual([1, 2, 3]);
|
|
});
|
|
|
|
it('handles an empty list without spawning workers', async () => {
|
|
expect(await mapWithConcurrency([], 4, async (n) => n)).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('lazyBoundedMap', () => {
|
|
it('returns promises immediately and resolves them without a barrier', async () => {
|
|
const started: number[] = [];
|
|
const promises = lazyBoundedMap([0, 1, 2], 1, async (n) => {
|
|
started.push(n);
|
|
await tick();
|
|
return n * 10;
|
|
});
|
|
expect(promises).toHaveLength(3); // the array of promises exists synchronously
|
|
expect(await promises[0]).toBe(0); // the first resolves on its own…
|
|
expect(started.length).toBeLessThan(3); // …without forcing the last item to run (no barrier)
|
|
expect(await Promise.all(promises)).toEqual([0, 10, 20]); // order + values preserved
|
|
});
|
|
|
|
it('caps in-flight work at `limit`', async () => {
|
|
let active = 0;
|
|
let peak = 0;
|
|
await Promise.all(
|
|
lazyBoundedMap(
|
|
Array.from({ length: 8 }, (_, i) => i),
|
|
3,
|
|
async (n) => {
|
|
active += 1;
|
|
peak = Math.max(peak, active);
|
|
await tick();
|
|
active -= 1;
|
|
return n;
|
|
},
|
|
),
|
|
);
|
|
expect(peak).toBeLessThanOrEqual(3);
|
|
expect(peak).toBeGreaterThan(1);
|
|
});
|
|
|
|
it('skips items via shouldContinue without running fn', async () => {
|
|
const ran: number[] = [];
|
|
let done = 0;
|
|
const out = await Promise.all(
|
|
lazyBoundedMap(
|
|
[1, 2, 3, 4, 5],
|
|
1,
|
|
async (n) => {
|
|
ran.push(n);
|
|
done += 1;
|
|
return n;
|
|
},
|
|
{ shouldContinue: () => done < 2 },
|
|
),
|
|
);
|
|
expect(ran).toEqual([1, 2]); // fn ran only twice
|
|
expect(out).toEqual([1, 2, undefined, undefined, undefined]); // skipped → undefined
|
|
});
|
|
});
|