vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Mutation probe and canary functions.

use super::*;

/// Run a mutation probe: apply each mutation, run the test, and report which mutations were killed.
#[inline]
pub fn mutation_probe(
    source_file: &Path,
    test_fn_name: &str,
    mutations: &[&dyn AppliedMutation],
    classes: &[MutationClass],
    cargo_test_args: &[&str],
) -> GateReport {
    let probe_start = Instant::now();
    let mut results = Vec::with_capacity(mutations.len());
    let mut feedback = Vec::new();

    let original = match fs::read_to_string(source_file) {
        Ok(s) => s,
        Err(err) => {
            return GateReport {
                test_name: test_fn_name.to_string(),
                source_file: source_file.to_path_buf(),
                results: vec![MutationResult {
                    mutation_id: "probe_init".to_string(),
                    description: format!(
                        "failed to read source file {}: {err}",
                        source_file.display()
                    ),
                    outcome: MutationOutcome::Skipped {
                        reason: format!("read error: {err}"),
                    },
                    duration: probe_start.elapsed(),
                }],
                total_duration: probe_start.elapsed(),
                feedback: Vec::new(),
            };
        }
    };

    for mutation in mutations {
        if !classes.is_empty() && !classes.contains(&mutation.class()) {
            continue;
        }

        let start = Instant::now();
        let mutated = match mutation.apply(&original) {
            Ok(s) => s,
            Err(err) => {
                results.push(MutationResult {
                    mutation_id: mutation.id().to_string(),
                    description: mutation.description().to_string(),
                    outcome: MutationOutcome::Skipped {
                        reason: err.to_string(),
                    },
                    duration: start.elapsed(),
                });
                continue;
            }
        };

        if mutated == original {
            results.push(MutationResult {
                mutation_id: mutation.id().to_string(),
                description: mutation.description().to_string(),
                outcome: MutationOutcome::Skipped {
                    reason: "mutation produced identical source".to_string(),
                },
                duration: start.elapsed(),
            });
            continue;
        }

        // Take a snapshot so we can restore on drop even if cargo
        // fails or we panic mid-probe.
        if let Err(reason) = assert_source_matches_original(source_file, &original) {
            results.push(MutationResult {
                mutation_id: mutation.id().to_string(),
                description: mutation.description().to_string(),
                outcome: MutationOutcome::Skipped { reason },
                duration: start.elapsed(),
            });
            break;
        }

        let mut snapshot = match SourceSnapshot::new(source_file) {
            Ok(s) => s,
            Err(err) => {
                results.push(MutationResult {
                    mutation_id: mutation.id().to_string(),
                    description: mutation.description().to_string(),
                    outcome: MutationOutcome::Skipped {
                        reason: format!("snapshot failed: {err}"),
                    },
                    duration: start.elapsed(),
                });
                continue;
            }
        };

        if let Err(err) = fs::write(source_file, mutated.as_bytes()) {
            if let Err(restore_err) = snapshot.restore_explicit() {
                eprintln!(
                    "vyre-conform H6: write-failed AND restore-failed for {}. \
                     write: {err}, restore: {restore_err}",
                    source_file.display()
                );
            }
            results.push(MutationResult {
                mutation_id: mutation.id().to_string(),
                description: mutation.description().to_string(),
                outcome: MutationOutcome::Skipped {
                    reason: format!("write failed: {err}"),
                },
                duration: start.elapsed(),
            });
            continue;
        }

        let outcome = run_cargo_test(test_fn_name, cargo_test_args);

        let killed = !matches!(outcome, MutationOutcome::Survived);
        let key = crate::enforce::layers::layer8_feedback_loop::MutationProbeKey {
            source_hash: {
                use std::hash::{Hash, Hasher};
                let mut hasher = std::collections::hash_map::DefaultHasher::new();
                original.hash(&mut hasher);
                hasher.finish()
            },
            test_hash: {
                use std::hash::{Hash, Hasher};
                let mut hasher = std::collections::hash_map::DefaultHasher::new();
                test_fn_name.hash(&mut hasher);
                hasher.finish()
            },
            mutation: mutation.id().to_string(),
        };
        let _ = crate::enforce::layers::layer8_feedback_loop::cache_mutation_probe(key, killed);

        if let Err(restore_err) = snapshot.restore_explicit() {
            eprintln!(
                "vyre-conform H6: explicit restore failed for {}: {restore_err}. \
                 The drop handler will retry on scope exit.",
                source_file.display()
            );
        }

        if matches!(outcome, MutationOutcome::Survived) {
            feedback.push(StructuredFeedback {
                mutation_id: mutation.id().to_string(),
                hint: mutation.hint(),
            });
        }

        results.push(MutationResult {
            mutation_id: mutation.id().to_string(),
            description: mutation.description().to_string(),
            outcome,
            duration: start.elapsed(),
        });
    }

    GateReport {
        test_name: test_fn_name.to_string(),
        source_file: source_file.to_path_buf(),
        results,
        total_duration: probe_start.elapsed(),
        feedback,
    }
}

// ---------------------------------------------------------------------------
// Acceptance self-test (callable from tests/harnesses_mutation_gate.rs)
// ---------------------------------------------------------------------------

/// Returns a canary `AppliedMutation` that replaces every `+` with
/// `-` in the source. Used by the gate's own acceptance test — not a
/// catalog entry.
#[inline]
pub fn canary_plus_to_minus() -> impl AppliedMutation {
    struct Canary;
    impl AppliedMutation for Canary {
        fn id(&self) -> &str {
            "canary_plus_to_minus"
        }
        fn description(&self) -> &str {
            "replace every '+' with '-' in the source"
        }
        fn class(&self) -> MutationClass {
            MutationClass::ArithmeticMutations
        }
        fn apply(&self, source: &str) -> Result<String, MutationApplyError> {
            if !source.contains('+') {
                return Err(MutationApplyError::NotApplicable {
                    reason: "no '+' in source".to_string(),
                });
            }
            Ok(source.replace('+', "-"))
        }
    }
    Canary
}