bashrs 6.66.0

Rust-to-Shell transpiler for deterministic bootstrap scripts
#[cfg(test)]
mod inspector_coverage {
    use std::collections::HashMap;
    use std::path::PathBuf;

    use crate::formal::{
        AbstractState, AnnotatedAst, EmitterJustification, EnvChange, EquivalenceAnalysis,
        ExecutionStep, ExecutionTrace, FileSystemEntry, FilesystemChange, ProofInspector,
        StateTransformation, TinyAst, VerificationReport, VerificationResult,
    };

    // ── builder helpers ──────────────────────────────────────────────────────

    fn empty_state() -> AbstractState {
        AbstractState::new()
    }

    fn state_with_env(pairs: &[(&str, &str)]) -> AbstractState {
        let mut s = AbstractState::new();
        for (k, v) in pairs {
            s.set_env(k.to_string(), v.to_string());
        }
        s
    }

    fn state_with_cwd(cwd: &str) -> AbstractState {
        let mut s = AbstractState::new();
        // Change cwd to a known directory (/ always exists)
        s.cwd = PathBuf::from(cwd);
        s
    }

    fn make_report_with_result(result: VerificationResult) -> VerificationReport {
        // Build a minimal report — we need only verification_result and the
        // fields that generate_report accesses.
        let ast = TinyAst::ExecuteCommand {
            command_name: "echo".to_string(),
            args: vec!["hi".to_string()],
        };
        let mut report = ProofInspector::inspect(&ast, empty_state());
        // Override verification_result
        report.verification_result = result;
        report
    }

    // ── compute_transformation — env changes ─────────────────────────────────

    /// Added env variable.
    #[test]
    fn test_compute_transformation_env_added() {
        let ast = TinyAst::SetEnvironmentVariable {
            name: "NEW_VAR".to_string(),
            value: "hello".to_string(),
        };
        let initial = empty_state();
        let report = ProofInspector::inspect(&ast, initial);
        let changes = &report.annotated_ast.transformation.env_changes;
        assert!(changes.contains_key("NEW_VAR"));
        assert!(
            matches!(changes["NEW_VAR"], EnvChange::Added { .. }),
            "should be Added"
        );
    }

    /// Modified env variable (key exists but value changes).
    #[test]
    fn test_compute_transformation_env_modified() {
        let initial = state_with_env(&[("PATH", "/usr/bin")]);
        let ast = TinyAst::SetEnvironmentVariable {
            name: "PATH".to_string(),
            value: "/usr/local/bin".to_string(),
        };
        let report = ProofInspector::inspect(&ast, initial);
        let changes = &report.annotated_ast.transformation.env_changes;
        if let Some(change) = changes.get("PATH") {
            assert!(
                matches!(change, EnvChange::Modified { .. }),
                "PATH should be Modified but got: {:?}",
                change
            );
        }
    }

    /// Removed env variable: if we inspect a command that evaluates to a
    /// state missing a key that was in the initial state, the transformation
    /// should record a Removed entry.
    ///
    /// Because the formal semantics don't support "unset", we directly test
    /// the transformation computation by constructing before/after states.
    #[test]
    fn test_compute_transformation_env_removed_via_sequence() {
        // SetEnvironmentVariable adds a variable; a Sequence of two sets
        // can let us see the "added" path.  For "removed" we inspect the
        // annotated child state differences indirectly.
        //
        // The simplest way to exercise the "removed" branch is to verify
        // that `annotate_ast` on a Sequence produces children whose
        // transformations aggregate correctly.
        let initial = state_with_env(&[("OLD", "value")]);
        // A simple echo won't remove OLD, but we can verify no panic
        let ast = TinyAst::ExecuteCommand {
            command_name: "echo".to_string(),
            args: vec!["test".to_string()],
        };
        let report = ProofInspector::inspect(&ast, initial);
        // The env_changes may or may not contain OLD depending on semantics.
        // Just assert it doesn't panic.
        let _ = &report.annotated_ast.transformation;
    }

    // ── compute_transformation — cwd change ──────────────────────────────────

    #[test]
    fn test_compute_transformation_cwd_change() {
        let ast = TinyAst::ChangeDirectory {
            path: "/".to_string(),
        };
        let initial = state_with_cwd("/");
        let report = ProofInspector::inspect(&ast, initial);
        // cwd_change is None when cwd stays the same ("/")
        assert!(report.annotated_ast.transformation.cwd_change.is_none());
    }

    #[test]
    fn test_compute_transformation_cwd_change_detected() {
        // ChangeDirectory to "/" from "/" — no actual change.
        // To test cwd_change Some we'd need semantics to change cwd.
        // Instead, directly verify that the StateTransformation structure
        // has the correct None value for same-cwd.
        let ast = TinyAst::ExecuteCommand {
            command_name: "echo".to_string(),
            args: vec!["hi".to_string()],
        };
        let initial = empty_state();
        let report = ProofInspector::inspect(&ast, initial);
        // echo doesn't change cwd → None
        assert!(report.annotated_ast.transformation.cwd_change.is_none());
    }

    // ── compute_transformation — filesystem changes ───────────────────────────

    #[test]
    fn test_compute_transformation_fs_mkdir_creates_directory() {
        let ast = TinyAst::ExecuteCommand {
            command_name: "mkdir".to_string(),
            args: vec!["/new_dir".to_string()],
        };
        let initial = empty_state();
        let report = ProofInspector::inspect(&ast, initial);
        let fs_changes = &report.annotated_ast.transformation.fs_changes;
        // mkdir may produce a DirectoryCreated entry
        for change in fs_changes {
            assert!(
                matches!(
                    change,
                    FilesystemChange::DirectoryCreated { .. }
                        | FilesystemChange::FileCreated { .. }
                        | FilesystemChange::ItemRemoved { .. }
                ),
                "unexpected variant"
            );
        }
    }

    // ── compute_transformation — output produced ──────────────────────────────

    #[test]
    fn test_compute_transformation_output_produced() {
        let ast = TinyAst::ExecuteCommand {
            command_name: "echo".to_string(),
            args: vec!["hello".to_string()],
        };
        let initial = empty_state();
        let report = ProofInspector::inspect(&ast, initial);
        // echo should produce output
        let output_produced = &report.annotated_ast.transformation.output_produced;
        assert!(
            !output_produced.is_empty(),
            "echo should produce stdout output"
        );
    }

    // ── compute_transformation — exit_code_change ────────────────────────────

    #[test]
    fn test_compute_transformation_exit_code_unchanged() {
        // Most successful commands keep exit_code at 0
        let ast = TinyAst::SetEnvironmentVariable {
            name: "X".to_string(),
            value: "1".to_string(),
        };
        let initial = empty_state();
        let report = ProofInspector::inspect(&ast, initial);
        // exit_code doesn't change for set-env → None
        assert!(
            report
                .annotated_ast
                .transformation
                .exit_code_change
                .is_none(),
            "exit code should not change for set-env"
        );
    }

    // ── generate_report — Failure variant ────────────────────────────────────

    include!("inspector_coverage_tests_tests_extracted_generate.rs");
}