#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
fn errors(source: &str) -> Vec<String> {
zerodds_idl_compose::compose_source(
"test.idl",
source,
&zerodds_idl_compose::PathLoader::bare(),
)
.diagnostics
.iter()
.filter(|d| d.severity == zerodds_idl_parse::Severity::Error)
.map(|d| d.message.clone())
.collect()
}
fn refuses_the_cycle(source: &str) -> bool {
errors(source)
.iter()
.any(|m| m.contains("closes a type cycle"))
}
#[test]
fn a_cycle_with_no_terminator_on_it_is_refused() {
for source in [
"struct Sa { Sa m; };",
"struct Sa { Sa m[3]; };",
"struct Sb;\nstruct Sa { Sb m; };\nstruct Sb { Sa m; };",
"union Ua switch (long) { case 1: Ua m; };",
] {
assert!(
refuses_the_cycle(source),
"{source:?} should be refused: {:?}",
errors(source)
);
}
}
#[test]
fn a_cycle_with_a_terminator_on_it_is_admitted() {
for source in [
"struct Sa { sequence<Sa> m; };",
"struct Sa { sequence<Sa, 4> m; };",
"struct Sa { @optional Sa m; };",
"struct Sa { map<long, Sa> m; };",
"struct Sb;\nstruct Sa { sequence<Sb> bs; };\nstruct Sb { sequence<Sa> as; };",
"struct Sb { long m; };\nstruct Sa { Sb m; };",
"struct Sa { long Sa_field; };",
] {
assert!(
!refuses_the_cycle(source),
"{source:?} is IDL 4.2 and was refused: {:?}",
errors(source)
);
}
}
#[test]
fn the_diagnostic_names_the_type_and_the_clause() {
let found = errors("module Ma { struct Sa { Sa m; }; };");
let message = found
.iter()
.find(|m| m.contains("closes a type cycle"))
.expect("the refusal");
assert!(message.contains("Ma::Sa"), "{message}");
assert!(message.contains("7.4.1.4.4.4.4"), "{message}");
}
#[test]
fn the_interop_corpus_case_for_this_is_refused_and_its_neighbours_are_not() {
let repo = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let case = |name: &str| {
let path = repo
.join("tests/interop-corpus")
.join(name)
.join("type.idl");
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()))
};
assert!(
refuses_the_cycle(&case("recursive-array")),
"recursive-array is the case IDL 4.2 §7.4.1.4.4.4.4 forbids"
);
for admitted in [
"recursive-self-sequence",
"recursive-mutual",
"recursive-through-map",
"recursive-optional-external",
"recursive-typedef-sequence",
"recursive-union-branch",
] {
assert!(
!refuses_the_cycle(&case(admitted)),
"{admitted} is admitted recursion and was refused"
);
}
}
#[test]
fn a_forward_declared_cycle_is_reported_once() {
let reported: Vec<String> =
errors("module m { struct Node; @appendable struct Node { long value; Node pair[2]; }; };")
.into_iter()
.filter(|m| m.contains("closes a type cycle"))
.collect();
assert_eq!(
reported.len(),
1,
"one cycle, one error — got {reported:#?}"
);
assert!(
reported[0].contains("::m::Node"),
"the error names the type: {}",
reported[0]
);
}
#[test]
fn external_indirection_requires_a_finite_value_not_just_a_pointer() {
let legal = [
"union Tag; struct Item { @external Tag element; }; union Tag switch (long) { case 0: long leaf; case 1: Item nested; };",
"union Tag; typedef Tag Ref; struct Item { @external Ref element; }; union Tag switch (long) { case 0: long leaf; case 1: Item nested; };",
"union Tag switch (long) { case 0: long leaf; case 1: @external Tag nested; };",
];
for source in legal {
let composed = zerodds_idl_compose::compose_source(
"external.idl",
source,
&zerodds_idl_compose::PathLoader::bare(),
);
assert!(errors(source).is_empty(), "{source}: {:?}", errors(source));
for encoding in [
zerodds_ast_layout::Encoding::PlainCdr1,
zerodds_ast_layout::Encoding::PlainCdr2,
] {
let key = zerodds_ast_layout::LayoutKey::data(
encoding,
zerodds_ast_layout::Endianness::Little,
);
for (id, _) in composed.ast.types() {
let result = zerodds_ast_layout::xcdr::plan(
&composed.ast,
id,
key,
&mut zerodds_ast_layout::xcdr::DepCollector::new(),
);
assert!(result.is_ok(), "{source}: {encoding:?}: {result:?}");
}
}
}
for source in [
"struct Node { @external Node next; };",
"struct A; struct B { @external A a; }; struct A { B b; };",
"union Tag; struct Item { @external Tag element; }; union Tag switch (long) { case 0: Item nested; default: Item other; };",
"union Tag switch (long) { case 0: long leaf; case 1: Tag nested; };",
"union Tag; struct Item { Tag element; }; union Tag switch (long) { case 0: long leaf; case 1: Item nested; };",
] {
assert!(refuses_the_cycle(source), "{source}: {:?}", errors(source));
}
}