use oa_core::{language, source_project::*, *}; use std::collections::BTreeMap; fn id(n: u128) -> Id { Id::from_u128(n) } #[test] fn authored_v3_bundle_upgrade_preserves_includes_nested_ownership_comments_and_identity() { let original = include_str!("fixtures/v3-data-contract.arcadia"); let header = original.find(" sa_function").unwrap(); let close = original.rfind('}').unwrap(); let entry = format!( "{} // Contracts stay in their original module.\n include \"contracts.arcadia\";\n}}\n", &original[..header] ); let fragment = original[header..close].replace( "items [@packet];", "items [@ // interface contract decision\npacket];", ); let bundle = SourceBundle { entry: "project.arcadia".into(), files: BTreeMap::from([ ("project.arcadia".into(), entry), ("contracts.arcadia".into(), fragment), ]), }; let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap(); assert_eq!(compiled.bundle.files.len(), 2); assert!( compiled.bundle.files["project.arcadia"].contains(&format!("arcadia {SCHEMA_VERSION};")) ); assert!(compiled.bundle.files["project.arcadia"].contains("include \"contracts.arcadia\";")); for text in [ "// Contracts stay in their original module.", "// Reused record parameters retain their own identities.", "// interface contract decision", ] { assert!( compiled .bundle .files .values() .any(|source| source.contains(text)), "Lost {text}" ); } assert_eq!(compiled.index.lexical_owners[&id(6)], id(5)); assert_eq!(compiled.index.lexical_owners[&id(7)], id(4)); assert_eq!(compiled.index.lexical_owners[&id(8)], id(4)); assert_eq!(compiled.index.lexical_owners[&id(17)], id(14)); let report = compiled.decoded.migration.as_ref().unwrap(); assert_eq!(report.from_schema, 3); for allocation in &report.generated_interface_allocations { assert_eq!( compiled.index.declarations[&allocation.id].path, "contracts.arcadia" ); } assert!( compiled .diagnostics .iter() .any(|d| d.diagnostic.code == "message_value_ambiguous_path") ); let second = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap(); assert_eq!(second.bundle, compiled.bundle); assert_eq!(second.decoded.project, compiled.decoded.project); assert!(second.decoded.migration.is_none()); let mut renamed = compiled.decoded.project.clone(); renamed.elements.get_mut(&id(6)).unwrap().name = "Latitude degrees".into(); let updated = update_bundle(&compiled, &renamed, ProjectLimits::default()).unwrap(); assert_eq!(updated.index.lexical_owners, compiled.index.lexical_owners); assert!(updated.bundle.files["contracts.arcadia"].contains("Latitude degrees")); assert!( bundle.files["project.arcadia"].contains("arcadia 3;"), "Migration does not mutate the caller's draft" ); } #[test] fn schema4_typed_values_supertype_paths_and_protocols_roundtrip() { let source = r#"arcadia 4; project data_contract "Data contract" { sa_data_type bounded "Bounded" { definition { Integer { minimum -9223372036854775808; maximum 9223372036854775807; }; }; default { Integer -9223372036854775808; }; unit "m"; } sa_data_type label_type "Label" { definition { String { minimum_length 1; maximum_length 4; }; }; default { String "航海"; }; } sa_data_type base "Base" { definition { Record { supertype none; }; }; } sa_data_type derived "Derived" { definition { Record { supertype @base; }; }; } sa_exchange_item operation "Operation" { mechanism Operation; sa_data_field value "Value" { type_ref { Named @bounded; }; direction In; storage Value; default [{ Integer 42; }]; } } sa_interface contract "Contract" { supertype none; } interface_item_allocation member "Operation member" { source @contract; target @operation; transmission Synchronous; acquisition Accept; order 3; } data_association association "Related records" { source @base; target @derived; source_role "parent"; target_role "children"; source_minimum 0; source_maximum 1; target_minimum 0; target_maximum none; source_navigable true; target_navigable true; } }"#; let parsed = language::parse(source).unwrap(); let formatted = language::format(&parsed); assert!(formatted.contains("supertype @base")); assert!(formatted.contains("transmission Synchronous")); assert_eq!(language::parse(&formatted).unwrap(), parsed); let compiled = compile_bundle(&format_bundle(&parsed), ProjectLimits::default()).unwrap(); assert_eq!(compiled.decoded.project, parsed); assert!(compiled.decoded.migration.is_none()); } #[test] fn omitted_ids_are_stabilized_before_allocation_identity_generation() { let bundle=SourceBundle{entry:"project.arcadia".into(),files:BTreeMap::from([("project.arcadia".into(),"arcadia 3; project old_model \"Old\" { sa_exchange_item event_item \"Event\" { kind Event; } sa_interface contract \"Contract\" { items [@event_item]; } }".into())])}; let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap(); let report = compiled.decoded.migration.as_ref().unwrap(); let allocation = &report.generated_interface_allocations[0]; assert_eq!( allocation.id, Id::new_v5( &compiled.decoded.project.id, format!( "v2-interface-item:{}:{}", allocation.source, allocation.target ) .as_bytes() ) ); let reopened = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap(); assert_eq!(reopened.decoded.project, compiled.decoded.project); assert_eq!(reopened.bundle, compiled.bundle); } #[test] fn inferring_a_legacy_path_keeps_rationale_inside_its_argument() { let source = include_str!("fixtures/v3-data-contract.arcadia") .replace( "type_ref { Named @coordinate; }; }\n }", "type_ref String; }\n }", ) .replace( "field @latitude; expression", "field @latitude; // original argument rationale\nexpression", ); let bundle = SourceBundle { entry: "project.arcadia".into(), files: BTreeMap::from([("project.arcadia".into(), source)]), }; let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap(); let source = &compiled.bundle.files["project.arcadia"]; let argument = &source[source.find("values [{").unwrap()..]; let argument = &argument[..argument.find('}').unwrap()]; assert!( argument.contains("// original argument rationale"), "{source}" ); assert!(argument.contains("path [@origin]"), "{source}"); assert_eq!(compiled.index.lexical_owners[&id(17)], id(14)); } #[test] fn qualified_value_reordering_and_path_reidentification_keep_each_arguments_comments() { let mut project = language::parse(include_str!("fixtures/v3-data-contract.arcadia")).unwrap(); let Some(SharedBody::Occurrence { body: OccurrenceBody::Exchange { values, .. }, .. }) = project.elements.get_mut(&id(17)).unwrap().shared_mut() else { panic!() }; *values = vec![ MessageValue { path: vec![id(7)], field: id(6), expression: "origin value".into(), }, MessageValue { path: vec![id(8)], field: id(6), expression: "destination value".into(), }, ]; let mut bundle = format_bundle(&project); for source in bundle.files.values_mut() { *source = source .replace( "expression \"origin value\";", "expression \"origin value\"; // origin argument rationale", ) .replace( "expression \"destination value\";", "expression \"destination value\"; // destination argument rationale", ); } let current = compile_bundle(&bundle, ProjectLimits::default()).unwrap(); let Some(SharedBody::Occurrence { body: OccurrenceBody::Exchange { values, .. }, .. }) = project.elements.get_mut(&id(17)).unwrap().shared_mut() else { panic!() }; values.reverse(); let renamed = Id::from_u128(100); let mut identities: BTreeMap<_, _> = project .identities() .into_iter() .map(|(id, _)| (id, id)) .collect(); identities.insert(id(7), renamed); reidentify(&mut project, id(7), renamed).unwrap(); project.elements.get_mut(&renamed).unwrap().alias = "revised_origin".into(); let result = update_bundle_with_identity_map(¤t, &project, &identities, ProjectLimits::default()) .unwrap(); let source = &result.bundle.files[&result.index.declarations[&id(17)].path]; let destination = source.find("// destination argument rationale").unwrap(); let origin = source.find("// origin argument rationale").unwrap(); assert!( destination < origin, "Distinct uses of the same terminal field must reorder as complete argument annotations: {source}" ); assert!(source[origin..].contains("path [@revised_origin]")); assert_eq!(result.decoded.project, project); }