ripr 0.6.0

Find static mutation-exposure gaps before expensive mutation testing
Documentation
use crate::domain::Finding;

pub(super) fn evidence_path_lines(finding: &Finding) -> Vec<String> {
    let mut lines = vec![
        format!(
            "reach {}: {}",
            finding.ripr.reach.state.as_str(),
            finding.ripr.reach.summary
        ),
        format!(
            "infection {}: {}",
            finding.ripr.infect.state.as_str(),
            finding.ripr.infect.summary
        ),
        format!(
            "propagation {}: {}",
            finding.ripr.propagate.state.as_str(),
            finding.ripr.propagate.summary
        ),
        format!(
            "observation {}: {}",
            finding.ripr.reveal.observe.state.as_str(),
            finding.ripr.reveal.observe.summary
        ),
        format!(
            "discriminator {}: {}",
            finding.ripr.reveal.discriminate.state.as_str(),
            finding.ripr.reveal.discriminate.summary
        ),
    ];

    for sink in &finding.flow_sinks {
        lines.push(format!(
            "local flow reaches {}: {} (line {})",
            sink.kind.label(),
            sink.text,
            sink.line
        ));
    }

    for test in finding.related_tests.iter().take(5) {
        let oracle_kind = display_label(test.oracle_kind.as_str());
        let mut line = format!(
            "related test {}:{} {} uses {} {} oracle",
            test.file.display(),
            test.line,
            test.name,
            test.oracle_strength.as_str(),
            oracle_kind
        );
        if let Some(oracle) = &test.oracle {
            line.push_str(&format!(": {oracle}"));
        }
        lines.push(line);
    }

    for value in finding.activation.observed_values.iter().take(8) {
        let context = display_label(value.context.as_str());
        lines.push(format!(
            "observed {} value {} at line {}",
            context, value.value, value.line
        ));
    }

    if lines.len() == 5 && !finding.evidence.is_empty() {
        lines.extend(finding.evidence.iter().cloned());
    }

    lines
}

pub(super) fn weakness_lines(finding: &Finding) -> Vec<String> {
    let discriminator_values: Vec<&str> = finding
        .activation
        .missing_discriminators
        .iter()
        .map(|fact| fact.value.as_str())
        .collect();
    let mut lines = finding
        .missing
        .iter()
        .filter(|missing| !is_duplicate_discriminator_missing(missing, &discriminator_values))
        .cloned()
        .collect::<Vec<_>>();
    for discriminator in &finding.activation.missing_discriminators {
        lines.push(format!(
            "missing discriminator {}: {}",
            discriminator.value, discriminator.reason
        ));
    }
    lines
}

fn is_duplicate_discriminator_missing(missing: &str, discriminator_values: &[&str]) -> bool {
    let Some(value) = missing.strip_prefix("Missing discriminator value: ") else {
        return false;
    };
    discriminator_values.contains(&value)
}

fn display_label(value: &str) -> String {
    value.replace('_', " ")
}