183 lines
8.8 KiB
Rust
183 lines
8.8 KiB
Rust
//! Incident-workspace saved-source oracle. No desktop/spacing producer is linked.
|
|
//! self-check BASE OUT | same BASE ACTUAL OUT | repair-c BASE ACTUAL OUT
|
|
//! lane BASE ACTUAL OUT VIEW LIFELINE X | message BASE ACTUAL OUT VIEW OCCURRENCE SEND_Y
|
|
use oa_core::{
|
|
DiagramLayout, Id, MAX_COORDINATE, Project, ScenarioLayout, execute_batch, source_files,
|
|
source_project::{ProjectLimits, compile_bundle},
|
|
validation::Severity,
|
|
};
|
|
use serde_json::{Value, json};
|
|
use std::{fs, io::Write, path::Path};
|
|
|
|
const ORIGINAL: &str = include_str!("../incident-workspace.arcadia");
|
|
|
|
fn id(last: u128) -> Id {
|
|
Id::from_u128(0x70200000000040008000000000000000 | last)
|
|
}
|
|
|
|
fn load(path: &Path) -> Project {
|
|
assert!(fs::symlink_metadata(path).unwrap().file_type().is_file());
|
|
let loaded = source_files::load_project(path, ProjectLimits::default()).unwrap();
|
|
assert!(
|
|
!loaded.diagnostics.iter().any(|d| d.diagnostic.severity == Severity::Error),
|
|
"source loader errors: {:?}", loaded.diagnostics
|
|
);
|
|
let raw = loaded.reachable_bundle();
|
|
assert_eq!(raw.files.len(), 1, "This incident oracle admits one source file only");
|
|
let compiled = compile_bundle(&raw, ProjectLimits::default()).unwrap();
|
|
assert_eq!(compiled.bundle, raw, "Saved source must need no migration/identity insertion");
|
|
execute_batch(&compiled.decoded.project, &[]).unwrap();
|
|
let project = compiled.decoded.project;
|
|
assert_eq!(project.id, id(1), "Wrong incident fixture Project");
|
|
assert_eq!(project.diagrams.len(), 3, "Expected the incident fixture's three Saved views");
|
|
project
|
|
}
|
|
|
|
fn layout(project: &mut Project, view: Id) -> &mut ScenarioLayout {
|
|
let diagram = project.diagrams.get_mut(&view).expect("Missing chosen saved view");
|
|
assert_eq!(diagram.projection.scenario(), Some(id(0x10)), "Wrong view owner");
|
|
let Some(DiagramLayout::Scenario(layout)) = &mut diagram.layout else {
|
|
panic!("This fixture comparison requires an existing Scenario payload")
|
|
};
|
|
layout
|
|
}
|
|
|
|
fn lane(project: &mut Project, view: Id, target: Id, x: f32) -> f32 {
|
|
assert!(x.is_finite() && x.abs() <= MAX_COORDINATE);
|
|
let rows: Vec<_> = layout(project, view).lanes.iter_mut()
|
|
.filter(|row| row.lifeline == target).collect();
|
|
assert_eq!(rows.len(), 1, "Exactly one existing owned lane override required");
|
|
let row = rows.into_iter().next().unwrap();
|
|
std::mem::replace(&mut row.x, x)
|
|
}
|
|
|
|
fn message(project: &mut Project, view: Id, target: Id, y: f32) -> f32 {
|
|
assert!(y.is_finite() && y.abs() <= MAX_COORDINATE);
|
|
let rows: Vec<_> = layout(project, view).messages.iter_mut()
|
|
.filter(|row| row.address.references.is_empty() && row.address.occurrence == target).collect();
|
|
assert_eq!(rows.len(), 1, "Exactly one existing direct occurrence override required");
|
|
let row = rows.into_iter().next().unwrap();
|
|
std::mem::replace(&mut row.send_y, y)
|
|
}
|
|
|
|
// Recursively sort object keys. Array order and every serialized scalar remain exact.
|
|
// Comparing serialized scalars also distinguishes -0.0 from 0.0, unlike float PartialEq.
|
|
fn canonical(value: &Value) -> String {
|
|
match value {
|
|
Value::Object(map) => {
|
|
let mut keys: Vec<_> = map.keys().collect();
|
|
keys.sort();
|
|
format!("{{{}}}", keys.into_iter().map(|key| {
|
|
format!("{}:{}", serde_json::to_string(key).unwrap(), canonical(&map[key]))
|
|
}).collect::<Vec<_>>().join(","))
|
|
}
|
|
Value::Array(items) => format!("[{}]", items.iter().map(canonical).collect::<Vec<_>>().join(",")),
|
|
_ => serde_json::to_string(value).unwrap(),
|
|
}
|
|
}
|
|
|
|
fn full(project: &Project) -> String {
|
|
canonical(&serde_json::to_value(project).unwrap())
|
|
}
|
|
|
|
fn put(path: &Path, bytes: &[u8]) {
|
|
let mut file = fs::OpenOptions::new().write(true).create_new(true).open(path).unwrap();
|
|
file.write_all(bytes).unwrap();
|
|
file.sync_all().unwrap();
|
|
}
|
|
|
|
fn self_check(base: &Project, out: &Path) {
|
|
let mut expected = base.clone();
|
|
assert_eq!(message(&mut expected, id(0x42), id(0x23), 424.), 300.);
|
|
assert_ne!(full(base), full(&expected));
|
|
assert_eq!(message(&mut expected, id(0x42), id(0x23), 300.), 424.);
|
|
assert_eq!(full(base), full(&expected));
|
|
|
|
assert_eq!(lane(&mut expected, id(0x40), id(0x12), 700.), 640.);
|
|
let lane_expected = full(&expected);
|
|
let mut wrong_view = expected.clone();
|
|
lane(&mut wrong_view, id(0x41), id(0x12), 700.);
|
|
assert_ne!(lane_expected, full(&wrong_view));
|
|
let mut wrong_semantics = expected.clone();
|
|
wrong_semantics.elements.get_mut(&id(0x21)).unwrap().name.push_str(" changed");
|
|
assert_ne!(lane_expected, full(&wrong_semantics));
|
|
let reordered_text = ORIGINAL.replacen("steps [@initial_report, @updated_report,", "steps [@updated_report, @initial_report,", 1);
|
|
assert_ne!(reordered_text, ORIGINAL);
|
|
let reordered = oa_core::language::parse(&reordered_text).unwrap();
|
|
assert_ne!(full(base), full(&reordered));
|
|
assert_eq!(lane(&mut expected, id(0x40), id(0x12), 640.), 700.);
|
|
assert_eq!(full(base), full(&expected));
|
|
|
|
assert_eq!(message(&mut expected, id(0x40), id(0x21), 272.), 252.);
|
|
let mut wrong_occurrence = expected.clone();
|
|
message(&mut wrong_occurrence, id(0x40), id(0x20), 272.);
|
|
assert_ne!(full(&expected), full(&wrong_occurrence));
|
|
let mut signed_zero = base.clone();
|
|
lane(&mut signed_zero, id(0x40), id(0x12), -0.0);
|
|
let negative = full(&signed_zero);
|
|
lane(&mut signed_zero, id(0x40), id(0x12), 0.0);
|
|
assert_ne!(negative, full(&signed_zero));
|
|
put(&out.join("self-check.json"), serde_json::to_string_pretty(&json!({
|
|
"status": "passed", "scope": "Oracle admission only; no native result",
|
|
"checks": ["repair_C_exact_300_to_424_and_inverse", "lane_A_only_and_inverse",
|
|
"other_saved_view_mutation_rejected", "semantic_name_mutation_rejected", "source_steps_reorder_rejected",
|
|
"repeated_exchange_occurrence_distinguished", "signed_zero_distinguished"]
|
|
})).unwrap().as_bytes());
|
|
}
|
|
|
|
fn main() {
|
|
let args: Vec<String> = std::env::args().collect();
|
|
let mode = args.get(1).expect("Expected self-check, same, repair-c, lane or message");
|
|
let self_check_mode = mode == "self-check";
|
|
let count = match mode.as_str() {
|
|
"self-check" => 4, "same" | "repair-c" => 5, "lane" | "message" => 8,
|
|
_ => panic!("Unknown comparison mode"),
|
|
};
|
|
assert_eq!(args.len(), count, "Wrong exact argument count");
|
|
let base_path = Path::new(&args[2]);
|
|
let out = Path::new(&args[if self_check_mode {3} else {4}]);
|
|
fs::create_dir(out).expect("Output directory must be new; preserve previous evidence");
|
|
let base = load(base_path);
|
|
put(&out.join("baseline.project.json"), full(&base).as_bytes());
|
|
if self_check_mode {
|
|
assert_eq!(fs::read_to_string(base_path).unwrap(), ORIGINAL, "Self-check requires exact embedded original");
|
|
self_check(&base, out);
|
|
println!("Oracle self-check passed; no native acceptance");
|
|
return;
|
|
}
|
|
let actual = load(Path::new(&args[3]));
|
|
let mut expected = base.clone();
|
|
let change = match mode.as_str() {
|
|
"same" => json!({"kind": "none"}),
|
|
"repair-c" => {
|
|
let old = message(&mut expected, id(0x42), id(0x23), 424.);
|
|
assert_eq!(old.to_bits(), 300_f32.to_bits(), "Repair must begin with C's exact 300");
|
|
json!({"kind": "message", "view": id(0x42), "target": id(0x23), "before": old, "after": 424.0})
|
|
}
|
|
"lane" | "message" => {
|
|
let view = args[5].parse::<Id>().unwrap();
|
|
let target = args[6].parse::<Id>().unwrap();
|
|
let value = args[7].parse::<f32>().unwrap();
|
|
let old = if mode == "lane" {lane(&mut expected, view, target, value)}
|
|
else {message(&mut expected, view, target, value)};
|
|
assert_ne!(old.to_bits(), value.to_bits(), "Use same for an unchanged/no-op check");
|
|
json!({"kind": mode, "view": view, "target": target, "before": old, "after": value,
|
|
"before_f32_bits": old.to_bits(), "after_f32_bits": value.to_bits()})
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
execute_batch(&expected, &[]).unwrap();
|
|
let expected_json = full(&expected);
|
|
let actual_json = full(&actual);
|
|
put(&out.join("expected.project.json"), expected_json.as_bytes());
|
|
put(&out.join("actual.project.json"), actual_json.as_bytes());
|
|
let equal = expected_json == actual_json;
|
|
put(&out.join("comparison.json"), serde_json::to_string_pretty(&json!({
|
|
"status": if equal {"passed"} else {"failed"}, "mode": mode,
|
|
"allowed_change": change, "full_project_exact": equal,
|
|
"scope": "Complete parsed saved Project; no runtime Scene, editor buffer, camera or history-stack claim"
|
|
})).unwrap().as_bytes());
|
|
assert!(equal, "Unexpected complete Project difference; inspect expected.project.json and actual.project.json");
|
|
println!("Full saved Project comparison passed: {mode}");
|
|
}
|