#![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::{CsGenError, CsGenOptions, generate_csharp};
fn parse(src: &str) -> zerodds_idl::ast::Specification {
zerodds_idl::parse(src, &ParserConfig::default()).expect("parse must succeed")
}
#[test]
fn empty_ast_produces_preamble_only() {
let cs = generate_csharp(&parse(""), &CsGenOptions::default()).expect("gen");
assert!(cs.contains("#nullable enable"));
assert!(cs.contains("using System;"));
assert!(!cs.contains("class "));
assert!(!cs.contains("namespace "));
}
#[test]
fn reserved_field_name_class_is_escaped_with_at_prefix() {
let ast = parse("struct S { long object; };");
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("gen");
assert!(cs.contains("Object"));
}
#[test]
fn reserved_field_name_with_pure_lowercase_keyword_escapes() {
let ast = parse("struct S { long event; };");
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("gen");
assert!(cs.contains("Event"));
}
#[test]
fn inheritance_self_loop_is_rejected() {
let ast = parse(
"struct A : B { long a; };\n\
struct B : A { long b; };",
);
let res = generate_csharp(&ast, &CsGenOptions::default());
assert!(
matches!(res, Err(CsGenError::InheritanceCycle { .. })),
"expected InheritanceCycle, got {res:?}",
);
}
#[test]
fn using_set_is_complete_for_full_typeset() {
let cs = generate_csharp(
&parse(
"struct S { \
@optional string s; \
sequence<long> v; \
long arr[3]; \
}; \
union U switch (long) { case 1: long a; }; \
exception E { long err; };",
),
&CsGenOptions::default(),
)
.expect("gen");
assert!(cs.contains("using System;"));
assert!(cs.contains("using System.Collections.Generic;"));
}
#[test]
fn namespace_three_level_hierarchy_emits_open_close_pairs() {
let cs = generate_csharp(
&parse("module A { module B { module C {}; }; };"),
&CsGenOptions::default(),
)
.expect("gen");
let opens = cs.matches("namespace ").count();
assert!(opens >= 6, "namespace tokens count: {opens}");
}
#[test]
fn custom_indent_width_changes_output() {
let two = generate_csharp(
&parse("module M { struct S { long x; }; };"),
&CsGenOptions {
indent_width: 2,
..Default::default()
},
)
.expect("gen");
let four = generate_csharp(
&parse("module M { struct S { long x; }; };"),
&CsGenOptions::default(),
)
.expect("gen");
assert_ne!(two, four, "indent width must influence output");
}
#[test]
fn interface_emits_csharp_interface() {
let ast = parse("interface I { void op(); };");
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(cs.contains("public interface I"));
}
#[test]
fn any_type_emits_omg_types_any() {
let ast = parse("struct S { any value; };");
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(cs.contains("Omg.Types.Any"));
}
#[test]
fn fixed_type_emits_decimal() {
let ast = parse("typedef fixed<5, 2> Money;");
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(cs.contains("decimal"));
}
#[test]
fn bitset_emits_struct() {
let ast = parse("bitset Flags { bitfield<3> a; };");
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(cs.contains("public struct Flags"));
assert!(cs.contains("public ulong A"));
}
#[test]
fn const_decl_is_emitted() {
let cs = generate_csharp(
&parse("const long MAX_SIZE = 1024;"),
&CsGenOptions::default(),
)
.expect("gen");
assert!(cs.contains("public const int MAX_SIZE = 1024;"));
}
#[test]
fn root_namespace_appears_outermost() {
let cs = generate_csharp(
&parse("module Inner { struct S { long x; }; };"),
&CsGenOptions {
root_namespace: Some("ZeroDDS".into()),
..Default::default()
},
)
.expect("gen");
let outer_open = cs.find("namespace ZeroDDS").expect("zerodds open");
let inner_open = cs.find("namespace Inner").expect("inner open");
assert!(outer_open < inner_open);
}
#[test]
fn forward_declared_struct_emits_partial_record_class() {
let cs = generate_csharp(
&parse("struct Forward; struct Forward { long x; };"),
&CsGenOptions::default(),
)
.expect("gen");
assert!(cs.contains("public partial record class Forward;"));
assert!(cs.contains("public partial record class Forward : ITopicType<Forward>"));
}
#[test]
fn single_module_with_constant_does_not_emit_extra_usings() {
let cs = generate_csharp(
&parse("module M { const long N = 10; };"),
&CsGenOptions::default(),
)
.expect("gen");
assert!(!cs.contains("System.Collections.Generic"));
}
#[test]
fn nested_modules_emit_nested_csharp_namespaces() {
let cs = generate_csharp(
&parse(
"module Outer { \
module Middle { \
struct Foo { long x; }; \
}; \
};",
),
&CsGenOptions::default(),
)
.expect("gen");
let outer = cs.find("namespace Outer").expect("outer");
let middle = cs.find("namespace Middle").expect("middle");
let foo = cs.find("record class Foo").expect("foo");
assert!(outer < middle);
assert!(middle < foo);
}
#[test]
fn deep_nested_sequence_emits_correct_ilist() {
let cs = generate_csharp(
&parse("struct S { sequence<sequence<long>> matrix; };"),
&CsGenOptions::default(),
)
.expect("gen");
assert!(cs.contains("ISequence<ISequence<int>>"));
}
#[test]
fn use_records_false_still_emits_record_for_now() {
let cs = generate_csharp(
&parse("struct S { long x; };"),
&CsGenOptions {
use_records: false,
..Default::default()
},
)
.expect("gen");
assert!(cs.contains("record class S"));
}
#[test]
fn empty_root_namespace_string_is_treated_as_none() {
let cs = generate_csharp(
&parse("module M {};"),
&CsGenOptions {
root_namespace: Some("".into()),
..Default::default()
},
)
.expect("gen");
assert!(cs.contains("namespace M"));
assert!(!cs.contains("namespace \n"));
}
#[test]
fn map_type_emits_idictionary() {
let res = zerodds_idl::parse(
"struct S { map<long, string> kv; };",
&ParserConfig::default(),
);
if let Ok(ast) = res {
if let Ok(cs) = generate_csharp(&ast, &CsGenOptions::default()) {
assert!(cs.contains("IDictionary<int, string>"));
}
}
}