94 lines
3.5 KiB
TypeScript
94 lines
3.5 KiB
TypeScript
/**
|
|
* Smoke test every example across every gallery bucket (Phase 1d-tests B).
|
|
*
|
|
* Extends the existing `examples-gallery-smoke.test.ts` (which covers
|
|
* only analog + digital) to cover the four buckets that had ZERO test
|
|
* coverage: 100-days, epaper, picow-wifi, circuits.
|
|
*
|
|
* Per example, asserts:
|
|
* • The `buildNetlist` pipeline produces a valid netlist (doesn't
|
|
* throw, has non-empty content).
|
|
* • If the example carries actual electrical components (filtering
|
|
* out firmware-only and pure-board canvases), the SPICE solve
|
|
* converges and returns at least one vector or branch current.
|
|
* • NodeVoltages are finite numbers (no NaN / Infinity).
|
|
*
|
|
* What we do NOT assert: behavioural specifics (values, MCU boot).
|
|
* Per-board MCU boot is in `board-kinds-smoke.test.ts` (Phase 1d-tests
|
|
* D); behavioural locks for canonical circuits live in dedicated
|
|
* spice-* test files.
|
|
*
|
|
* Fidelity (memory `feedback_tests_import_real_code`): imports each
|
|
* example array from its source module + uses the production
|
|
* `exampleToBuildNetlistInput` helper.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { hundredDaysExamples } from '../data/examples-100-days';
|
|
import { epaperExamples } from '../data/examples-displays-epaper';
|
|
import { circuitExamples } from '../data/examples-circuits';
|
|
import { exampleToBuildNetlistInput } from '../utils/exampleToBuildNetlistInput';
|
|
import { solveInput } from './helpers/solveInput';
|
|
import type { ExampleProject } from '../data/examples';
|
|
|
|
interface SmokeOutcome {
|
|
id: string;
|
|
title: string;
|
|
status: 'ok' | 'no-vectors' | 'invalid-numerics' | 'error';
|
|
detail?: string;
|
|
}
|
|
|
|
async function smokeOne(example: ExampleProject): Promise<SmokeOutcome> {
|
|
try {
|
|
const input = exampleToBuildNetlistInput(example);
|
|
// Firmware-only examples (e.g. blink-on-arduino-only) have no
|
|
// SPICE components after filtering boards. Building the netlist
|
|
// is still a valid sanity check; skip the solve.
|
|
if (input.components.length === 0 && input.wires.length === 0) {
|
|
return { id: example.id, title: example.title, status: 'ok', detail: 'no-SPICE-content' };
|
|
}
|
|
const result = await solveInput(input);
|
|
const voltages = Object.values(result.nodeVoltages);
|
|
if (voltages.length === 0 && Object.values(result.branchCurrents).length === 0) {
|
|
return { id: example.id, title: example.title, status: 'no-vectors' };
|
|
}
|
|
const bad = voltages.find((v) => !Number.isFinite(v));
|
|
if (bad !== undefined) {
|
|
return {
|
|
id: example.id,
|
|
title: example.title,
|
|
status: 'invalid-numerics',
|
|
detail: String(bad),
|
|
};
|
|
}
|
|
return { id: example.id, title: example.title, status: 'ok' };
|
|
} catch (err) {
|
|
return {
|
|
id: example.id,
|
|
title: example.title,
|
|
status: 'error',
|
|
detail: err instanceof Error ? err.message : String(err),
|
|
};
|
|
}
|
|
}
|
|
|
|
function buildSuite(bucket: string, examples: ExampleProject[]): void {
|
|
describe(`${bucket} gallery — ${examples.length} examples solve on real ngspice`, () => {
|
|
it.each(examples.map((ex) => [ex.id, ex] as const))(
|
|
'%s',
|
|
{ timeout: 30_000 },
|
|
async (_id, example) => {
|
|
const outcome = await smokeOne(example);
|
|
if (outcome.status !== 'ok') {
|
|
throw new Error(
|
|
`[${outcome.id}] "${outcome.title}" → ${outcome.status}${outcome.detail ? ': ' + outcome.detail : ''}`,
|
|
);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
buildSuite('100-days', hundredDaysExamples);
|
|
buildSuite('epaper-displays', epaperExamples);
|
|
buildSuite('circuits', circuitExamples);
|