use std::{
path::{Path, PathBuf},
process::{Command, Output},
};
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn examples_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("examples")
}
fn counter_simple_dir() -> PathBuf {
examples_dir().join("counter_simple")
}
fn counter_effects_dir() -> PathBuf {
examples_dir().join("counter_effects")
}
fn kv_store_dir() -> PathBuf {
examples_dir().join("key_value_store")
}
fn set_lib_path(cmd: &mut Command, lib_dir: &Path) {
if cfg!(target_os = "macos") {
cmd.env("DYLD_LIBRARY_PATH", lib_dir);
} else {
cmd.env("LD_LIBRARY_PATH", lib_dir);
}
}
fn build_rust_bridge() -> Result<PathBuf, Box<dyn std::error::Error>> {
let rust_bridge = counter_simple_dir().join("rust_bridge");
let build = Command::new("cargo")
.args(["build", "--manifest-path"])
.arg(rust_bridge.join("Cargo.toml"))
.output()?;
assert!(
build.status.success(),
"rust_bridge build failed:\n{}",
String::from_utf8_lossy(&build.stderr)
);
Ok(rust_bridge.join("target/debug"))
}
fn assert_all_passed(output: &Output, label: &str) {
assert_passed(output, label, 4);
}
fn assert_passed(output: &Output, label: &str, expected: u32) {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"{label} failed (exit {}):\nstdout:\n{stdout}\nstderr:\n{stderr}",
output.status,
);
let expected_str = format!("{expected} passed, 0 failed");
assert!(
stdout.contains(&expected_str),
"not all {label} tests passed (expected \"{expected_str}\"):\n{stdout}"
);
}
#[test]
fn tokio_host_e2e() -> TestResult {
println!("--- example: counter_simple / tokio_host ---");
let tokio_dir = counter_simple_dir().join("tokio_host");
let output = Command::new("cargo")
.args(["run", "--manifest-path"])
.arg(tokio_dir.join("Cargo.toml"))
.output()?;
assert_all_passed(&output, "tokio_host");
Ok(())
}
#[test]
#[ignore = "requires `go` — run with `nix develop .#ffi` or `--include-ignored`"]
fn go_ffi_e2e() -> TestResult {
println!("--- example: counter_simple / go_host ---");
let lib_dir = build_rust_bridge()?;
let mut go_cmd = Command::new("go");
go_cmd
.args(["run", "."])
.current_dir(counter_simple_dir().join("go_host"));
set_lib_path(&mut go_cmd, &lib_dir);
let output = go_cmd.output()?;
assert_all_passed(&output, "Go FFI");
Ok(())
}
#[test]
#[ignore = "requires `javac` — run with `nix develop .#ffi` or `--include-ignored`"]
fn java_ffi_e2e() -> TestResult {
println!("--- example: counter_simple / java_host ---");
let lib_dir = build_rust_bridge()?;
let java_dir = counter_simple_dir().join("java_host");
let compile = Command::new("javac")
.arg(java_dir.join("CounterHost.java"))
.arg("-d")
.arg(java_dir.join("out"))
.output()?;
assert!(
compile.status.success(),
"javac failed:\n{}",
String::from_utf8_lossy(&compile.stderr)
);
let mut java_cmd = Command::new("java");
java_cmd
.arg("-cp")
.arg(java_dir.join("out"))
.arg(format!("-Djava.library.path={}", lib_dir.display()))
.arg("CounterHost");
set_lib_path(&mut java_cmd, &lib_dir);
let output = java_cmd.output()?;
assert_all_passed(&output, "Java FFI");
Ok(())
}
#[test]
#[ignore = "requires `python3` — run with `nix develop .#ffi` or `--include-ignored`"]
fn python_ffi_e2e() -> TestResult {
println!("--- example: counter_simple / python_host ---");
build_rust_bridge()?;
let output = Command::new("python3")
.arg(counter_simple_dir().join("python_host/counter_host.py"))
.output()?;
assert_all_passed(&output, "Python FFI");
Ok(())
}
fn build_effects_bridge() -> Result<PathBuf, Box<dyn std::error::Error>> {
let effects_bridge = counter_effects_dir().join("effects_bridge");
let build = Command::new("cargo")
.args(["build", "--manifest-path"])
.arg(effects_bridge.join("Cargo.toml"))
.output()?;
assert!(
build.status.success(),
"effects_bridge build failed:\n{}",
String::from_utf8_lossy(&build.stderr)
);
Ok(effects_bridge.join("target/debug"))
}
#[test]
#[ignore = "requires `go` — run with `nix develop .#ffi` or `--include-ignored`"]
fn go_effects_e2e() -> TestResult {
println!("--- example: counter_effects / go_host ---");
let lib_dir = build_effects_bridge()?;
let mut go_cmd = Command::new("go");
go_cmd
.args(["run", "."])
.current_dir(counter_effects_dir().join("go_host"));
set_lib_path(&mut go_cmd, &lib_dir);
let output = go_cmd.output()?;
assert_passed(&output, "Go effects FFI", 6);
Ok(())
}
#[test]
#[ignore = "requires `javac` — run with `nix develop .#ffi` or `--include-ignored`"]
fn java_effects_e2e() -> TestResult {
println!("--- example: counter_effects / java_host ---");
let lib_dir = build_effects_bridge()?;
let java_dir = counter_effects_dir().join("java_host");
let compile = Command::new("javac")
.arg(java_dir.join("EffectsHost.java"))
.arg("-d")
.arg(java_dir.join("out"))
.output()?;
assert!(
compile.status.success(),
"javac failed:\n{}",
String::from_utf8_lossy(&compile.stderr)
);
let mut java_cmd = Command::new("java");
java_cmd
.arg("-cp")
.arg(java_dir.join("out"))
.arg(format!("-Djava.library.path={}", lib_dir.display()))
.arg("EffectsHost");
set_lib_path(&mut java_cmd, &lib_dir);
let output = java_cmd.output()?;
assert_passed(&output, "Java effects FFI", 6);
Ok(())
}
#[test]
#[ignore = "requires `python3` — run with `nix develop .#ffi` or `--include-ignored`"]
fn python_effects_e2e() -> TestResult {
println!("--- example: counter_effects / python_host ---");
build_effects_bridge()?;
let output = Command::new("python3")
.arg(counter_effects_dir().join("python_host/effects_host.py"))
.output()?;
assert_passed(&output, "Python effects FFI", 6);
Ok(())
}
fn build_kv_bridge() -> Result<PathBuf, Box<dyn std::error::Error>> {
let kv_bridge = kv_store_dir().join("kv_bridge");
let build = Command::new("cargo")
.args(["build", "--manifest-path"])
.arg(kv_bridge.join("Cargo.toml"))
.output()?;
assert!(
build.status.success(),
"kv_bridge build failed:\n{}",
String::from_utf8_lossy(&build.stderr)
);
Ok(kv_bridge.join("target/debug"))
}
#[test]
#[ignore = "requires `go` — run with `nix develop .#ffi` or `--include-ignored`"]
fn go_kv_e2e() -> TestResult {
println!("--- example: key_value_store / go_host ---");
let lib_dir = build_kv_bridge()?;
let mut go_cmd = Command::new("go");
go_cmd
.args(["run", "."])
.current_dir(kv_store_dir().join("go_host"));
set_lib_path(&mut go_cmd, &lib_dir);
let output = go_cmd.output()?;
assert_passed(&output, "Go KV FFI", 7);
Ok(())
}
#[test]
#[ignore = "requires `javac` — run with `nix develop .#ffi` or `--include-ignored`"]
fn java_kv_e2e() -> TestResult {
println!("--- example: key_value_store / java_host ---");
let lib_dir = build_kv_bridge()?;
let java_dir = kv_store_dir().join("java_host");
let compile = Command::new("javac")
.arg(java_dir.join("KvHost.java"))
.arg("-d")
.arg(java_dir.join("out"))
.output()?;
assert!(
compile.status.success(),
"javac failed:\n{}",
String::from_utf8_lossy(&compile.stderr)
);
let mut java_cmd = Command::new("java");
java_cmd
.arg("-cp")
.arg(java_dir.join("out"))
.arg(format!("-Djava.library.path={}", lib_dir.display()))
.arg("KvHost");
set_lib_path(&mut java_cmd, &lib_dir);
let output = java_cmd.output()?;
assert_passed(&output, "Java KV FFI", 7);
Ok(())
}
#[test]
#[ignore = "requires `python3` — run with `nix develop .#ffi` or `--include-ignored`"]
fn python_kv_e2e() -> TestResult {
println!("--- example: key_value_store / python_host ---");
build_kv_bridge()?;
let output = Command::new("python3")
.arg(kv_store_dir().join("python_host/kv_host.py"))
.output()?;
assert_passed(&output, "Python KV FFI", 7);
Ok(())
}