#![allow(
clippy::expect_used,
clippy::unwrap_used,
clippy::panic,
clippy::print_stderr,
clippy::print_stdout,
clippy::field_reassign_with_default,
clippy::manual_flatten,
clippy::collapsible_if,
clippy::empty_line_after_doc_comments,
clippy::uninlined_format_args,
clippy::drop_non_drop,
missing_docs
)]
use zerodds_qos::{HistoryKind, ReliabilityKind};
use zerodds_xml::{
DdsXml, ResolvedParticipant, XmlError, apply_to_factory, parse_application_libraries,
parse_dds_xml, parse_domain_libraries, parse_domain_participant_libraries,
};
use zerodds_xml::{ParticipantFactoryAdapter, topic_filter_matches};
#[test]
fn cyclone_default_roundtrip_all_four_libraries() {
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<dds xmlns="http://www.omg.org/spec/DDS-XML">
<qos_library name="ql">
<qos_profile name="Reliable">
<datawriter_qos>
<reliability><kind>RELIABLE</kind></reliability>
</datawriter_qos>
</qos_profile>
</qos_library>
<domain_library name="dl">
<domain name="MyDomain" domain_id="42">
<register_type name="StateType" type_ref="MyTypes::State"/>
<topic name="StateTopic" register_type_ref="StateType"/>
</domain>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="MyParticipant" domain_ref="dl::MyDomain">
<publisher name="Pub1">
<data_writer name="StateWriter" topic_ref="StateTopic" qos_profile_ref="ql::Reliable"/>
</publisher>
<subscriber name="Sub1">
<data_reader name="StateReader" topic_ref="StateTopic"/>
</subscriber>
</domain_participant>
</domain_participant_library>
<application_library name="al">
<application name="MyApp">
<domain_participant ref="dpl::MyParticipant"/>
</application>
</application_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
assert_eq!(d.qos_libraries.len(), 1);
assert_eq!(d.domain_libraries.len(), 1);
assert_eq!(d.participant_libraries.len(), 1);
assert_eq!(d.application_libraries.len(), 1);
let resolved = d
.resolve_participant("dpl::MyParticipant")
.expect("resolve");
assert_eq!(resolved.domain_id, 42);
assert_eq!(resolved.publishers[0].data_writers[0].name, "StateWriter");
}
#[test]
fn domain_with_two_topics_and_register_types() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="3">
<register_type name="A" type_ref="ns::A"/>
<register_type name="B" type_ref="ns::B"/>
<topic name="TA" register_type_ref="A"/>
<topic name="TB" register_type_ref="B"/>
</domain>
</domain_library>
</dds>"#;
let libs = parse_domain_libraries(xml).expect("parse");
let d = &libs[0].domains[0];
assert_eq!(d.topics.len(), 2);
assert_eq!(d.register_types.len(), 2);
assert!(d.register_type("A").is_some());
assert!(d.topic("TB").is_some());
}
#[test]
fn cross_library_domain_ref_resolves() {
let xml = r#"<dds>
<domain_library name="my_lib">
<domain name="MyDomain" domain_id="7">
<register_type name="T"/>
</domain>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="my_lib::MyDomain"/>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let r = d.resolve_participant("dpl::P").expect("resolve");
assert_eq!(r.domain_id, 7);
assert_eq!(r.domain.name, "MyDomain");
}
#[test]
fn unresolved_domain_ref_errors() {
let xml = r#"<dds>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="missing::Foo"/>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let err = d.resolve_participant("dpl::P").expect_err("missing");
assert!(matches!(err, XmlError::UnresolvedReference(_)));
}
#[test]
fn participant_inheritance_one_level() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="A" domain_id="1"/>
<domain name="B" domain_id="2"/>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="Parent" domain_ref="dl::A">
<domain_participant_qos>
<entity_factory>
<autoenable_created_entities>true</autoenable_created_entities>
</entity_factory>
</domain_participant_qos>
</domain_participant>
<domain_participant name="Child" domain_ref="dl::B" base_name="Parent"/>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let r = d.resolve_participant("dpl::Child").expect("resolve");
assert_eq!(r.domain_id, 2);
let qos = r.qos.as_ref().expect("qos");
assert!(qos.entity_factory.is_some());
assert!(qos.entity_factory.unwrap().autoenable_created_entities);
}
#[test]
fn participant_inheritance_three_levels() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="0"/>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="A" domain_ref="dl::D">
<publisher name="P1"/>
</domain_participant>
<domain_participant name="B" domain_ref="dl::D" base_name="A">
<publisher name="P2"/>
</domain_participant>
<domain_participant name="C" domain_ref="dl::D" base_name="B">
<publisher name="P3"/>
</domain_participant>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let r = d.resolve_participant("dpl::C").expect("resolve");
assert_eq!(r.inheritance_chain.len(), 3);
assert_eq!(r.publishers.len(), 3);
let names: Vec<&str> = r.publishers.iter().map(|p| p.name.as_str()).collect();
assert!(names.contains(&"P1"));
assert!(names.contains(&"P2"));
assert!(names.contains(&"P3"));
}
#[test]
fn participant_inheritance_cycle_detected() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="0"/>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="A" domain_ref="dl::D" base_name="B"/>
<domain_participant name="B" domain_ref="dl::D" base_name="A"/>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let err = d.resolve_participant("dpl::A").expect_err("cycle");
assert!(matches!(err, XmlError::CircularInheritance(_)));
}
#[test]
fn application_unresolved_participant() {
let xml = r#"<dds>
<application_library name="al">
<application name="A">
<domain_participant ref="missing::P"/>
</application>
</application_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let err = d.resolve_application("al::A").expect_err("missing");
assert!(matches!(err, XmlError::UnresolvedReference(_)));
}
#[test]
fn topic_filter_glob_matches() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="0">
<register_type name="T"/>
<topic name="StateTopic" register_type_ref="T">
<topic_filter>State*</topic_filter>
</topic>
</domain>
</domain_library>
</dds>"#;
let libs = parse_domain_libraries(xml).expect("parse");
let t = &libs[0].domains[0].topics[0];
let filter = t.topic_filter.as_deref().expect("filter");
assert!(topic_filter_matches(filter, "StateTopic"));
assert!(topic_filter_matches(filter, "StateXY"));
assert!(!topic_filter_matches(filter, "OtherTopic"));
}
#[test]
fn datawriter_inherits_publisher_qos() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="0">
<register_type name="T"/>
<topic name="Topic1" register_type_ref="T"/>
</domain>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D">
<publisher name="Pub1">
<publisher_qos>
<partition><name>g1</name></partition>
</publisher_qos>
<data_writer name="W1" topic_ref="Topic1"/>
</publisher>
</domain_participant>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let r = d.resolve_participant("dpl::P").expect("resolve");
let w = &r.publishers[0].data_writers[0];
let qos = w.qos.as_ref().expect("inherited");
let names = qos.partition.as_ref().expect("partition");
assert_eq!(names.names, vec!["g1".to_string()]);
}
#[test]
fn publisher_without_qos_yields_none() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="0"/>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D">
<publisher name="Pub1"/>
</domain_participant>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let r = d.resolve_participant("dpl::P").expect("resolve");
assert!(r.publishers[0].qos.is_none());
}
#[test]
fn topic_qos_inline_and_profile_ref_both_resolve() {
let xml = r#"<dds>
<qos_library name="ql">
<qos_profile name="P">
<topic_qos>
<reliability><kind>RELIABLE</kind></reliability>
</topic_qos>
</qos_profile>
</qos_library>
<domain_library name="dl">
<domain name="D" domain_id="0">
<register_type name="T"/>
<topic name="InlineQ" register_type_ref="T">
<topic_qos>
<history><kind>KEEP_ALL</kind></history>
</topic_qos>
</topic>
<topic name="RefQ" register_type_ref="T" qos_profile_ref="ql::P"/>
</domain>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="DP" domain_ref="dl::D"/>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let r = d.resolve_participant("dpl::DP").expect("resolve");
let inline = r
.topics
.iter()
.find(|t| t.name == "InlineQ")
.expect("inline");
let qos = inline.qos.as_ref().expect("inline-qos");
assert_eq!(qos.history.unwrap().kind, HistoryKind::KeepAll);
let refq = r.topics.iter().find(|t| t.name == "RefQ").expect("refq");
let qos = refq.qos.as_ref().expect("ref-qos");
assert_eq!(qos.reliability.unwrap().kind, ReliabilityKind::Reliable);
}
#[test]
fn empty_dds_root_is_valid() {
let d = parse_dds_xml(r#"<dds/>"#).expect("parse");
assert!(d.qos_libraries.is_empty());
assert!(d.domain_libraries.is_empty());
assert!(d.participant_libraries.is_empty());
assert!(d.application_libraries.is_empty());
}
#[test]
fn dtd_rejected_in_dds_xml() {
let xml = r#"<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<dds><domain_library name="L"/></dds>"#;
let err = parse_dds_xml(xml).expect_err("dtd");
assert!(matches!(err, XmlError::InvalidXml(_)));
}
#[test]
fn multiple_domain_libraries_coexist() {
let xml = r#"<dds>
<domain_library name="A"><domain name="D1" domain_id="1"/></domain_library>
<domain_library name="B"><domain name="D2" domain_id="2"/></domain_library>
</dds>"#;
let libs = parse_domain_libraries(xml).expect("parse");
assert_eq!(libs.len(), 2);
assert_eq!(libs[0].name, "A");
assert_eq!(libs[1].name, "B");
}
#[test]
fn mixed_top_level_with_cross_refs() {
let xml = r#"<dds>
<qos_library name="ql">
<qos_profile name="Reliable">
<datawriter_qos>
<reliability><kind>RELIABLE</kind></reliability>
</datawriter_qos>
<datareader_qos>
<reliability><kind>RELIABLE</kind></reliability>
</datareader_qos>
</qos_profile>
</qos_library>
<domain_library name="dl">
<domain name="D" domain_id="5">
<register_type name="T"/>
<topic name="T1" register_type_ref="T"/>
</domain>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D">
<publisher name="Pub">
<data_writer name="W" topic_ref="T1" qos_profile_ref="ql::Reliable"/>
</publisher>
</domain_participant>
</domain_participant_library>
<application_library name="al">
<application name="App">
<domain_participant ref="dpl::P"/>
</application>
</application_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let apps = d.resolve_application("al::App").expect("resolve-app");
assert_eq!(apps.len(), 1);
let p = &apps[0];
assert_eq!(p.domain_id, 5);
let w = &p.publishers[0].data_writers[0];
let q = w.qos.as_ref().expect("qos");
assert_eq!(q.reliability.unwrap().kind, ReliabilityKind::Reliable);
}
#[test]
fn spec_example_deployment_file() {
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<dds xmlns="http://www.omg.org/spec/DDS-XML">
<qos_library name="qos_lib">
<qos_profile name="Reliable">
<datawriter_qos>
<reliability><kind>RELIABLE</kind></reliability>
<history><kind>KEEP_ALL</kind></history>
</datawriter_qos>
</qos_profile>
</qos_library>
<domain_library name="my_lib">
<domain name="MyDomain" domain_id="42">
<register_type name="StateType" type_ref="MyTypes::State"/>
<topic name="StateTopic" register_type_ref="StateType"/>
</domain>
</domain_library>
<domain_participant_library name="dp_lib">
<domain_participant name="ParentParticipant" domain_ref="my_lib::MyDomain"/>
<domain_participant name="MyParticipant" domain_ref="my_lib::MyDomain"
base_name="ParentParticipant">
<publisher name="Pub1">
<data_writer name="StateWriter" topic_ref="StateTopic"
qos_profile_ref="qos_lib::Reliable"/>
</publisher>
</domain_participant>
</domain_participant_library>
<application_library name="app_lib">
<application name="MyApp">
<domain_participant ref="dp_lib::MyParticipant"/>
</application>
</application_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let apps = d.resolve_application("app_lib::MyApp").expect("apps");
assert_eq!(apps.len(), 1);
let p = &apps[0];
assert_eq!(p.domain_id, 42);
assert_eq!(p.inheritance_chain.len(), 2);
let w = &p.publishers[0].data_writers[0];
assert_eq!(w.topic.name, "StateTopic");
assert_eq!(w.topic.type_name, "MyTypes::State");
let qos = w.qos.as_ref().expect("qos");
assert_eq!(qos.reliability.unwrap().kind, ReliabilityKind::Reliable);
assert_eq!(qos.history.unwrap().kind, HistoryKind::KeepAll);
}
#[test]
fn resolve_participant_returns_flat_snapshot() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="11">
<register_type name="T"/>
<topic name="X" register_type_ref="T"/>
</domain>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D">
<subscriber name="S">
<data_reader name="R" topic_ref="X"/>
</subscriber>
</domain_participant>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let r = d.resolve_participant("dpl::P").expect("resolve");
assert_eq!(r.lookup_path, "dpl::P");
assert_eq!(r.name, "P");
assert_eq!(r.domain_id, 11);
assert_eq!(r.topics.len(), 1);
assert_eq!(r.subscribers[0].data_readers[0].name, "R");
assert_eq!(r.subscribers[0].data_readers[0].topic.name, "X");
}
#[test]
fn factory_adapter_skeleton_invocation() {
use core::cell::Cell;
struct CountingAdapter {
calls: Cell<usize>,
}
impl ParticipantFactoryAdapter for CountingAdapter {
fn apply(&self, _p: &ResolvedParticipant) -> Result<(), XmlError> {
self.calls.set(self.calls.get() + 1);
Ok(())
}
}
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="1"/>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D"/>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let r = d.resolve_participant("dpl::P").expect("resolve");
let adapter = CountingAdapter {
calls: Cell::new(0),
};
apply_to_factory(&r, &adapter).expect("apply");
assert_eq!(adapter.calls.get(), 1);
}
#[test]
fn standalone_loaders_work_in_isolation() {
let xml1 = r#"<dds>
<application_library name="al">
<application name="A">
<domain_participant ref="dpl::P"/>
</application>
</application_library>
</dds>"#;
let apps = parse_application_libraries(xml1).expect("parse");
assert_eq!(apps[0].applications[0].name, "A");
let xml2 = r#"<dds>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D"/>
</domain_participant_library>
</dds>"#;
let dps = parse_domain_participant_libraries(xml2).expect("parse");
assert_eq!(dps[0].participants[0].name, "P");
}
#[test]
fn topic_with_unknown_register_type_ref_errors() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="0">
<topic name="T1" register_type_ref="MissingType"/>
</domain>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D"/>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let err = d.resolve_participant("dpl::P").expect_err("missing");
assert!(matches!(err, XmlError::UnresolvedReference(_)));
}
#[test]
fn datawriter_qos_profile_ref_unresolved() {
let xml = r#"<dds>
<domain_library name="dl">
<domain name="D" domain_id="0">
<register_type name="T"/>
<topic name="T1" register_type_ref="T"/>
</domain>
</domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D">
<publisher name="Pub">
<data_writer name="W" topic_ref="T1" qos_profile_ref="ql::Missing"/>
</publisher>
</domain_participant>
</domain_participant_library>
</dds>"#;
let d = parse_dds_xml(xml).expect("parse");
let err = d.resolve_participant("dpl::P").expect_err("missing");
assert!(matches!(err, XmlError::UnresolvedReference(_)));
}
#[test]
fn find_participant_and_domain() {
let xml = r#"<dds>
<domain_library name="dl"><domain name="D" domain_id="3"/></domain_library>
<domain_participant_library name="dpl">
<domain_participant name="P" domain_ref="dl::D"/>
</domain_participant_library>
</dds>"#;
let d: DdsXml = parse_dds_xml(xml).expect("parse");
assert_eq!(d.find_domain("dl::D").expect("d").domain_id, 3);
assert_eq!(d.find_participant("dpl::P").expect("p").name, "P");
assert!(matches!(
d.find_domain("nope::X"),
Err(XmlError::UnresolvedReference(_))
));
assert!(matches!(
d.find_participant("X"),
Err(XmlError::UnresolvedReference(_))
));
}