59 lines
2.7 KiB
JavaScript
59 lines
2.7 KiB
JavaScript
import { fnv1a64 } from "./replay.mjs";
|
|
|
|
export const ENDPOINT_REPLAY_SCHEMA = "endpoint-replay.v1";
|
|
|
|
const clone = (value) => structuredClone(value);
|
|
const unsigned = (value) => { const copy = clone(value); delete copy.state_hash; return copy; };
|
|
|
|
export function firstDifference(expected, actual, path = "$") {
|
|
if (Object.is(expected, actual)) return null;
|
|
if (typeof expected !== typeof actual) return { path, expected, actual };
|
|
if (expected === null || actual === null || typeof expected !== "object") return { path, expected, actual };
|
|
if (Array.isArray(expected) !== Array.isArray(actual)) return { path, expected, actual };
|
|
if (Array.isArray(expected)) {
|
|
if (expected.length !== actual.length) return { path: `${path}.length`, expected: expected.length, actual: actual.length };
|
|
for (let index = 0; index < expected.length; index += 1) {
|
|
const difference = firstDifference(expected[index], actual[index], `${path}[${index}]`);
|
|
if (difference) return difference;
|
|
}
|
|
return null;
|
|
}
|
|
const keys = [...new Set([...Object.keys(expected), ...Object.keys(actual)])].sort();
|
|
for (const key of keys) {
|
|
const difference = firstDifference(expected[key], actual[key], `${path}.${key}`);
|
|
if (difference) return difference;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function endpointHash(endpoint) {
|
|
return fnv1a64(endpoint);
|
|
}
|
|
|
|
export function compareEndpoints(expected, actual) {
|
|
const difference = firstDifference(expected, actual);
|
|
return { equal: difference === null, difference, expected_hash: endpointHash(expected), actual_hash: endpointHash(actual) };
|
|
}
|
|
|
|
export function createEndpoint({ session, includeTimeline = false } = {}) {
|
|
if (!session) throw new Error("endpoint_session_required");
|
|
const task = session.task.export();
|
|
const scheduler = session.scheduler.export();
|
|
const endpoint = {
|
|
tick: session.tick,
|
|
task: { schema: task.schema, state: task.state, mode: task.mode, program_status: task.program_status, simulation_tick: task.simulation_tick, current_line: task.current_line },
|
|
scheduler: { schema: scheduler.schema, budget: scheduler.budget, phase_trace: scheduler.phase_trace, tick: scheduler.tick },
|
|
authoritative: unsigned(session.authoritative.export()),
|
|
motion: unsigned(session.motion.export()),
|
|
plc: unsigned(session.plc.export()),
|
|
hal_runtime: unsigned(session.hal.export()),
|
|
device_io: unsigned(session.deviceIo.export()),
|
|
configuration_batch: unsigned(session.configurationBatch.export()),
|
|
io_ack: unsigned(session.ioAck.export()),
|
|
callbacks: unsigned(session.callbacks.export()),
|
|
python: session.python.exportState(),
|
|
};
|
|
if (includeTimeline) endpoint.timeline = session.timeline.export();
|
|
return clone(endpoint);
|
|
}
|