#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use serde_json::json;
use zerodds_ast_core::ScopedName;
use zerodds_wire_probe::encode::{ProbeError, model_from_idl};
use zerodds_wire_probe::fill::{Vendor, fill};
fn source(idl: &str, type_name: &str, values: &serde_json::Value, vendor: Vendor) -> String {
emit(idl, type_name, values, vendor).expect("the emitter")
}
fn emit(
idl: &str,
type_name: &str,
values: &serde_json::Value,
vendor: Vendor,
) -> Result<String, ProbeError> {
let ast = model_from_idl(idl, "probe.idl").expect("the model");
let segments: Vec<String> = type_name
.split("::")
.filter(|segment| !segment.is_empty())
.map(std::string::ToString::to_string)
.collect();
let type_ = ast
.find_by_scoped_name(&ScopedName::absolute(segments))
.ok()
.and_then(zerodds_ast::AnyId::as_type)
.expect("the type");
fill(&ast, type_, values, vendor)
}
#[test]
fn a_wstring_is_emitted_as_a_typed_array_and_never_as_a_wide_literal() {
let idl = "@final struct WStr { wstring w; };";
for vendor in Vendor::ALL {
let out = source(idl, "WStr", &json!({ "w": "ab" }), vendor);
assert!(
out.contains("static __typeof__(*sample.w) zd_wstr_1[] = { 0x0061, 0x0062, 0x0000 };"),
"{vendor:?}: {out}"
);
assert!(out.contains("sample.w = zd_wstr_1;"), "{vendor:?}: {out}");
assert!(!out.contains("L\""), "{vendor:?}: {out}");
}
}
#[test]
fn a_wstring_outside_the_bmp_emits_both_of_its_code_units() {
let idl = "@final struct WStr { wstring w; };";
let out = source(idl, "WStr", &json!({ "w": "\u{1d11e}" }), Vendor::Cyclone);
assert!(out.contains("{ 0xd834, 0xdd1e, 0x0000 }"), "{out}");
}
#[test]
fn a_sequence_of_wstring_takes_its_element_type_from_the_sequence() {
let idl = "@final struct WSeq { sequence<wstring> ws; };";
let cyclone = source(idl, "WSeq", &json!({ "ws": ["ab"] }), Vendor::Cyclone);
assert!(
cyclone.contains("static __typeof__(*sample.ws._buffer) zd_seq_1[1];"),
"{cyclone}"
);
assert!(
cyclone
.contains("static __typeof__(*zd_seq_1[0]) zd_wstr_2[] = { 0x0061, 0x0062, 0x0000 };"),
"{cyclone}"
);
assert!(!cyclone.contains("L\""), "{cyclone}");
let rti = source(idl, "WSeq", &json!({ "ws": ["ab"] }), Vendor::Rti);
assert!(
rti.contains("DDS_WstringSeq_set_length(&sample.ws, 1);"),
"{rti}"
);
assert!(
rti.contains("(*DDS_WstringSeq_get_reference(&sample.ws, 0)) = zd_wstr_1;"),
"{rti}"
);
}
#[test]
fn a_bitmask_is_emitted_as_its_flag_names_ored_together() {
let idl = "\
@bit_bound(8) bitmask Perm { READ, WRITE, EXEC };
@final struct B8 { Perm p; };";
for vendor in Vendor::ALL {
let out = source(idl, "B8", &json!({ "p": ["READ", "WRITE"] }), vendor);
assert_eq!(out.trim(), "sample.p = READ | WRITE;", "{vendor:?}");
}
}
#[test]
fn a_bitmask_inside_a_module_carries_the_flattened_module_path() {
let idl = "\
module app {
bitmask Flags { A, B };
@final struct Holder { Flags f; };
};";
let out = source(idl, "app::Holder", &json!({ "f": ["B"] }), Vendor::Rti);
assert!(out.contains("sample.f = B;"), "{out}");
let out = source(idl, "app::Holder", &json!({ "f": ["B"] }), Vendor::Cyclone);
assert!(out.contains("sample.f = app_B;"), "{out}");
}
#[test]
fn an_empty_bitmask_is_emitted_as_zero_and_not_as_nothing() {
let idl = "\
bitmask Flags { A, B };
@final struct Holder { Flags f; };";
let out = source(idl, "Holder", &json!({ "f": [] }), Vendor::Cyclone);
assert_eq!(out.trim(), "sample.f = 0;");
}
#[test]
fn a_misspelled_flag_is_refused_with_the_declared_names() {
let idl = "\
bitmask Flags { A, B };
@final struct Holder { Flags f; };";
let error = emit(idl, "Holder", &json!({ "f": ["C"] }), Vendor::Cyclone).expect_err("a typo");
assert!(
matches!(&error, ProbeError::Unknown { declared, .. }
if declared == &["A".to_string(), "B".to_string()]),
"{error}"
);
}
#[test]
fn the_constructs_with_no_c_binding_are_refused_by_name() {
let cases = [
(
"bitset B { bitfield<3> a; };\n@final struct H { B packed; };",
json!({ "packed": { "a": 1 } }),
"bitset",
),
(
"@final struct H { map<long, long> m; };",
json!({ "m": [[1, 2]] }),
"map",
),
(
"@final struct H { fixed<5,2> f; };",
json!({ "f": "1.00" }),
"fixed",
),
];
for (idl, values, construct) in cases {
for vendor in Vendor::ALL {
let error = emit(idl, "H", &values, vendor).expect_err("a refusal");
assert!(
matches!(&error, ProbeError::Unsupported { kind, .. } if kind.contains(construct)),
"{construct}, {vendor:?}: {error}"
);
}
}
}
#[test]
fn a_narrow_string_still_takes_each_vendors_own_idiom() {
let idl = "@final struct Str { string s; };";
assert_eq!(
source(idl, "Str", &json!({ "s": "abc" }), Vendor::Cyclone).trim(),
"sample.s = (char *)\"abc\";"
);
assert_eq!(
source(idl, "Str", &json!({ "s": "abc" }), Vendor::Rti).trim(),
"sample.s = DDS_String_dup(\"abc\");"
);
}
#[test]
fn the_c_emitter_fills_an_inherited_member() {
let idl = "
struct Base { long base_a; short base_b; };
struct Sample : Base { double own; };
";
let values = json!({"base_a": -1234, "base_b": -2, "own": 2.5});
let source = source(idl, "::Sample", &values, Vendor::Cyclone);
assert!(
source.contains("sample.parent.base_a"),
"base_a unfilled:\n{source}"
);
assert!(
source.contains("sample.parent.base_b"),
"base_b unfilled:\n{source}"
);
assert!(source.contains("sample.own"), "own unfilled:\n{source}");
}
#[test]
fn the_c_emitter_refuses_a_key_that_names_no_member() {
let idl = "struct Sample { long a; };";
let values = json!({"a": 1, "nope": 2});
match emit(idl, "::Sample", &values, Vendor::Cyclone) {
Err(ProbeError::Unknown { path, .. }) => assert_eq!(path, "Sample.nope"),
other => panic!("expected a refusal, got {other:?}"),
}
}