use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.canonicalize()
.expect("resolve repo root")
}
fn read(path: &Path) -> String {
std::fs::read_to_string(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()))
}
fn assert_clippy_lines_are_workspace_wide(source: &str, path: &Path) {
let clippy_lines: Vec<&str> = source
.lines()
.map(str::trim)
.map(|l| l.trim_start_matches("- run:").trim())
.filter(|l| l.starts_with("cargo clippy"))
.collect();
assert!(
!clippy_lines.is_empty(),
"expected at least one `cargo clippy` invocation in {}",
path.display()
);
for line in &clippy_lines {
assert!(
line.contains("--workspace") && line.contains("--all-targets"),
"clippy invocation in {} must include both `--workspace` and \
`--all-targets` — the narrow `cargo clippy -- -D warnings` form \
does not compile test targets, so lints inside `#[cfg(test)]` \
modules go undetected (17-REVIEW.md WR-08). Found: {line:?}",
path.display()
);
assert!(
line.contains("-D warnings"),
"clippy invocation in {} must fail on warnings (`-D warnings`). \
Found: {line:?}",
path.display()
);
}
}
#[test]
fn check_script_fails_fast_before_any_cargo_invocation() {
let path = repo_root().join("scripts/check.sh");
let script = read(&path);
let lines: Vec<&str> = script
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.collect();
let set_idx = lines
.iter()
.position(|l| l.starts_with("set -") && l.contains('e'))
.expect("expected a `set -e`-family line in scripts/check.sh");
assert!(
lines[set_idx].contains("-euo pipefail") || lines[set_idx].contains("pipefail"),
"scripts/check.sh must use `set -euo pipefail`, not a weaker form — \
an unset variable or a failing command in a pipe would otherwise \
pass silently. Found: {:?}",
lines[set_idx]
);
if let Some(first_cargo_idx) = lines.iter().position(|l| l.contains("cargo ")) {
assert!(
set_idx < first_cargo_idx,
"fail-fast (line {set_idx}) must precede every `cargo` invocation \
(first at line {first_cargo_idx}) — see 15-REVIEW.md CR-01."
);
}
}
#[test]
fn check_script_clippy_lints_test_targets() {
let path = repo_root().join("scripts/check.sh");
let script = read(&path);
assert_clippy_lines_are_workspace_wide(&script, &path);
}
#[test]
fn ci_workflow_delegates_to_the_shared_check_script() {
let path = repo_root().join(".github/workflows/ci.yml");
let workflow = read(&path);
for target in [
"scripts/check.sh test",
"scripts/check.sh clippy",
"scripts/check.sh fmt",
] {
assert!(
workflow.contains(target),
"{} must invoke `{target}` so local and CI runs execute the same \
commands. If this moved, move the parity guards with it.",
path.display()
);
}
let inlined: Vec<&str> = workflow
.lines()
.map(str::trim)
.filter(|l| l.starts_with("- run: cargo ") || l.starts_with("run: cargo "))
.collect();
assert!(
inlined.is_empty(),
"{} must not invoke cargo directly — every check goes through \
scripts/check.sh so CI and local cannot drift. Found: {inlined:?}",
path.display()
);
}
#[test]
fn devcontainer_runcmd_fails_fast_before_any_check() {
let path = repo_root().join(".github/workflows/devcontainer.yml");
let workflow = read(&path);
let mut lines = workflow.lines();
for line in lines.by_ref() {
if line.trim_start() == "runCmd: |" {
break;
}
}
let block_indent = workflow
.lines()
.find(|l| l.trim_start() == "runCmd: |")
.map(|l| l.len() - l.trim_start().len())
.expect("find `runCmd: |` in devcontainer.yml");
let mut cmd_lines = Vec::new();
for line in lines {
if line.trim().is_empty() {
continue;
}
if line.len() - line.trim_start().len() <= block_indent {
break;
}
cmd_lines.push(line.trim());
}
assert!(
!cmd_lines.is_empty(),
"could not locate command lines inside `runCmd: |` in {}",
path.display()
);
assert_eq!(
cmd_lines[0], "set -e",
"`runCmd`'s first command line must be exactly `set -e` (15-REVIEW.md \
CR-01). Found: {:?}\nfull runCmd: {cmd_lines:#?}",
cmd_lines[0]
);
assert!(
cmd_lines.iter().any(|l| l.contains("scripts/check.sh")),
"devcontainer.yml must delegate to scripts/check.sh so it does not \
become a second definition of green. Found: {cmd_lines:#?}"
);
}
#[test]
fn devcontainer_job_name_matches_the_required_status_check() {
let path = repo_root().join(".github/workflows/devcontainer.yml");
let workflow = read(&path);
assert!(
workflow.contains("name: Build + test in devcontainer"),
"{} must define a job named exactly `Build + test in devcontainer` — \
it is a required status check on develop. Verify with:\n \
gh api repos/denniyahh/devflow/rules/branches/develop",
path.display()
);
}
#[test]
fn ci_workflow_runs_the_pinned_devcontainer_image() {
let root = repo_root();
let workflow = read(&root.join(".github/workflows/ci.yml"));
let devcontainer = read(&root.join(".devcontainer/devcontainer.json"));
let image = devcontainer
.lines()
.map(str::trim)
.find(|l| l.starts_with("\"image\""))
.and_then(|l| l.split('"').nth(3))
.expect("read \"image\" from devcontainer.json")
.to_string();
assert!(
!image.contains(":latest") && image.contains(':'),
"devcontainer image must be pinned to an explicit tag, never a \
floating one. Found: {image:?}"
);
let container_lines = workflow
.lines()
.map(str::trim)
.filter(|l| l.starts_with("image:"))
.count();
assert!(
container_lines > 0,
"ci.yml must run its jobs in a `container:` so the OS and toolchain \
match the devcontainer"
);
for line in workflow
.lines()
.map(str::trim)
.filter(|l| l.starts_with("image:"))
{
let ci_image = line.trim_start_matches("image:").trim();
assert_eq!(
ci_image, image,
"ci.yml runs {ci_image:?} but devcontainer.json declares \
{image:?} — they must be identical or local checks stop \
predicting CI (scripts/assert-image-parity.sh enforces this at \
runtime too)."
);
}
}
#[test]
fn devflow_test_clippy_matches_ci_scope() {
let path = repo_root().join("crates/devflow-cli/src/commands.rs");
let src = read(&path);
let has_narrow_form = src.contains("\"cargo clippy -- -D warnings\"");
assert!(
!has_narrow_form,
"`devflow test` must not use the narrow `cargo clippy -- -D warnings` \
form — it does not compile test targets, making a local green weaker \
than a CI green (17-REVIEW.md WR-10). See {}",
path.display()
);
}