storebound/tests/ci-plan.test.ts

68 lines
4.1 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import { balance, depsOf, GLOBAL, HEAVY, junitTimes, listTests, relatedTests, rustNeeded, shardCount } from '../tools/ci-plan';
describe('tools/ci-plan: test shards', () => {
test('shard count: cores - 1, between 1 and 8, env override', () => {
expect(shardCount({}, 10)).toBe(8);
expect(shardCount({}, 4)).toBe(3);
expect(shardCount({}, 1)).toBe(1);
expect(shardCount({ STOREBOUND_CI_SHARDS: '3' }, 10)).toBe(3);
expect(shardCount({ STOREBOUND_CI_SHARDS: 'x' }, 10)).toBe(8);
});
test('balance: every file once, longest first, heavy suites never share a shard', () => {
const cost: Record<string, number> = { h1: 10, h2: 9, a: 5, b: 4, c: 3, d: 2, e: 1 };
const shards = balance(Object.keys(cost), 3, f => cost[f]!, ['h1', 'h2']);
expect(shards.flat().sort()).toEqual(Object.keys(cost).sort());
for (const s of shards) expect(s.filter(f => f.startsWith('h')).length).toBeLessThanOrEqual(1);
const loads = shards.map(s => s.reduce((n, f) => n + cost[f]!, 0));
expect(Math.max(...loads) - Math.min(...loads)).toBeLessThanOrEqual(cost.a!);
});
test('balance: more heavy suites than shards still places each once; empty shards dropped', () => {
expect(balance(['h1', 'h2', 'h3'], 2, () => 1, ['h1', 'h2', 'h3']).flat().sort()).toEqual(['h1', 'h2', 'h3']);
expect(balance(['a'], 8, () => 1)).toEqual([['a']]);
});
test('JUnit report: per-file seconds from the top-level suites only', () => {
const xml = `<testsuites><testsuite name="tests/a.test.ts" file="tests/a.test.ts" tests="2" time="1.5" hostname="h">
<testsuite name="group" file="tests/a.test.ts" time="0.2"></testsuite></testsuite>
<testsuite name="tests/b.test.ts" file="tests/b.test.ts" time="0.25"></testsuite></testsuites>`;
expect(junitTimes(xml)).toEqual({ 'tests/a.test.ts': 1.5, 'tests/b.test.ts': 0.25 });
});
test('every heavy suite exists and every test file is found', () => {
const all = listTests();
for (const h of HEAVY) expect(all).toContain(h);
expect(all).toContain('tests/ci-plan.test.ts');
expect(all).toContain('apps/desktop/tests/webview.test.ts');
expect(all.some(f => f.includes('node_modules') || f.includes('/fixtures/'))).toBe(false);
});
});
describe('tools/ci-plan: related tests (ci:quick)', () => {
const all = listTests();
test('imports, transitive imports and spawned CLI paths', () => {
expect(depsOf('tests/ci-plan.test.ts')).toEqual(['tools/ci-plan.ts']);
expect(relatedTests(['tools/ci-plan.ts'], all)).toEqual(['tests/ci-plan.test.ts']);
// a CLI library change reaches the tests that import it directly and through other modules
const upsert = relatedTests(['cli/lib/upsert.ts'], all);
expect(upsert).toContain('tests/idempotency.test.ts');
expect(upsert.length).toBeGreaterThan(3);
});
test('a changed test runs itself; a global file runs everything; docs run nothing', () => {
expect(relatedTests(['tests/pure.test.ts'], all)).toEqual(['tests/pure.test.ts']);
expect(relatedTests(['package.json'], all)).toEqual(all);
expect(GLOBAL.test('bunfig.toml')).toBe(true);
expect(relatedTests([['docs/nobody', 'reads.md'].join('-')], all)).toEqual([]);
});
test('a browser suite runs for its own module and its near imports, not for every shared core module', () => {
expect(relatedTests(['cli/lib/upsert.ts'], all, undefined, HEAVY)).not.toContain('tests/console-fill.test.ts');
expect(relatedTests(['cli/lib/upsert.ts'], all)).toContain('tests/console-fill.test.ts');
expect(relatedTests(['cli/console/fill.ts'], all, undefined, HEAVY)).toContain('tests/console-fill.test.ts');
expect(relatedTests(['tests/capture-web.test.ts'], all, undefined, HEAVY)).toEqual(['tests/capture-web.test.ts']);
});
test('Rust steps only for the Tauri crate or the sidecar build, or with CI=full / unknown diff', () => {
expect(rustNeeded(['cli/index.ts'], {})).toBe(false);
expect(rustNeeded(['apps/desktop/src-tauri/src/main.rs'], {})).toBe(true);
expect(rustNeeded(['tools/build-sidecars.ts'], {})).toBe(true);
expect(rustNeeded([], { CI: 'full' })).toBe(true);
expect(rustNeeded(null, {})).toBe(true);
});
});