use std::path::{Path, PathBuf};
use std::process::Command;
fn repo_root() -> PathBuf {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest
.parent()
.and_then(Path::parent)
.expect("fulgur-cli crate should be nested under <repo>/crates")
.to_path_buf()
}
fn render_example(example_dir: &Path, out_path: &Path) {
let root = repo_root();
let html = example_dir.join("index.html");
assert!(html.exists(), "missing HTML: {}", html.display());
let fontconfig = root.join("examples/.fontconfig/fonts.conf");
assert!(
fontconfig.exists(),
"missing pinned fontconfig: {}",
fontconfig.display()
);
let fulgur_bin = PathBuf::from(env!("CARGO_BIN_EXE_fulgur"));
let mut cmd = Command::new(&fulgur_bin);
cmd.current_dir(&root)
.env("FONTCONFIG_FILE", &fontconfig)
.arg("render")
.arg(&html);
let mut image_paths: Vec<PathBuf> = std::fs::read_dir(example_dir)
.expect("readdir example")
.filter_map(|entry| {
let path = entry.ok()?.path();
let ext = path.extension().and_then(|e| e.to_str())?;
if matches!(ext.to_ascii_lowercase().as_str(), "png" | "jpg" | "gif") {
Some(path)
} else {
None
}
})
.collect();
image_paths.sort();
for path in &image_paths {
let filename = path.file_name().and_then(|n| n.to_str()).expect("filename");
cmd.arg("--image")
.arg(format!("{}={}", filename, path.display()));
}
cmd.arg("-o").arg(out_path);
let status = cmd.status().expect("spawn fulgur");
assert!(
status.success(),
"fulgur render failed for {}",
example_dir.display()
);
}
fn assert_example_deterministic(example_name: &str) {
let root = repo_root();
let example_dir = root.join("examples").join(example_name);
let tmp = tempdir();
let out_a = tmp.join(format!("{example_name}-a.pdf"));
let out_b = tmp.join(format!("{example_name}-b.pdf"));
render_example(&example_dir, &out_a);
render_example(&example_dir, &out_b);
let a = std::fs::read(&out_a).expect("read a");
let b = std::fs::read(&out_b).expect("read b");
assert_eq!(
a.len(),
b.len(),
"{example_name}: PDF length differs between runs ({} vs {})",
a.len(),
b.len()
);
assert!(
a == b,
"{example_name}: PDFs differ byte-by-byte between runs — determinism broken"
);
assert!(a.starts_with(b"%PDF"), "{example_name}: not a valid PDF");
}
fn tempdir() -> PathBuf {
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let dir = std::env::temp_dir().join(format!(
"fulgur-examples-det-{}-{}",
std::process::id(),
nanos
));
std::fs::create_dir_all(&dir).expect("mkdir tempdir");
dir
}
#[test]
fn border_radius_example_is_deterministic() {
assert_example_deterministic("border-radius");
}
#[test]
fn header_footer_example_is_deterministic() {
assert_example_deterministic("header-footer");
}
#[test]
fn header_footer_split_example_is_deterministic() {
assert_example_deterministic("header-footer-split");
}
#[test]
fn image_example_is_deterministic() {
assert_example_deterministic("image");
}
#[test]
fn link_stylesheet_example_is_deterministic() {
assert_example_deterministic("link-stylesheet");
}
#[test]
fn svg_example_is_deterministic() {
assert_example_deterministic("svg");
}
#[test]
fn table_header_example_is_deterministic() {
assert_example_deterministic("table-header");
}
#[test]
fn text_align_example_is_deterministic() {
assert_example_deterministic("text-align");
}
#[test]
fn text_decoration_example_is_deterministic() {
assert_example_deterministic("text-decoration");
}
#[test]
fn pseudo_content_url_example_is_deterministic() {
assert_example_deterministic("pseudo-content-url");
}
#[cfg(target_os = "linux")]
#[test]
fn committed_svg_matches_rendered() {
let root = repo_root();
let committed = root.join("examples/svg/index.pdf");
assert!(
committed.exists(),
"committed PDF missing: {}",
committed.display()
);
let tmp = tempdir();
let out = tmp.join("svg-rendered.pdf");
render_example(&root.join("examples/svg"), &out);
let rendered = std::fs::read(&out).expect("read rendered");
let on_disk = std::fs::read(&committed).expect("read committed");
assert_eq!(
rendered.len(),
on_disk.len(),
"examples/svg/index.pdf is stale — run `mise run update-examples` \
to regenerate ({} bytes expected, {} bytes committed)",
rendered.len(),
on_disk.len()
);
assert!(
rendered == on_disk,
"examples/svg/index.pdf is stale — run `mise run update-examples`"
);
}