#![cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::Command;
fn repo() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn write(root: &Path, rel: &str, body: &str) {
let p = root.join(rel);
std::fs::create_dir_all(p.parent().expect("parent")).expect("mkdir");
std::fs::write(p, body).expect("write");
}
fn git(root: &Path, args: &[&str]) {
let ok = Command::new("git")
.args(args)
.current_dir(root)
.env("GIT_AUTHOR_NAME", "t")
.env("GIT_AUTHOR_EMAIL", "t@example.com")
.env("GIT_COMMITTER_NAME", "t")
.env("GIT_COMMITTER_EMAIL", "t@example.com")
.status()
.expect("git")
.success();
assert!(ok, "git {args:?}");
}
const CARGO_SHIM: &str = r#"#!/usr/bin/env bash
set -uo pipefail
case "${1:-}" in
test)
echo "test result: ok. 1 passed; 0 failed; 44 ignored; 0 measured; 0 filtered out"
;;
llvm-cov)
printf 'TOTAL 100 0 100.00%% 100 0 100.00%% 100 0 96.40%% 100 0 100.00%%\n'
;;
mutants)
echo ran > "$MUT_CALLED"
out=""
while [ $# -gt 0 ]; do
if [ "$1" = "--output" ]; then out="$2"; fi
shift
done
case "${MUT_OUTCOMES}" in
none) echo "INFO No mutants to filter" ;;
clean)
mkdir -p "$out/mutants.out"
echo '{"total_mutants":1,"outcomes":[{"summary":"CaughtMutant"}]}' > "$out/mutants.out/outcomes.json"
;;
missed)
mkdir -p "$out/mutants.out"
echo '{"total_mutants":1,"outcomes":[{"summary":"MissedMutant","scenario":{"Mutant":{"file":"src/lib.rs","span":{"start":{"line":1}},"genre":"g","replacement":"r"}}}]}' > "$out/mutants.out/outcomes.json"
;;
esac
;;
*) echo "cargo shim: unexpected $*" >&2; exit 2 ;;
esac
"#;
const PMAT_SHIM: &str = "#!/usr/bin/env bash\nexit 0\n";
struct Fixture {
_dir: tempfile::TempDir,
root: PathBuf,
bin: PathBuf,
called: PathBuf,
}
fn fixture(second_commit: &[(&str, &str)]) -> Fixture {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path().join("tree");
std::fs::create_dir_all(&root).expect("mkdir");
let gate = std::fs::read_to_string(repo().join("scripts/dogfood/coverage.sh"))
.expect("the gate under test");
write(&root, "scripts/dogfood/coverage.sh", &gate);
write(
&root,
"Makefile",
"coverage:\n\tcargo llvm-cov --workspace --locked --fail-under-lines 95\n",
);
let annotations = std::fs::read_to_string(repo().join("scripts/dogfood/coverage.sh"))
.expect("gate")
.lines()
.find_map(|l| {
l.strip_prefix("APRENDER_ANNOTATIONS=")
.map(|n| n.trim().to_string())
})
.expect("APRENDER_ANNOTATIONS in the gate")
.parse::<usize>()
.expect("a number");
let mut body = String::new();
for i in 0..annotations {
body.push_str(&format!(
"#[cfg_attr(not(feature = \"aprender-corpus\"), ignore = \"corpus\")]\nfn a{i}() {{}}\n"
));
}
write(&root, "crates/forjar-contracts/src/lib.rs", &body);
write(&root, "src/lib.rs", "pub fn f() -> u32 { 1 }\n");
git(&root, &["init", "-q", "-b", "main"]);
git(&root, &["config", "commit.gpgsign", "false"]);
git(&root, &["config", "core.hooksPath", "/nonexistent"]);
git(&root, &["add", "-A"]);
git(&root, &["commit", "-qm", "base"]);
git(&root, &["update-ref", "refs/remotes/origin/main", "HEAD"]);
for (rel, body) in second_commit {
write(&root, rel, body);
}
if !second_commit.is_empty() {
git(&root, &["add", "-A"]);
git(&root, &["commit", "-qm", "branch"]);
}
let bin = dir.path().join("bin");
std::fs::create_dir_all(&bin).expect("mkdir bin");
for (name, body) in [("cargo", CARGO_SHIM), ("pmat", PMAT_SHIM)] {
let p = bin.join(name);
std::fs::write(&p, body).expect("shim");
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(0o755)).expect("chmod");
}
Fixture {
called: dir.path().join("mut-called"),
_dir: dir,
root,
bin,
}
}
fn run(fx: &Fixture, outcomes: &str) -> (i32, String) {
let _ = std::fs::remove_file(&fx.called);
let path = format!(
"{}:{}",
fx.bin.display(),
std::env::var("PATH").unwrap_or_default()
);
let out = Command::new("bash")
.arg("scripts/dogfood/coverage.sh")
.current_dir(&fx.root)
.env("PATH", path)
.env("MUT_OUTCOMES", outcomes)
.env("MUT_CALLED", &fx.called)
.output()
.expect("run gate F");
let text =
String::from_utf8_lossy(&out.stdout).to_string() + &String::from_utf8_lossy(&out.stderr);
(out.status.code().unwrap_or(-1), text)
}
fn mutants_ran(fx: &Fixture) -> bool {
fx.called.exists()
}
#[test]
fn a_diff_whose_only_rust_change_is_a_test_passes_and_says_why() {
let fx = fixture(&[("tests/falsification_new.rs", "#[test]\nfn t() {}\n")]);
let (code, text) = run(&fx, "none");
assert_eq!(
code, 0,
"gate F went red on a branch with nothing to mutate: {text}"
);
assert!(
text.contains("none under src/"),
"it passed without saying why the mutation arm was empty: {text}"
);
}
#[test]
fn a_diff_touching_src_still_demands_mutants() {
let fx = fixture(&[("src/lib.rs", "pub fn f() -> u32 { 2 }\n")]);
let (code, text) = run(&fx, "none");
assert_ne!(
code, 0,
"a src/ change with no mutation outcome passed: {text}"
);
assert!(text.contains("UNMEASURED"), "wrong reason: {text}");
assert!(
mutants_ran(&fx),
"cargo mutants was never invoked for a src/ change"
);
}
#[test]
fn a_surviving_mutant_in_src_is_red() {
let fx = fixture(&[("src/lib.rs", "pub fn f() -> u32 { 2 }\n")]);
let (code, text) = run(&fx, "missed");
assert_ne!(code, 0, "a survivor passed: {text}");
assert!(text.contains("survived"), "wrong reason: {text}");
}
#[test]
fn a_src_change_whose_mutants_are_caught_passes() {
let fx = fixture(&[("src/lib.rs", "pub fn f() -> u32 { 2 }\n")]);
let (code, text) = run(&fx, "clean");
assert_eq!(code, 0, "{text}");
assert!(
mutants_ran(&fx),
"the mutation arm was skipped for a src/ change: {text}"
);
}
#[test]
fn a_diff_with_no_rust_change_skips_the_mutation_arm() {
let fx = fixture(&[("README.md", "prose\n")]);
let (code, text) = run(&fx, "none");
assert_eq!(code, 0, "{text}");
assert!(
!mutants_ran(&fx),
"cargo mutants ran for a diff with no .rs file: {text}"
);
}