89 lines
5.3 KiB
TypeScript
89 lines
5.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { existsSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { balance, deferBrowser, depsOf, GLOBAL, HEAVY, junitTimes, listTests, mergeTimings, relatedTests, ROOT, rustNeeded, shardCount } from '../tools/ci-plan';
|
|
|
|
describe('tools/ci-plan: browser suites deferred on a busy machine', () => {
|
|
test('deferred only when the 1-minute load is above 2x the cores, never with CI=full', () => {
|
|
expect(deferBrowser({}, 21, 10)).toBe(true);
|
|
expect(deferBrowser({}, 20, 10)).toBe(false);
|
|
expect(deferBrowser({ CI: 'full' }, 500, 10)).toBe(false);
|
|
expect(deferBrowser({ CI: 'true' }, 500, 10)).toBe(true);
|
|
});
|
|
});
|
|
|
|
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; the heavy suites first, in one dedicated shard; light files balanced over the rest', () => {
|
|
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());
|
|
expect(shards[0]).toEqual(['h1', 'h2']);
|
|
const rest = shards.slice(1);
|
|
expect(rest).toHaveLength(2);
|
|
for (const s of rest) expect(s.some(f => f.startsWith('h'))).toBe(false);
|
|
const loads = rest.map(s => s.reduce((n, f) => n + cost[f]!, 0));
|
|
expect(Math.max(...loads) - Math.min(...loads)).toBeLessThanOrEqual(cost.a!);
|
|
});
|
|
test('balance: two browser shards split the heavy suites by cost; one shard in all takes everything; empty shards dropped', () => {
|
|
const cost: Record<string, number> = { h1: 10, h2: 9, h3: 2, a: 1 };
|
|
const shards = balance(Object.keys(cost), 4, f => cost[f]!, ['h1', 'h2', 'h3'], 2);
|
|
expect(shards.slice(0, 2).map(s => [...s].sort())).toEqual([['h1'], ['h2', 'h3']]);
|
|
expect(shards.slice(2)).toEqual([['a']]);
|
|
expect(balance(['h1', 'h2', 'a'], 1, () => 1, ['h1', 'h2']).flat().sort()).toEqual(['a', 'h1', 'h2']);
|
|
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('recorded timings: a new file takes its time; a known one moves halfway, never up past the new time', () => {
|
|
const t = mergeTimings({ 'a.test.ts': 10, 'b.test.ts': 40, 'keep.test.ts': 3 }, { 'a.test.ts': 30, 'b.test.ts': 20, 'new.test.ts': 5 });
|
|
expect(t).toEqual({ 'a.test.ts': 20, 'b.test.ts': 20, 'keep.test.ts': 3, 'new.test.ts': 5 });
|
|
});
|
|
|
|
test('the browser suites exist but are never in the CI test list (bun run test:browser only)', () => {
|
|
const all = listTests();
|
|
for (const h of HEAVY) { expect(existsSync(join(ROOT, h))).toBe(true); expect(all).not.toContain(h); }
|
|
expect(all.some(f => f.startsWith('tests/browser/'))).toBe(false);
|
|
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('ci:quick never picks a browser suite, whatever changed', () => {
|
|
for (const f of ['cli/lib/upsert.ts', 'cli/console/fill.ts', 'package.json']) expect(relatedTests([f], all).some(t => t.startsWith('tests/browser/'))).toBe(false);
|
|
});
|
|
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);
|
|
});
|
|
});
|