#![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 std::process::Command;
use zerodds_idl::config::ParserConfig;
use zerodds_idl_csharp::{CsGenOptions, generate_csharp};
fn dotnet_available() -> bool {
Command::new("dotnet")
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn check_compiles(src: &str) -> Result<(), String> {
if !dotnet_available() {
eprintln!("WARNING: skipping C# compile-check, no dotnet in PATH");
return Ok(());
}
let ast =
zerodds_idl::parse(src, &ParserConfig::default()).map_err(|e| format!("parse: {e:?}"))?;
let cs_source =
generate_csharp(&ast, &CsGenOptions::default()).map_err(|e| format!("gen: {e:?}"))?;
let tmp = tempfile::tempdir().map_err(|e| e.to_string())?;
let csproj = r#"<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<NoWarn>CS0168;CS8019;CS8632</NoWarn>
</PropertyGroup>
</Project>
"#;
let stub = "namespace Omg.Types { \
using System.Collections; using System.Collections.Generic; \
public interface ITopicType<T> {} \
public interface ISequence<T> : System.Collections.Generic.IList<T> {} \
public interface IBoundedSequence<T> : ISequence<T> { int Bound { get; } } \
public sealed class SequenceList<T> : ISequence<T> { \
private readonly List<T> _items = new(); \
public int Count => _items.Count; \
public bool IsReadOnly => false; \
public T this[int i] { get => _items[i]; set => _items[i] = value; } \
public void Add(T x) => _items.Add(x); \
public void Insert(int i, T x) => _items.Insert(i, x); \
public void Clear() => _items.Clear(); \
public bool Contains(T x) => _items.Contains(x); \
public void CopyTo(T[] a, int i) => _items.CopyTo(a, i); \
public IEnumerator<T> GetEnumerator() => _items.GetEnumerator(); \
public int IndexOf(T x) => _items.IndexOf(x); \
public bool Remove(T x) => _items.Remove(x); \
public void RemoveAt(int i) => _items.RemoveAt(i); \
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); \
} \
}\n";
let cdr_stub = "namespace ZeroDDS.Cdr { \
using System; using System.Collections.Generic; \
public enum EndianMode { LittleEndian, BigEndian } \
public enum ExtensibilityKind { Final, Appendable, Mutable } \
public sealed class XcdrException : System.Exception { public XcdrException(string m) : base(m) {} } \
public interface IDdsTopicType<T> where T : notnull { \
string TypeName { get; } \
bool IsKeyed { get; } \
ExtensibilityKind Extensibility { get; } \
byte[] Encode(T sample); \
byte[] Encode(T sample, EndianMode endian); \
T Decode(System.ReadOnlySpan<byte> bytes); \
byte[] KeyHash(T sample); \
} \
public readonly struct DHeaderScope : System.IDisposable { public void Dispose() {} } \
public readonly struct DHeaderReadScope { public int BodyStart {get;} public int BodyEnd {get;} public int PreviousOrigin {get;} } \
public sealed class Xcdr2Writer { \
public Xcdr2Writer() {} \
public Xcdr2Writer(EndianMode e) {} \
public void Align(int a) {} \
public void WriteByte(byte v) {} \
public void WriteBytes(System.ReadOnlySpan<byte> d) {} \
public void WriteBool(bool v) {} \
public void WriteOctet(byte v) {} \
public void WriteInt16(short v) {} \
public void WriteUInt16(ushort v) {} \
public void WriteInt32(int v) {} \
public void WriteUInt32(uint v) {} \
public void WriteInt64(long v) {} \
public void WriteUInt64(ulong v) {} \
public void WriteFloat32(float v) {} \
public void WriteFloat64(double v) {} \
public void WriteWChar(char v) {} \
public void WriteString(string v) {} \
public void WriteWString(string v) {} \
public void WriteSequenceLength(int c) {} \
public DHeaderScope BeginAppendable() => default; \
public DHeaderScope BeginMutable() => default; \
public DHeaderScope BeginDHeader() => default; \
public void WriteEmHeader(uint id, int lc, bool mu) {} \
public void WriteEmHeaderNextInt(uint id, int lc, bool mu, uint nx) {} \
public byte[] ToArray() => System.Array.Empty<byte>(); \
} \
public ref struct Xcdr2Reader { \
public Xcdr2Reader(System.ReadOnlySpan<byte> b) {} \
public Xcdr2Reader(System.ReadOnlySpan<byte> b, EndianMode e) {} \
public bool ReadBool() => default; \
public byte ReadByte() => default; \
public byte ReadOctet() => default; \
public short ReadInt16() => default; \
public ushort ReadUInt16() => default; \
public int ReadInt32() => default; \
public uint ReadUInt32() => default; \
public long ReadInt64() => default; \
public ulong ReadUInt64() => default; \
public float ReadFloat32() => default; \
public double ReadFloat64() => default; \
public char ReadWChar() => default; \
public string ReadString() => string.Empty; \
public string ReadWString() => string.Empty; \
public int ReadSequenceLength() => default; \
public DHeaderReadScope BeginDHeader() => default; \
public void EndDHeader(DHeaderReadScope s) {} \
public bool DHeaderDone(DHeaderReadScope s) => true; \
public (uint MemberId, int Lc, bool MustUnderstand) ReadEmHeader() => default; \
} \
public static class Md5 { public static byte[] Hash(System.ReadOnlySpan<byte> d) => new byte[16]; } \
}\n";
std::fs::write(tmp.path().join("Generated.csproj"), csproj).map_err(|e| e.to_string())?;
std::fs::write(tmp.path().join("OmgTypesStub.cs"), stub).map_err(|e| e.to_string())?;
std::fs::write(tmp.path().join("ZeroDDSCdrStub.cs"), cdr_stub).map_err(|e| e.to_string())?;
std::fs::write(tmp.path().join("Generated.cs"), &cs_source).map_err(|e| e.to_string())?;
let output = Command::new("dotnet")
.args(["build", "--nologo", "--verbosity", "quiet"])
.current_dir(tmp.path())
.output()
.map_err(|e| e.to_string())?;
if output.status.success() {
Ok(())
} else {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!(
"dotnet build FAILED:\n--- source ---\n{cs_source}\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}"
))
}
}
#[test]
fn compiles_simple_struct() {
check_compiles("struct Point { long x; long y; };").expect("simple struct must compile");
}
#[test]
fn compiles_struct_with_string_sequence() {
check_compiles("struct Bag { string name; sequence<long> ids; };")
.expect("string+seq must compile");
}
#[test]
fn compiles_module_nesting() {
check_compiles("module Outer { module Inner { struct S { long x; }; }; };")
.expect("nested modules must compile");
}
#[test]
fn compiles_enum() {
check_compiles("enum Color { RED, GREEN, BLUE };").expect("enum must compile");
}
#[test]
fn compiles_union() {
check_compiles(
"union U switch (long) { case 1: long a; case 2: double b; default: octet c; };",
)
.expect("union must compile");
}
#[test]
fn compiles_typedef() {
check_compiles("typedef long Counter;").expect("typedef must compile");
}
#[test]
fn compiles_inheritance() {
check_compiles("struct Base { long base_field; }; struct Child : Base { long child_field; };")
.expect("inheritance must compile");
}