Migrate frozen data contracts and authored source to schema 4
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
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 4;"));
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user