use std::path::{Path, PathBuf};
use std::process::Command;
fn repo_root() -> PathBuf {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest_dir
.parent()
.and_then(|p| p.parent())
.map(PathBuf::from)
.unwrap_or(manifest_dir)
}
fn init_temp_git_repo(dir: &Path) {
let out = Command::new("git")
.args(["init", "-q"])
.current_dir(dir)
.output()
.expect("git init");
assert!(out.status.success(), "git init failed: {:?}", out);
}
fn run_script(script: &Path, cwd: &Path) -> std::process::Output {
Command::new("bash")
.arg(script)
.current_dir(cwd)
.output()
.expect("invoke script")
}
#[test]
fn lint_docs_rejects_quoted_override_deny_outside_allowlist() {
let root = repo_root();
let script = root.join("scripts").join("lint-docs.sh");
assert!(script.exists(), "lint-docs.sh missing");
let tmp = tempfile::tempdir().expect("tempdir");
init_temp_git_repo(tmp.path());
let target_dir = tmp.path().join("crates").join("dummy");
std::fs::create_dir_all(&target_dir).expect("mkdir");
let target_file = target_dir.join("offender.json");
let mut content = String::from("{\n ");
content.push('"');
content.push_str("override_deny");
content.push('"');
content.push_str(": []\n}\n");
std::fs::write(&target_file, content).expect("write");
let out = run_script(&script, tmp.path());
assert!(
!out.status.success(),
"lint-docs.sh should reject the quoted legacy form; \
stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
combined.contains("forbidden") || combined.contains("override_deny"),
"expected diagnostic about the violation; got: {combined}"
);
}
#[test]
fn lint_docs_accepts_clean_tree() {
let root = repo_root();
let script = root.join("scripts").join("lint-docs.sh");
let tmp = tempfile::tempdir().expect("tempdir");
init_temp_git_repo(tmp.path());
let out = run_script(&script, tmp.path());
assert!(
out.status.success(),
"lint-docs.sh should accept a clean tree; stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn alias_inventory_rejects_naked_serde_alias() {
let root = repo_root();
let script = root.join("scripts").join("test-list-aliases.sh");
assert!(script.exists(), "test-list-aliases.sh missing");
let tmp = tempfile::tempdir().expect("tempdir");
init_temp_git_repo(tmp.path());
let target_dir = tmp.path().join("crates").join("dummy").join("src");
std::fs::create_dir_all(&target_dir).expect("mkdir");
std::fs::write(
target_dir.join("lib.rs"),
r#"// Naked alias — no /// ALIAS marker above.
#[derive(serde::Deserialize)]
pub struct Bad {
#[serde(alias = "old_name")]
pub new_name: String,
}
"#,
)
.expect("write");
let out = run_script(&script, tmp.path());
assert!(
!out.status.success(),
"test-list-aliases.sh should reject a naked alias; \
stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
combined.contains("MISSING") || combined.contains("ALIAS"),
"expected diagnostic about the missing marker; got: {combined}"
);
}
#[test]
fn alias_inventory_rejects_marker_missing_field() {
let root = repo_root();
let script = root.join("scripts").join("test-list-aliases.sh");
let tmp = tempfile::tempdir().expect("tempdir");
init_temp_git_repo(tmp.path());
let target_dir = tmp.path().join("crates").join("dummy").join("src");
std::fs::create_dir_all(&target_dir).expect("mkdir");
std::fs::write(
target_dir.join("lib.rs"),
r#"
#[derive(serde::Deserialize)]
pub struct Bad {
/// ALIAS(canonical="new_name", introduced="v0.41.0", remove_by="v1.0.0")
#[serde(alias = "old_name")]
pub new_name: String,
}
"#,
)
.expect("write");
let out = run_script(&script, tmp.path());
assert!(
!out.status.success(),
"test-list-aliases.sh should reject a marker missing the 'issue' \
field; stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn alias_inventory_rejects_unapproved_deprecated_module_reach_in() {
let root = repo_root();
let script = root.join("scripts").join("test-list-aliases.sh");
let tmp = tempfile::tempdir().expect("tempdir");
init_temp_git_repo(tmp.path());
let src = tmp.path().join("crates").join("dummy").join("src");
std::fs::create_dir_all(&src).expect("mkdir");
std::fs::write(
src.join("deprecated_schema.rs"),
"// pretend deprecated module\n",
)
.expect("write");
std::fs::write(
src.join("rogue_caller.rs"),
"fn x() { let _g = crate::deprecated_schema::WarningCounterGuard::begin(); }\n",
)
.expect("write");
let out = run_script(&script, tmp.path());
assert!(
!out.status.success(),
"test-list-aliases.sh should reject the fully-qualified \
crate::deprecated_schema:: reach-in from a non-approved file; \
stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
combined.contains("UNAPPROVED"),
"expected UNAPPROVED diagnostic; got: {combined}"
);
}