1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//! Integration tests for the Python mutation rule (#203).
//!
//! These run REAL cosmic-ray over the fixture projects via the SDK
//! ([`mutation::measure_python`]), which spawns the bundled Python adapter (`python3 -m
//! testing_conventions.mutation.main`, #248) to drive cosmic-ray in-process, and assert the
//! surviving-mutant set — the Python parallel of the Rust (#201) and TypeScript (#202) arms.
//! Per the #3 guardrail the
//! *projects themselves* are the fixtures: `killed` (every mutant caught by an asserting
//! test) reports no survivors, and `survivors` (an assertion-light test that runs the
//! code but pins nothing) reports several — the gap mutation testing exposes that
//! coverage can't.
//!
//! Each test runs against its own staged copy (cosmic-ray mutates in place) so the
//! parallel runs never collide. Requires a `python3` with cosmic-ray + pytest installed and
//! the source package importable (`PYTHONPATH=packages/python/python`).
mod common;
use common::Staged;
use testing_conventions::mutation::measure_python;
#[test]
fn killed_reports_no_survivors() {
let project = Staged::python("killed");
let survivors = 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 survivors_are_reported() {
let project = Staged::python("survivors");
let survivors = 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_mutation_exemption_drops_the_survivors() {
// Exempting the survivors' file lifts all of them — an equivalent / deliberately
// defensive mutation, waived with a reason via `[[python.exempt]] rules = ["mutation"]`.
let project = Staged::python("survivors");
let exempt = vec!["calc.py".to_string()];
let survivors = measure_python(
project.path(),
&exempt,
&std::collections::BTreeMap::new(),
None,
)
.expect("cosmic-ray runs");
assert!(
survivors.is_empty(),
"the exemption should drop every survivor; got {survivors:?}"
);
}