#![allow(clippy::print_stdout, clippy::print_stderr, clippy::dbg_macro)]
use std::collections::BTreeMap;
use std::panic::AssertUnwindSafe;
use std::path::{Path, PathBuf};
use std::process::Command;
use walkdir::WalkDir;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Lanes {
TextOnly,
TextAndClippy,
}
struct GateLanguage {
name: &'static str,
lanes: Lanes,
rust_crate_marker: &'static str,
clippy_exclusion_reason: &'static str,
}
const GATE_LANGUAGES: &[GateLanguage] = &[
GateLanguage {
name: "ffi",
lanes: Lanes::TextAndClippy,
rust_crate_marker: "-ffi",
clippy_exclusion_reason: "",
},
GateLanguage {
name: "python",
lanes: Lanes::TextAndClippy,
rust_crate_marker: "-py",
clippy_exclusion_reason: "",
},
GateLanguage {
name: "node",
lanes: Lanes::TextAndClippy,
rust_crate_marker: "-node",
clippy_exclusion_reason: "",
},
GateLanguage {
name: "wasm",
lanes: Lanes::TextAndClippy,
rust_crate_marker: "-wasm",
clippy_exclusion_reason: "",
},
GateLanguage {
name: "jni",
lanes: Lanes::TextAndClippy,
rust_crate_marker: "-jni",
clippy_exclusion_reason: "",
},
GateLanguage {
name: "kotlin_android",
lanes: Lanes::TextOnly,
rust_crate_marker: "",
clippy_exclusion_reason: "emits Kotlin and Gradle sources; its Rust side is the paired jni crate",
},
GateLanguage {
name: "java",
lanes: Lanes::TextOnly,
rust_crate_marker: "",
clippy_exclusion_reason: "emits Java sources; its Rust side is the paired jni crate",
},
GateLanguage {
name: "ruby",
lanes: Lanes::TextOnly,
rust_crate_marker: "",
clippy_exclusion_reason: "rb-sys needs libruby headers and a matching interpreter at build time",
},
GateLanguage {
name: "php",
lanes: Lanes::TextOnly,
rust_crate_marker: "",
clippy_exclusion_reason: "ext-php-rs needs php-dev headers at build time",
},
GateLanguage {
name: "elixir",
lanes: Lanes::TextOnly,
rust_crate_marker: "",
clippy_exclusion_reason: "the rustler NIF crate links against an Erlang runtime",
},
GateLanguage {
name: "swift",
lanes: Lanes::TextOnly,
rust_crate_marker: "",
clippy_exclusion_reason: "the swift-bridge crate's build script needs the Swift toolchain",
},
GateLanguage {
name: "go",
lanes: Lanes::TextOnly,
rust_crate_marker: "",
clippy_exclusion_reason: "emits cgo and Go sources, no Rust crate of its own",
},
GateLanguage {
name: "csharp",
lanes: Lanes::TextOnly,
rust_crate_marker: "",
clippy_exclusion_reason: "emits C# over the FFI surface, no Rust crate of its own",
},
];
fn fixture_language_list() -> String {
let names: Vec<String> = GATE_LANGUAGES
.iter()
.map(|language| format!(" \"{}\",", language.name))
.collect();
format!("\n{}\n", names.join("\n"))
}
fn clippy_lane_languages() -> Vec<&'static str> {
GATE_LANGUAGES
.iter()
.filter(|language| language.lanes == Lanes::TextAndClippy)
.map(|language| language.name)
.collect()
}
#[path = "generated_output_downstream_gate/fixture.rs"]
mod fixture;
use fixture::FIXTURE_CARGO_TOML;
struct GateTool {
program: &'static str,
display: &'static str,
probe: &'static [&'static str],
check_args: &'static [&'static str],
install_hint: &'static str,
}
const CARGO_SORT: GateTool = GateTool {
program: "cargo",
display: "cargo sort",
probe: &["sort", "--version"],
check_args: &["sort", "--check"],
install_hint: "cargo install cargo-sort (or `task setup`)",
};
const POLY: GateTool = GateTool {
program: "poly",
display: "poly",
probe: &["--version"],
check_args: &["fmt", "--check", "."],
install_hint: "brew install goldziher/tap/poly (or `task setup`)",
};
#[path = "generated_output_downstream_gate/poly_fmt_exclusions.rs"]
mod poly_fmt_exclusions;
use poly_fmt_exclusions::poly_fmt_check_args;
#[path = "generated_output_downstream_gate/ffi_allowlist_gate.rs"]
mod ffi_allowlist_gate;
#[path = "generated_output_downstream_gate/foreign_cfg_gate.rs"]
mod foreign_cfg_gate;
const CARGO: GateTool = GateTool {
program: "cargo",
display: "cargo clippy",
probe: &["clippy", "--version"],
check_args: &["clippy", "--all-targets", "--", "-D", "warnings"],
install_hint: "rustup component add clippy",
};
fn resolve_tools(tools: &[&GateTool]) {
let mut missing = Vec::new();
for tool in tools {
let runnable = Command::new(tool.program)
.args(tool.probe)
.output()
.is_ok_and(|output| output.status.success());
if !runnable {
missing.push(format!(
" {} — not installed or not runnable; {}",
tool.display, tool.install_hint
));
}
}
assert!(
missing.is_empty(),
"the generated-output gate cannot run without its downstream tooling.\n\
This is a failure, not a skip: with these tools absent the gate examines nothing.\n{}",
missing.join("\n")
);
}
fn assert_emitted_tree_is_isolated(emitted_root: &Path) -> PathBuf {
let emitted = emitted_root
.canonicalize()
.unwrap_or_else(|error| panic!("canonicalize emitted root {}: {error}", emitted_root.display()));
let repo_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.canonicalize()
.expect("canonicalize alef manifest directory");
assert!(
emitted != repo_root && !emitted.starts_with(&repo_root),
"the gate would have run over alef's own workspace instead of an emitted tree.\n\
emitted root: {}\n alef root: {}",
emitted.display(),
repo_root.display()
);
assert!(
!repo_root.starts_with(&emitted),
"the emitted root contains alef's own workspace, so the lanes would lint alef too.\n\
emitted root: {}\n alef root: {}",
emitted.display(),
repo_root.display()
);
for ancestor in emitted.ancestors().skip(1) {
assert!(
!ancestor.join("Cargo.toml").is_file(),
"the emitted tree is nested inside a cargo workspace rooted at {}.\n\
Cargo commands would resolve that parent manifest and lint crates this gate \
never generated.\n emitted root: {}",
ancestor.display(),
emitted.display()
);
}
emitted
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Sabotage {
None,
MisorderedCargoTable,
WideTomlArrayIndent,
CargoManifestIndentDrift,
RedundantPointerCast,
}
struct EmittedTree {
root: PathBuf,
manifests: Vec<PathBuf>,
toml_files: Vec<PathBuf>,
manifest_snapshots: BTreeMap<PathBuf, String>,
_workspace: tempfile::TempDir,
}
impl EmittedTree {
fn new(workspace: tempfile::TempDir, root: PathBuf) -> Self {
let core_manifest = root.join("Cargo.toml");
let foreign_core_manifest = root.join("foreign_core/Cargo.toml");
let mut manifests = Vec::new();
let mut toml_files = Vec::new();
for path in WalkDir::new(&root)
.into_iter()
.filter_map(Result::ok)
.map(walkdir::DirEntry::into_path)
.filter(|path| path.is_file())
{
if path.extension().is_some_and(|extension| extension == "toml") {
toml_files.push(path.clone());
}
let is_hand_authored = path == core_manifest || path == foreign_core_manifest;
if path.file_name().is_some_and(|name| name == "Cargo.toml") && !is_hand_authored {
manifests.push(path);
}
}
manifests.sort();
toml_files.sort();
assert!(
!manifests.is_empty(),
"generation emitted no Cargo.toml outside the fixture's own crate, so the cargo \
lanes would examine nothing.\n emitted root: {}",
root.display()
);
let mut manifest_snapshots = BTreeMap::new();
for manifest in &manifests {
let text = std::fs::read_to_string(manifest).unwrap_or_default();
assert!(
!text.contains("name = \"alef\""),
"the examined manifest set contains alef's own package: {}",
manifest.display()
);
manifest_snapshots.insert(manifest.clone(), text);
}
Self {
root,
manifests,
toml_files,
manifest_snapshots,
_workspace: workspace,
}
}
fn manifest_dirs(&self) -> Vec<&Path> {
self.manifests.iter().filter_map(|manifest| manifest.parent()).collect()
}
}
fn emit_tree(sabotage: Sabotage) -> EmittedTree {
let workspace = tempfile::tempdir().expect("create fixture workspace");
let root = workspace
.path()
.canonicalize()
.unwrap_or_else(|_| workspace.path().to_path_buf());
foreign_cfg_gate::write_fixture_workspace(&root);
assert_emitted_tree_is_isolated(&root);
for stage in ["generate", "scaffold"] {
let outcome = run_tool(env!("CARGO_BIN_EXE_alef"), &[stage], &root);
assert!(
outcome.passed,
"`alef {stage}` failed over the gate fixture, so there is no emitted tree to check:\n\
--- {} ---\n{}",
outcome.command, outcome.output
);
}
let tree = EmittedTree::new(workspace, root);
inject(&tree, sabotage);
tree
}
fn inject(tree: &EmittedTree, sabotage: Sabotage) {
match sabotage {
Sabotage::None => {}
Sabotage::MisorderedCargoTable => {
let manifest = tree
.manifests
.first()
.expect("manifest set is non-empty by EmittedTree::new");
let text = std::fs::read_to_string(manifest).expect("read manifest to sabotage");
let misordered = format!("[lints.clippy]\nredundant_clone = \"deny\"\n\n{text}");
std::fs::write(manifest, misordered).expect("write misordered manifest");
}
Sabotage::WideTomlArrayIndent => {
let target = tree
.toml_files
.iter()
.find(|path| {
path.file_name().is_some_and(|name| name != "Cargo.toml")
&& path.strip_prefix(&tree.root).is_ok_and(|relative| {
!relative
.components()
.any(|component| component.as_os_str().to_string_lossy().starts_with('.'))
})
})
.expect("emitted tree contains a TOML file outside a dot-directory and not named Cargo.toml");
let text = std::fs::read_to_string(target).expect("read toml to sabotage");
let widened = format!("{text}\n[gate_sabotage]\nvalues = [\n \"a\",\n \"b\",\n]\n");
std::fs::write(target, widened).expect("write wide-indent toml");
}
Sabotage::CargoManifestIndentDrift => {
let manifest = tree
.manifests
.first()
.expect("manifest set is non-empty by EmittedTree::new");
let text = std::fs::read_to_string(manifest).expect("read manifest to sabotage");
let drifted = widen_key_value_spacing(&text);
assert_ne!(
drifted,
text,
"the whitespace sabotage produced no change -- {} has no `key = value` line to widen",
manifest.display()
);
std::fs::write(manifest, drifted).expect("write indent-drifted manifest");
}
Sabotage::RedundantPointerCast => {
let target = emitted_rust_source(tree).expect("emitted tree contains a Rust source file");
let text = std::fs::read_to_string(&target).expect("read rust source to sabotage");
let with_cast = format!("{text}\n{REDUNDANT_CAST_SNIPPET}");
std::fs::write(&target, with_cast).expect("write redundant-cast source");
}
}
}
const REDUNDANT_CAST_SNIPPET: &str = r"
#[allow(dead_code)]
pub struct GateSabotageHandle {
value: u64,
}
#[allow(dead_code)]
pub fn gate_sabotage_into_handle(value: Box<GateSabotageHandle>) -> i64 {
let raw = Box::into_raw(value) as *const GateSabotageHandle;
raw as *const GateSabotageHandle as i64
}
";
fn widen_key_value_spacing(manifest: &str) -> String {
let mut widened: String = manifest
.lines()
.map(|line| {
let trimmed = line.trim_start();
if trimmed.is_empty() || trimmed.starts_with('[') || trimmed.starts_with('#') {
return line.to_owned();
}
let indent = &line[..line.len() - trimmed.len()];
match trimmed.split_once(" = ") {
Some((key, value)) => format!("{indent}{key} = {value}"),
None => line.to_owned(),
}
})
.collect::<Vec<_>>()
.join("\n");
widened.push('\n');
widened
}
fn emitted_rust_source(tree: &EmittedTree) -> Option<PathBuf> {
let mut candidates: Vec<PathBuf> = clippy_manifest_dirs(tree)
.iter()
.flat_map(|dir| WalkDir::new(dir).into_iter().filter_map(Result::ok))
.map(walkdir::DirEntry::into_path)
.filter(|path| path.is_file() && path.extension().is_some_and(|extension| extension == "rs"))
.filter(|path| path.components().any(|component| component.as_os_str() == "src"))
.collect();
candidates.sort();
let allows_the_lint = |path: &PathBuf| {
std::fs::read_to_string(path).is_ok_and(|body| {
body.lines()
.filter(|line| line.starts_with("#!["))
.any(|line| line.contains("clippy::unnecessary_cast"))
})
};
let lintable: Vec<PathBuf> = candidates
.iter()
.filter(|path| !allows_the_lint(path))
.cloned()
.collect();
assert!(
!lintable.is_empty() || candidates.is_empty(),
"every emitted Rust source allows `clippy::unnecessary_cast` at crate level, so the \
redundant-cast sabotage cannot be detected anywhere and the clippy lane's self-check \
would pass without examining anything. Candidates:\n{}",
candidates
.iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join("\n")
);
lintable.into_iter().next()
}
struct LaneOutcome {
command: String,
passed: bool,
output: String,
}
fn run_tool(program: &str, args: &[&str], cwd: &Path) -> LaneOutcome {
let command = format!("{program} {} (in {})", args.join(" "), cwd.display());
let output = Command::new(program)
.args(args)
.current_dir(cwd)
.output()
.unwrap_or_else(|error| panic!("running `{command}`: {error}"));
let mut combined = String::from_utf8_lossy(&output.stdout).into_owned();
combined.push_str(&String::from_utf8_lossy(&output.stderr));
LaneOutcome {
command,
passed: output.status.success(),
output: combined,
}
}
fn cargo_sort_lane(tree: &EmittedTree) -> Vec<LaneOutcome> {
let root = assert_emitted_tree_is_isolated(&tree.root);
assert_eq!(root, tree.root, "isolation guard returned a different root");
tree.manifest_dirs()
.into_iter()
.map(|dir| run_tool(CARGO_SORT.program, CARGO_SORT.check_args, dir))
.collect()
}
fn cargo_manifest_byte_lane(tree: &EmittedTree) -> Vec<LaneOutcome> {
tree.manifests
.iter()
.map(|manifest| {
let command = format!("byte-compare {} against alef's emitted bytes", manifest.display());
let current = std::fs::read_to_string(manifest).unwrap_or_default();
let original = tree
.manifest_snapshots
.get(manifest)
.unwrap_or_else(|| panic!("no emitted-bytes snapshot recorded for {}", manifest.display()));
match first_difference(original, ¤t) {
None => LaneOutcome {
command,
passed: true,
output: String::new(),
},
Some((line_number, expected, actual)) => LaneOutcome {
command,
passed: false,
output: format!(
"{} differs from alef's emitted bytes at line {line_number}:\n \
emitted: {expected:?}\n on disk: {actual:?}",
manifest.display()
),
},
}
})
.collect()
}
fn first_difference(expected: &str, actual: &str) -> Option<(usize, String, String)> {
let mut expected_lines = expected.lines();
let mut actual_lines = actual.lines();
let mut line_number = 0usize;
loop {
line_number += 1;
match (expected_lines.next(), actual_lines.next()) {
(None, None) => return None,
(expected_line, actual_line) if expected_line == actual_line => {}
(expected_line, actual_line) => {
return Some((
line_number,
expected_line
.unwrap_or("<no line -- file is shorter than emitted>")
.to_owned(),
actual_line
.unwrap_or("<no line -- file is shorter than emitted>")
.to_owned(),
));
}
}
}
}
fn clippy_manifest_dirs(tree: &EmittedTree) -> Vec<PathBuf> {
let mut selected = Vec::new();
let mut unmatched = Vec::new();
for language in GATE_LANGUAGES
.iter()
.filter(|language| language.lanes == Lanes::TextAndClippy)
{
assert!(
!language.rust_crate_marker.is_empty(),
"language `{}` is in the clippy lane but declares no rust_crate_marker, so its \
crate cannot be located in the emitted tree",
language.name
);
let matches: Vec<PathBuf> = tree
.manifest_dirs()
.into_iter()
.filter(|dir| {
dir.file_name()
.is_some_and(|name| name.to_string_lossy().ends_with(language.rust_crate_marker))
})
.map(Path::to_path_buf)
.collect();
if matches.is_empty() {
unmatched.push(format!(" {} (marker `{}`)", language.name, language.rust_crate_marker));
}
selected.extend(matches);
}
assert!(
unmatched.is_empty(),
"no emitted crate directory matched these clippy-lane languages, so clippy would \
examine nothing for them:\n{}\nemitted crate directories: {:?}",
unmatched.join("\n"),
tree.manifest_dirs()
.iter()
.filter_map(|dir| dir.file_name())
.collect::<Vec<_>>()
);
selected.sort();
selected.dedup();
selected
}
fn poly_fmt_lane(tree: &EmittedTree) -> LaneOutcome {
let root = assert_emitted_tree_is_isolated(&tree.root);
let args = poly_fmt_check_args();
let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect();
run_tool(POLY.program, &arg_refs, &root)
}
fn clippy_lane(tree: &EmittedTree) -> Vec<LaneOutcome> {
let root = assert_emitted_tree_is_isolated(&tree.root);
assert_eq!(root, tree.root, "isolation guard returned a different root");
clippy_manifest_dirs(tree)
.iter()
.map(|dir| run_tool(CARGO.program, CARGO.check_args, dir))
.collect()
}
fn report(outcomes: &[LaneOutcome]) -> String {
outcomes
.iter()
.filter(|outcome| !outcome.passed)
.map(|outcome| format!("--- {} ---\n{}", outcome.command, outcome.output))
.collect::<Vec<_>>()
.join("\n")
}
fn any_failed(outcomes: &[LaneOutcome]) -> bool {
outcomes.iter().any(|outcome| !outcome.passed)
}
#[test]
#[ignore = "needs cargo-sort; run via `task gate:generated-output` or the CI gate job"]
fn emitted_tree_passes_cargo_sort() {
resolve_tools(&[&CARGO_SORT]);
let tree = emit_tree(Sabotage::None);
let outcomes = cargo_sort_lane(&tree);
assert!(
!any_failed(&outcomes),
"`cargo sort --check` rejected alef-generated manifests:\n{}",
report(&outcomes)
);
}
#[test]
#[ignore = "needs poly; run via `task gate:generated-output` or the CI gate job"]
fn emitted_tree_passes_poly_fmt() {
resolve_tools(&[&POLY]);
let tree = emit_tree(Sabotage::None);
let outcome = poly_fmt_lane(&tree);
assert!(
outcome.passed,
"`poly fmt --check` rejected alef-generated output:\n--- {} ---\n{}",
outcome.command, outcome.output
);
}
#[test]
#[ignore = "regenerates a full emitted tree via the alef binary; run via `task gate:generated-output` \
or the CI gate job"]
fn emitted_tree_passes_cargo_manifest_byte_lane() {
let tree = emit_tree(Sabotage::None);
let outcomes = cargo_manifest_byte_lane(&tree);
assert!(
!any_failed(&outcomes),
"an untouched emitted tree must byte-match its own snapshot, or this lane is not a \
faithful byte comparison:\n{}",
report(&outcomes)
);
}
#[test]
#[ignore = "compiles the emitted crates; run via the CI gate job"]
fn emitted_tree_passes_clippy() {
resolve_tools(&[&CARGO]);
let tree = emit_tree(Sabotage::None);
let outcomes = clippy_lane(&tree);
assert!(
!any_failed(&outcomes),
"`cargo clippy -- -D warnings` rejected alef-generated crates:\n{}",
report(&outcomes)
);
}
#[test]
#[ignore = "needs cargo-sort; run via `task gate:generated-output` or the CI gate job"]
fn cargo_sort_lane_catches_a_misordered_table() {
resolve_tools(&[&CARGO_SORT]);
let clean = emit_tree(Sabotage::None);
let control = cargo_sort_lane(&clean);
assert!(
!any_failed(&control),
"the control tree must be green, or the sabotage proves nothing:\n{}",
report(&control)
);
let sabotaged = emit_tree(Sabotage::MisorderedCargoTable);
let outcomes = cargo_sort_lane(&sabotaged);
assert!(
any_failed(&outcomes),
"a `[lints.clippy]` table ahead of `[dependencies]` did not fail `cargo sort --check`, \
so this lane is not examining the emitted manifests"
);
}
#[test]
#[ignore = "needs poly; run via `task gate:generated-output` or the CI gate job"]
fn poly_fmt_lane_catches_a_wide_toml_array_indent() {
resolve_tools(&[&POLY]);
let clean = emit_tree(Sabotage::None);
let clean_outcome = poly_fmt_lane(&clean);
assert!(
clean_outcome.passed,
"the control tree must be green, or the sabotage proves nothing:\n{}",
clean_outcome.output
);
let sabotaged = emit_tree(Sabotage::WideTomlArrayIndent);
let outcome = poly_fmt_lane(&sabotaged);
assert!(
!outcome.passed,
"a four-space TOML array indent did not fail `poly fmt --check`, so this lane is not \
examining the emitted TOML"
);
}
#[test]
#[ignore = "needs cargo-sort; run via `task gate:generated-output` or the CI gate job"]
fn cargo_manifest_byte_lane_catches_indentation_only_drift() {
resolve_tools(&[&CARGO_SORT]);
let clean = emit_tree(Sabotage::None);
let clean_outcomes = cargo_manifest_byte_lane(&clean);
assert!(
!any_failed(&clean_outcomes),
"the control tree must be byte-identical to its own snapshot, or the sabotage proves \
nothing:\n{}",
report(&clean_outcomes)
);
let sabotaged = emit_tree(Sabotage::CargoManifestIndentDrift);
let byte_outcomes = cargo_manifest_byte_lane(&sabotaged);
assert!(
any_failed(&byte_outcomes),
"widening the spacing around every `key = value` pair's `=` did not fail the byte \
comparison, so this lane is not examining the emitted manifest's bytes"
);
let sort_outcomes = cargo_sort_lane(&sabotaged);
assert!(
!any_failed(&sort_outcomes),
"`cargo sort --check` rejected an indentation-only change, so it is not the blind spot \
this lane exists to cover -- update the doc comments on `Sabotage::CargoManifestIndentDrift` \
and `cargo_manifest_byte_lane` if cargo-sort's behaviour has changed:\n{}",
report(&sort_outcomes)
);
}
#[test]
#[ignore = "compiles the emitted crates; run via the CI gate job"]
fn clippy_lane_catches_a_redundant_pointer_cast() {
resolve_tools(&[&CARGO]);
let clean = emit_tree(Sabotage::None);
let control = clippy_lane(&clean);
assert!(
!any_failed(&control),
"the control tree must be green, or the sabotage proves nothing:\n{}",
report(&control)
);
let sabotaged = emit_tree(Sabotage::RedundantPointerCast);
let outcomes = clippy_lane(&sabotaged);
assert!(
any_failed(&outcomes),
"a redundant pointer cast did not fail `cargo clippy -- -D warnings`, so this lane is \
not examining the emitted Rust"
);
}
#[test]
fn isolation_guard_rejects_alefs_own_workspace() {
let repo_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let result = std::panic::catch_unwind(AssertUnwindSafe(|| assert_emitted_tree_is_isolated(&repo_root)));
assert!(
result.is_err(),
"the isolation guard accepted alef's own workspace as an emitted tree — the failure \
mode this gate exists to prevent"
);
}
#[test]
fn isolation_guard_rejects_a_tree_nested_in_a_cargo_workspace() {
let outer = tempfile::tempdir().expect("create outer workspace");
std::fs::write(outer.path().join("Cargo.toml"), FIXTURE_CARGO_TOML).expect("write outer manifest");
let nested = outer.path().join("nested");
std::fs::create_dir_all(&nested).expect("create nested tree");
let result = std::panic::catch_unwind(AssertUnwindSafe(|| assert_emitted_tree_is_isolated(&nested)));
assert!(
result.is_err(),
"the isolation guard accepted a tree whose parent holds a Cargo.toml; cargo would \
resolve that parent manifest and lint crates the gate never generated"
);
}
#[test]
fn isolation_guard_accepts_a_standalone_temp_tree() {
let workspace = tempfile::tempdir().expect("create workspace");
let accepted = assert_emitted_tree_is_isolated(workspace.path());
assert!(
accepted.is_absolute(),
"the guard must return a canonical absolute root, got {}",
accepted.display()
);
}
const GATE_JOB: &str = "generated-output-gate";
fn workflow_job_block(workflow: &str, job: &str) -> Option<String> {
let header = format!(" {job}:");
let mut lines = workflow.lines().skip_while(|line| line.trim_end() != header);
let first = lines.next()?;
let mut block = String::from(first);
for line in lines {
let is_sibling_job = line.starts_with(" ") && !line.starts_with(" ") && line.trim_end().ends_with(':');
if is_sibling_job {
break;
}
block.push('\n');
block.push_str(line);
}
Some(block)
}
#[test]
fn ci_workflow_runs_the_generated_output_gate() {
let workflow_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".github/workflows/ci.yml");
let workflow = std::fs::read_to_string(&workflow_path)
.unwrap_or_else(|error| panic!("read {}: {error}", workflow_path.display()));
let block = workflow_job_block(&workflow, GATE_JOB).unwrap_or_else(|| {
panic!(
"{} has no `{GATE_JOB}` job. Every lane in this file is #[ignore]d, so with that job \
gone nothing runs the downstream gate at all.",
workflow_path.display()
)
});
let required: &[(&str, &str)] = &[
(
"--test generated_output_downstream_gate",
"the gate job must invoke this test binary by name",
),
(
"--ignored",
"every lane in this file is #[ignore]d, so the gate job must pass --ignored or it \
runs none of them",
),
(
"cargo-sort",
"the gate job must install cargo-sort, or the cargo sort lane fails on a missing tool",
),
(
"goldziher/tap/poly",
"the gate job must install poly, or the poly fmt lane fails on a missing tool",
),
(
"clippy",
"the gate job must install the clippy component, or the clippy lane fails on a \
missing tool",
),
];
let missing: Vec<String> = required
.iter()
.filter(|(needle, _)| !block.contains(needle))
.map(|(needle, reason)| format!(" `{needle}` — {reason}"))
.collect();
assert!(
missing.is_empty(),
"the `{GATE_JOB}` job in {} no longer wires up the generated-output gate:\n{}\n\
--- job block as parsed ---\n{block}",
workflow_path.display(),
missing.join("\n")
);
}
#[test]
fn workflow_job_block_stops_at_the_next_job() {
let workflow = concat!(
"jobs:\n",
" first:\n steps:\n - run: marker-in-first\n",
" second:\n steps:\n - run: marker-in-second\n",
);
let first = workflow_job_block(workflow, "first").expect("first job block");
assert!(first.contains("marker-in-first"), "block must contain its own steps");
assert!(
!first.contains("marker-in-second"),
"block leaked into the following job, so job-scoped assertions would be meaningless"
);
assert!(
workflow_job_block(workflow, "absent").is_none(),
"a job that does not exist must not resolve to a block"
);
}
#[test]
fn every_clippy_lane_exclusion_is_justified() {
let unjustified: Vec<&str> = GATE_LANGUAGES
.iter()
.filter(|language| language.lanes == Lanes::TextOnly && language.clippy_exclusion_reason.trim().is_empty())
.map(|language| language.name)
.collect();
assert!(
unjustified.is_empty(),
"these languages sit out the clippy lane with no stated reason: {unjustified:?}"
);
assert!(
!clippy_lane_languages().is_empty(),
"no language is in the clippy lane, so the clippy gate would examine nothing"
);
}