use std::collections::BTreeSet;
use std::fs;
use std::path::Path;
use std::process::Command;
const SNAPSHOT_PATH: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/api_surface_snapshot.json"
);
#[test]
fn api_surface_matches_committed_snapshot() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let output = Command::new("cargo")
.args(["public-api", "-ss", "--all-features", "-p", "weir"])
.current_dir(root)
.output()
.expect("cargo-public-api should be installed and runnable");
if !output.status.success() {
eprintln!(
"WARNING: cargo-public-api failed, skipping API snapshot comparison:\nstderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
return;
}
let current_lines: BTreeSet<String> = String::from_utf8_lossy(&output.stdout)
.lines()
.map(|l| l.trim().to_owned())
.filter(|l| !l.is_empty())
.collect();
let snapshot_raw =
fs::read_to_string(SNAPSHOT_PATH).expect("API snapshot file should be readable");
let snapshot_lines: BTreeSet<String> = serde_json::from_str::<Vec<String>>(&snapshot_raw)
.expect("API snapshot should be valid JSON array of strings")
.into_iter()
.map(|l| l.trim().to_owned())
.filter(|l| !l.is_empty())
.collect();
let removed: Vec<&String> = snapshot_lines.difference(¤t_lines).collect();
let added: Vec<&String> = current_lines.difference(&snapshot_lines).collect();
if !removed.is_empty() || !added.is_empty() {
let manifest =
fs::read_to_string(root.join("Cargo.toml")).expect("Cargo.toml should be readable");
let has_bump_comment = manifest.contains("# api-breaking-change")
|| manifest.contains("# version-bump")
|| manifest.contains("# breaking");
let mut message = String::new();
message.push_str("Public API surface changed.\n");
if !removed.is_empty() {
message.push_str(&format!("\nRemoved ({}):\n", removed.len()));
for item in &removed {
message.push_str(&format!(" - {item}\n"));
}
}
if !added.is_empty() {
message.push_str(&format!("\nAdded ({}):\n", added.len()));
for item in &added {
message.push_str(&format!(" + {item}\n"));
}
}
if !has_bump_comment {
message.push_str(
"\nIf this change is intentional, add a version-bump comment to Cargo.toml.\n",
);
}
panic!("{message}");
}
}
#[test]
fn api_snapshot_file_is_sorted_and_unique() {
let snapshot_raw =
fs::read_to_string(SNAPSHOT_PATH).expect("API snapshot file should be readable");
let lines: Vec<String> = serde_json::from_str::<Vec<String>>(&snapshot_raw)
.expect("API snapshot should be valid JSON array of strings")
.into_iter()
.map(|l| l.trim().to_owned())
.filter(|l| !l.is_empty())
.collect();
let mut sorted = lines.clone();
sorted.sort();
sorted.dedup();
assert_eq!(
lines, sorted,
"API snapshot must be sorted and contain no duplicates"
);
}