/ proof-of-concept / plate-dynamics-lab-2
Sign in
proof-of-concept / plate-dynamics-lab-2
plate-dynamics-lab-2 / packages / core / test / generate.test.ts
132 lines · 4.7 KBBlameHistoryRaw
1import { describe, expect, it } from "vitest";
2import {
3 LIBRARY_FILES,
4 METHODS,
5 RR_METHOD,
6 SCENARIO_PRESETS,
7 buildPlanBundle,
8 buildRunBundle,
9 formatNumber,
10 generateParamsBlock,
11 isPlainStruct,
12 modesKey,
13 parseMethodPlan,
14 validateSpec,
15 withEdits,
16} from "../src/index";
17import { runner, testSpec } from "./helpers";
19describe("source generation and editing", () => {
20 it("formats numbers as exact, readable MATLAB literals", () => {
21 expect(formatNumber(200e9)).toBe("200e9");
22 expect(formatNumber(0.002)).toBe("0.002");
23 expect(formatNumber(96000)).toBe("96000");
24 expect(formatNumber(1e-3)).toBe("0.001");
25 expect(formatNumber(2.5e-7)).toBe("250e-9");
26 expect(Number(formatNumber(68.9e9))).toBe(68.9e9);
27 expect(formatNumber(-3)).toBe("-3");
28 });
30 it("round-trips the spec through the params block", async () => {
31 const spec = testSpec();
32 const block = generateParamsBlock(spec);
33 const run = await runner.run({
34 bundle: {
35 files: [{ name: "p.m", source: block }, ...LIBRARY_FILES],
36 entry: "p.m",
37 resultVariable: "params",
38 },
39 });
40 expect(run.ok).toBe(true);
41 if (!run.ok || !isPlainStruct(run.value)) return;
42 const plate = run.value.fields.plate;
43 const mat = run.value.fields.material;
44 const hammer = run.value.fields.hammer;
45 if (!isPlainStruct(plate) || !isPlainStruct(mat) || !isPlainStruct(hammer))
46 throw new Error("params fields are not structs");
47 expect(plate.fields.thickness).toBe(spec.plate.thickness);
48 expect(mat.fields.E).toBe(spec.material.E);
49 expect(mat.fields.name).toBe(spec.material.name);
50 expect(hammer.fields.restitution).toBe(spec.hammer.restitution);
51 });
53 it("plans every scenario with both methods", async () => {
54 for (const sc of SCENARIO_PRESETS)
55 for (const m of METHODS) {
56 const run = await runner.run({ bundle: buildPlanBundle(sc.spec, m) });
57 expect(run.ok, `${sc.id}/${m.id}: ${run.ok ? "" : run.error}`).toBe(true);
58 if (!run.ok) continue;
59 const plan = parseMethodPlan(run.value);
60 expect(plan.method).toBe(m.id);
61 expect(plan.modes_expected).toBeGreaterThan(0);
62 expect(plan.pendulum_hz).toBeGreaterThan(0);
63 expect(plan.hertz_duration).toBeGreaterThan(0);
64 expect(plan.work).toBeGreaterThan(0);
65 }
66 });
68 it("runs the edited source, and surfaces numbl errors with file and line", async () => {
69 const spec = testSpec();
70 const bundle = buildRunBundle(spec, RR_METHOD);
71 const main = bundle.files.find((f) => f.name === "main.m")!.source;
72 const edited = withEdits(bundle, {
73 "main.m": main.replace(
74 "modes = plate_rr_modes(params);",
75 "modes = undefined_function_xyz(params);",
76 ),
77 });
78 const run = await runner.run({ bundle: edited });
79 expect(run.ok).toBe(false);
80 if (run.ok) return;
81 expect(run.error).toMatch(/undefined_function_xyz/);
82 expect(run.error).toMatch(/main\.m/);
83 });
85 it("reports a missing result variable rather than an empty screen", async () => {
86 const spec = testSpec();
87 const bundle = buildRunBundle(spec, RR_METHOD);
88 const edited = withEdits(bundle, { "main.m": "x = 1;" });
89 const run = await runner.run({ bundle: edited });
90 expect(run.ok).toBe(false);
91 if (!run.ok) expect(run.error).toMatch(/result/);
92 });
94 it("validates obviously bad specs, and accepts every scenario", () => {
95 const spec = testSpec();
96 spec.probe.x = 10;
97 spec.hammer.restitution = 1.5;
98 spec.hole.edge_offset = 0.1;
99 const issues = validateSpec(spec);
100 expect(issues.some((i) => i.path === "probe" && i.severity === "error")).toBe(true);
101 expect(issues.some((i) => i.path === "hammer.restitution")).toBe(true);
102 expect(issues.some((i) => i.path === "hole.edge_offset")).toBe(true);
103 for (const sc of SCENARIO_PRESETS)
104 expect(
105 validateSpec(sc.spec).filter((i) => i.severity === "error"),
106 sc.id,
107 ).toEqual([]);
108 });
110 it("keys the modes on what they depend on, and nothing else", () => {
111 const a = testSpec();
112 const b = testSpec();
113 b.strike.x = 0.005;
114 b.hammer.mass = 0.02;
115 b.material.Q = 500;
116 b.thread.dashpot = 0;
117 b.mic.distance = 1;
118 b.render.duration = 1;
119 expect(modesKey(a, "rr")).toBe(modesKey(b, "rr"));
120 expect(modesKey(a, "fem")).toBe(modesKey(b, "fem"));
121 const c = testSpec();
122 c.plate.thickness = 0.002;
123 expect(modesKey(a, "rr")).not.toBe(modesKey(c, "rr"));
124 const d = testSpec();
125 d.fem.mesh_x = 32;
126 expect(modesKey(a, "rr")).toBe(modesKey(d, "rr"));
127 expect(modesKey(a, "fem")).not.toBe(modesKey(d, "fem"));
128 const e = testSpec();
129 e.render.fs = 48000;
130 expect(modesKey(a, "rr")).not.toBe(modesKey(e, "rr"));
131 });
132});
↑↓ move↵openescclose