#![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_idl::config::ParserConfig;
use zerodds_idl_csharp::{CsGenOptions, generate_csharp};
fn gen_cs(src: &str) -> String {
let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse");
generate_csharp(&ast, &CsGenOptions::default()).expect("gen")
}
#[test]
fn idl_naming_default_uses_dotnet_pascal_case() {
let cs = gen_cs("struct my_struct { long my_field; };");
assert!(
cs.contains("MyStruct") || cs.contains("my_struct"),
"neither .NET-PascalCase nor IDL-naming present:\n{cs}"
);
}
#[test]
fn camel_case_member_naming_for_pascalized_idl_names() {
let cs = gen_cs("struct S { long my_field; };");
assert!(cs.contains("MyField") || cs.contains("my_field"));
}
#[test]
fn standalone_constant_emits_const_value_class() {
let cs = gen_cs("const long MAX = 100;");
assert!(cs.contains("MAX"));
assert!(cs.contains("const") || cs.contains("static"));
}
#[test]
fn unbounded_sequence_member_emits_isequence_marker() {
let cs = gen_cs("struct S { sequence<long> values; };");
assert!(
cs.contains("ISequence") || cs.contains("IList"),
"neither ISequence<T> nor IList<T> emitted:\n{cs}"
);
}
#[test]
fn bounded_sequence_member_emits_ibounded_sequence() {
let cs = gen_cs("struct S { sequence<long, 5> values; };");
assert!(cs.contains("Bounded") || cs.contains("IList") || cs.contains("ISequence"));
}
#[test]
fn struct_emits_public_class_or_record_class() {
let cs = gen_cs("struct S { long x; };");
assert!(cs.contains("class S") || cs.contains("record S"));
}
#[test]
fn union_emits_discriminator_class() {
let cs = gen_cs(
r#"
union U switch (long) {
case 1: long a;
case 2: float b;
};
"#,
);
assert!(cs.contains("class U") || cs.contains("record U"));
}
#[test]
fn typedef_alias_works_for_recursive_pattern() {
let cs = gen_cs(
r#"
typedef sequence<long> LongSeq;
struct Holder { LongSeq items; };
"#,
);
assert!(cs.contains("LongSeq"));
assert!(cs.contains("Holder"));
}
#[test]
fn typedef_emits_alias_record_or_using() {
let cs = gen_cs("typedef long MyLong;");
assert!(cs.contains("MyLong"), "typedef-alias fehlt:\n{cs}");
}
#[test]
fn interface_forward_decl_has_no_csharp_output() {
let parse = zerodds_idl::parse("interface Foo;", &ParserConfig::default());
if let Ok(ast) = parse {
let res = generate_csharp(&ast, &CsGenOptions::default());
if let Ok(cs) = res {
assert!(
!cs.contains("interface Foo {"),
"forward-decl emittiert vollen Interface-Body"
);
}
}
}
#[test]
fn union_with_octet_discriminator_supported() {
let cs = gen_cs(
r#"
union U switch (octet) {
case 1: long a;
};
"#,
);
assert!(cs.contains("U"));
}
#[test]
fn verbatim_annotation_with_csharp_language_inlines_text() {
let cs = gen_cs(
r#"
@verbatim(language="csharp", placement=BEFORE_DECLARATION, text="// pre-decl marker")
struct PlainStruct { long x; };
"#,
);
assert!(cs.contains("PlainStruct"));
assert!(
cs.contains("// pre-decl marker"),
"@verbatim BEFORE_DECLARATION fehlt:\n{cs}"
);
let pos_marker = cs.find("// pre-decl marker").unwrap_or(usize::MAX);
let pos_class = cs.find("record class PlainStruct").unwrap_or(usize::MAX);
assert!(
pos_marker < pos_class,
"Marker muss vor record class stehen:\n{cs}"
);
}
#[test]
fn verbatim_annotation_csharp_alias_cs_matches() {
let cs = gen_cs(
r#"
@verbatim(language="cs", placement=AFTER_DECLARATION, text="// trailer")
struct S { long x; };
"#,
);
let pos_marker = cs.find("// trailer").unwrap_or(usize::MAX);
let pos_close = cs.rfind("}").unwrap_or(usize::MAX);
assert!(
pos_marker != usize::MAX && pos_marker > pos_close,
"AFTER_DECLARATION verbatim muss nach record-Block stehen:\n{cs}"
);
}
#[test]
fn bitset_emits_struct_with_value_field() {
let cs = gen_cs(r#"bitset BS { bitfield<3> a; bitfield<5> b; };"#);
assert!(cs.contains("public struct BS"), "struct BS fehlt:\n{cs}");
assert!(cs.contains("public ulong Value"));
assert!(cs.contains("public ulong A"), "Property A fehlt:\n{cs}");
assert!(cs.contains("public ulong B"));
assert!(cs.contains("0x7UL"));
assert!(cs.contains("0x1FUL"));
}
#[test]
fn bitmask_emits_flags_enum() {
let cs = gen_cs(r#"@bit_bound(8) bitmask Flags { READ, WRITE, EXEC };"#);
assert!(cs.contains("[System.Flags]"), "[Flags] fehlt:\n{cs}");
assert!(cs.contains("public enum Flags : byte"));
assert!(cs.contains("READ = 1UL << 0"));
assert!(cs.contains("WRITE = 1UL << 1"));
assert!(cs.contains("EXEC = 1UL << 2"));
}
#[test]
fn bitset_total_width_over_64_returns_error() {
let ast = zerodds_idl::parse(
r#"bitset BS { bitfield<40> a; bitfield<30> b; };"#,
&ParserConfig::default(),
);
if let Ok(ast) = ast {
let result = generate_csharp(&ast, &CsGenOptions::default());
assert!(result.is_err());
}
}
#[test]
fn valuetype_emits_abstract_and_concrete_class() {
let parse = zerodds_idl::parse(
r#"valuetype VT { public long x; };"#,
&ParserConfig::default(),
);
if let Ok(ast) = parse {
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(
cs.contains("public abstract class VTAbstract"),
"VTAbstract fehlt:\n{cs}"
);
assert!(cs.contains("public class VT : VTAbstract"));
assert!(cs.contains("X { get; set; }"), "Pascal-Property X:\n{cs}");
}
}
#[test]
fn valuetype_private_state_emits_protected_property() {
let parse = zerodds_idl::parse(
r#"valuetype VT { private long secret; };"#,
&ParserConfig::default(),
);
if let Ok(ast) = parse {
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(
cs.contains("protected abstract"),
"private->protected Mapping fehlt:\n{cs}"
);
}
}
#[test]
fn valuetype_factory_emits_void_abstract_method() {
let parse = zerodds_idl::parse(
r#"valuetype VT { public long x; factory create(in long x); };"#,
&ParserConfig::default(),
);
if let Ok(ast) = parse {
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(cs.contains("public abstract void create"), "factory:\n{cs}");
}
}
#[test]
fn non_service_interface_emits_csharp_interface() {
let cs = gen_cs(
r#"
interface Calc {
long add(in long a, in long b);
readonly attribute long version;
};
"#,
);
assert!(
cs.contains("public interface Calc"),
"C# interface fehlt:\n{cs}"
);
assert!(cs.contains("add"));
assert!(cs.contains("Version"));
}
#[test]
fn any_member_emits_omg_types_any() {
let cs = gen_cs(r#"struct M { any value; };"#);
assert!(cs.contains("Omg.Types.Any"), "Omg.Types.Any fehlt:\n{cs}");
}
#[test]
fn fixed_member_emits_csharp_decimal() {
let cs = gen_cs(r#"struct M { fixed<10,2> price; };"#);
assert!(cs.contains("decimal"), "C# decimal-Mapping fehlt:\n{cs}");
}
#[test]
fn shared_member_emits_shared_marker_attribute() {
let cs = gen_cs(
r#"
struct WithShared {
@shared long ptr;
};
"#,
);
assert!(cs.contains("[Shared]"), "[Shared]-Attribute fehlt:\n{cs}");
}
#[test]
fn verbatim_annotation_other_language_not_emitted_in_csharp() {
let cs = gen_cs(
r#"
@verbatim(language="java", placement=BEFORE_DECLARATION, text="// java only")
struct S { long x; };
"#,
);
assert!(
!cs.contains("// java only"),
"Java-verbatim darf nicht in C#-Output:\n{cs}"
);
}
#[test]
fn csharp_mapping_options_have_sensible_defaults() {
let opts = CsGenOptions::default();
let cs = gen_csharp_with_opts("struct S { long x; };", &opts);
assert!(cs.contains("S"));
}
fn gen_csharp_with_opts(src: &str, opts: &CsGenOptions) -> String {
let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse");
generate_csharp(&ast, opts).expect("gen")
}
#[test]
fn fixed_type_emits_decimal() {
let parse = zerodds_idl::parse("struct S { fixed<5,2> price; };", &ParserConfig::default());
if let Ok(ast) = parse {
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(cs.contains("decimal"));
}
}
#[test]
fn any_type_emits_omg_types_any() {
let parse = zerodds_idl::parse("struct S { any value; };", &ParserConfig::default());
if let Ok(ast) = parse {
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(cs.contains("Omg.Types.Any"));
}
}
#[test]
fn bitset_short_form_emits_struct() {
let parse = zerodds_idl::parse("bitset BS { bitfield<3> a; };", &ParserConfig::default());
if let Ok(ast) = parse {
let res = generate_csharp(&ast, &CsGenOptions::default());
assert!(res.is_ok(), "bitset sollte jetzt unterstuetzt sein");
assert!(res.expect("ok").contains("public struct BS"));
}
}