//! 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 { 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> { 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::>() .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::>() .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::>() .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." ); }