#![allow(
clippy::expect_used,
clippy::panic,
clippy::print_stderr,
clippy::unwrap_used
)]
use std::path::PathBuf;
use std::process::Command;
use zerodds_ast::Ast;
use zerodds_lang_rules::adapter::interface::{ServiceMapping, member_id};
const ENHANCED: &str = "
module base {
exception Refused { string why; };
interface Base2 {
long ident() raises (Refused);
attribute long level;
};
};
module enh {
exception TooBig { long limit; };
typedef sequence<long> Longs;
interface Calc {
long add(in long a, in long b);
long scale(in long k) raises (TooBig);
void swap(inout long v);
void ping();
void notify(in string text);
Longs all(in long n, out string label);
};
interface Fast {
@Mutable long square(in long x);
};
interface Derived2 : base::Base2 {
long more();
};
};
";
fn root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
}
fn javac_available() -> bool {
Command::new("javac")
.arg("-version")
.output()
.is_ok_and(|out| out.status.success())
}
fn model(idl: &str) -> Ast {
let parsed = zerodds_idl_parse::parse(idl);
assert!(
parsed.diagnostics().is_empty(),
"{:?}",
parsed.diagnostics()
);
let lowered = zerodds_idl_lower::lower(&parsed.syntax(), "enhanced.idl");
assert!(lowered.diagnostics.is_empty(), "{:?}", lowered.diagnostics);
let mut ast: Ast = lowered.ast;
let report = ast.resolve_all_by_name().expect("resolve");
assert!(report.is_clean(), "{:?}", report.broken);
ast
}
fn generate(idl: &str, mapping: ServiceMapping) -> String {
zerodds_lang_rules::generate::source_with_mapping(
&model(idl),
zerodds_lang_cst::Language::Java,
zerodds_lang_rules::emit::interface_rust::rpc_layout_key(),
zerodds_lang_rules::emit::encode::CodecOptions::default(),
mapping,
)
.expect("the Java column takes this model")
}
fn compiles(text: &str, tag: &str) -> Result<(), String> {
let root = root();
let dir = std::env::temp_dir().join(format!("zd-enh-java-{}-{tag}", std::process::id()));
let classes = dir.join("classes");
std::fs::create_dir_all(&classes).map_err(|e| e.to_string())?;
let mut files = Vec::new();
for unit in zerodds_lang_rules::generate::java_units(text) {
let body = if unit.class == "ZdCodec" {
format!("import org.zerodds.wire.*;\n\n{}", unit.text)
} else {
unit.text.clone()
};
let file = dir.join(format!("{}.java", unit.class));
std::fs::write(&file, body).map_err(|e| e.to_string())?;
files.push(file);
}
for package in [
"runtime/java/src/main/java/org/omg/dds/rpc",
"runtime/java/src/main/java/org/zerodds/ddsrpc",
] {
for entry in std::fs::read_dir(root.join(package)).map_err(|e| e.to_string())? {
let path = entry.map_err(|e| e.to_string())?.path();
if path.extension().is_some_and(|ext| ext == "java") {
files.push(path);
}
}
}
if let Ok(keep) = std::env::var("ZD_KEEP_GENERATED") {
let _ = std::fs::write(PathBuf::from(keep).join(format!("{tag}.java.txt")), text);
}
let output = Command::new("javac")
.args(["-encoding", "UTF-8", "-Xlint:all,-unchecked", "-Werror"])
.arg("-d")
.arg(&classes)
.arg("-sourcepath")
.arg(format!(
"{}:{}:{}",
root.join("runtime/java/src/main/java").display(),
root.join("crates/java-omgdds/java/src/main/java").display(),
dir.display()
))
.args(&files)
.output()
.map_err(|e| e.to_string())?;
let result = if output.status.success() {
Ok(())
} else {
Err(String::from_utf8_lossy(&output.stderr).into_owned())
};
let _ = std::fs::remove_dir_all(&dir);
result
}
#[test]
fn the_enhanced_java_compiles() {
if !javac_available() {
eprintln!("SKIPPED: `javac` is not usable.");
return;
}
for (tag, mapping) in [
("enhanced", ServiceMapping::Enhanced),
("enhanced-empty", ServiceMapping::EnhancedEmptyStructures),
] {
compiles(&generate(ENHANCED, mapping), tag).unwrap_or_else(|why| panic!("{tag}: {why}"));
}
}
#[test]
fn the_codec_writes_the_member_ids_and_length_codes_of_the_clause() {
let text = generate(ENHANCED, ServiceMapping::Enhanced);
for (member, code) in [("add", 4), ("remoteEx", 2), ("x", 2), ("result", 4)] {
assert!(
text.contains(&format!(
"writeEmheader({}, {code}, false)",
member_id(member)
)),
"the member `{member}` has length code {code}"
);
}
assert!(text.contains("encode_Calc_add_In"), "{text}");
assert!(
!text.contains(&format!("writeEmheader({}, 2, false)", member_id("a"))),
"`Calc_add_In` has no headers"
);
}
#[test]
fn the_function_call_classes_are_written_over_the_choice_holders() {
let text = generate(ENHANCED, ServiceMapping::Enhanced);
assert!(text.contains("class CalcClientImpl"));
assert!(text.contains("class CalcServiceImpl"));
assert!(text.contains("request.add = new Calc_add_In();"), "{text}");
assert!(text.contains("Calc_add_Result result = reply.add;"));
assert!(text.contains("new org.omg.dds.rpc.RemoteUnknownExceptionError("));
assert!(text.contains("if (request.add != null)"));
assert!(text.contains("REMOTE_EX_UNKNOWN_OPERATION"));
assert!(text.contains("ServiceMapping.ENHANCED"));
assert!(text.contains("EnhancedReplySupport<"));
assert!(!text.contains("Calc_Call"));
}
#[test]
fn a_derived_interface_answers_the_inherited_operation_itself() {
let text = generate(ENHANCED, ServiceMapping::Enhanced);
assert!(text.contains("class Derived2ClientImpl"));
assert!(
text.contains("Base2Codec.encode_Base2_ident_In"),
"the codec of the base's In structure is the base's own"
);
assert!(text.contains("request.ident = new"), "{text}");
assert!(
!text.contains("registerBase"),
"the derived client and service hold no client or service of a base"
);
}
#[test]
fn a_choice_holds_exactly_one_member_on_both_sides_of_the_codec() {
let text = generate(ENHANCED, ServiceMapping::Enhanced);
assert!(
text.contains("Calc_Request holds exactly one member (DDS-RPC 1.0 §7.5.1.2.1.1)"),
"the encoder of the request"
);
assert!(
text.contains("Calc_Reply holds exactly one member (DDS-RPC 1.0 §7.5.1.2.1.1)"),
"the encoder of the reply"
);
assert!(
text.contains("Calc_add_Result holds exactly one member (DDS-RPC 1.0 §7.5.1.2.1.1)"),
"the encoder of a Result"
);
let flat = text.split_whitespace().collect::<Vec<_>>().join(" ");
assert!(
flat.contains(
"r.readerSkipTo(after); if (r.position() < end && r.readerStatus() == \
org.zerodds.wire.Reader.Status.OK) { r.readerRefuse(\
org.zerodds.wire.Reader.Status.OUT_OF_RANGE); }"
),
"the decoder refuses a second member"
);
assert!(
text.contains("Calc_Request is malformed (DDS-RPC 1.0 §7.5.1.2.1.1): "),
"the type support raises what the reader refused"
);
}
#[test]
fn the_support_sets_the_interface_name() {
let text = generate(ENHANCED, ServiceMapping::Enhanced);
assert!(text.contains("INTERFACE_NAME = \"enh_Calc\""), "{text}");
assert!(text.contains(".interfaceName(INTERFACE_NAME)"), "{text}");
assert!(
text.contains(".annotatedServiceName(SERVICE_NAME)"),
"{text}"
);
}
#[test]
fn the_basic_mapping_is_not_touched() {
let text = generate(ENHANCED, ServiceMapping::Basic);
assert!(!text.contains("ServiceMapping.ENHANCED"));
assert!(text.contains("Calc_Call"), "the Basic mapping's union");
}
#[test]
fn the_empty_structure_reading_has_no_dummy_member_in_the_codec() {
let dummy = generate(ENHANCED, ServiceMapping::Enhanced);
let empty = generate(ENHANCED, ServiceMapping::EnhancedEmptyStructures);
assert!(
dummy.contains("UnusedMember"),
"the dummy member is an octet"
);
assert!(
!empty.contains("Calc_ping_In.dummy") && !empty.contains("ping.dummy"),
"an @Empty structure has no octet"
);
}