216 lines
9.3 KiB
Rust
216 lines
9.3 KiB
Rust
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("arcadia 5;"));
|
|
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);
|
|
}
|