Files
ARCADIA-Studio/harness/experiments/constructor-defaults-20260911/probe.rs
T

317 lines
12 KiB
Rust

//! Existing source-7 admission experiment; imports no desktop producer.
use oa_core::{source_project::*, *};
use std::{fs, path::Path};
struct Case {
kind: &'static str,
fields: Vec<(&'static str, &'static str, bool)>,
expected: OccurrenceBody,
}
fn cases() -> Vec<Case> {
vec![
Case {
kind: "Exchange",
fields: vec![
("exchange", "none", false),
("sender", "none", false),
("receiver", "none", false),
("message_kind", "Asynchronous", false),
("reply_to", "none", false),
("values", "[]", false),
("operation", "none", false),
],
expected: OccurrenceBody::Exchange {
exchange: None,
sender: None,
receiver: None,
message_kind: MessageKind::Asynchronous,
reply_to: None,
values: vec![],
operation: None,
},
},
Case {
kind: "Action",
fields: vec![("action", "none", false), ("lifeline", "none", false)],
expected: OccurrenceBody::Action {
action: None,
lifeline: None,
},
},
Case {
kind: "State",
fields: vec![("state", "none", false), ("lifeline", "none", false)],
expected: OccurrenceBody::State {
state: None,
lifeline: None,
},
},
Case {
kind: "SequenceReference",
fields: vec![("sequence", "none", false)],
expected: OccurrenceBody::SequenceReference { sequence: None },
},
Case {
kind: "ScenarioUse",
fields: vec![("scenario", "none", false), ("bindings", "[]", true)],
expected: OccurrenceBody::ScenarioUse {
scenario: None,
bindings: vec![],
},
},
Case {
kind: "Control",
fields: vec![("kind", "Choice", true)],
expected: OccurrenceBody::Control {
kind: ControlKind::Choice,
},
},
Case {
kind: "Fragment",
fields: vec![("operator", "Alternative", true), ("branches", "[]", true)],
expected: OccurrenceBody::Fragment {
operator: FragmentOperator::Alternative,
branches: vec![],
},
},
Case {
kind: "Branch",
fields: vec![("guard", "\"\"", true), ("occurrences", "[]", true)],
expected: OccurrenceBody::Branch {
guard: String::new(),
occurrences: vec![],
},
},
]
}
fn id(last: u8) -> Id {
Id::parse_str(&format!("00000000-0000-4000-8000-{last:012}")).unwrap()
}
fn fixture(layer: Layer, kind: &str, fields: &str) -> String {
let prefix = if layer == Layer::Oa { "oa" } else { "sa" };
let sequence = if layer == Layer::Oa {
"oa_process"
} else {
"sa_functional_chain"
};
let viewpoint = if layer == Layer::Oa {
"OperationalActivity"
} else {
"Functional"
};
let root_sequence = matches!(
kind,
"Exchange" | "Action" | "Control" | "SequenceReference"
);
let owner_kind = if root_sequence {
sequence.to_owned()
} else {
format!("{prefix}_scenario")
};
let owner_view = if root_sequence {
String::new()
} else {
format!("viewpoint {viewpoint};")
};
let step = if kind == "Branch" {
"holder"
} else {
"subject"
};
let parent = if kind == "Branch" { "holder" } else { "owner" };
let holder = if kind == "Branch" {
format!(
"{prefix}_occurrence holder \"Fragment owner\" id \"{}\" {{ parent @owner; body {{ Fragment {{ operator Alternative; branches [@subject]; }}; }}; }}",
id(4)
)
} else {
String::new()
};
format!(
"arcadia 7;\nproject defaults \"Constructor defaults\" id \"{}\" {{\n {owner_kind} owner \"Owner\" id \"{}\" {{ {owner_view} steps [@{step}]; }}\n {holder}\n // Authored omissions must stay omitted through no-op and a name edit.\n {prefix}_occurrence subject \"Subject\" id \"{}\" {{ parent @{parent}; body {{ {kind} {{ {fields} }}; }}; }}\n}}\n",
id(1),
id(2),
id(3)
)
}
fn bundle(source: &str) -> SourceBundle {
SourceBundle {
entry: "project.arcadia".into(),
files: [("project.arcadia".into(), source.into())]
.into_iter()
.collect(),
}
}
fn compile(source: &str) -> Result<CompiledSourceProject, Vec<SourceDiagnostic>> {
compile_bundle(&bundle(source), ProjectLimits::default())
}
fn body(project: &Project) -> &OccurrenceBody {
let Some(SharedBody::Occurrence { body, .. }) = project.elements[&id(3)].shared() else {
panic!("Subject is not an occurrence")
};
body
}
fn accepted(out: &Path, name: &str, source: &str, expected: &Project) -> usize {
fs::write(out.join(format!("{name}.arcadia")), source).unwrap();
let compiled = compile(source).unwrap_or_else(|e| panic!("{name}: {e:?}"));
assert_eq!(
&compiled.decoded.project, expected,
"{name}: exact complete Project"
);
assert_eq!(
compiled.bundle,
bundle(source),
"{name}: explicit UUIDs need no stabilization edit"
);
execute_batch(expected, &[]).unwrap();
let noop = update_bundle(&compiled, expected, ProjectLimits::default()).unwrap();
assert_eq!(
noop.bundle, compiled.bundle,
"{name}: no-op preserves all authored omissions"
);
let renamed = execute_batch(
expected,
&[Command::Rename(id(3), "Renamed subject".into())],
)
.unwrap();
let edited = update_bundle(&compiled, &renamed, ProjectLimits::default()).unwrap();
assert_eq!(
edited.bundle,
bundle(&source.replace("\"Subject\"", "\"Renamed subject\"")),
"{name}: name-only edit preserves constructor spelling"
);
let canonical = language::format(expected);
fs::write(out.join(format!("{name}.canonical.arcadia")), &canonical).unwrap();
assert_eq!(
compile(&canonical).unwrap().decoded.project,
*expected,
"{name}: canonical roundtrip"
);
if matches!(
body(expected),
OccurrenceBody::Exchange {
operation: None,
..
}
) {
assert!(canonical.contains("message_kind Asynchronous;"));
assert!(canonical.contains("reply_to none;") && canonical.contains("values [];"));
assert!(
!canonical.contains("operation "),
"None operation is deliberately skipped by canonical serialization"
);
}
validation::validate(expected).len()
}
fn main() {
let out_arg = std::env::args().nth(1).expect("output directory");
let out = Path::new(&out_arg);
fs::create_dir_all(out).unwrap();
let mut rows = String::from("case\tlayer\tresult\tdetail\n");
let mut passes = 0;
let mut rejects = 0;
for layer in [Layer::Oa, Layer::Sa] {
for case in cases() {
let explicit_fields = case
.fields
.iter()
.map(|(name, value, _)| format!("{name} {value};"))
.collect::<Vec<_>>()
.join(" ");
let explicit = fixture(layer, case.kind, &explicit_fields);
let baseline = compile(&explicit)
.unwrap_or_else(|e| panic!("{layer} {} explicit: {e:?}", case.kind));
assert_eq!(body(&baseline.decoded.project), &case.expected);
let name = format!("{layer}-{}-explicit", case.kind);
let issues = accepted(out, &name, &explicit, &baseline.decoded.project);
rows.push_str(&format!("{name}\t{layer}\taccept\texact Project, raw/no-op/name/canonical; {issues} model issues\n"));
passes += 1;
for (omit, _, required) in &case.fields {
let fields = case
.fields
.iter()
.filter(|(field, _, _)| field != omit)
.map(|(field, value, _)| format!("{field} {value};"))
.collect::<Vec<_>>()
.join(" ");
let source = fixture(layer, case.kind, &fields);
let name = format!("{layer}-{}-omit-{omit}", case.kind);
if *required {
fs::write(out.join(format!("{name}.arcadia")), &source).unwrap();
let errors = compile(&source).expect_err("Required field omission must reject");
let detail = format!("{errors:?}");
assert!(
detail.contains("missing field") && detail.contains(omit),
"{name}: must fail on the missing constructor field: {detail}"
);
rows.push_str(&format!("{name}\t{layer}\treject\t{detail}\n"));
rejects += 1;
} else {
let issues = accepted(out, &name, &source, &baseline.decoded.project);
rows.push_str(&format!("{name}\t{layer}\taccept\texact Project, raw/no-op/name/canonical; {issues} model issues\n"));
passes += 1;
}
}
if case.fields.iter().any(|(_, _, required)| !required) {
let fields = case
.fields
.iter()
.filter(|(_, _, required)| *required)
.map(|(field, value, _)| format!("{field} {value};"))
.collect::<Vec<_>>()
.join(" ");
let name = format!("{layer}-{}-omit-all-optional", case.kind);
let issues = accepted(
out,
&name,
&fixture(layer, case.kind, &fields),
&baseline.decoded.project,
);
rows.push_str(&format!("{name}\t{layer}\taccept\texact Project, raw/no-op/name/canonical; {issues} model issues\n"));
passes += 1;
}
}
let prefix = if layer == Layer::Oa { "oa" } else { "sa" };
let source = format!(
"arcadia 7;\nproject defaults \"Defaults\" id \"{}\" {{ {prefix}_scenario scene \"Scene\" id \"{}\" {{ }} }}\n",
id(1),
id(2)
);
let compiled = compile(&source).unwrap();
let explicit = source.replace("{ }", "{ viewpoint MixedLegacy; }");
assert_eq!(
compiled.decoded.project,
compile(&explicit).unwrap().decoded.project
);
assert_eq!(compiled.bundle, bundle(&source));
assert!(matches!(
compiled.decoded.project.elements[&id(2)].shared(),
Some(SharedBody::Scenario {
viewpoint: ScenarioViewpoint::MixedLegacy,
..
})
));
let canonical = language::format(&compiled.decoded.project);
assert_eq!(
compile(&canonical).unwrap().decoded.project,
compiled.decoded.project
);
fs::write(
out.join(format!("{layer}-scenario-viewpoint-omitted.arcadia")),
source,
)
.unwrap();
fs::write(
out.join(format!("{layer}-scenario-viewpoint-explicit.arcadia")),
explicit,
)
.unwrap();
rows.push_str(&format!("{layer}-scenario-viewpoint\t{layer}\taccept\texact MixedLegacy Project, retained omission, canonical roundtrip\n"));
passes += 1;
}
fs::write(out.join("results.tsv"), rows).unwrap();
println!(
"PASS: {passes} accepted explicit/omitted comparisons; {rejects} missing-required-field refusals; both source-7 layers. Full Project, canonical roundtrip, no-op and exact name-only source retention checked."
);
}