Log in
main 0 open branches 33 files Download ZIP Browse files
test/golden.test.js 59 lines · 2.3 KB · javascript
1
// Golden tests: dotter must send exactly the commands the official NextDraw
2
// Python API sends for the same scenario (status polls excluded, since their
3
// count depends on timing). Fixtures are recorded by
4
// scripts/golden/gen_golden.py; scenarios without a fixture are skipped.
5
6
import { describe, expect, test } from 'bun:test';
7
import { existsSync, readFileSync } from 'node:fs';
8
import { join } from 'node:path';
9
import { commands, makeMachine } from './helpers.js';
10
11
const dir = join(import.meta.dir, 'golden');
12
const scenarios = JSON.parse(readFileSync(join(dir, 'scenarios.json'), 'utf8'));
13
14
const OPTION_NAMES = {
15
  handling: 'handling', speed_pendown: 'speedPendown', speed_penup: 'speedPenup', accel: 'accel',
16
  pen_pos_up: 'penPosUp', pen_pos_down: 'penPosDown', pen_rate_lower: 'penRateLower',
17
  pen_rate_raise: 'penRateRaise', units: 'units', model: 'model',
18
};
19
const OP_NAMES = { draw_path: 'drawPath' };
20
21
export async function runScenario(spec) {
22
  const { nd, sim } = makeMachine({}, (n) => {
23
    n.options.homing = false;
24
    for (const [k, v] of Object.entries(spec.options)) n.options[OPTION_NAMES[k]] = v;
25
  });
26
  if (!(await nd.connect())) throw new Error('connect failed');
27
  for (const [op, ...args] of spec.ops) {
28
    if (op === 'draw_path_circle') {
29
      const [cx, cy, r, n] = args;
30
      const pts = [];
31
      for (let i = 0; i <= n; i++) pts.push([cx + r * Math.cos(2 * Math.PI * i / n), cy + r * Math.sin(2 * Math.PI * i / n)]);
32
      await nd.drawPath(pts);
33
    } else {
34
      await nd[OP_NAMES[op] ?? op](...args);
35
    }
36
  }
37
  await nd.disconnect();
38
  return { cmds: commands(sim), sim };
39
}
40
41
describe('golden command streams vs. the Python API', () => {
42
  for (const [name, spec] of Object.entries(scenarios)) {
43
    const fixture = join(dir, 'expected', `${name}.json`);
44
    const run = existsSync(fixture) ? test : test.skip;
45
    run(name, async () => {
46
      const expected = JSON.parse(readFileSync(fixture, 'utf8'));
47
      const { cmds } = await runScenario(spec);
48
      expect(cmds).toEqual(expected);
49
    });
50
  }
51
52
  // These run regardless: every scenario must be safe on the simulator.
53
  for (const [name, spec] of Object.entries(scenarios)) {
54
    test(`${name} is safe on the simulator`, async () => {
55
      const { sim } = await runScenario(spec);
56
      expect(sim.violations).toEqual([]);
57
    });
58
  }
59
});