use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use image::{ImageBuffer, Rgba};
use serde_json::json;
use tempfile::TempDir;
fn cli_binary() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_doom-eternal"))
}
#[test]
fn cli_imports_model_exports_into_editable_root() {
let temp_dir = TempDir::new().expect("tempdir");
let repo_root = temp_dir.path().join("repo");
create_repo_scaffold(&repo_root);
let manifest_path = repo_root
.join("build")
.join("rtx-editable-source-set16")
.join("required-editable-textures.json");
let editable_texture = repo_root
.join("build")
.join("rtx-editable-source-set16")
.join("models")
.join("customization")
.join("characters")
.join("doomslayer")
.join("set16")
.join("doomslayer_game_helmet_set16.png");
write_manifest(&manifest_path, &editable_texture);
let model_export_root = temp_dir.path().join("modelExports");
let export_png = model_export_root
.join("doomslayer_cine_3p")
.join("images")
.join("doomslayer_game_helmet.png");
write_png(&export_png);
let output = Command::new(cli_binary())
.arg("--repo-root")
.arg(&repo_root)
.arg("import")
.arg("--model-export-root")
.arg(&model_export_root)
.arg("--manifest")
.arg(&manifest_path)
.arg("--editable-root")
.arg(repo_root.join("build").join("rtx-editable-source-set16"))
.output()
.expect("run import");
assert!(
output.status.success(),
"stdout:\n{}\n\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(editable_texture.is_file());
assert!(
String::from_utf8_lossy(&output.stdout).contains("Imported 1 texture export(s)."),
"stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
let second_output = Command::new(cli_binary())
.arg("--repo-root")
.arg(&repo_root)
.arg("import")
.arg("--model-export-root")
.arg(&model_export_root)
.arg("--manifest")
.arg(&manifest_path)
.arg("--editable-root")
.arg(repo_root.join("build").join("rtx-editable-source-set16"))
.output()
.expect("run import again");
assert!(
second_output.status.success(),
"stdout:\n{}\n\nstderr:\n{}",
String::from_utf8_lossy(&second_output.stdout),
String::from_utf8_lossy(&second_output.stderr)
);
assert!(
String::from_utf8_lossy(&second_output.stdout)
.contains("Reused 1 existing editable texture export(s)."),
"stdout:\n{}",
String::from_utf8_lossy(&second_output.stdout)
);
}
#[test]
fn cli_stages_idstudio_tree_from_temp_output() {
let temp_dir = TempDir::new().expect("tempdir");
let repo_root = temp_dir.path().join("repo");
create_repo_scaffold(&repo_root);
let output_mod_root = repo_root.join("dist").join("xylex-rtx-slayer-pack");
let editable_png = output_mod_root
.join("editable")
.join("models")
.join("customization")
.join("characters")
.join("doomslayer")
.join("set16")
.join("doomslayer_game_helmet_set16.png");
let generated_decl = output_mod_root
.join("warehouse")
.join("generated")
.join("decls")
.join("material2")
.join("sample.decl");
write_png(&editable_png);
fs::create_dir_all(
generated_decl
.parent()
.expect("generated decl should have parent"),
)
.expect("generated parent");
fs::write(&generated_decl, "declType( material2 ) {}\n").expect("generated decl");
let stage_root = temp_dir.path().join("idStudio").join("xylex-stage");
let output = Command::new(cli_binary())
.arg("--repo-root")
.arg(&repo_root)
.arg("stage-idstudio")
.arg("--output-mod-root")
.arg(&output_mod_root)
.arg("--stage-root")
.arg(&stage_root)
.output()
.expect("run stage-idstudio");
assert!(
output.status.success(),
"stdout:\n{}\n\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(stage_root
.join("base")
.join("models")
.join("customization")
.join("characters")
.join("doomslayer")
.join("set16")
.join("doomslayer_game_helmet_set16.tga")
.is_file());
assert!(stage_root
.join("base")
.join("generated")
.join("decls")
.join("material2")
.join("sample.decl")
.is_file());
assert!(stage_root.join("xylex-idstudio-stage.json").is_file());
}
#[test]
fn cli_packs_export_mirror_into_chunked_archives() {
let temp_dir = TempDir::new().expect("tempdir");
let repo_root = temp_dir.path().join("repo");
create_repo_scaffold(&repo_root);
let exports_root = temp_dir.path().join("exports");
let models_root = temp_dir.path().join("modelExports");
write_png(&exports_root.join("warehouse").join("one.png"));
write_png(&exports_root.join("warehouse").join("two.png"));
write_png(
&models_root
.join("doomslayer_cine_3p")
.join("images")
.join("three.png"),
);
let output_root = repo_root.join("dist").join("samuel-export-mirror");
let output = Command::new(cli_binary())
.arg("--repo-root")
.arg(&repo_root)
.arg("mirror")
.arg("pack")
.arg("--samuel-export-root")
.arg(&exports_root)
.arg("--model-export-root")
.arg(&models_root)
.arg("--output-root")
.arg(&output_root)
.arg("--chunk-size-mb")
.arg("1")
.output()
.expect("run mirror pack");
assert!(
output.status.success(),
"stdout:\n{}\n\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(output_root.join("mirror-manifest.json").is_file());
assert!(output_root.join("mirror-part-0001.zip").is_file());
}
fn create_repo_scaffold(repo_root: &Path) {
fs::create_dir_all(repo_root.join("config")).expect("config dir");
fs::create_dir_all(repo_root.join("mod")).expect("mod dir");
fs::create_dir_all(repo_root.join("assets").join("source").join("logos")).expect("logos dir");
fs::write(
repo_root
.join("config")
.join("rtx-pack-logo-placements.json"),
"[]\n",
)
.expect("placement config");
fs::write(repo_root.join("mod").join("EternalMod.json"), "{}\n").expect("mod metadata");
}
fn write_manifest(manifest_path: &Path, editable_texture: &Path) {
fs::create_dir_all(
manifest_path
.parent()
.expect("manifest should have parent directory"),
)
.expect("manifest parent");
let manifest = json!({
"message": "test import",
"sourceSet": "set52",
"targetSet": "set16",
"exports": [
{
"target": "third-person-helmet",
"relativeTexture": "models/customization/characters/doomslayer/set16/doomslayer_game_helmet_set16.tga",
"sourceTexture": "C:/test/source.tga",
"editableTexture": editable_texture,
}
]
});
fs::write(
manifest_path,
serde_json::to_string_pretty(&manifest).expect("serialize manifest"),
)
.expect("write manifest");
}
fn write_png(path: &Path) {
fs::create_dir_all(path.parent().expect("png should have parent")).expect("png parent");
let image = ImageBuffer::<Rgba<u8>, Vec<u8>>::from_pixel(2, 2, Rgba([255, 255, 255, 255]));
image.save(path).expect("save png");
}