fallow_output/health_mutation.rs
1//! Mutation evidence in a health report, read from a saved `fallow mutation`
2//! report with `fallow health --mutation-report <file>`.
3//!
4//! The mutation score is a separate value. It never changes the CRAP score of
5//! a finding or the composite health score.
6
7use serde::{Deserialize, Serialize};
8
9use crate::{MutationCompletionStatus, MutationStatusCounts};
10
11/// Whether the mutation evidence still describes the current source file.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
14#[serde(rename_all = "snake_case")]
15pub enum HealthMutationState {
16 /// The file has the SHA-256 digest of the report.
17 Fresh,
18 /// The file changed, is missing, or has no digest in the report. The
19 /// evidence has no score and no counts.
20 Stale,
21}
22
23/// The mutation evidence of one complexity finding.
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
26pub struct HealthFindingMutation {
27 /// Whether the evidence describes the current file.
28 pub state: HealthMutationState,
29 /// Why the evidence is stale. Absent when the state is `fresh`.
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub stale_reason: Option<HealthMutationStaleReason>,
32 /// The mutation score of the function, a fraction from 0 to 1:
33 /// `(killed + timeout) / (killed + timeout + survived)`. Absent when the
34 /// state is `stale`, and when no mutant of the function ran to a tested
35 /// result.
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub score: Option<f64>,
38 /// The number of mutants of the function for each status. Absent when
39 /// the state is `stale`.
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub counts: Option<MutationStatusCounts>,
42}
43
44/// Why mutation evidence is stale.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
47#[serde(rename_all = "kebab-case")]
48pub enum HealthMutationStaleReason {
49 /// The file does not exist or cannot be read.
50 FileMissing,
51 /// The report has no digest for the file.
52 NoDigest,
53 /// The SHA-256 digest of the file differs from the report.
54 SourceChanged,
55}
56
57/// One file of the mutation report that does not match the current source.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
60pub struct HealthMutationStaleFile {
61 /// The root-relative path, with forward slashes.
62 pub path: String,
63 /// Why the file is stale.
64 pub reason: HealthMutationStaleReason,
65}
66
67/// The join of a saved mutation report with the complexity findings.
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
70pub struct HealthMutationSummary {
71 /// The completion status of the run that wrote the report. Only
72 /// `complete` makes an empty survivor list conclusive. Absent when the
73 /// report has no completion.
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub completion: Option<MutationCompletionStatus>,
76 /// The number of function results in the report.
77 pub functions: usize,
78 /// The number of complexity findings with fresh mutation evidence.
79 pub findings_fresh: usize,
80 /// The number of complexity findings with stale mutation evidence.
81 pub findings_stale: usize,
82 /// The number of complexity findings that no function result of the
83 /// report matches. A finding matches when the path, the first line and
84 /// the name are equal.
85 pub findings_unmatched: usize,
86 /// The report files that do not match the current source, sorted by
87 /// path. Their functions have no score.
88 #[serde(default, skip_serializing_if = "Vec::is_empty")]
89 pub stale_files: Vec<HealthMutationStaleFile>,
90}