use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn cli_bin() -> PathBuf {
let var = std::env::var("CARGO_BIN_EXE_oxihuman").unwrap_or_else(|_| {
let manifest = env!("CARGO_MANIFEST_DIR");
let root = Path::new(manifest)
.parent()
.expect("should succeed")
.parent()
.expect("should succeed");
root.join("target")
.join("debug")
.join("oxihuman")
.to_string_lossy()
.into_owned()
});
PathBuf::from(var)
}
const MINIMAL_OBJ: &str = "\
# OxiHuman e2e test fixture
v -0.2 0.0 -0.15
v 0.2 0.0 -0.15
v 0.2 1.7 -0.15
v -0.2 1.7 -0.15
v -0.2 0.0 0.15
v 0.2 0.0 0.15
v 0.2 1.7 0.15
v -0.2 1.7 0.15
vn 0.0 0.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 1.0
vn 0.0 0.0 1.0
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
f 1/1/1 2/2/2 3/3/3
f 1/1/1 3/3/3 4/4/4
f 5/5/5 7/7/7 6/6/6
f 5/5/5 8/8/8 7/7/7
f 1/1/1 4/4/4 8/8/8
f 1/1/1 8/8/8 5/5/5
f 2/2/2 6/6/6 7/7/7
f 2/2/2 7/7/7 3/3/3
f 3/3/3 7/7/7 8/8/8
f 3/3/3 8/8/8 4/4/4
f 1/1/1 5/5/5 6/6/6
f 1/1/1 6/6/6 2/2/2
";
fn write_base_obj(name: &str) -> PathBuf {
let dir = std::env::temp_dir();
let path = dir.join(format!("oxihuman_e2e_base_{}.obj", name));
fs::write(&path, MINIMAL_OBJ).expect("could not write base OBJ");
path
}
fn tmp_path(name: &str, ext: &str) -> PathBuf {
std::env::temp_dir().join(format!(
"oxihuman_e2e_{}_{}.{}",
name,
std::process::id(),
ext
))
}
fn run_cli(args: &[&str]) -> (String, String, std::process::ExitStatus) {
let bin = cli_bin();
let out = Command::new(&bin)
.args(args)
.output()
.unwrap_or_else(|e| panic!("failed to run {:?}: {}", bin, e));
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
(stdout, stderr, out.status)
}
#[test]
fn test_generate_basic() {
let base = write_base_obj("gen_basic");
let out = tmp_path("gen_basic", "glb");
let (_stdout, _stderr, status) = run_cli(&[
"generate",
"--base",
base.to_str().expect("should succeed"),
"--output",
out.to_str().expect("should succeed"),
]);
assert!(
status.success(),
"generate (basic) exited with non-zero status; stderr: {}",
_stderr
);
assert!(
out.exists(),
"output GLB file was not created: {}",
out.display()
);
let _ = fs::remove_file(&out);
let _ = fs::remove_file(&base);
}
#[test]
fn test_generate_with_params() {
let base = write_base_obj("gen_params");
let out = tmp_path("gen_params", "glb");
let params = r#"{"height":0.8,"weight":0.3,"muscle":0.5,"age":0.5}"#;
let (_stdout, _stderr, status) = run_cli(&[
"generate",
"--base",
base.to_str().expect("should succeed"),
"--params",
params,
"--output",
out.to_str().expect("should succeed"),
]);
assert!(
status.success(),
"generate (with params) exited with non-zero status; stderr: {}",
_stderr
);
assert!(out.exists(), "output GLB not found: {}", out.display());
let bytes = fs::read(&out).expect("should succeed");
assert!(bytes.len() >= 4, "GLB output too small");
assert_eq!(&bytes[0..4], b"glTF", "GLB magic bytes mismatch");
let _ = fs::remove_file(&out);
let _ = fs::remove_file(&base);
}
#[test]
fn test_export_obj() {
let base = write_base_obj("exp_obj");
let glb = tmp_path("exp_obj_glb", "glb");
let obj = tmp_path("exp_obj_out", "obj");
let (_so, _se, st) = run_cli(&[
"generate",
"--base",
base.to_str().expect("should succeed"),
"--output",
glb.to_str().expect("should succeed"),
"--output-obj",
obj.to_str().expect("should succeed"),
]);
assert!(
st.success(),
"generate (for OBJ test) failed; stderr: {}",
_se
);
assert!(obj.exists(), "OBJ output not found: {}", obj.display());
let content = fs::read_to_string(&obj).expect("should succeed");
assert!(content.contains("v "), "OBJ output has no vertex lines");
assert!(content.contains("f "), "OBJ output has no face lines");
let _ = fs::remove_file(&glb);
let _ = fs::remove_file(&obj);
let _ = fs::remove_file(&base);
}
#[test]
fn test_export_glb() {
let base = write_base_obj("exp_glb");
let out = tmp_path("exp_glb", "glb");
let (_so, _se, status) = run_cli(&[
"generate",
"--base",
base.to_str().expect("should succeed"),
"--output",
out.to_str().expect("should succeed"),
]);
assert!(status.success(), "generate (GLB) failed; stderr: {}", _se);
let bytes = fs::read(&out).expect("should succeed");
assert!(bytes.len() >= 12, "GLB too small ({} bytes)", bytes.len());
assert_eq!(&bytes[0..4], b"glTF", "GLB magic bytes mismatch");
let version = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
assert_eq!(version, 2, "expected GLB version 2, got {}", version);
let _ = fs::remove_file(&out);
let _ = fs::remove_file(&base);
}
#[test]
fn test_export_stl() {
let base = write_base_obj("exp_stl");
let out = tmp_path("exp_stl", "stl");
let (_so, _se, status) = run_cli(&[
"stl",
"--base",
base.to_str().expect("should succeed"),
"--output",
out.to_str().expect("should succeed"),
]);
assert!(status.success(), "stl export failed; stderr: {}", _se);
assert!(out.exists(), "STL not found: {}", out.display());
let content = fs::read_to_string(&out).expect("should succeed");
assert!(
content.trim_start().starts_with("solid"),
"STL output does not start with 'solid'"
);
assert!(
content.contains("endsolid"),
"STL output does not contain 'endsolid'"
);
let _ = fs::remove_file(&out);
let _ = fs::remove_file(&base);
}
const EMPTY_PACK_MANIFEST_TOML: &str = r#"version = "0.1.0"
entries = []
[stats]
total_files = 0
allowed_files = 0
blocked_files = 0
total_deltas = 0
estimated_memory_bytes = 0
"#;
#[test]
fn test_pack_load() {
let dir = std::env::temp_dir().join(format!("oxihuman_e2e_pack_{}", std::process::id()));
fs::create_dir_all(&dir).expect("should succeed");
let manifest_path = dir.join("pack.toml");
fs::write(&manifest_path, EMPTY_PACK_MANIFEST_TOML).expect("should succeed");
let (_so, _se, status) = run_cli(&[
"validate",
"--pack",
manifest_path.to_str().expect("should succeed"),
]);
assert!(status.success(), "pack validate failed; stderr: {}", _se);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn test_info_command() {
let base = write_base_obj("info_cmd");
let glb = tmp_path("info_cmd", "glb");
let (_so, _se, st) = run_cli(&[
"generate",
"--base",
base.to_str().expect("should succeed"),
"--output",
glb.to_str().expect("should succeed"),
]);
assert!(
st.success(),
"generate (for info test) failed; stderr: {}",
_se
);
let (stdout, _se, status) = run_cli(&["info", glb.to_str().expect("should succeed")]);
assert!(status.success(), "info command failed; stderr: {}", _se);
assert!(
stdout.contains("GLB") || stdout.contains("glb"),
"info output should mention GLB format; got: {}",
stdout
);
let _ = fs::remove_file(&glb);
let _ = fs::remove_file(&base);
}
#[test]
fn test_invalid_params() {
let base = write_base_obj("invalid_params");
let out = tmp_path("invalid_params", "glb");
let (_so, _se, status) = run_cli(&[
"generate",
"--base",
base.to_str().expect("should succeed"),
"--params",
"not-valid-json!!!",
"--output",
out.to_str().expect("should succeed"),
]);
assert!(
!status.success(),
"expected non-zero exit for invalid params, but got success"
);
assert!(
!_se.contains("panicked"),
"CLI panicked on invalid params (should have returned error): {}",
_se
);
let _ = fs::remove_file(&out);
let _ = fs::remove_file(&base);
}
#[test]
fn test_pipeline_generate_export() {
let base = write_base_obj("pipeline");
let glb = tmp_path("pipeline_glb", "glb");
let obj = tmp_path("pipeline_obj", "obj");
let (_so, _se, st) = run_cli(&[
"generate",
"--base",
base.to_str().expect("should succeed"),
"--output",
glb.to_str().expect("should succeed"),
"--output-obj",
obj.to_str().expect("should succeed"),
]);
assert!(st.success(), "pipeline generate failed; stderr: {}", _se);
assert!(glb.exists(), "pipeline GLB not found");
assert!(obj.exists(), "pipeline OBJ not found");
let (stdout, _se, st2) = run_cli(&["info", obj.to_str().expect("should succeed")]);
assert!(st2.success(), "info (OBJ) failed; stderr: {}", _se);
assert!(
stdout.contains("Vertices") || stdout.contains("vertices"),
"info output should mention vertices; got: {}",
stdout
);
let glb2 = tmp_path("pipeline_glb2", "glb");
let (_so3, _se3, st3) = run_cli(&[
"generate",
"--base",
obj.to_str().expect("should succeed"),
"--output",
glb2.to_str().expect("should succeed"),
]);
assert!(
st3.success(),
"re-generate from OBJ failed; stderr: {}",
_se3
);
assert!(glb2.exists(), "re-generated GLB not found");
let bytes1 = fs::read(&glb).expect("should succeed");
let bytes2 = fs::read(&glb2).expect("should succeed");
assert_eq!(&bytes1[0..4], b"glTF", "GLB1 magic bytes wrong");
assert_eq!(&bytes2[0..4], b"glTF", "GLB2 magic bytes wrong");
let _ = fs::remove_file(&glb);
let _ = fs::remove_file(&glb2);
let _ = fs::remove_file(&obj);
let _ = fs::remove_file(&base);
}