## Coverage Report

#### Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/ruchy/src/runtime/deterministic.rs

    Line
    Count
    Source
    1
    //! Deterministic execution support for REPL replay testing
    2
    //!
    3
    //! Implements the `DeterministicRepl` trait for the Ruchy REPL to enable
    4
    //! deterministic replay for testing and educational assessment.
    5
    6
    use anyhow::Result;
    7
    use std::collections::HashMap;
    8
    use sha2::{Sha256, Digest};
    9
    10
    use crate::runtime::repl::{Repl, Value};
    11
    use crate::runtime::replay::{
    12
        DeterministicRepl, ReplayResult, StateCheckpoint, ResourceUsage,
    13
        ValidationResult, Divergence
    14
    };
    15
    16
    /// Mock time source for deterministic time operations
    17
    pub struct MockTime {
    18
        current_ns: u64,
    19
    }
    20
    21
    impl Default for MockTime {
    22
    0
        fn default() -> Self {
    23
    0
            Self::new()
    24
    0
        }
    25
    }
    26
    27
    impl MockTime {
    28
    0
        pub fn new() -> Self {
    29
    0
            Self { current_ns: 0 }
    30
    0
        }
    31
    32
    0
        pub fn advance(&mut self, ns: u64) {
    33
    0
            self.current_ns += ns;
    34
    0
        }
    35
    36
    0
        pub fn now(&self) -> u64 {
    37
    0
            self.current_ns
    38
    0
        }
    39
    }
    40
    41
    /// Deterministic random number generator
    42
    pub struct DeterministicRng {
    43
        seed: u64,
    44
        state: u64,
    45
    }
    46
    47
    impl DeterministicRng {
    48
    0
        pub fn new(seed: u64) -> Self {
    49
    0
            Self {
    50
    0
                seed,
    51
    0
                state: seed,
    52
    0
            }
    53
    0
        }
    54
    55
    0
        pub fn next(&mut self) -> u64 {
    56
            // Simple LCG for deterministic pseudo-random numbers
    57
    0
            self.state = self.state.wrapping_mul(6_364_136_223_846_793_005)
    58
    0
                .wrapping_add(1_442_695_040_888_963_407);
    59
    0
            self.state
    60
    0
        }
    61
    62
    0
        pub fn reset(&mut self) {
    63
    0
            self.state = self.seed;
    64
    0
        }
    65
    }
    66
    67
    /// Extension trait to make Repl deterministic
    68
    impl DeterministicRepl for Repl {
    69
    0
        fn execute_with_seed(&mut self, input: &str, _seed: u64) -> ReplayResult {
    70
            // Store current resource usage start point
    71
    0
            let start_heap = self.estimate_heap_usage();
    72
    0
            let start_stack = self.estimate_stack_depth();
    73
    0
            let start_time = std::time::Instant::now();
    74
    75
            // Note: RNG seed will be set when random number support is added to REPL
    76
            // Currently executes normally as REPL doesn't use RNG yet
    77
    78
            // Execute the input
    79
    0
            let output = self.eval(input).map(|s| {
    80
                // Convert string output back to Value
    81
                // This is a simplified conversion - in production we'd preserve the actual Value
    82
    0
                if s == "()" || s.is_empty() {
    83
    0
                    Value::Unit
    84
    0
                } else if s == "true" {
    85
    0
                    Value::Bool(true)
    86
    0
                } else if s == "false" {
    87
    0
                    Value::Bool(false)
    88
    0
                } else if let Ok(n) = s.parse::<i64>() {
    89
    0
                    Value::Int(n)
    90
    0
                } else if s.starts_with('"') && s.ends_with('"') {
    91
    0
                    Value::String(s[1..s.len()-1].to_string())
    92
                } else {
    93
                    // For complex types, we store as string representation
    94
    0
                    Value::String(s)
    95
                }
    96
    0
            });
    97
    98
            // Calculate resource usage
    99
    0
            let heap_bytes = self.estimate_heap_usage() - start_heap;
    100
    0
            let stack_depth = self.estimate_stack_depth() - start_stack;
    101
    0
            let cpu_ns = start_time.elapsed().as_nanos() as u64;
    102
    103
            // Compute state hash
    104
    0
            let state_hash = self.compute_state_hash();
    105
    106
    0
            ReplayResult {
    107
    0
                output,
    108
    0
                state_hash,
    109
    0
                resource_usage: ResourceUsage {
    110
    0
                    heap_bytes,
    111
    0
                    stack_depth,
    112
    0
                    cpu_ns,
    113
    0
                },
    114
    0
            }
    115
    0
        }
    116
    117
    0
        fn checkpoint(&self) -> StateCheckpoint {
    118
    0
            let mut bindings = HashMap::new();
    119
    0
            let type_environment = HashMap::new();
    120
    121
            // Extract all variable bindings
    122
    0
            for (name, value) in self.get_bindings() {
    123
    0
                bindings.insert(name.clone(), format!("{value:?}"));
    124
    0
            }
    125
    126
            // Extract type environment if available
    127
            // Type tracking will be implemented when static analysis is added
    128
    129
    0
            StateCheckpoint {
    130
    0
                bindings,
    131
    0
                type_environment,
    132
    0
                state_hash: self.compute_state_hash(),
    133
    0
                resource_usage: ResourceUsage {
    134
    0
                    heap_bytes: self.estimate_heap_usage(),
    135
    0
                    stack_depth: self.estimate_stack_depth(),
    136
    0
                    cpu_ns: 0, // Not meaningful for a checkpoint
    137
    0
                },
    138
    0
            }
    139
    0
        }
    140
    141
    0
        fn restore(&mut self, checkpoint: &StateCheckpoint) -> Result<()> {
    142
            // Clear current state
    143
    0
            self.clear_bindings();
    144
    145
            // Restore bindings
    146
    0
            let bindings = self.get_bindings_mut();
    147
    0
            for (name, value_str) in &checkpoint.bindings {
    148
                // This is simplified - in production we'd properly deserialize the values
    149
    0
                let value = if value_str == "Unit" {
    150
    0
                    Value::Unit
    151
    0
                } else if value_str == "true" {
    152
    0
                    Value::Bool(true)
    153
    0
                } else if value_str == "false" {
    154
    0
                    Value::Bool(false)
    155
    0
                } else if let Ok(n) = value_str.parse::<i64>() {
    156
    0
                    Value::Int(n)
    157
                } else {
    158
    0
                    Value::String(value_str.clone())
    159
                };
    160
    161
    0
                bindings.insert(name.clone(), value);
    162
            }
    163
    164
    0
            Ok(())
    165
    0
        }
    166
    167
    0
        fn validate_determinism(&self, other: &Self) -> ValidationResult {
    168
    0
            let mut divergences = vec![];
    169
    170
            // Compare variable bindings
    171
    0
            for (name, value) in self.get_bindings() {
    172
    0
                match other.get_bindings().get(name) {
    173
    0
                    Some(other_value) if value == other_value => {
    174
    0
                        // Values match, good
    175
    0
                    }
    176
    0
                    Some(other_value) => {
    177
    0
                        divergences.push(Divergence::State {
    178
    0
                            expected_hash: format!("{value:?}"),
    179
    0
                            actual_hash: format!("{other_value:?}"),
    180
    0
                        });
    181
    0
                    }
    182
    0
                    None => {
    183
    0
                        divergences.push(Divergence::State {
    184
    0
                            expected_hash: format!("{value:?}"),
    185
    0
                            actual_hash: "missing".to_string(),
    186
    0
                        });
    187
    0
                    }
    188
                }
    189
            }
    190
    191
            // Check for variables in other but not in self
    192
    0
            for name in other.get_bindings().keys() {
    193
    0
                if !self.get_bindings().contains_key(name) {
    194
    0
                    divergences.push(Divergence::State {
    195
    0
                        expected_hash: "missing".to_string(),
    196
    0
                        actual_hash: format!("variable: {name}"),
    197
    0
                    });
    198
    0
                }
    199
            }
    200
    201
    0
            ValidationResult {
    202
    0
                is_deterministic: divergences.is_empty(),
    203
    0
                divergences,
    204
    0
            }
    205
    0
        }
    206
    }
    207
    208
    // Helper methods for Repl
    209
    impl Repl {
    210
        /// Estimate heap usage in bytes (simplified)
    211
    0
        fn estimate_heap_usage(&self) -> usize {
    212
            // Rough estimate based on number of variables and their sizes
    213
    0
            let mut total = 0;
    214
    215
    0
            for value in self.get_bindings().values() {
    216
    0
                total += std::mem::size_of_val(value);
    217
    0
                total += match value {
    218
    0
                    Value::String(s) => s.capacity(),
    219
    0
                    Value::List(items) => items.len() * std::mem::size_of::<Value>(),
    220
    0
                    Value::Object(map) => map.len() * (32 + std::mem::size_of::<Value>()),
    221
    0
                    Value::HashMap(map) => map.len() * (std::mem::size_of::<Value>() * 2),
    222
    0
                    _ => 0,
    223
                };
    224
            }
    225
    226
    0
            total
    227
    0
        }
    228
    229
        /// Estimate current stack depth (simplified)
    230
    0
        fn estimate_stack_depth(&self) -> usize {
    231
            // This is a placeholder - real implementation would track actual call stack
    232
            // For now, we return a fixed estimate based on the number of bindings
    233
    0
            self.get_bindings().len() / 10 + 1
    234
    0
        }
    235
    236
        /// Compute a hash of the current state for comparison
    237
    0
        fn compute_state_hash(&self) -> String {
    238
    0
            let mut hasher = Sha256::new();
    239
    240
            // Sort variables by name for deterministic hashing
    241
    0
            let mut sorted_vars: Vec<_> = self.get_bindings().iter().collect();
    242
    0
            sorted_vars.sort_by_key(|(name, _)| name.as_str());
    243
    244
    0
            for (name, value) in sorted_vars {
    245
    0
                hasher.update(name.as_bytes());
    246
    0
                hasher.update(b":");
    247
    0
                hasher.update(format!("{value:?}").as_bytes());
    248
    0
                hasher.update(b";");
    249
    0
            }
    250
    251
    0
            format!("{:x}", hasher.finalize())
    252
    0
        }
    253
    }
    254
    255
    #[cfg(test)]
    256
    mod tests {
    257
        use super::*;
    258
    259
        #[test]
    260
        fn test_deterministic_execution() {
    261
            let mut repl1 = Repl::new().unwrap();
    262
            let mut repl2 = Repl::new().unwrap();
    263
    264
            // Execute same commands with same seed
    265
            let result1 = repl1.execute_with_seed("let x = 42", 12345);
    266
            let result2 = repl2.execute_with_seed("let x = 42", 12345);
    267
    268
            // Results should be identical
    269
            assert!(result1.output.is_ok());
    270
            assert!(result2.output.is_ok());
    271
            assert_eq!(result1.state_hash, result2.state_hash);
    272
        }
    273
    274
        #[test]
    275
        fn test_checkpoint_restore() {
    276
            let mut repl = Repl::new().unwrap();
    277
    278
            // Create some state
    279
            repl.eval("let x = 10").unwrap();
    280
            repl.eval("let y = 20").unwrap();
    281
    282
            // Create checkpoint using DeterministicRepl trait
    283
            let checkpoint = DeterministicRepl::checkpoint(&repl);
    284
    285
            // Modify state
    286
            repl.eval("let x = 99").unwrap();
    287
            repl.eval("let z = 30").unwrap();
    288
    289
            // Restore checkpoint
    290
            DeterministicRepl::restore(&mut repl, &checkpoint).unwrap();
    291
    292
            // Check that state was restored
    293
            // Note: Values are restored from debug format, so they may have different representation
    294
            assert!(repl.eval("x").is_ok());
    295
            assert!(repl.eval("y").is_ok());
    296
            // z should not exist after restore
    297
            assert!(repl.eval("z").is_err());
    298
        }
    299
    300
        #[test]
    301
        fn test_determinism_validation() {
    302
            let mut repl1 = Repl::new().unwrap();
    303
            let mut repl2 = Repl::new().unwrap();
    304
    305
            // Same operations
    306
            repl1.eval("let x = 1").unwrap();
    307
            repl2.eval("let x = 1").unwrap();
    308
    309
            let validation = repl1.validate_determinism(&repl2);
    310
            assert!(validation.is_deterministic);
    311
            assert!(validation.divergences.is_empty());
    312
    313
            // Different operations
    314
            repl1.eval("let y = 2").unwrap();
    315
            repl2.eval("let y = 3").unwrap();
    316
    317
            let validation = repl1.validate_determinism(&repl2);
    318
            assert!(!validation.is_deterministic);
    319
            assert!(!validation.divergences.is_empty());
    320
        }
    321
    }