testing-conventions 0.0.147

Enforce testing conventions in libraries (Python, TypeScript, and Rust).
Documentation
mod common;

use common::{expect_tested, Staged};
use testing_conventions::mutation::measure_python;

#[test]
fn killed_reports_no_survivors() {
    let package = Staged::python("killed");
    let (_, survivors) = expect_tested(
        measure_python(
            &package.path().join("src"),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(
        survivors.is_empty(),
        "every mutant should be caught; got {survivors:?}"
    );
}

#[test]
fn survivors_are_reported() {
    let package = Staged::python("survivors");
    let (_, survivors) = expect_tested(
        measure_python(
            &package.path().join("src"),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(
        !survivors.is_empty(),
        "the assertion-light suite should leave survivors under the scan path"
    );
    assert!(
        survivors.iter().all(|m| m.file == "calc.py"),
        "survivors are reported relative to the scan path; got {survivors:?}"
    );
    assert!(
        survivors.iter().all(|m| m.description.contains("(-> ")),
        "each survivor names the source its mutation produced; got {survivors:?}"
    );
}

#[test]
fn a_test_file_nested_below_the_scan_path_is_never_mutated() {
    let package = Staged::python_nested("nested_tests");
    let (count, survivors) = expect_tested(
        measure_python(
            &package.path().join("src"),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(count > 0, "the engine ran, so the count is non-zero");
    assert!(
        survivors
            .iter()
            .all(|m| !m.file.ends_with("_test.py") && !m.file.starts_with("test_")),
        "the suite judges the mutants and is never mutated itself; got {survivors:?}"
    );
    assert!(
        survivors.is_empty(),
        "every mutant should be caught; got {survivors:?}"
    );
}

#[test]
fn a_loose_tree_with_no_manifest_reports_root_relative_survivors() {
    let project = Staged::python_loose("loose_survivors");
    let (_, survivors) = expect_tested(
        measure_python(
            project.path(),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(
        !survivors.is_empty(),
        "the assertion-light suite should leave survivors"
    );
    assert!(
        survivors.iter().all(|m| m.file == "calc.py"),
        "every survivor is in calc.py; got {survivors:?}"
    );
}

#[test]
fn a_helper_module_under_tests_is_never_mutated() {
    let project = Staged::python_loose_test_helpers("loose_test_helpers");
    let (count, survivors) = expect_tested(
        measure_python(
            project.path(),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(count > 0, "calc.py is mutated, so the count is non-zero");
    assert!(
        survivors.is_empty(),
        "tests/integration/served.py is test code and yields no mutants; got {survivors:?}"
    );
}

#[test]
fn a_loose_tree_with_no_manifest_passes_when_all_mutants_are_killed() {
    let project = Staged::python_loose("loose_killed");
    let (_, survivors) = expect_tested(
        measure_python(
            project.path(),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(
        survivors.is_empty(),
        "every mutant should be caught; got {survivors:?}"
    );
}

#[test]
fn a_declaration_only_module_leaves_no_survivors() {
    let package = Staged::python_declaration_only("declaration_only");
    let (_, survivors) = expect_tested(
        measure_python(
            &package.path().join("src"),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(
        survivors.is_empty(),
        "settings.py has no function or control flow, so its mutants are not subjects; got {survivors:?}"
    );
}

#[test]
fn a_mutant_inside_an_annotation_leaves_no_survivors() {
    let package = Staged::python("annotations");
    let (count, survivors) = expect_tested(
        measure_python(
            &package.path().join("src"),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(
        survivors.is_empty(),
        "an annotation is never evaluated, so its mutants are not judged; got {survivors:?}"
    );
    assert!(
        count > 0,
        "the body and the parameter default are still judged, so the count is non-zero"
    );
}

#[test]
fn a_mutation_exemption_drops_the_survivors() {
    let package = Staged::python("survivors");
    let exempt = vec!["calc.py".to_string()];
    let (_, survivors) = expect_tested(
        measure_python(
            &package.path().join("src"),
            &exempt,
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(
        survivors.is_empty(),
        "the exemption should drop every survivor; got {survivors:?}"
    );
}

#[test]
fn a_mutant_its_colocated_test_kills_never_runs_the_rest_of_the_suite() {
    let project = Staged::python_loose_with_neighbor("colocated_first");
    let (count, survivors) = expect_tested(
        measure_python(
            project.path(),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(count > 0, "calc.py is mutated, so the count is non-zero");
    assert!(
        survivors.is_empty(),
        "calc_test.py kills every mutant; got {survivors:?}"
    );
    let runs = std::fs::read_to_string(project.path().join("runs.log")).unwrap_or_default();
    assert_eq!(
        runs.lines().count(),
        1,
        "a_test.py runs only in the clean baseline, never for a mutant calc_test.py kills"
    );
}

#[test]
fn a_mutant_only_another_test_kills_is_still_killed() {
    let project = Staged::python_loose_with_neighbor("colocated_fallback");
    let (count, survivors) = expect_tested(
        measure_python(
            project.path(),
            &[],
            &std::collections::BTreeMap::new(),
            None,
        )
        .expect("cosmic-ray runs"),
    );
    assert!(count > 0, "calc.py is mutated, so the count is non-zero");
    assert!(
        survivors.is_empty(),
        "a mutant the colocated test misses falls back to the full suite; got {survivors:?}"
    );
}

#[test]
fn an_adapter_failure_surfaces_its_output() {
    let dir = std::env::temp_dir().join(format!("tc-mut-py-fail-{}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let err = measure_python(&dir, &[], &std::collections::BTreeMap::new(), None).unwrap_err();
    let _ = std::fs::remove_dir_all(&dir);
    assert!(
        format!("{err:#}").contains("the Python mutation adapter failed"),
        "got: {err:#}"
    );
}