use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
#[test]
fn scan_output_is_included_by_consumer_crate() {
let fixture = temp_fixture("include_flow");
create_fixture_crate(&fixture);
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let output = Command::new(cargo)
.arg("test")
.arg("--manifest-path")
.arg(fixture.join("Cargo.toml"))
.arg("--target-dir")
.arg(fixture.join("target"))
.output()
.unwrap();
assert!(
output.status.success(),
"fixture cargo test failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
!fixture.join("src/gates_registry.rs").exists(),
"registry must not be written into src/"
);
let generated = find_file(&fixture.join("target"), "gates_registry.rs");
let generated = generated.expect("generated registry should exist under target OUT_DIR");
let contents = fs::read_to_string(generated).unwrap();
assert_eq!(
registry_entries(&contents),
[
"&crate::gates::alpha::REGISTERED,",
"&crate::gates::beta::REGISTERED,",
"&crate::gates::gamma::REGISTERED,"
],
"generated registry should expose exactly the three fixture REGISTERED consts"
);
assert!(
contents.contains("pub static ALL_GATES: &[&'static dyn crate::gates::Gate]"),
"generated registry should preserve the requested ALL_GATES slice type"
);
let _ = fs::remove_dir_all(fixture);
}
fn create_fixture_crate(root: &Path) {
let build_scan_path = Path::new(env!("CARGO_MANIFEST_DIR"));
fs::create_dir_all(root.join("src/gates")).unwrap();
fs::write(
root.join("Cargo.toml"),
format!(
r#"[package]
name = "vyre-build-scan-include-fixture"
version = "0.0.0"
edition = "2021"
[build-dependencies]
vyre-build-scan = {{ path = "{}" }}
"#,
build_scan_path.display()
),
)
.unwrap();
fs::write(
root.join("build.rs"),
r#"fn main() {
vyre_build_scan::scan(&vyre_build_scan::Registry {
scan_dir: "src/gates",
const_name: "ALL_GATES",
element_type: "&'static dyn crate::gates::Gate",
item_const_name: "REGISTERED",
output_file: "gates_registry.rs",
module_prefix: "crate::gates",
});
}
"#,
)
.unwrap();
fs::write(
root.join("src/lib.rs"),
r#"pub mod gates;
#[cfg(test)]
mod tests {
use super::gates::ALL_GATES;
#[test]
fn all_gates_shape_is_stable() {
let _: &[&'static dyn crate::gates::Gate] = ALL_GATES;
let names: Vec<&'static str> = ALL_GATES.iter().map(|gate| gate.name()).collect();
assert_eq!(names, ["alpha", "beta", "gamma"]);
}
}
"#,
)
.unwrap();
fs::write(
root.join("src/gates/mod.rs"),
r#"pub trait Gate: Sync {
fn name(&self) -> &'static str;
}
pub mod alpha;
pub mod beta;
pub mod gamma;
include!(concat!(env!("OUT_DIR"), "/gates_registry.rs"));
"#,
)
.unwrap();
write_gate(root, "alpha", "Alpha");
write_gate(root, "beta", "Beta");
write_gate(root, "gamma", "Gamma");
}
fn write_gate(root: &Path, module: &str, ty: &str) {
fs::write(
root.join(format!("src/gates/{module}.rs")),
format!(
r#"pub struct {ty};
impl crate::gates::Gate for {ty} {{
fn name(&self) -> &'static str {{
"{module}"
}}
}}
pub const REGISTERED: {ty} = {ty};
"#
),
)
.unwrap();
}
fn registry_entries(contents: &str) -> Vec<&str> {
contents
.lines()
.map(str::trim)
.filter(|line| line.starts_with("&crate::gates::"))
.collect()
}
fn find_file(root: &Path, name: &str) -> Option<PathBuf> {
let mut pending = vec![root.to_path_buf()];
while let Some(dir) = pending.pop() {
for entry in fs::read_dir(dir).ok()? {
let entry = entry.ok()?;
let path = entry.path();
if path.is_dir() {
pending.push(path);
} else if path.file_name().and_then(|file| file.to_str()) == Some(name) {
return Some(path);
}
}
}
None
}
fn temp_fixture(suffix: &str) -> PathBuf {
let mut base = env::temp_dir();
base.push(format!("vyre-build-scan-{}-{}", std::process::id(), suffix));
let _ = fs::remove_dir_all(&base);
base
}