#![cfg(unix)]
use std::path::Path;
use std::process::Command;
use anodizer_core::test_helpers::test_sources::{
declared_under_test_cfg, is_test_only_cfg, is_test_source_path, test_sources,
};
use tempfile::TempDir;
const LIB_RS: &str = include_str!("fixtures/audit_scripts/lib.rs.txt");
const ATTR_GAP_RS: &str = include_str!("fixtures/audit_scripts/attr_gap.rs.txt");
const REGISTRY_RS: &str = include_str!("fixtures/audit_scripts/registry.rs.txt");
const INTEGRATION_RS: &str = include_str!("fixtures/audit_scripts/integration.rs.txt");
const TESTS_RS: &str = include_str!("fixtures/audit_scripts/tests.rs.txt");
const NAMED_TESTS_RS: &str = include_str!("fixtures/audit_scripts/named_tests.rs.txt");
fn fixture_tree() -> TempDir {
let dir = TempDir::new().expect("tempdir");
for (rel, body) in [
("crates/demo/src/lib.rs", LIB_RS),
("crates/demo/src/attr_gap.rs", ATTR_GAP_RS),
("crates/demo/tests/spawn.rs", INTEGRATION_RS),
("crates/demo/src/tests.rs", TESTS_RS),
("crates/demo/src/named_tests.rs", NAMED_TESTS_RS),
("crates/core/src/artifact/registry.rs", REGISTRY_RS),
] {
let path = dir.path().join(rel);
std::fs::create_dir_all(path.parent().unwrap()).expect("fixture dir");
std::fs::write(&path, body).expect("fixture file");
}
dir
}
const LIVE_HOST_CONSTS_RS: &str = include_str!("fixtures/audit_scripts/live_host_consts.rs.txt");
const LIVE_HOST_TESTS_RS: &str = include_str!("fixtures/audit_scripts/live_host_tests.rs.txt");
fn live_host_tree(with_consts: bool) -> TempDir {
let dir = TempDir::new().expect("tempdir");
let mut files = vec![("crates/demo/src/tests.rs", LIVE_HOST_TESTS_RS)];
if with_consts {
files.push(("crates/demo/src/hosts.rs", LIVE_HOST_CONSTS_RS));
}
for (rel, body) in files {
let path = dir.path().join(rel);
std::fs::create_dir_all(path.parent().unwrap()).expect("fixture dir");
std::fs::write(&path, body).expect("fixture file");
}
dir
}
#[test]
fn an_unregistered_default_host_constant_stops_the_audit() {
let dir = live_host_tree(true);
let (code, out) = run_audit("audit-test-live-host.sh", dir.path());
assert_eq!(code, 1, "{out}");
assert!(out.contains("DEMO_PUSH_SOURCE"), "{out}");
assert!(!out.contains("DEMO_LOCAL_SOURCE"), "{out}");
assert!(!out.contains("DEMO_DOC_SOURCE"), "{out}");
assert!(!out.contains("COMMUNITY_PUSH_SOURCE"), "{out}");
}
#[test]
fn a_test_reaching_a_registry_is_reported_and_a_bounded_one_is_not() {
let dir = live_host_tree(false);
let (code, out) = run_audit("audit-test-live-host.sh", dir.path());
let (forged_line, _) = at(LIVE_HOST_TESTS_RS, "fn a_forged_marker_in_a_string_literal");
let (unbounded_line, _) = at(LIVE_HOST_TESTS_RS, "fn nothing_bounds_the_endpoint_here");
assert_eq!(
hits(&out),
vec![
format!(
"crates/demo/src/tests.rs:{forged_line}: a_forged_marker_in_a_string_literal_bounds_nothing"
),
format!("crates/demo/src/tests.rs:{unbounded_line}: nothing_bounds_the_endpoint_here"),
],
"{out}"
);
assert_eq!(code, 1, "{out}");
}
const PROSE_VOICE_RS: &str = include_str!("fixtures/audit_scripts/prose_voice.rs.txt");
const PROSE_VOICE_SH: &str = include_str!("fixtures/audit_scripts/prose_voice.sh.txt");
const PROSE_VOICE_YML: &str = include_str!("fixtures/audit_scripts/prose_voice.yml.txt");
const PROSE_NAME_RS: &str = include_str!("fixtures/audit_scripts/prose_name.rs.txt");
fn prose_tree(files: &[(&str, &str)]) -> TempDir {
let dir = TempDir::new().expect("tempdir");
for (rel, body) in files {
let path = dir.path().join(rel);
std::fs::create_dir_all(path.parent().unwrap()).expect("fixture dir");
std::fs::write(&path, body).expect("fixture file");
}
dir
}
#[test]
fn first_person_in_a_comment_is_reported_and_code_or_quoted_prose_is_not() {
let dir = prose_tree(&[
("crates/demo/src/lib.rs", PROSE_VOICE_RS),
(".claude/scripts/demo.sh", PROSE_VOICE_SH),
(".github/workflows/demo.yml", PROSE_VOICE_YML),
]);
let (code, out) = run_audit("audit-prose.sh", dir.path());
let (mirrored, _) = at(PROSE_VOICE_RS, "/// We mirror");
let (narrated, _) = at(PROSE_VOICE_RS, "// Claude wrote");
assert_eq!(
hits(&out),
vec![
format!(
"crates/demo/src/lib.rs:{narrated}: // Claude wrote the loop; the next reader was not there for it."
),
format!("crates/demo/src/lib.rs:{mirrored}: /// We mirror the Cargo.toml branch here."),
],
"{out}"
);
let (sh_line, sh_text) = at(PROSE_VOICE_SH, "# We keep the runner");
let (yml_line, yml_text) = at(PROSE_VOICE_YML, "# Our release workflow");
assert!(
out.contains(&format!(".claude/scripts/demo.sh:{sh_line}: {sh_text}")),
"{out}"
);
assert!(
out.contains(&format!(
".github/workflows/demo.yml:{yml_line}: {yml_text}"
)),
"{out}"
);
assert!(
!out.contains("trailing comment"),
"a trailing `#` is not a whole-line comment: {out}"
);
assert_eq!(code, 1, "{out}");
}
#[test]
fn the_misspelled_tool_name_is_reported_and_the_config_alias_is_not() {
let dir = prose_tree(&[("crates/demo/src/lib.rs", PROSE_NAME_RS)]);
let (code, out) = run_audit("audit-prose.sh", dir.path());
let (misspelled, text) = at(PROSE_NAME_RS, "which is the misspelling");
assert_eq!(
hits(&out),
vec![format!("crates/demo/src/lib.rs:{misspelled}:{text}")],
"{out}"
);
let (aliased, _) = at(PROSE_NAME_RS, "serde(alias");
assert!(
!out.contains(&format!("crates/demo/src/lib.rs:{aliased}:")),
"the config alias spells the old name as DATA: {out}"
);
assert_eq!(code, 1, "{out}");
}
fn bash() -> Command {
let homebrew = ["/opt/homebrew/bin/bash", "/usr/local/bin/bash"]
.into_iter()
.find(|candidate| Path::new(candidate).is_file());
Command::new(homebrew.unwrap_or("bash"))
}
fn awk() -> Command {
let gawk = [
"/opt/homebrew/opt/gawk/libexec/gnubin/awk",
"/usr/local/opt/gawk/libexec/gnubin/awk",
]
.into_iter()
.find(|candidate| Path::new(candidate).is_file());
Command::new(gawk.unwrap_or("awk"))
}
fn run_audit(script: &str, root: &Path) -> (i32, String) {
run_audit_with_path(script, root, None)
}
fn run_audit_with_path(script: &str, root: &Path, shim: Option<&Path>) -> (i32, String) {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(".claude/scripts")
.join(script);
let mut command = bash();
command.arg(&path).arg(root);
if let Some(shim) = shim {
let inherited = std::env::var("PATH").unwrap_or_default();
command.env("PATH", format!("{}:{inherited}", shim.display()));
}
let out = command
.output()
.unwrap_or_else(|e| panic!("running {}: {e}", path.display()));
let mut text = String::from_utf8_lossy(&out.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&out.stderr));
(out.status.code().unwrap_or(-1), text)
}
fn hits(output: &str) -> Vec<String> {
let mut found: Vec<String> = output
.lines()
.filter(|l| l.starts_with("crates/") && l.contains(".rs:"))
.map(str::to_string)
.collect();
found.sort();
found
}
fn at(src: &str, needle: &str) -> (usize, String) {
let (index, line) = src
.lines()
.enumerate()
.find(|(_, l)| l.contains(needle))
.unwrap_or_else(|| panic!("the fixture no longer contains `{needle}`"));
(index + 1, line.trim().to_string())
}
#[test]
fn binary_name_audit_reads_production_after_an_inline_test_module() {
let dir = fixture_tree();
let (code, out) = run_audit("audit-binary-name.sh", dir.path());
let (line, text) = at(LIB_RS, "let production_binary");
assert_eq!(
hits(&out),
vec![format!(
"crates/demo/src/lib.rs:{line} (fn after_the_inline_module): {text}"
)],
"{out}"
);
assert_eq!(code, 1, "{out}");
}
#[test]
fn tag_family_audit_reads_production_after_an_inline_test_module() {
let dir = fixture_tree();
let (code, out) = run_audit("audit-tag-family.sh", dir.path());
let (line, text) = at(LIB_RS, "let _production_family");
let (forged_line, forged_text) = at(LIB_RS, "tag-family-ok: fake");
assert_eq!(
hits(&out),
vec![
format!("crates/demo/src/lib.rs:{line} (fn after_the_inline_module): {text}"),
format!("crates/demo/src/lib.rs:{forged_line} (fn forged_tag_family): {forged_text}"),
],
"{out}"
);
assert_eq!(code, 1, "{out}");
}
#[test]
fn test_isolation_audit_reports_test_code_only() {
let dir = fixture_tree();
let (code, out) = run_audit("audit-test-isolation.sh", dir.path());
let (inline_line, inline_text) = at(LIB_RS, "INLINE_ONLY");
let (sibling_line, sibling_text) = at(TESTS_RS, "SIBLING_FILE");
let (hand_line, hand_text) = at(TESTS_RS, "SIBLING_JUSTIFIED");
let (named_line, named_text) = at(NAMED_TESTS_RS, "NAMED_SIBLING_FILE");
let (file_line, file_text) = at(INTEGRATION_RS, "INTEGRATION_FILE");
let (env_forge_line, env_forge_text) = at(TESTS_RS, "env-ok: fake");
let (cwd_forge_line, cwd_forge_text) = at(TESTS_RS, "cwd-ok: fake");
assert_eq!(
hits(&out),
vec![
format!("crates/demo/src/lib.rs:{inline_line}: [env] {inline_text}"),
format!("crates/demo/src/named_tests.rs:{named_line}: [env] {named_text}"),
format!("crates/demo/src/tests.rs:{hand_line}: [env-guard] {hand_text}"),
format!("crates/demo/src/tests.rs:{env_forge_line}: [env] {env_forge_text}"),
format!("crates/demo/src/tests.rs:{cwd_forge_line}: [cwd] {cwd_forge_text}"),
format!("crates/demo/src/tests.rs:{sibling_line}: [env] {sibling_text}"),
format!("crates/demo/tests/spawn.rs:{file_line}: [env] {file_text}"),
],
"{out}"
);
assert_eq!(code, 1, "{out}");
}
#[test]
fn test_isolation_audit_demands_a_guard_behind_every_marked_env_mutation() {
let dir = fixture_tree();
let (_, out) = run_audit("audit-test-isolation.sh", dir.path());
let (hand_line, _) = at(TESTS_RS, "SIBLING_JUSTIFIED");
let (guarded_line, _) = at(TESTS_RS, "SIBLING_GUARDED");
let reported: Vec<String> = hits(&out)
.into_iter()
.filter(|h| h.contains("[env-guard]"))
.collect();
assert_eq!(
reported,
vec![format!(
"crates/demo/src/tests.rs:{hand_line}: [env-guard] unsafe {{ std::env::set_var(\"SIBLING_JUSTIFIED\", \"1\") }}; // env-ok: serialised by serial(sibling_env)"
)],
"only the hand-restored call is reported; line {guarded_line} binds a guard.\n{out}"
);
}
#[test]
fn spawn_retry_audit_reports_test_context_only() {
let dir = fixture_tree();
let (code, out) = run_audit("audit-test-spawn-retry.sh", dir.path());
let (inline_line, inline_text) = at(LIB_RS, r#"arg("status")"#);
let (file_line, file_text) = at(INTEGRATION_RS, r#"arg("init")"#);
let (forged_line, forged_text) = at(TESTS_RS, "spawn-retry-ok: fake");
assert_eq!(
hits(&out),
vec![
format!("crates/demo/src/lib.rs:{inline_line}: {inline_text}"),
format!("crates/demo/src/tests.rs:{forged_line}: {forged_text}"),
format!("crates/demo/tests/spawn.rs:{file_line}: {file_text}"),
],
"{out}"
);
assert_eq!(code, 1, "{out}");
}
#[test]
fn exec_writer_audit_reports_every_mode_spelling_in_test_context_only() {
let dir = fixture_tree();
let (code, out) = run_audit("audit-test-exec-writer.sh", dir.path());
let (set_mode_line, set_mode_text) = at(LIB_RS, "perms.set_mode");
let (from_mode_line, from_mode_text) = at(TESTS_RS, r#""sibling-stub""#);
let (builder_line, builder_text) = at(TESTS_RS, r#""opts-stub""#);
let (forged_line, forged_text) = at(TESTS_RS, "exec-writer-ok: fake");
assert_eq!(
hits(&out),
vec![
format!("crates/demo/src/lib.rs:{set_mode_line}: {set_mode_text}"),
format!("crates/demo/src/tests.rs:{from_mode_line}: {from_mode_text}"),
format!("crates/demo/src/tests.rs:{builder_line}: {builder_text}"),
format!("crates/demo/src/tests.rs:{forged_line}: {forged_text}"),
],
"{out}"
);
assert_eq!(code, 1, "{out}");
}
#[test]
fn log_status_audit_reads_its_marker_from_the_comment_half() {
let dir = fixture_tree();
let (code, out) = run_audit("audit-log-status.sh", dir.path());
let (forged_line, forged_text) = at(LIB_RS, "status-ok: fake");
assert_eq!(
hits(&out),
vec![format!(
"crates/demo/src/lib.rs:{forged_line}: {forged_text}"
)],
"{out}"
);
assert_eq!(code, 1, "{out}");
}
#[test]
fn repo_identity_audit_reads_its_markers_from_the_comment_half() {
let dir = fixture_tree();
let (code, out) = run_audit("audit-repo-identity.sh", dir.path());
let (slug_line, slug_text) = at(LIB_RS, "slug-ok: fake");
let (token_line, token_text) = at(LIB_RS, "token-ok: fake");
assert_eq!(
hits(&out),
vec![
format!("crates/demo/src/lib.rs:{slug_line}: {slug_text}"),
format!("crates/demo/src/lib.rs:{token_line}: {token_text}"),
],
"{out}"
);
assert_eq!(code, 1, "{out}");
}
#[test]
fn a_collection_whose_grep_fails_stops_the_audit() {
for status in [2, 127] {
let dir = fixture_tree();
let shim = dir.path().join("shim");
std::fs::create_dir_all(&shim).expect("shim dir");
anodizer_core::test_helpers::fake_tool::write_executable_script(
&shim.join("grep"),
&format!("#!/bin/sh\nprintf 'grep: unusable\\n' >&2\nexit {status}\n"),
);
let (code, out) = run_audit_with_path("audit-log-status.sh", dir.path(), Some(&shim));
assert_eq!(code, 2, "grep exiting {status} must stop the audit: {out}");
assert!(
out.contains(&format!(
"file collection exited {status}; the scan did not run."
)),
"{out}"
);
}
}
fn run_collector(dir: &Path, body: &str, stdin: &str) -> (i32, String) {
use std::io::Write;
let lib = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(".claude/scripts/lib");
let script = format!(
"set -euo pipefail\nsource {}/require-bash.sh\nsource {}/scan.sh\n{body}\n",
lib.display(),
lib.display()
);
let mut child = bash()
.arg("-c")
.arg(&script)
.current_dir(dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("spawning bash");
child
.stdin
.take()
.expect("stdin")
.write_all(stdin.as_bytes())
.expect("feeding stdin");
let out = child.wait_with_output().expect("collector output");
let mut text = String::from_utf8_lossy(&out.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&out.stderr));
(out.status.code().unwrap_or(-1), text)
}
#[test]
fn the_collector_reads_a_detached_option_argument_as_grep_does() {
let dir = fixture_tree();
let (code, out) = run_collector(
dir.path(),
"collect_files X -rl --include '*.rs' -- 'set_var' crates\nprintf 'n=%d\\n' \"${#X[@]}\"",
"",
);
assert_eq!(code, 0, "{out}");
assert!(
out.contains("n=4"),
"a detached option argument must reach grep as written: {out}"
);
}
#[test]
fn the_collector_refuses_a_call_without_the_separator() {
let dir = fixture_tree();
let (code, out) = run_collector(
dir.path(),
"collect_files X -rl 'set_var' crates\nprintf 'reached\\n'",
"",
);
assert_eq!(code, 2, "{out}");
assert!(out.contains("collect_files called without --"), "{out}");
assert!(!out.contains("reached"), "the call must not return: {out}");
}
#[test]
fn the_collector_never_reads_the_callers_stdin() {
let dir = fixture_tree();
let (code, out) = run_collector(
dir.path(),
"collect_files X -- 'set_var'\nprintf 'n=%d\\n' \"${#X[@]}\"",
"set_var from stdin\n",
);
assert_eq!(code, 0, "{out}");
assert!(out.contains("n=0"), "stdin is not a scan root: {out}");
}
#[test]
fn a_collection_whose_every_named_root_is_absent_returns_empty() {
let dir = fixture_tree();
let (code, out) = run_collector(
dir.path(),
"collect_files X -r -- 'set_var' crates/*/absent\nprintf 'n=%d\\n' \"${#X[@]}\"",
"set_var from stdin\n",
);
assert_eq!(code, 0, "{out}");
assert!(
out.contains("n=0"),
"an absent named root scans nothing: {out}"
);
}
#[test]
fn a_rootless_recursive_collection_is_refused_naming_the_flag() {
let dir = fixture_tree();
for flag in ["-r", "-R", "-rl", "--recursive", "--dereference-recursive"] {
let (code, out) = run_collector(
dir.path(),
&format!("collect_files X {flag} -- 'set_var'\nprintf 'n=%d\\n' \"${{#X[@]}}\""),
"",
);
assert_eq!(code, 2, "{flag} with no root must not run: {out}");
assert!(
out.contains(&format!("called with {flag} and no root")),
"the refusal must name {flag}: {out}"
);
}
}
#[test]
fn a_rootless_non_recursive_collection_is_still_allowed() {
let dir = fixture_tree();
let (code, out) = run_collector(
dir.path(),
"collect_files X --include='*.rs' -- 'set_var'\nprintf 'n=%d\\n' \"${#X[@]}\"",
"set_var from stdin\n",
);
assert_eq!(code, 0, "{out}");
assert!(out.contains("n=0"), "{out}");
}
#[test]
fn an_absent_optional_scan_root_is_not_a_failed_scan() {
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join("crates/demo/src/tests.rs");
std::fs::create_dir_all(path.parent().expect("parent")).expect("fixture dir");
std::fs::write(&path, TESTS_RS).expect("fixture file");
let (code, out) = run_audit("audit-test-isolation.sh", dir.path());
assert_ne!(
code, 2,
"an absent crates/*/tests is not a scan failure: {out}"
);
assert!(!out.contains("the scan did not run"), "{out}");
assert!(
!hits(&out).is_empty(),
"crates/*/src must still be scanned: {out}"
);
}
#[test]
fn a_tree_with_no_serial_attribute_reports_a_clean_scan() {
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join("crates/demo/src/lib.rs");
std::fs::create_dir_all(path.parent().expect("parent")).expect("fixture dir");
std::fs::write(&path, "pub fn f() -> u8 { 1 }\n").expect("fixture file");
let (code, out) = run_audit("audit-serial-groups.sh", dir.path());
assert_eq!(code, 0, "a tree with no #[serial] scans clean: {out}");
assert!(
out.contains("all 0 #[serial] attributes name a group (0 distinct groups)"),
"{out}"
);
}
#[test]
fn every_audit_script_sources_the_bash_floor() {
let mut walked = 0usize;
let mut missing = Vec::new();
let mut restated = Vec::new();
for script in audit_scripts() {
let name = script
.file_name()
.expect("file name")
.to_string_lossy()
.into_owned();
let body = std::fs::read_to_string(&script).expect("script body");
walked += 1;
if !body.contains("source \"$LIB_DIR/require-bash.sh\"") {
missing.push(name.clone());
}
if body.contains("BASH_VERSINFO") || body.contains("bash --version") {
restated.push(name);
}
}
assert!(
missing.is_empty(),
"every audit script sources lib/require-bash.sh; these do not: {missing:?}"
);
assert!(
restated.is_empty(),
"the bash floor is asserted once, in lib/require-bash.sh; these restate it: {restated:?}"
);
assert!(
walked >= 14,
"expected every audit script to be walked, found {walked}"
);
}
#[test]
fn a_scanner_that_cannot_load_its_awk_library_fails_loudly() {
let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let dir = TempDir::new().expect("temp dir");
let lib = dir.path().join("lib");
std::fs::create_dir(&lib).expect("lib dir");
for shared in ["require-bash.sh", "scan.sh"] {
std::fs::copy(
repo.join(".claude/scripts/lib").join(shared),
lib.join(shared),
)
.unwrap_or_else(|e| panic!("copy lib/{shared}: {e}"));
}
let mut checked = 0usize;
for entry in std::fs::read_dir(repo.join(".claude/scripts")).expect("scripts dir") {
let src = entry.expect("script entry").path();
let name = src
.file_name()
.expect("file name")
.to_string_lossy()
.into_owned();
if !name.starts_with("audit-") || !name.ends_with(".sh") {
continue;
}
if !std::fs::read_to_string(&src)
.expect("script body")
.contains("-f \"$LIB_DIR/")
{
continue;
}
checked += 1;
let copy = dir.path().join(&name);
std::fs::copy(&src, ©).expect("copy the script beside an empty lib dir");
let out = bash()
.arg(©)
.arg(&repo)
.output()
.unwrap_or_else(|e| panic!("running {name}: {e}"));
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(
out.status.code(),
Some(2),
"{name} must exit 2 (\"the scan did not run\"), never 0 or the \
violations-found 1.\n{stdout}{stderr}"
);
assert!(
stderr.contains(".awk"),
"{name} must leave the awk error visible, got: {stderr}"
);
assert!(
stderr.contains("the scan did not run"),
"{name} must say the scan did not run, got: {stderr}"
);
}
assert!(
checked >= 8,
"expected every awk-library scanner to be driven, found {checked}"
);
}
#[test]
fn every_named_test_source_is_declared_cfg_test() {
let mut undeclared = Vec::new();
let mut seen = 0usize;
for src in anodizer_core::test_helpers::test_sources::workspace_crate_dirs()
.into_iter()
.map(|krate| krate.join("src"))
.filter(|src| src.is_dir())
{
for file in test_sources(&src) {
seen += 1;
if let Err(why) = declared_under_test_cfg(&file) {
undeclared.push(why);
}
}
}
assert!(seen > 0, "no name-matched test file in the workspace");
assert!(
undeclared.is_empty(),
"name-matched test files not declared `#[cfg(test)] mod …;` by their parent: {undeclared:?}"
);
}
const AGREEMENT_PATHS: &[&str] = &[
"crates/demo/src/tests.rs",
"crates/demo/src/foo_tests.rs",
"crates/demo/src/_tests.rs",
"crates/demo/src/mytests.rs",
"crates/demo/src/Foo_tests.rs",
"crates/demo/src/tests.rs.bak",
"crates/demo/src/lib.rs",
"crates/demo/src/process/tests/mod.rs",
"crates/demo/tests/integration.rs",
"crates/demo/tests/nested/case.rs",
"crates/tests/tests/case.rs",
"src/tests/helper.rs",
"tests.rs",
"foo_tests.rs",
];
#[test]
fn test_source_predicate_agrees_with_the_awk_lexer() {
let lib = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(".claude/scripts/lib");
let dir = TempDir::new().expect("temp dir");
let driver = dir.path().join("driver.awk");
std::fs::write(&driver, "{ print (is_test_file($0) ? \"1\" : \"0\") }\n").expect("driver");
let list = dir.path().join("paths.txt");
std::fs::write(&list, format!("{}\n", AGREEMENT_PATHS.join("\n"))).expect("path list");
let out = awk()
.arg("-f")
.arg(lib.join("rust-lex.awk"))
.arg("-f")
.arg(lib.join("test-regions.awk"))
.arg("-f")
.arg(&driver)
.arg(&list)
.output()
.expect("running awk");
assert!(
out.status.success(),
"awk failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let awk: Vec<&str> = std::str::from_utf8(&out.stdout)
.expect("awk output is utf-8")
.lines()
.collect();
assert_eq!(
awk.len(),
AGREEMENT_PATHS.len(),
"awk answered {} of {} paths",
awk.len(),
AGREEMENT_PATHS.len()
);
let disagreements: Vec<String> = AGREEMENT_PATHS
.iter()
.zip(&awk)
.map(|(path, verdict)| (path, *verdict == "1", is_test_source_path(Path::new(path))))
.filter(|(_, awk_says, rust_says)| awk_says != rust_says)
.map(|(path, awk_says, rust_says)| format!("{path}: awk={awk_says} rust={rust_says}"))
.collect();
assert!(
disagreements.is_empty(),
"is_test_file and is_test_source_path must agree: {disagreements:?}"
);
}
const AGREEMENT_CFG_LINES: &[&str] = &[
"#[cfg(test)]",
" #[cfg(test)] mod tests;",
"#[cfg(all(test, unix))]",
"#[cfg(all(test, not(windows)))]",
"#[cfg(all(feature = \"x\", test))]",
"#[cfg(all(all(test), unix))]",
"#[cfg(any(test, feature = \"x\"))]",
"#[cfg(all(any(test, unix), windows))]",
"#[cfg(not(test))]",
"#[cfg(not(all(test, unix)))]",
"#[cfg(feature = \"testing\")]",
"#[cfg(unix)]",
"// gated by #[cfg(test)] somewhere else",
"mod tests;",
];
#[test]
fn test_only_cfg_predicate_agrees_with_the_awk_lexer() {
let lib = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(".claude/scripts/lib");
let dir = TempDir::new().expect("temp dir");
let driver = dir.path().join("driver.awk");
std::fs::write(
&driver,
"{ print (is_test_only_cfg($0) ? \"1\" : \"0\") }\n",
)
.expect("driver");
let list = dir.path().join("cfg-lines.txt");
std::fs::write(&list, format!("{}\n", AGREEMENT_CFG_LINES.join("\n"))).expect("cfg lines");
let out = awk()
.arg("-f")
.arg(lib.join("rust-lex.awk"))
.arg("-f")
.arg(&driver)
.arg(&list)
.output()
.expect("running awk");
assert!(
out.status.success(),
"awk failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let awk: Vec<&str> = std::str::from_utf8(&out.stdout)
.expect("awk output is utf-8")
.lines()
.collect();
assert_eq!(
awk.len(),
AGREEMENT_CFG_LINES.len(),
"awk answered {} of {} predicate lines",
awk.len(),
AGREEMENT_CFG_LINES.len()
);
let disagreements: Vec<String> = AGREEMENT_CFG_LINES
.iter()
.zip(&awk)
.map(|(line, verdict)| (line, *verdict == "1", is_test_only_cfg(line)))
.filter(|(_, awk_says, rust_says)| awk_says != rust_says)
.map(|(line, awk_says, rust_says)| format!("{line}: awk={awk_says} rust={rust_says}"))
.collect();
assert!(
disagreements.is_empty(),
"the two `is_test_only_cfg` spellings must agree: {disagreements:?}"
);
}
fn strip_trailing_comment(line: &str) -> &str {
match line.split_once(" #") {
Some((before, _)) => before.trim_end(),
None => line.trim_end(),
}
}
fn is_top_level_key(line: &str) -> bool {
if !line.starts_with(" ") || line.starts_with(" ") {
return false;
}
if !line
.chars()
.nth(2)
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
{
return false;
}
strip_trailing_comment(line).ends_with(':')
}
fn taskfile_block_opt(taskfile: &str, target: &str) -> Option<String> {
let key = format!(" {target}:");
let mut lines = taskfile
.lines()
.skip_while(|l| strip_trailing_comment(l) != key);
let first = lines.next()?;
let mut block = String::from(first);
for line in lines {
if is_top_level_key(line) {
break;
}
block.push('\n');
block.push_str(line);
}
Some(block)
}
fn taskfile_block(taskfile: &str, target: &str) -> String {
taskfile_block_opt(taskfile, target)
.unwrap_or_else(|| panic!("no ` {target}:` in Taskfile.yml"))
}
fn child_tasks(block: &str) -> Vec<String> {
let mut children = Vec::new();
let mut in_deps = false;
for line in block.lines() {
let code = strip_trailing_comment(line);
let trimmed = code.trim();
if code.starts_with(" ") && !code.starts_with(" ") && trimmed.contains(':') {
in_deps = false;
if let Some(rest) = trimmed.strip_prefix("deps:") {
let rest = rest.trim();
match rest.strip_prefix('[').and_then(|r| r.strip_suffix(']')) {
Some(inline) => children.extend(
inline
.split(',')
.map(|n| n.trim().to_string())
.filter(|n| !n.is_empty()),
),
None => in_deps = rest.is_empty(),
}
continue;
}
}
if let Some(item) = trimmed.strip_prefix("- ") {
let item = item.trim();
match item.strip_prefix("task: ") {
Some(name) => children.push(name.trim().to_string()),
None if in_deps => children.push(item.to_string()),
None => {}
}
}
}
children
}
fn reachable_tasks(taskfile: &str, roots: &[&str]) -> Vec<String> {
let mut seen: Vec<String> = Vec::new();
let mut queue: Vec<(String, Option<String>)> =
roots.iter().map(|r| ((*r).to_string(), None)).collect();
while let Some((name, parent)) = queue.pop() {
if seen.contains(&name) {
continue;
}
let block = match (taskfile_block_opt(taskfile, &name), &parent) {
(Some(block), _) => block,
(None, Some(parent)) => {
panic!("`task {parent}` has a dep the walk cannot resolve: `{name}`")
}
(None, None) => taskfile_block(taskfile, &name),
};
for child in child_tasks(&block) {
queue.push((child, Some(name.clone())));
}
seen.push(name);
}
seen.sort();
seen
}
fn runs_task(block: &str, target: &str) -> bool {
child_tasks(block).iter().any(|child| child == target)
}
#[test]
fn a_taskfile_key_with_a_trailing_comment_ends_the_previous_block() {
let snippet = "tasks:\n first:\n cmds:\n - echo one\n second: # trailing\n cmds:\n - echo two\n";
let first = taskfile_block(snippet, "first");
assert!(
first.contains("echo one") && !first.contains("echo two"),
"the commented key must end the first block, got:\n{first}"
);
let second = taskfile_block(snippet, "second");
assert!(
second.contains("echo two"),
"a commented key must still open its own block, got:\n{second}"
);
}
#[test]
fn a_multibyte_char_at_the_key_column_is_not_a_key() {
assert!(!is_top_level_key(" — a dashed comment line"));
assert!(is_top_level_key(" doc:"));
assert!(is_top_level_key(" doc: # rustdoc"));
assert!(!is_top_level_key(" - task: doc"));
}
#[test]
fn the_task_walk_visits_a_diamond_once() {
let snippet = "tasks:\n a:\n cmds:\n - task: b\n - task: c\n b:\n deps: [d]\n c:\n cmds:\n - task: d\n d:\n cmds:\n - echo leaf\n";
let walked = reachable_tasks(snippet, &["a"]);
assert_eq!(
walked,
vec!["a", "b", "c", "d"],
"the shared child `d` is walked once, not once per parent"
);
}
#[test]
fn the_task_walk_terminates_on_a_cycle() {
let snippet = "tasks:\n a:\n cmds:\n - task: b\n b:\n cmds:\n - task: a\n";
assert_eq!(reachable_tasks(snippet, &["a"]), vec!["a", "b"]);
}
#[test]
fn an_unparsed_dep_names_its_parent_and_its_raw_text() {
let snippet = "tasks:\n t:\n deps: [{task: x}]\n cmds:\n - echo hi\n";
let panic = std::panic::catch_unwind(|| reachable_tasks(snippet, &["t"]))
.expect_err("an unparsed dep must fail the walk");
let message = panic
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| panic.downcast_ref::<&str>().copied())
.unwrap_or_default()
.to_string();
assert_eq!(
message, "`task t` has a dep the walk cannot resolve: `{task: x}`",
"the failure must name the walk's own gap, not a missing target"
);
}
#[test]
fn a_dep_is_an_edge_in_both_spellings() {
let inline = "tasks:\n t:\n deps: [one, two]\n cmds:\n - echo hi\n";
assert_eq!(child_tasks(&taskfile_block(inline, "t")), ["one", "two"]);
let block = "tasks:\n t:\n deps:\n - one\n - task: two\n cmds:\n - echo hi\n - task: three\n";
assert_eq!(
child_tasks(&taskfile_block(block, "t")),
["one", "two", "three"]
);
let cmds_only = "tasks:\n t:\n cmds:\n - cargo build\n";
assert!(child_tasks(&taskfile_block(cmds_only, "t")).is_empty());
let sibling = "tasks:\n t:\n cmds:\n - task: docs:validate-readme\n";
let block = taskfile_block(sibling, "t");
assert!(!runs_task(&block, "doc"));
assert!(runs_task(&block, "docs:validate-readme"));
}
#[test]
fn rustdoc_gate_is_wired_into_gate_and_ci_never_commit() {
let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let taskfile = std::fs::read_to_string(repo.join("Taskfile.yml")).expect("Taskfile.yml");
let doc = taskfile_block(&taskfile, "doc");
assert!(
doc.contains("_check:mem-headroom"),
"the `doc:` target must refuse to start without memory headroom, got:\n{doc}"
);
assert!(
doc.contains("cargo doc --workspace --no-deps --document-private-items"),
"the `doc:` target must run the workspace rustdoc, got:\n{doc}"
);
let headroom = taskfile_block(&taskfile, "_check:mem-headroom");
let legs: Vec<&str> = headroom
.lines()
.map(str::trim)
.filter(|l| l.starts_with("- sh:"))
.collect();
assert_eq!(
legs.len(),
2,
"the memory precondition is a digits guard followed by a floor test, got:\n{headroom}"
);
assert!(
legs[0].contains("^[0-9]+$"),
"the first leg must reject a non-numeric MemAvailable reading, got: {}",
legs[0]
);
assert!(
legs[1].contains("-ge 8388608"),
"the second leg must test the 8 GB floor, got: {}",
legs[1]
);
for leg in &legs {
assert!(
leg.contains("uname"),
"every leg short-circuits off Linux, which alone publishes /proc/meminfo, got: {leg}"
);
}
let gate = taskfile_block(&taskfile, "gate");
assert!(
runs_task(&gate, "doc"),
"`task gate` must run the rustdoc gate, got:\n{gate}"
);
let commit_path = reachable_tasks(&taskfile, &["commit"]);
for name in &commit_path {
let block = taskfile_block(&taskfile, name);
assert!(
!runs_task(&block, "doc"),
"`task {name}` is reachable from `task commit`, so it must not chain the rustdoc gate, got:\n{block}"
);
}
assert_eq!(
commit_path.len(),
28,
"the set of tasks `task commit` reaches changed; re-check that none of them runs the rustdoc gate and update the count: {commit_path:?}"
);
let ci = std::fs::read_to_string(repo.join(".github/workflows/ci.yml")).expect("ci.yml");
assert!(
ci.contains("\n rustdoc:\n"),
"ci.yml must carry a `rustdoc` job"
);
assert!(
ci.contains("run: task doc"),
"ci.yml's rustdoc job must run `task doc`"
);
let mirror = std::fs::read_to_string(repo.join(".claude/scripts/audit-gate-mirror.sh"))
.expect("audit-gate-mirror.sh");
assert!(
mirror.contains(r#"[rustdoc]="doc""#),
"the gate mirror must map ci.yml's rustdoc job to the local `doc` target"
);
}
fn low_memory_awk_shim() -> tempfile::TempDir {
let real = Command::new("sh")
.args(["-c", "command -v awk"])
.output()
.expect("locating awk");
let real = String::from_utf8_lossy(&real.stdout).trim().to_string();
assert!(!real.is_empty(), "awk must be on PATH for this pin");
let dir = tempfile::tempdir().expect("shim dir");
let shim = dir.path().join("awk");
anodizer_core::test_helpers::fake_tool::write_executable_script(
&shim,
&format!(
"#!/usr/bin/env bash\n\
for a in \"$@\"; do\n\
case \"$a\" in *MemAvailable*) echo 1024; exit 0 ;; esac\n\
done\n\
exec {real} \"$@\"\n"
),
);
dir
}
#[test]
fn the_gate_mirror_audit_walks_the_graph_on_a_host_under_the_memory_floor() {
if Command::new("sh")
.args([
"-c",
"command -v task >/dev/null && command -v yq >/dev/null",
])
.status()
.map(|s| !s.success())
.unwrap_or(true)
{
eprintln!(
"SKIP the_gate_mirror_audit_walks_the_graph_on_a_host_under_the_memory_floor: task or yq missing"
);
return;
}
let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let shim = low_memory_awk_shim();
let (code, out) = run_audit_with_path("audit-gate-mirror.sh", &repo, Some(shim.path()));
assert_eq!(
code, 0,
"a structural walk of the task graph spawns no rustdoc, so the memory \
floor must not decide it; got:\n{out}"
);
}
#[test]
#[cfg(target_os = "linux")]
fn the_memory_floor_still_refuses_a_gate_run_on_a_host_under_it() {
if Command::new("sh")
.args(["-c", "command -v task >/dev/null"])
.status()
.map(|s| !s.success())
.unwrap_or(true)
{
eprintln!(
"SKIP the_memory_floor_still_refuses_a_gate_run_on_a_host_under_it: task missing"
);
return;
}
let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let shim = low_memory_awk_shim();
let inherited = std::env::var("PATH").unwrap_or_default();
let out = Command::new("task")
.args(["-n", "gate"])
.current_dir(&repo)
.env("PATH", format!("{}:{inherited}", shim.path().display()))
.env_remove("ANODIZER_STRUCTURAL_WALK")
.output()
.expect("task -n gate");
let text = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
!out.status.success() && text.contains("8388608 kB (8 GB) floor"),
"without the walk's own bypass the floor must still refuse; got:\n{text}"
);
}
fn audit_scripts() -> Vec<std::path::PathBuf> {
let dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(".claude/scripts");
let mut found: Vec<std::path::PathBuf> = std::fs::read_dir(&dir)
.expect("scripts dir")
.map(|e| e.expect("script entry").path())
.filter(|p| {
let name = p.file_name().unwrap_or_default().to_string_lossy();
name.starts_with("audit-") && name.ends_with(".sh")
})
.collect();
found.sort();
found
}
const COMMAND_WRAPPERS: &[&str] = &[
"command", "exec", "env", "xargs", "nice", "time", "sudo", "busybox", "toybox",
];
const RESERVED_WORDS: &[&str] = &[
"if", "then", "elif", "else", "while", "until", "do", "coproc", "{", "}", "!",
];
const PROBES: &[&str] = &["type", "hash", "which"];
const EXEC_OPTIONS: &[&str] = &["-exec", "-execdir", "-ok", "-okdir"];
const AWK_NAMES: &[&str] = &["awk", "gawk", "mawk", "nawk"];
fn basename(word: &str) -> &str {
word.rsplit('/').next().unwrap_or(word)
}
fn is_assignment(word: &str) -> bool {
match word.find('=') {
None | Some(0) => false,
Some(split) => {
let name = &word[..split];
name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
&& name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
}
}
fn redirection(word: &str) -> Option<bool> {
let rest = word.trim_start_matches(|c: char| c.is_ascii_digit());
let rest = rest.strip_prefix('&').unwrap_or(rest);
let target = rest.trim_start_matches(['<', '>', '&']);
(target.len() < rest.len()).then_some(!target.is_empty())
}
fn command_word(segment: &str) -> Option<(&str, &str)> {
let words: Vec<&str> = segment.split_whitespace().collect();
let mut i = 0;
while i < words.len() {
let word = words[i].trim_start_matches('\\');
let base = basename(word);
if base == "command" && matches!(words.get(i + 1), Some(&"-v" | &"-V")) {
return None;
}
if PROBES.contains(&base) {
return None;
}
if let Some(target_attached) = redirection(word) {
i += if target_attached { 1 } else { 2 };
continue;
}
if base == "case" {
i += words[i..]
.iter()
.position(|w| *w == "in")
.map_or(words.len(), |p| p + 1);
continue;
}
if word.is_empty()
|| word.starts_with('-')
|| word.chars().all(|c| c.is_ascii_digit())
|| RESERVED_WORDS.contains(&word)
|| is_assignment(word)
|| COMMAND_WRAPPERS.contains(&base)
|| word.ends_with(')')
{
i += 1;
continue;
}
return Some((word, base));
}
None
}
struct Segment {
line: usize,
text: String,
substituted: bool,
after_or: bool,
}
fn forbidden_word(segment: &Segment) -> Option<String> {
let mut rest = segment.text.as_str();
let in_find = command_word(rest).is_some_and(|(_, base)| base == "find");
loop {
if let Some(why) = forbidden_at(rest, segment) {
return Some(why);
}
if !in_find {
return None;
}
let mut words = rest.split_whitespace();
let opened = words.find(|w| EXEC_OPTIONS.contains(w))?;
let offset = rest.find(opened)? + opened.len();
rest = &rest[offset..];
}
}
fn forbidden_at(text: &str, segment: &Segment) -> Option<String> {
let (word, base) = command_word(text)?;
if word.starts_with('$') && word.len() > 1 {
return Some(format!("a variable in command position (`{word}`)"));
}
if base == "eval" {
return Some("eval".to_string());
}
if AWK_NAMES.contains(&base) {
return Some(format!("awk invoked directly (`{word}`)"));
}
if segment.after_or && base == "true" {
return Some("a swallowed failure (`|| true`)".to_string());
}
if segment.substituted && base == "grep" {
return Some(format!("a collection outside collect_files (`{word}`)"));
}
None
}
fn forbidden_command(body: &str) -> Option<String> {
command_segments(body).iter().find_map(forbidden_word)
}
#[derive(Default)]
struct SegmentSink {
segments: Vec<Segment>,
current: String,
test_expr: bool,
after_or: bool,
}
impl SegmentSink {
fn take(&mut self, line: usize, substituted: bool, next_after_or: bool) {
let text = std::mem::take(&mut self.current);
if !self.test_expr {
self.segments.push(Segment {
line,
text,
substituted,
after_or: self.after_or,
});
}
self.test_expr = false;
self.after_or = next_after_or;
}
}
fn command_segments(body: &str) -> Vec<Segment> {
#[derive(PartialEq)]
enum Quote {
Bare,
Single,
Double,
}
let mut sink = SegmentSink::default();
let mut stack = vec![Quote::Bare];
let mut substituted = vec![false];
let mut heredoc: Option<String> = None;
let mut continued;
let mut in_test_expr = false;
for (index, line) in body.lines().enumerate() {
if let Some(delimiter) = &heredoc {
if line.trim() == delimiter.as_str() {
heredoc = None;
}
continue;
}
continued = false;
let chars: Vec<char> = line.chars().collect();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
let inside = substituted.iter().any(|&s| s);
if stack.last() == Some(&Quote::Single) {
if c == '\'' {
stack.pop();
substituted.pop();
} else {
sink.current.push(c);
}
i += 1;
continue;
}
sink.test_expr |= in_test_expr;
let double = stack.last() == Some(&Quote::Double);
if c == '\\' {
match chars.get(i + 1) {
Some(&escaped) => sink.current.push(escaped),
None => continued = true,
}
i += 2;
} else if c == '"' {
if double {
stack.pop();
substituted.pop();
} else {
stack.push(Quote::Double);
substituted.push(false);
}
i += 1;
} else if c == '$' && chars.get(i + 1) == Some(&'(') {
stack.push(Quote::Bare);
substituted.push(true);
sink.take(index + 1, inside, false);
i += 2;
} else if !double && matches!(c, '<' | '>') && chars.get(i + 1) == Some(&'(') {
stack.push(Quote::Bare);
substituted.push(true);
sink.take(index + 1, inside, false);
i += 2;
} else if double {
sink.current.push(c);
i += 1;
} else if c == '\'' {
stack.push(Quote::Single);
substituted.push(false);
i += 1;
} else if c == '#' && (i == 0 || chars[i - 1].is_whitespace()) {
break;
} else if c == '(' && chars.get(i + 1) == Some(&'(') {
i = arithmetic_end(&chars, i);
sink.current.clear();
} else if c == '(' && sink.current.trim().is_empty() {
stack.push(Quote::Bare);
substituted.push(false);
sink.take(index + 1, inside, false);
i += 1;
} else if c == ')' && stack.len() > 1 {
stack.pop();
substituted.pop();
sink.take(index + 1, inside, false);
i += 1;
} else if matches!(c, '|' | '&') && chars.get(i + 1) == Some(&c) {
sink.take(index + 1, inside, c == '|');
i += 2;
} else if matches!(c, '|' | ';' | '&' | '`') {
sink.take(index + 1, inside, false);
i += 1;
} else {
sink.current.push(c);
if c == '[' && sink.current.ends_with("[[") {
in_test_expr = true;
} else if c == ']' && sink.current.ends_with("]]") {
in_test_expr = false;
}
i += 1;
}
}
if !continued && stack.len() == 1 && stack[0] == Quote::Bare {
let inside = substituted.iter().any(|&s| s);
let carry = sink.current.trim().is_empty() && sink.after_or;
sink.take(index + 1, inside, carry);
in_test_expr = false;
}
heredoc = heredoc_delimiter(line).map(str::to_string);
}
sink.segments
}
fn arithmetic_end(chars: &[char], open: usize) -> usize {
let mut i = open + 2;
while i + 1 < chars.len() {
if chars[i] == ')' && chars[i + 1] == ')' {
return i + 2;
}
i += 1;
}
chars.len()
}
fn heredoc_delimiter(line: &str) -> Option<&str> {
let mut rest = line;
while let Some(start) = rest.find("<<") {
let after = &rest[start + 2..];
if let Some(here_string) = after.strip_prefix('<') {
rest = here_string;
continue;
}
let after = after.strip_prefix('-').unwrap_or(after).trim_start();
let (quote, word) = match after.chars().next() {
Some(q @ ('\'' | '"')) => (Some(q), &after[1..]),
_ => (None, after),
};
let end = match quote {
Some(q) => word.find(q),
None => Some(
word.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
.unwrap_or(word.len()),
),
};
match end {
Some(0) | None => rest = after,
Some(end) => return Some(&word[..end]),
}
}
None
}
#[test]
fn every_awk_invocation_goes_through_the_shared_runner() {
let mut forbidden = Vec::new();
let mut scanners = 0usize;
for script in audit_scripts() {
let name = script.file_name().expect("file name").to_string_lossy();
let body = std::fs::read_to_string(&script).expect("script body");
for segment in command_segments(&body) {
if let Some(why) = forbidden_word(&segment) {
forbidden.push(format!(
"{name}:{}: {why}: {}",
segment.line,
segment.text.trim()
));
}
}
if body.contains("run_scanner ") || body.contains("collect_files ") {
scanners += 1;
assert!(
body.contains("source \"$LIB_DIR/scan.sh\""),
"{name} calls run_scanner/collect_files without sourcing lib/scan.sh"
);
}
for (index, line) in logical_lines(&body) {
if collect_without_separator(&line) {
forbidden.push(format!(
"{name}:{index}: collect_files without a `--` separator: {}",
line.trim()
));
}
}
}
assert!(
forbidden.is_empty(),
"every awk program runs through lib/scan.sh's run_scanner and every collection through \
collect_files; these do not: {forbidden:#?}"
);
assert!(
scanners >= 15,
"expected every scanning script to be walked, found {scanners}"
);
}
fn collect_without_separator(line: &str) -> bool {
line.contains("collect_files ") && !line.contains(" -- ")
}
#[test]
fn the_separator_rule_reads_both_spellings() {
for line in [
"collect_files FILES -rlE 'x' crates --include='*.rs'",
"collect_files X -r 'set_var' crates",
] {
assert!(collect_without_separator(line), "must be refused: {line}");
}
for line in [
"collect_files FILES -rlE --include='*.rs' -- 'x' crates",
"collect_files X -r -- 'set_var' crates/*/src",
"run_scanner violations -f \"$LIB_DIR/rust-lex.awk\" -f - \"${FILES[@]}\"",
] {
assert!(!collect_without_separator(line), "must be allowed: {line}");
}
}
fn logical_lines(body: &str) -> Vec<(usize, String)> {
let mut joined: Vec<(usize, String)> = Vec::new();
let mut pending: Option<(usize, String)> = None;
for (index, raw) in body.lines().enumerate() {
let continued = raw.ends_with('\\');
let piece = raw.strip_suffix('\\').unwrap_or(raw);
match pending.as_mut() {
Some((_, text)) => {
text.push(' ');
text.push_str(piece.trim_start());
}
None => pending = Some((index + 1, piece.to_string())),
}
if !continued {
joined.push(pending.take().expect("a started line"));
}
}
if let Some(last) = pending {
joined.push(last);
}
joined
}
#[test]
fn the_runner_pin_recognises_every_awk_spelling() {
for line in [
"awk -f prog.awk file",
"gawk -f prog.awk file",
"mawk -f prog.awk file",
"nawk -f prog.awk file",
"command awk -f prog.awk file",
"exec awk -f prog.awk file",
"env awk -f prog.awk file",
"env LC_ALL=C awk -f prog.awk file",
"xargs awk -f prog.awk",
"nice -n 5 awk -f prog.awk file",
"/usr/bin/awk -f prog.awk file",
"\\awk -f prog.awk file",
"violations=\"$(awk -f prog.awk file)\"",
"printf '%s' \"$x\" | awk -f prog.awk",
"hits=$(cat file | /usr/local/bin/gawk '{ print }')",
"$AWK -f prog.awk file",
"${AWK} -f prog.awk file",
"eval \"$program\"",
"/usr/bin/env awk -f prog.awk file",
"/usr/bin/env -S awk -f prog.awk file",
"mapfile -t FILES < <(awk -f prog.awk file)",
"readarray -t FILES < <(gawk -f prog.awk file)",
"2>/dev/null awk -f prog.awk file",
"2> /dev/null awk -f prog.awk file",
"( awk -f prog.awk file )",
"if [ -f x ]; then awk -f prog.awk x; fi",
"for f in *; do awk -f prog.awk \"$f\"; done",
"'awk' -f prog.awk file",
"mapfile -t FILES < <(grep -rl x crates)",
"files=\"$(grep -rl x crates)\"",
"hits=\"$(sed -n 1p file)\" || true",
"case \"$mode\" in\n scan) awk -f prog.awk file ;;\nesac",
"case \"$mode\" in\n scan|run) gawk -f prog.awk file ;;\nesac",
"case \"$mode\" in scan) awk -f prog.awk file ;; esac",
"find crates -name '*.rs' -exec awk -f prog.awk {} +",
"find crates -name '*.rs' -execdir mawk -f prog.awk {} \\;",
"find crates -name '*.rs' -ok nawk -f prog.awk {} \\;",
"find crates -name '*.rs' -okdir awk -f prog.awk {} \\;",
"sed -n 1p file ||\ntrue",
"coproc awk -f prog.awk file",
"busybox awk -f prog.awk file",
"toybox awk -f prog.awk file",
] {
assert!(
forbidden_command(line).is_some(),
"the runner pin must reject: {line}"
);
}
for line in [
"# awk is named in this comment",
"run_scanner violations -f \"$LIB_DIR/rust-lex.awk\" -f - \"${FILES[@]}\"",
"collect_files FILES -rlE --include='*.rs' -- 'x' crates",
"grep -qE -- \"task: ${target}\" <<< \"$combined\"",
"LIB_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)/lib\"",
"done <<< \"$mod_decls\"",
"command -v awk > /dev/null 2>&1",
"type gawk",
"hash mawk 2>/dev/null",
"which nawk",
"# a swallowed failure looks like `|| true` — never write one",
"arr+=(\"$f\")",
"case \"$x\" in awk) ;; esac",
"grep -rn -- '-exec awk' .claude/scripts",
] {
assert!(
forbidden_command(line).is_none(),
"the runner pin must accept {line}, got {:?}",
forbidden_command(line)
);
}
}
#[test]
fn a_scanner_whose_inline_program_is_broken_fails_loudly() {
let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let scripts = repo.join(".claude/scripts");
let dir = TempDir::new().expect("temp dir");
let lib = dir.path().join("lib");
std::fs::create_dir(&lib).expect("lib dir");
for entry in std::fs::read_dir(scripts.join("lib")).expect("lib dir") {
let src = entry.expect("lib entry").path();
std::fs::copy(&src, lib.join(src.file_name().expect("file name"))).expect("copy lib file");
}
const SCRIPT: &str = "audit-log-status.sh";
let body = std::fs::read_to_string(scripts.join(SCRIPT)).expect("script body");
let broken = body.replacen("<<'AWK'\n", "<<'AWK'\n(((\n", 1);
assert_ne!(
broken, body,
"{SCRIPT} no longer opens its program with <<'AWK'"
);
let copy = dir.path().join(SCRIPT);
std::fs::write(©, broken).expect("write the broken copy");
let out = bash()
.arg(©)
.arg(&repo)
.output()
.expect("running the broken scanner");
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert_eq!(
out.status.code(),
Some(2),
"a syntax error in the program must exit 2, not the violations-found 1.\n{stdout}{stderr}"
);
assert!(
stderr.contains("audit-log-status: awk scanner exited")
&& stderr.contains("the scan did not run"),
"the runner must name the script and say the scan did not run, got: {stderr}"
);
assert!(
stderr.contains("awk:"),
"awk's own diagnostic must stay visible, got: {stderr}"
);
}