#![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::path::{Path, PathBuf};
use std::process::Command;
use std::sync::OnceLock;
use zerodds_idl::config::ParserConfig;
use zerodds_idl_java::{JavaGenOptions, generate_java_files};
fn javac_available() -> bool {
Command::new("javac")
.arg("-version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn manifest() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn collect_java(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_java(&path, out);
} else if path.extension().is_some_and(|e| e == "java") {
out.push(path);
}
}
}
fn real_runtime_classes() -> Option<&'static Path> {
static CELL: OnceLock<Option<(tempfile::TempDir, PathBuf)>> = OnceLock::new();
CELL.get_or_init(|| {
if !javac_available() {
eprintln!("WARNING: skipping Java compile-checks, no javac in PATH");
return None;
}
let tmp = tempfile::tempdir().ok()?;
let out = tmp.path().join("classes");
std::fs::create_dir_all(&out).ok()?;
let mut srcs = Vec::new();
collect_java(
&manifest().join("../java-omgdds/java/src/main/java"),
&mut srcs,
);
collect_java(&manifest().join("runtime"), &mut srcs);
assert!(
!srcs.is_empty(),
"no real runtime sources found — check crate layout"
);
let output = Command::new("javac")
.arg("-nowarn")
.arg("-d")
.arg(&out)
.args(&srcs)
.output()
.ok()?;
if !output.status.success() {
panic!(
"real Java runtime failed to compile (java-omgdds + idl-java/runtime):\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
Some((tmp, out))
})
.as_ref()
.map(|(_, p)| p.as_path())
}
fn check_compiles(src: &str) -> Result<(), String> {
let Some(classes) = real_runtime_classes() else {
return Ok(()); };
let ast =
zerodds_idl::parse(src, &ParserConfig::default()).map_err(|e| format!("parse: {e:?}"))?;
let files =
generate_java_files(&ast, &JavaGenOptions::default()).map_err(|e| format!("gen: {e:?}"))?;
if files.is_empty() {
return Ok(());
}
let tmp = tempfile::tempdir().map_err(|e| e.to_string())?;
let mut paths = Vec::new();
for f in &files {
let dir = if f.package_path.is_empty() {
tmp.path().to_path_buf()
} else {
tmp.path().join(f.package_path.replace('.', "/"))
};
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
let path = dir.join(format!("{}.java", f.class_name));
std::fs::write(&path, &f.source).map_err(|e| e.to_string())?;
paths.push(path);
}
let output = Command::new("javac")
.arg("-nowarn")
.arg("-classpath")
.arg(classes)
.arg("-d")
.arg(tmp.path().join("out"))
.args(&paths)
.output()
.map_err(|e| e.to_string())?;
if output.status.success() {
Ok(())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("javac FAILED:\n--- stderr ---\n{stderr}"))
}
}
#[test]
fn real_runtime_compiles() {
if !javac_available() {
eprintln!("skipping — no javac");
return;
}
assert!(
real_runtime_classes().is_some(),
"real Java runtime must compile"
);
}
#[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_inheritance() {
check_compiles("struct Base { long base_field; }; struct Child : Base { long child_field; };")
.expect("inheritance must compile");
}
#[test]
fn compiles_keyed_struct() {
check_compiles("struct Sensor { @key long id; double value; };")
.expect("keyed struct must compile");
}
#[test]
fn compiles_optional_member() {
check_compiles("struct S { @optional long maybe; };").expect("optional 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_exception() {
check_compiles("exception NotFound { string what_; };").expect("exception must compile");
}
#[test]
fn compiles_unsigned_workaround() {
check_compiles("struct U { unsigned short us; unsigned long ul; unsigned long long ull; };")
.expect("unsigned must compile");
}
#[test]
fn compiles_array_member() {
check_compiles("struct M { long matrix[3][4]; };").expect("array must compile");
}
#[test]
fn compiles_service_against_real_rpc_runtime() {
check_compiles(
"@service interface Calc { \
long add(in long a, in long b); \
void scale(inout long acc, in long factor); \
void note(out long handle); \
};",
)
.expect("service codegen must compile against the real RPC runtime");
}
#[test]
fn compiles_service_with_struct_params_against_real_runtime() {
check_compiles(
"struct Vec3 { double x; double y; double z; }; \
@service interface Geometry { \
Vec3 normalize(in Vec3 v); \
void store(inout Vec3 acc); \
};",
)
.expect("service with struct params must compile against the real runtime");
}