use crate::runtime::interpreter::Value;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct EventId(pub u64);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SemVer {
major: u32,
minor: u32,
patch: u32,
}
impl SemVer {
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplSession {
pub version: SemVer,
pub metadata: SessionMetadata,
pub environment: Environment,
pub timeline: Vec<TimestampedEvent>,
pub checkpoints: BTreeMap<EventId, StateCheckpoint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionMetadata {
pub session_id: String,
pub created_at: String,
pub ruchy_version: String,
pub student_id: Option<String>,
pub assignment_id: Option<String>,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Environment {
pub seed: u64,
pub feature_flags: Vec<String>,
pub resource_limits: ResourceLimits,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLimits {
pub heap_mb: usize,
pub stack_kb: usize,
pub cpu_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimestampedEvent {
pub id: EventId,
pub timestamp_ns: u64,
pub event: Event,
pub causality: Vec<EventId>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Event {
Input {
text: String,
mode: InputMode,
},
Output {
result: EvalResult,
stdout: Vec<u8>,
stderr: Vec<u8>,
},
StateChange {
bindings_delta: HashMap<String, String>, state_hash: String,
},
ResourceUsage {
heap_bytes: usize,
stack_depth: usize,
cpu_ns: u64,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum InputMode {
Interactive,
Paste,
File,
Script,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EvalResult {
Success { value: String },
Error { message: String },
Unit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateCheckpoint {
pub bindings: HashMap<String, String>,
pub type_environment: HashMap<String, String>,
pub state_hash: String,
pub resource_usage: ResourceUsage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceUsage {
pub heap_bytes: usize,
pub stack_depth: usize,
pub cpu_ns: u64,
}
#[derive(Debug)]
pub struct ReplayResult {
pub output: Result<Value>,
pub state_hash: String,
pub resource_usage: ResourceUsage,
}
pub trait DeterministicRepl {
fn execute_with_seed(&mut self, input: &str, seed: u64) -> ReplayResult;
fn checkpoint(&self) -> StateCheckpoint;
fn restore(&mut self, checkpoint: &StateCheckpoint) -> Result<()>;
fn validate_determinism(&self, other: &Self) -> ValidationResult;
}
#[derive(Debug)]
pub struct ValidationResult {
pub is_deterministic: bool,
pub divergences: Vec<Divergence>,
}
#[derive(Debug, Clone)]
pub enum Divergence {
Output {
expected: String,
actual: String,
},
State {
expected_hash: String,
actual_hash: String,
},
Resources {
expected: ResourceUsage,
actual: ResourceUsage,
},
}
pub struct ReplayValidator {
pub strict_mode: bool,
pub tolerance: ResourceTolerance,
}
#[derive(Debug, Clone)]
pub struct ResourceTolerance {
pub heap_bytes_percent: f64,
pub cpu_ns_percent: f64,
}
impl Default for ResourceTolerance {
fn default() -> Self {
Self {
heap_bytes_percent: 10.0, cpu_ns_percent: 20.0, }
}
}
#[derive(Debug, Default)]
pub struct ValidationReport {
pub passed: bool,
pub total_events: usize,
pub successful_events: usize,
pub divergences: Vec<(EventId, Divergence)>,
}
impl ValidationReport {
pub fn new() -> Self {
Self::default()
}
pub fn add_divergence(&mut self, event_id: EventId, divergence: Divergence) {
self.divergences.push((event_id, divergence));
self.passed = false;
}
}
impl ReplayValidator {
pub fn new(strict_mode: bool) -> Self {
Self {
strict_mode,
tolerance: ResourceTolerance::default(),
}
}
pub fn validate_session(
&self,
recorded: &ReplSession,
implementation: &mut impl DeterministicRepl,
) -> ValidationReport {
let mut report = ValidationReport::new();
report.total_events = recorded.timeline.len();
for event in &recorded.timeline {
if let Event::Input { text, .. } = &event.event {
let result = implementation.execute_with_seed(text, recorded.environment.seed);
if let Some(expected_output) = self.find_next_output(recorded, event.id) {
if self.outputs_equivalent(&result, expected_output) {
report.successful_events += 1;
} else {
report.add_divergence(
event.id,
Divergence::Output {
expected: format!("{expected_output:?}"),
actual: format!("{:?}", result.output),
},
);
}
}
if !self.tolerance_accepts(&result.resource_usage) {
report.add_divergence(
event.id,
Divergence::Resources {
expected: ResourceUsage {
heap_bytes: 0,
stack_depth: 0,
cpu_ns: 0,
},
actual: result.resource_usage.clone(),
},
);
}
}
}
if report.divergences.is_empty() {
report.passed = true;
}
report
}
fn find_next_output<'a>(
&self,
session: &'a ReplSession,
after_id: EventId,
) -> Option<&'a Event> {
session
.timeline
.iter()
.find(|e| e.id > after_id && matches!(e.event, Event::Output { .. }))
.map(|e| &e.event)
}
fn outputs_equivalent(&self, result: &ReplayResult, expected: &Event) -> bool {
match expected {
Event::Output {
result: expected_result,
..
} => match (&result.output, expected_result) {
(Ok(value), EvalResult::Success { value: expected }) => {
format!("{value:?}") == *expected
}
(Err(e), EvalResult::Error { message }) => e.to_string().contains(message),
_ => false,
},
_ => false,
}
}
fn tolerance_accepts(&self, _usage: &ResourceUsage) -> bool {
true
}
}
pub struct SessionRecorder {
session: ReplSession,
next_event_id: u64,
start_time: Instant,
}
impl SessionRecorder {
pub fn new(metadata: SessionMetadata) -> Self {
use std::time::SystemTime;
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let session_hash = {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
std::hash::Hasher::write(&mut hasher, metadata.session_id.as_bytes());
std::hash::Hasher::finish(&hasher)
};
let unique_seed = now.wrapping_add(session_hash);
Self {
session: ReplSession {
version: SemVer::new(1, 0, 0),
metadata,
environment: Environment {
seed: unique_seed,
feature_flags: vec![],
resource_limits: ResourceLimits {
heap_mb: 100,
stack_kb: 8192,
cpu_ms: 5000,
},
},
timeline: vec![],
checkpoints: BTreeMap::new(),
},
next_event_id: 1,
start_time: Instant::now(),
}
}
pub fn record_input(&mut self, text: String, mode: InputMode) -> EventId {
let id = EventId(self.next_event_id);
self.next_event_id += 1;
let event = TimestampedEvent {
id,
timestamp_ns: self.elapsed_ns(),
event: Event::Input { text, mode },
causality: vec![],
};
self.session.timeline.push(event);
id
}
pub fn record_output(&mut self, result: Result<Value>) -> EventId {
let id = EventId(self.next_event_id);
self.next_event_id += 1;
let eval_result = match result {
Ok(Value::Nil) => EvalResult::Unit,
Ok(value) => EvalResult::Success {
value: format!("{value:?}"),
},
Err(e) => EvalResult::Error {
message: e.to_string(),
},
};
let event = TimestampedEvent {
id,
timestamp_ns: self.elapsed_ns(),
event: Event::Output {
result: eval_result,
stdout: vec![],
stderr: vec![],
},
causality: vec![],
};
self.session.timeline.push(event);
id
}
pub fn add_checkpoint(&mut self, event_id: EventId, checkpoint: StateCheckpoint) {
self.session.checkpoints.insert(event_id, checkpoint);
}
pub fn get_session(&self) -> &ReplSession {
&self.session
}
pub fn into_session(self) -> ReplSession {
self.session
}
fn elapsed_ns(&self) -> u64 {
self.start_time.elapsed().as_nanos() as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn create_test_metadata() -> SessionMetadata {
SessionMetadata {
session_id: "test-001".to_string(),
created_at: "2025-08-28T10:00:00Z".to_string(),
ruchy_version: "1.23.0".to_string(),
student_id: Some("student-123".to_string()),
assignment_id: Some("assignment-456".to_string()),
tags: vec!["test".to_string(), "development".to_string()],
}
}
fn create_test_environment() -> Environment {
Environment {
seed: 12345,
feature_flags: vec![],
resource_limits: ResourceLimits {
heap_mb: 1024,
stack_kb: 256,
cpu_ms: 5000,
},
}
}
fn create_test_checkpoint() -> StateCheckpoint {
let mut bindings = HashMap::new();
bindings.insert("x".to_string(), "42".to_string());
bindings.insert("y".to_string(), "\"hello\"".to_string());
let mut type_environment = HashMap::new();
type_environment.insert("x".to_string(), "Int".to_string());
type_environment.insert("y".to_string(), "String".to_string());
StateCheckpoint {
bindings,
type_environment,
state_hash: "abc123def456".to_string(),
resource_usage: ResourceUsage {
heap_bytes: 1024,
stack_depth: 5,
cpu_ns: 1_000_000,
},
}
}
#[cfg(test)]
mod enabled_tests {
use super::*;
#[test]
fn test_semver_creation() {
let version = SemVer::new(1, 2, 3);
assert_eq!(version.major, 1);
assert_eq!(version.minor, 2);
assert_eq!(version.patch, 3);
let version2 = SemVer::new(1, 2, 3);
assert_eq!(version, version2);
let version3 = SemVer::new(1, 2, 4);
assert_ne!(version, version3);
}
#[test]
fn test_session_metadata() {
let metadata = create_test_metadata();
assert_eq!(metadata.session_id, "test-001");
assert_eq!(metadata.ruchy_version, "1.23.0");
assert_eq!(metadata.student_id, Some("student-123".to_string()));
assert_eq!(metadata.assignment_id, Some("assignment-456".to_string()));
assert_eq!(metadata.tags.len(), 2);
assert!(metadata.tags.contains(&"test".to_string()));
assert!(metadata.tags.contains(&"development".to_string()));
let json = serde_json::to_string(&metadata).expect("operation should succeed in test");
let deserialized: SessionMetadata =
serde_json::from_str(&json).expect("operation should succeed in test");
assert_eq!(metadata.session_id, deserialized.session_id);
assert_eq!(metadata.student_id, deserialized.student_id);
}
#[test]
fn test_environment() {
let environment = create_test_environment();
assert_eq!(environment.seed, 12345);
assert!(environment.feature_flags.is_empty());
assert_eq!(environment.resource_limits.heap_mb, 1024);
let json =
serde_json::to_string(&environment).expect("operation should succeed in test");
let deserialized: Environment =
serde_json::from_str(&json).expect("operation should succeed in test");
assert_eq!(environment.seed, deserialized.seed);
}
#[test]
fn test_event_id_ordering() {
let id1 = EventId(1);
let id2 = EventId(2);
let id3 = EventId(1);
assert!(id1 < id2);
assert!(id2 > id1);
assert_eq!(id1, id3);
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(id1);
set.insert(id2);
set.insert(id3); assert_eq!(set.len(), 2);
}
#[test]
fn test_resource_usage() {
let usage = ResourceUsage {
heap_bytes: 2048,
stack_depth: 10,
cpu_ns: 5_000_000,
};
assert_eq!(usage.heap_bytes, 2048);
assert_eq!(usage.stack_depth, 10);
assert_eq!(usage.cpu_ns, 5_000_000);
let json = serde_json::to_string(&usage).expect("operation should succeed in test");
let deserialized: ResourceUsage =
serde_json::from_str(&json).expect("operation should succeed in test");
assert_eq!(usage.heap_bytes, deserialized.heap_bytes);
assert_eq!(usage.stack_depth, deserialized.stack_depth);
assert_eq!(usage.cpu_ns, deserialized.cpu_ns);
}
#[test]
fn test_state_checkpoint() {
let checkpoint = create_test_checkpoint();
assert_eq!(checkpoint.bindings.len(), 2);
assert_eq!(checkpoint.bindings.get("x"), Some(&"42".to_string()));
assert_eq!(checkpoint.bindings.get("y"), Some(&"\"hello\"".to_string()));
assert_eq!(checkpoint.type_environment.len(), 2);
assert_eq!(
checkpoint.type_environment.get("x"),
Some(&"Int".to_string())
);
assert_eq!(
checkpoint.type_environment.get("y"),
Some(&"String".to_string())
);
assert_eq!(checkpoint.state_hash, "abc123def456");
assert_eq!(checkpoint.resource_usage.heap_bytes, 1024);
let json =
serde_json::to_string(&checkpoint).expect("operation should succeed in test");
let deserialized: StateCheckpoint =
serde_json::from_str(&json).expect("operation should succeed in test");
assert_eq!(checkpoint.state_hash, deserialized.state_hash);
assert_eq!(checkpoint.bindings.len(), deserialized.bindings.len());
}
#[test]
fn test_eval_result_variants() {
let success = EvalResult::Success {
value: "42".to_string(),
};
match success {
EvalResult::Success { ref value } => assert_eq!(value, "42"),
_ => panic!("Expected Success variant"),
}
let error = EvalResult::Error {
message: "Division by zero".to_string(),
};
match error {
EvalResult::Error { message } => assert_eq!(message, "Division by zero"),
_ => panic!("Expected Error variant"),
}
let unit = EvalResult::Unit;
match unit {
EvalResult::Unit => {}
_ => panic!("Expected Unit variant"),
}
let json = serde_json::to_string(&success).expect("operation should succeed in test");
let deserialized: EvalResult =
serde_json::from_str(&json).expect("operation should succeed in test");
match deserialized {
EvalResult::Success { value } => assert_eq!(value, "42"),
_ => panic!("Deserialization failed"),
}
}
#[test]
fn test_session_recorder_basic() {
let metadata = create_test_metadata();
let mut recorder = SessionRecorder::new(metadata);
let session = recorder.get_session();
assert_eq!(session.metadata.session_id, "test-001");
assert_eq!(session.timeline.len(), 0);
assert_eq!(session.checkpoints.len(), 0);
let input_id = recorder.record_input("let x = 42".to_string(), InputMode::Interactive);
assert_eq!(input_id, EventId(1));
let output_id = recorder.record_output(Ok(Value::Nil));
assert_eq!(output_id, EventId(2));
let session = recorder.get_session();
assert_eq!(session.timeline.len(), 2);
}
#[test]
fn test_session_recorder_sequential() {
let metadata = create_test_metadata();
let mut recorder = SessionRecorder::new(metadata);
let inputs = ["let x = 1", "let y = 2", "x + y", "println(x + y)"];
let mut event_ids = Vec::new();
for (i, input) in inputs.iter().enumerate() {
let input_id = recorder.record_input((*input).to_string(), InputMode::Interactive);
event_ids.push(input_id);
assert_eq!(input_id, EventId((i as u64 * 2) + 1));
let output_id = recorder.record_output(Ok(Value::Integer(i as i64 + 1)));
event_ids.push(output_id);
assert_eq!(output_id, EventId((i as u64 * 2) + 2));
}
let session = recorder.get_session();
assert_eq!(session.timeline.len(), 8); assert_eq!(event_ids.len(), 8);
}
#[test]
fn test_session_recorder_with_checkpoints() {
let metadata = create_test_metadata();
let mut recorder = SessionRecorder::new(metadata);
let _input_id1 =
recorder.record_input("let x = 42".to_string(), InputMode::Interactive);
let output_id1 = recorder.record_output(Ok(Value::Nil));
let checkpoint1 = create_test_checkpoint();
recorder.add_checkpoint(output_id1, checkpoint1);
let _input_id2 = recorder.record_input("x * 2".to_string(), InputMode::Interactive);
let output_id2 = recorder.record_output(Ok(Value::Integer(84)));
let mut checkpoint2 = create_test_checkpoint();
checkpoint2.state_hash = "def456abc789".to_string();
recorder.add_checkpoint(output_id2, checkpoint2);
let session = recorder.get_session();
assert_eq!(session.checkpoints.len(), 2);
assert!(session.checkpoints.contains_key(&output_id1));
assert!(session.checkpoints.contains_key(&output_id2));
let retrieved_checkpoint1 = session
.checkpoints
.get(&output_id1)
.expect("operation should succeed in test");
assert_eq!(retrieved_checkpoint1.state_hash, "abc123def456");
let retrieved_checkpoint2 = session
.checkpoints
.get(&output_id2)
.expect("operation should succeed in test");
assert_eq!(retrieved_checkpoint2.state_hash, "def456abc789");
}
#[test]
fn test_input_modes() {
let metadata = create_test_metadata();
let mut recorder = SessionRecorder::new(metadata);
let _interactive_id =
recorder.record_input("2 + 2".to_string(), InputMode::Interactive);
let _batch_id =
recorder.record_input("let batch = true".to_string(), InputMode::Script);
let session = recorder.get_session();
assert_eq!(session.timeline.len(), 2);
for event in &session.timeline {
if let Event::Input { mode, .. } = &event.event {
assert!(matches!(mode, InputMode::Interactive | InputMode::Script));
}
}
}
#[test]
fn test_session_conversion() {
let metadata = create_test_metadata();
let mut recorder = SessionRecorder::new(metadata);
recorder.record_input("test input".to_string(), InputMode::Interactive);
recorder.record_output(Ok(Value::from_string("test output".to_string())));
{
let session_ref = recorder.get_session();
assert_eq!(session_ref.metadata.session_id, "test-001");
assert_eq!(session_ref.timeline.len(), 2);
}
let session = recorder.into_session();
assert_eq!(session.metadata.session_id, "test-001");
assert_eq!(session.timeline.len(), 2);
assert_eq!(session.checkpoints.len(), 0);
}
#[test]
fn test_validation_report() {
let mut report = ValidationReport::new();
assert!(report.divergences.is_empty());
let divergence1 = Divergence::Output {
expected: "42".to_string(),
actual: "43".to_string(),
};
let divergence2 = Divergence::State {
expected_hash: "abc123".to_string(),
actual_hash: "def456".to_string(),
};
report.add_divergence(EventId(1), divergence1);
report.add_divergence(EventId(2), divergence2);
assert_eq!(report.divergences.len(), 2);
assert!(report.divergences.iter().any(|(id, _)| *id == EventId(1)));
assert!(report.divergences.iter().any(|(id, _)| *id == EventId(2)));
}
#[test]
fn test_session_with_errors() {
let metadata = create_test_metadata();
let mut recorder = SessionRecorder::new(metadata);
recorder.record_input("let x = 42".to_string(), InputMode::Interactive);
recorder.record_output(Ok(Value::Nil));
recorder.record_input("x / 0".to_string(), InputMode::Interactive);
recorder.record_output(Err(anyhow::anyhow!("Division by zero")));
recorder.record_input("x / 2".to_string(), InputMode::Interactive);
recorder.record_output(Ok(Value::Integer(21)));
let session = recorder.get_session();
assert_eq!(session.timeline.len(), 6);
let error_output = &session.timeline[3]; match &error_output.event {
Event::Output { result, .. } => {
match result {
EvalResult::Error { .. } => {} _ => panic!("Expected error result"),
}
}
_ => panic!("Expected output event"),
}
}
#[test]
fn test_resource_tolerance() {
let tolerance = ResourceTolerance::default();
assert!(tolerance.heap_bytes_percent > 0.0);
assert!(tolerance.cpu_ns_percent > 0.0);
let custom_tolerance = ResourceTolerance {
heap_bytes_percent: 15.0,
cpu_ns_percent: 25.0,
};
assert_eq!(custom_tolerance.heap_bytes_percent, 15.0);
assert_eq!(custom_tolerance.cpu_ns_percent, 25.0);
}
#[test]
fn test_complete_session_serialization() {
let metadata = create_test_metadata();
let mut recorder = SessionRecorder::new(metadata);
recorder.record_input("let x = 42".to_string(), InputMode::Interactive);
recorder.record_output(Ok(Value::Integer(42)));
let checkpoint = create_test_checkpoint();
recorder.add_checkpoint(EventId(2), checkpoint);
let session = recorder.into_session();
let json = serde_json::to_string(&session).expect("operation should succeed in test");
let deserialized: ReplSession =
serde_json::from_str(&json).expect("operation should succeed in test");
assert_eq!(
session.metadata.session_id,
deserialized.metadata.session_id
);
assert_eq!(session.timeline.len(), deserialized.timeline.len());
assert_eq!(session.checkpoints.len(), deserialized.checkpoints.len());
assert_eq!(session.environment.seed, deserialized.environment.seed);
assert_eq!(session.version, deserialized.version);
}
#[test]
fn test_replay_validator_creation() {
let validator = ReplayValidator::new(true);
assert!(validator.strict_mode);
let loose_validator = ReplayValidator::new(false);
assert!(!loose_validator.strict_mode);
}
#[test]
fn test_event_input_creation() {
let event = Event::Input {
text: "let x = 42".to_string(),
mode: InputMode::Interactive,
};
match event {
Event::Input { text, mode } => {
assert_eq!(text, "let x = 42");
assert_eq!(mode, InputMode::Interactive);
}
_ => panic!("Expected Input event"),
}
}
#[test]
fn test_event_output_creation() {
let event = Event::Output {
result: EvalResult::Success {
value: "42".to_string(),
},
stdout: vec![b'h', b'e', b'l', b'l', b'o'],
stderr: vec![],
};
match event {
Event::Output {
result,
stdout,
stderr,
} => {
match result {
EvalResult::Success { value } => assert_eq!(value, "42"),
_ => panic!("Expected success"),
}
assert_eq!(stdout, vec![b'h', b'e', b'l', b'l', b'o']);
assert!(stderr.is_empty());
}
_ => panic!("Expected Output event"),
}
}
#[test]
fn test_event_state_change() {
let mut bindings_delta = HashMap::new();
bindings_delta.insert("x".to_string(), "42".to_string());
let event = Event::StateChange {
bindings_delta: bindings_delta.clone(),
state_hash: "abc123".to_string(),
};
match event {
Event::StateChange {
bindings_delta: delta,
state_hash,
} => {
assert_eq!(delta.get("x"), Some(&"42".to_string()));
assert_eq!(state_hash, "abc123");
}
_ => panic!("Expected StateChange event"),
}
}
#[test]
fn test_event_resource_usage() {
let event = Event::ResourceUsage {
heap_bytes: 1024,
stack_depth: 10,
cpu_ns: 1_000_000,
};
match event {
Event::ResourceUsage {
heap_bytes,
stack_depth,
cpu_ns,
} => {
assert_eq!(heap_bytes, 1024);
assert_eq!(stack_depth, 10);
assert_eq!(cpu_ns, 1_000_000);
}
_ => panic!("Expected ResourceUsage event"),
}
}
#[test]
fn test_input_mode_variants() {
let modes = [
InputMode::Interactive,
InputMode::Paste,
InputMode::File,
InputMode::Script,
];
for i in 0..modes.len() {
for j in (i + 1)..modes.len() {
assert_ne!(modes[i], modes[j]);
}
}
for mode in &modes {
let json = serde_json::to_string(mode).expect("operation should succeed in test");
let deserialized: InputMode =
serde_json::from_str(&json).expect("operation should succeed in test");
assert_eq!(*mode, deserialized);
}
}
#[test]
fn test_divergence_output() {
let divergence = Divergence::Output {
expected: "42".to_string(),
actual: "43".to_string(),
};
match divergence {
Divergence::Output { expected, actual } => {
assert_eq!(expected, "42");
assert_eq!(actual, "43");
}
_ => panic!("Expected Output divergence"),
}
}
#[test]
fn test_divergence_state() {
let divergence = Divergence::State {
expected_hash: "abc123".to_string(),
actual_hash: "def456".to_string(),
};
match divergence {
Divergence::State {
expected_hash,
actual_hash,
} => {
assert_eq!(expected_hash, "abc123");
assert_eq!(actual_hash, "def456");
}
_ => panic!("Expected State divergence"),
}
}
#[test]
fn test_divergence_resources() {
let expected = ResourceUsage {
heap_bytes: 1024,
stack_depth: 5,
cpu_ns: 1_000_000,
};
let actual = ResourceUsage {
heap_bytes: 2048,
stack_depth: 10,
cpu_ns: 2_000_000,
};
let divergence = Divergence::Resources {
expected: expected.clone(),
actual: actual.clone(),
};
match divergence {
Divergence::Resources {
expected: exp,
actual: act,
} => {
assert_eq!(exp.heap_bytes, 1024);
assert_eq!(act.heap_bytes, 2048);
}
_ => panic!("Expected Resources divergence"),
}
}
#[test]
fn test_timestamped_event() {
let event = TimestampedEvent {
id: EventId(1),
timestamp_ns: 1_000_000_000,
event: Event::Input {
text: "test".to_string(),
mode: InputMode::Interactive,
},
causality: vec![],
};
assert_eq!(event.id, EventId(1));
assert_eq!(event.timestamp_ns, 1_000_000_000);
assert!(event.causality.is_empty());
}
#[test]
fn test_timestamped_event_with_causality() {
let event = TimestampedEvent {
id: EventId(3),
timestamp_ns: 3_000_000_000,
event: Event::Output {
result: EvalResult::Unit,
stdout: vec![],
stderr: vec![],
},
causality: vec![EventId(1), EventId(2)],
};
assert_eq!(event.id, EventId(3));
assert_eq!(event.causality.len(), 2);
assert!(event.causality.contains(&EventId(1)));
assert!(event.causality.contains(&EventId(2)));
}
#[test]
fn test_repl_session_structure() {
let session = ReplSession {
version: SemVer::new(1, 0, 0),
metadata: create_test_metadata(),
environment: create_test_environment(),
timeline: vec![],
checkpoints: BTreeMap::new(),
};
assert_eq!(session.version, SemVer::new(1, 0, 0));
assert_eq!(session.metadata.session_id, "test-001");
assert_eq!(session.environment.seed, 12345);
assert!(session.timeline.is_empty());
assert!(session.checkpoints.is_empty());
}
#[test]
fn test_validation_report_passed() {
let mut report = ValidationReport::new();
report.total_events = 10;
report.successful_events = 10;
report.passed = true;
assert!(report.passed);
assert_eq!(report.total_events, 10);
assert_eq!(report.successful_events, 10);
assert!(report.divergences.is_empty());
}
#[test]
fn test_session_recorder_timestamps() {
let metadata = create_test_metadata();
let mut recorder = SessionRecorder::new(metadata);
recorder.record_input("first".to_string(), InputMode::Interactive);
std::thread::sleep(std::time::Duration::from_millis(1));
recorder.record_input("second".to_string(), InputMode::Interactive);
let session = recorder.get_session();
assert_eq!(session.timeline.len(), 2);
let ts1 = session.timeline[0].timestamp_ns;
let ts2 = session.timeline[1].timestamp_ns;
assert!(ts2 > ts1, "Second timestamp should be greater than first");
}
#[test]
fn test_session_recorder_unique_seeds() {
let metadata1 = SessionMetadata {
session_id: "session-1".to_string(),
created_at: "2025-01-01T00:00:00Z".to_string(),
ruchy_version: "1.0.0".to_string(),
student_id: None,
assignment_id: None,
tags: vec![],
};
let metadata2 = SessionMetadata {
session_id: "session-2".to_string(),
created_at: "2025-01-01T00:00:00Z".to_string(),
ruchy_version: "1.0.0".to_string(),
student_id: None,
assignment_id: None,
tags: vec![],
};
let recorder1 = SessionRecorder::new(metadata1);
let recorder2 = SessionRecorder::new(metadata2);
assert_ne!(
recorder1.get_session().environment.seed,
recorder2.get_session().environment.seed
);
}
#[test]
fn test_resource_limits() {
let limits = ResourceLimits {
heap_mb: 512,
stack_kb: 4096,
cpu_ms: 10000,
};
assert_eq!(limits.heap_mb, 512);
assert_eq!(limits.stack_kb, 4096);
assert_eq!(limits.cpu_ms, 10000);
let json = serde_json::to_string(&limits).expect("operation should succeed in test");
let deserialized: ResourceLimits =
serde_json::from_str(&json).expect("operation should succeed in test");
assert_eq!(limits.heap_mb, deserialized.heap_mb);
assert_eq!(limits.stack_kb, deserialized.stack_kb);
assert_eq!(limits.cpu_ms, deserialized.cpu_ms);
}
#[test]
fn test_semver_clone_r163() {
let version = SemVer::new(2, 3, 4);
let cloned = version.clone();
assert_eq!(version, cloned);
assert_eq!(cloned.major, 2);
assert_eq!(cloned.minor, 3);
assert_eq!(cloned.patch, 4);
}
#[test]
fn test_semver_debug_r163() {
let version = SemVer::new(1, 2, 3);
let debug_str = format!("{:?}", version);
assert!(debug_str.contains("SemVer"));
assert!(debug_str.contains("major"));
assert!(debug_str.contains("minor"));
assert!(debug_str.contains("patch"));
}
#[test]
fn test_event_id_debug_r163() {
let id = EventId(42);
let debug_str = format!("{:?}", id);
assert!(debug_str.contains("EventId"));
assert!(debug_str.contains("42"));
}
#[test]
fn test_event_id_clone_r163() {
let id = EventId(100);
let cloned = id.clone();
assert_eq!(id, cloned);
assert_eq!(cloned.0, 100);
}
#[test]
fn test_event_id_copy_r163() {
let id = EventId(50);
let copied = id; assert_eq!(id, copied); }
#[test]
fn test_resource_usage_clone_r163() {
let usage = ResourceUsage {
heap_bytes: 4096,
stack_depth: 20,
cpu_ns: 5_000_000,
};
let cloned = usage.clone();
assert_eq!(usage.heap_bytes, cloned.heap_bytes);
assert_eq!(usage.stack_depth, cloned.stack_depth);
assert_eq!(usage.cpu_ns, cloned.cpu_ns);
}
#[test]
fn test_resource_tolerance_clone_r163() {
let tolerance = ResourceTolerance {
heap_bytes_percent: 15.0,
cpu_ns_percent: 25.0,
};
let cloned = tolerance.clone();
assert!(
(tolerance.heap_bytes_percent - cloned.heap_bytes_percent).abs() < f64::EPSILON
);
assert!((tolerance.cpu_ns_percent - cloned.cpu_ns_percent).abs() < f64::EPSILON);
}
#[test]
fn test_validation_report_default_r163() {
let report = ValidationReport::default();
assert!(!report.passed);
assert_eq!(report.total_events, 0);
assert_eq!(report.successful_events, 0);
assert!(report.divergences.is_empty());
}
#[test]
fn test_state_checkpoint_clone_r163() {
let checkpoint = create_test_checkpoint();
let cloned = checkpoint.clone();
assert_eq!(checkpoint.state_hash, cloned.state_hash);
assert_eq!(checkpoint.bindings.len(), cloned.bindings.len());
assert_eq!(
checkpoint.type_environment.len(),
cloned.type_environment.len()
);
}
#[test]
fn test_environment_clone_r163() {
let env = create_test_environment();
let cloned = env.clone();
assert_eq!(env.seed, cloned.seed);
assert_eq!(env.feature_flags.len(), cloned.feature_flags.len());
}
#[test]
fn test_session_metadata_clone_r163() {
let metadata = create_test_metadata();
let cloned = metadata.clone();
assert_eq!(metadata.session_id, cloned.session_id);
assert_eq!(metadata.student_id, cloned.student_id);
assert_eq!(metadata.tags.len(), cloned.tags.len());
}
#[test]
fn test_repl_session_clone_r163() {
let session = ReplSession {
version: SemVer::new(1, 0, 0),
metadata: create_test_metadata(),
environment: create_test_environment(),
timeline: vec![],
checkpoints: BTreeMap::new(),
};
let cloned = session.clone();
assert_eq!(session.version, cloned.version);
assert_eq!(session.metadata.session_id, cloned.metadata.session_id);
}
#[test]
fn test_divergence_clone_r163() {
let divergence = Divergence::Output {
expected: "foo".to_string(),
actual: "bar".to_string(),
};
let cloned = divergence.clone();
match cloned {
Divergence::Output { expected, actual } => {
assert_eq!(expected, "foo");
assert_eq!(actual, "bar");
}
_ => panic!("Expected Output divergence"),
}
}
#[test]
fn test_timestamped_event_clone_r163() {
let event = TimestampedEvent {
id: EventId(5),
timestamp_ns: 123456789,
event: Event::Input {
text: "test".to_string(),
mode: InputMode::File,
},
causality: vec![EventId(1), EventId(2)],
};
let cloned = event.clone();
assert_eq!(event.id, cloned.id);
assert_eq!(event.timestamp_ns, cloned.timestamp_ns);
assert_eq!(event.causality.len(), cloned.causality.len());
}
#[test]
fn test_eval_result_clone_r163() {
let result = EvalResult::Success {
value: "42".to_string(),
};
let cloned = result.clone();
match cloned {
EvalResult::Success { value } => assert_eq!(value, "42"),
_ => panic!("Expected Success"),
}
}
#[test]
fn test_event_clone_input_r163() {
let event = Event::Input {
text: "let x = 1".to_string(),
mode: InputMode::Paste,
};
let cloned = event.clone();
match cloned {
Event::Input { text, mode } => {
assert_eq!(text, "let x = 1");
assert_eq!(mode, InputMode::Paste);
}
_ => panic!("Expected Input event"),
}
}
#[test]
fn test_event_clone_output_r163() {
let event = Event::Output {
result: EvalResult::Unit,
stdout: vec![1, 2, 3],
stderr: vec![4, 5],
};
let cloned = event.clone();
match cloned {
Event::Output {
result,
stdout,
stderr,
} => {
assert!(matches!(result, EvalResult::Unit));
assert_eq!(stdout, vec![1, 2, 3]);
assert_eq!(stderr, vec![4, 5]);
}
_ => panic!("Expected Output event"),
}
}
#[test]
fn test_semver_serialization_roundtrip_r163() {
let version = SemVer::new(3, 14, 159);
let json = serde_json::to_string(&version).expect("should serialize");
let deserialized: SemVer = serde_json::from_str(&json).expect("should deserialize");
assert_eq!(version, deserialized);
}
#[test]
fn test_event_id_serialization_roundtrip_r163() {
let id = EventId(999);
let json = serde_json::to_string(&id).expect("should serialize");
let deserialized: EventId = serde_json::from_str(&json).expect("should deserialize");
assert_eq!(id, deserialized);
}
#[test]
fn test_resource_usage_debug_r163() {
let usage = ResourceUsage {
heap_bytes: 1024,
stack_depth: 10,
cpu_ns: 1000000,
};
let debug_str = format!("{:?}", usage);
assert!(debug_str.contains("ResourceUsage"));
assert!(debug_str.contains("heap_bytes"));
assert!(debug_str.contains("stack_depth"));
assert!(debug_str.contains("cpu_ns"));
}
#[test]
fn test_resource_tolerance_debug_r163() {
let tolerance = ResourceTolerance::default();
let debug_str = format!("{:?}", tolerance);
assert!(debug_str.contains("ResourceTolerance"));
assert!(debug_str.contains("heap_bytes_percent"));
assert!(debug_str.contains("cpu_ns_percent"));
}
#[test]
fn test_validation_report_debug_r163() {
let report = ValidationReport::new();
let debug_str = format!("{:?}", report);
assert!(debug_str.contains("ValidationReport"));
assert!(debug_str.contains("passed"));
assert!(debug_str.contains("total_events"));
}
#[test]
fn test_replay_result_debug_r163() {
let result = ReplayResult {
output: Ok(Value::Integer(42)),
state_hash: "hash123".to_string(),
resource_usage: ResourceUsage {
heap_bytes: 100,
stack_depth: 5,
cpu_ns: 1000,
},
};
let debug_str = format!("{:?}", result);
assert!(debug_str.contains("ReplayResult"));
assert!(debug_str.contains("output"));
assert!(debug_str.contains("state_hash"));
}
#[test]
fn test_validation_result_debug_r163() {
let result = ValidationResult {
is_deterministic: true,
divergences: vec![],
};
let debug_str = format!("{:?}", result);
assert!(debug_str.contains("ValidationResult"));
assert!(debug_str.contains("is_deterministic"));
assert!(debug_str.contains("divergences"));
}
#[test]
fn test_state_checkpoint_debug_r163() {
let checkpoint = create_test_checkpoint();
let debug_str = format!("{:?}", checkpoint);
assert!(debug_str.contains("StateCheckpoint"));
assert!(debug_str.contains("bindings"));
assert!(debug_str.contains("state_hash"));
}
#[test]
fn test_empty_session_timeline_r163() {
let session = ReplSession {
version: SemVer::new(1, 0, 0),
metadata: create_test_metadata(),
environment: create_test_environment(),
timeline: vec![],
checkpoints: BTreeMap::new(),
};
assert!(session.timeline.is_empty());
assert_eq!(session.timeline.len(), 0);
}
#[test]
fn test_session_with_feature_flags_r163() {
let env = Environment {
seed: 54321,
feature_flags: vec!["experimental".to_string(), "debug".to_string()],
resource_limits: ResourceLimits {
heap_mb: 256,
stack_kb: 128,
cpu_ms: 1000,
},
};
assert_eq!(env.feature_flags.len(), 2);
assert!(env.feature_flags.contains(&"experimental".to_string()));
assert!(env.feature_flags.contains(&"debug".to_string()));
}
#[test]
fn test_validation_report_multiple_divergences_r163() {
let mut report = ValidationReport::new();
report.total_events = 5;
report.add_divergence(
EventId(1),
Divergence::Output {
expected: "1".to_string(),
actual: "2".to_string(),
},
);
report.add_divergence(
EventId(2),
Divergence::State {
expected_hash: "a".to_string(),
actual_hash: "b".to_string(),
},
);
report.add_divergence(
EventId(3),
Divergence::Resources {
expected: ResourceUsage {
heap_bytes: 100,
stack_depth: 1,
cpu_ns: 100,
},
actual: ResourceUsage {
heap_bytes: 200,
stack_depth: 2,
cpu_ns: 200,
},
},
);
assert_eq!(report.divergences.len(), 3);
assert!(!report.passed);
}
} }