//! Property-based tests for Intel 400x state machine verification //! //! These tests use property-based testing to verify the correctness //! of state machine behavior, timing properties, and system invariants. use crate::mocks::*; use proptest::prelude::*; use rusty_emu::components::common::intel_400x::*; use rusty_emu::pin::PinValue; use std::time::{Duration, Instant}; #[cfg(test)] mod state_machine_properties { use super::*; proptest! { #[test] fn test_timing_state_invariants( state in proptest_helpers::arb_timing_state() ) { // Test that state queries are consistent match state { TimingState::Idle => { prop_assert!(state.is_idle()); prop_assert!(!state.is_address_phase()); prop_assert!(!state.is_waiting_latency()); prop_assert!(!state.is_driving_data()); } TimingState::AddressPhase => { prop_assert!(!state.is_idle()); prop_assert!(state.is_address_phase()); prop_assert!(!state.is_waiting_latency()); prop_assert!(!state.is_driving_data()); } TimingState::WaitLatency => { prop_assert!(!state.is_idle()); prop_assert!(!state.is_address_phase()); prop_assert!(state.is_waiting_latency()); prop_assert!(!state.is_driving_data()); } TimingState::DriveData => { prop_assert!(!state.is_idle()); prop_assert!(!state.is_address_phase()); prop_assert!(!state.is_waiting_latency()); prop_assert!(state.is_driving_data()); } } } #[test] fn test_memory_state_conversions_roundtrip( mem_state in proptest_helpers::arb_memory_state() ) { // Test that conversions to TimingState and back preserve the state let timing_state: TimingState = mem_state.into(); let back_to_mem: MemoryState = timing_state.into(); // They should be equivalent (though not necessarily identical due to enum differences) match (&mem_state, &back_to_mem) { (MemoryState::Idle, MemoryState::Idle) => prop_assert!(true), (MemoryState::AddressPhase, MemoryState::AddressPhase) => prop_assert!(true), (MemoryState::WaitLatency, MemoryState::WaitLatency) => prop_assert!(true), (MemoryState::DriveData, MemoryState::DriveData) => prop_assert!(true), _ => prop_assert!(false, "State conversion mismatch: {:?} -> {:?}", mem_state, back_to_mem), } } #[test] fn test_ram_state_conversions_roundtrip( ram_state in proptest_helpers::arb_ram_state() ) { // Test that conversions to TimingState and back work correctly let timing_state: TimingState = ram_state.into(); let back_to_ram: RamState = timing_state.into(); // Verify the conversion maintains the essential state information match (&ram_state, &back_to_ram) { (RamState::Idle, RamState::Idle) => prop_assert!(true), (RamState::AddressPhase, RamState::AddressPhase) => prop_assert!(true), (RamState::WaitLatency, RamState::WaitLatency) => prop_assert!(true), // ReadData, WriteData, and OutputPort all map to DriveData in TimingState (RamState::ReadData, RamState::ReadData) => prop_assert!(true), (RamState::WriteData, RamState::ReadData) => prop_assert!(true), // This is expected (RamState::OutputPort, RamState::ReadData) => prop_assert!(true), // This is expected _ => prop_assert!(false, "Unexpected state conversion: {:?} -> {:?}", ram_state, back_to_ram), } } } #[test] fn test_address_assembly_properties() { let mut scenario = MockScenario::new("TestAddressAssembly"); // Test that address assembly is deterministic and correct let test_cases = vec![ (0x00, 0x00, 0x0000), (0x12, 0x34, 0x1234), (0xFF, 0xFF, 0xFFFF), (0x0A, 0x0B, 0x0A0B), (0x01, 0x23, 0x0123), ]; for (high, low, _expected) in test_cases { scenario.component.set_address_high_nibble(Some(high)); scenario.component.set_address_low_nibble(Some(low)); scenario.component.set_full_address_ready(true); // In a real implementation, this would test the actual trait method // For now, we verify our mock state is consistent assert_eq!(scenario.component.get_address_high_nibble(), Some(high)); assert_eq!(scenario.component.get_address_low_nibble(), Some(low)); assert_eq!(scenario.component.get_full_address_ready(), true); // Clear for next test scenario.component.set_address_high_nibble(None); scenario.component.set_address_low_nibble(None); scenario.component.set_full_address_ready(false); } } proptest! { #[test] fn test_address_nibble_assembly( high_nibble in proptest_helpers::arb_address_nibble(), low_nibble in proptest_helpers::arb_address_nibble() ) { // Test that address assembly works for all valid nibble combinations let expected_address = ((high_nibble as u16) << 4) | (low_nibble as u16); let mut scenario = MockScenario::new("TestNibbleAssembly"); scenario.component.set_address_high_nibble(Some(high_nibble)); scenario.component.set_address_low_nibble(Some(low_nibble)); // Verify the nibbles are stored correctly prop_assert_eq!(scenario.component.get_address_high_nibble(), Some(high_nibble)); prop_assert_eq!(scenario.component.get_address_low_nibble(), Some(low_nibble)); // The expected address should be correctly calculated prop_assert_eq!(expected_address, ((high_nibble as u16) << 4) | (low_nibble as u16)); } #[test] fn test_data_bus_bit_pattern_consistency( data_value in proptest_helpers::arb_data_value() ) { let mut scenario = MockScenario::new("TestDataBusConsistency"); // Set data bus value scenario.set_data_bus_value(data_value); // Read it back let read_value = scenario.get_data_bus_value(); // Should be identical (roundtrip consistency) prop_assert_eq!(data_value, read_value); // Verify individual bits for i in 0..4 { let expected_bit = (data_value >> i) & 1; let pin_name = format!("D{}", i); let actual_pin_value = scenario.component.get_pin_value(&pin_name); match (expected_bit, actual_pin_value) { (0, Some(PinValue::Low)) => prop_assert!(true), (1, Some(PinValue::High)) => prop_assert!(true), _ => prop_assert!(false, "Bit {} mismatch for value 0x{:02X}", i, data_value), } } } #[test] fn test_timing_duration_properties( duration_nanos in proptest_helpers::arb_duration() ) { let duration = Duration::from_nanos(duration_nanos); // Test duration properties prop_assert!(duration.as_nanos() >= 0); prop_assert_eq!(duration.as_nanos(), duration_nanos); // Test that our timing constants are reasonable prop_assert!(TimingConstants::DEFAULT_ACCESS_TIME > Duration::from_nanos(0)); prop_assert!(TimingConstants::FAST_ACCESS_TIME > Duration::from_nanos(0)); prop_assert!(TimingConstants::ADDRESS_SETUP > Duration::from_nanos(0)); prop_assert!(TimingConstants::DATA_VALID > Duration::from_nanos(0)); } } } #[cfg(test)] mod timing_invariant_tests { use super::*; #[test] fn test_state_machine_never_invalid() { // Test that our state machine never enters invalid states let mut scenario = MockScenario::new("TestStateMachineValidity"); let valid_states = vec![ TimingState::Idle, TimingState::AddressPhase, TimingState::WaitLatency, TimingState::DriveData, ]; for state in valid_states { scenario.component.set_timing_state(state); // State should be queryable and consistent assert_eq!(scenario.component.get_timing_state(), state); // State queries should be mutually exclusive where appropriate let idle = state.is_idle(); let address = state.is_address_phase(); let waiting = state.is_waiting_latency(); let driving = state.is_driving_data(); // Only one "active" state should be true at a time let active_states = vec![address, waiting, driving]; let active_count = active_states.iter().filter(|&&x| x).count(); if idle { assert_eq!(active_count, 0, "Idle state should not have active flags"); } else { assert!( active_count > 0, "Non-idle state should have at least one active flag" ); } } } #[test] fn test_address_latch_invariants() { let mut scenario = MockScenario::new("TestAddressLatchInvariants"); // Test address latch state consistency scenario.component.set_address_high_nibble(Some(0x12)); scenario.component.set_address_low_nibble(Some(0x34)); scenario.component.set_full_address_ready(true); // All three should be consistent assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12)); assert_eq!(scenario.component.get_address_low_nibble(), Some(0x34)); assert_eq!(scenario.component.get_full_address_ready(), true); // Test partial state scenario.component.set_address_high_nibble(Some(0x56)); scenario.component.set_address_low_nibble(None); scenario.component.set_full_address_ready(false); assert_eq!(scenario.component.get_address_high_nibble(), Some(0x56)); assert_eq!(scenario.component.get_address_low_nibble(), None); assert_eq!(scenario.component.get_full_address_ready(), false); // Test empty state scenario.component.set_address_high_nibble(None); scenario.component.set_address_low_nibble(None); scenario.component.set_full_address_ready(false); assert_eq!(scenario.component.get_address_high_nibble(), None); assert_eq!(scenario.component.get_address_low_nibble(), None); assert_eq!(scenario.component.get_full_address_ready(), false); } #[test] fn test_pin_state_consistency() { let scenario = MockScenario::new("TestPinConsistency"); // Test that pin states are consistent let test_values = vec![PinValue::Low, PinValue::High, PinValue::HighZ]; for value in test_values { scenario.component.set_pin_value("D0", value); assert_eq!(scenario.component.get_pin_value("D0"), Some(value)); scenario.component.set_pin_value("PHI1", value); assert_eq!(scenario.component.get_pin_value("PHI1"), Some(value)); scenario.component.set_pin_value("SYNC", value); assert_eq!(scenario.component.get_pin_value("SYNC"), Some(value)); } } proptest! { #[test] fn test_bus_operation_invariants( initial_value in proptest_helpers::arb_data_value(), new_value in proptest_helpers::arb_data_value() ) { let mut scenario = MockScenario::new("TestBusInvariants"); // Set initial value scenario.set_data_bus_value(initial_value); prop_assert_eq!(scenario.get_data_bus_value(), initial_value); // Change to new value scenario.set_data_bus_value(new_value); prop_assert_eq!(scenario.get_data_bus_value(), new_value); // Values should be different (unless they happen to be the same) if initial_value != new_value { // This is just documenting expected behavior prop_assert!(true); } } } } #[cfg(test)] mod edge_case_tests { use super::*; #[test] fn test_boundary_timing_values() { // Test boundary conditions for timing let zero_duration = Duration::from_nanos(0); let small_duration = Duration::from_nanos(1); let large_duration = Duration::from_nanos(1_000_000_000); // 1 second // Test that our timing system can handle these values let mut scenario = MockScenario::new("TestBoundaryTiming"); scenario.time_provider.advance(zero_duration); scenario.time_provider.advance(small_duration); scenario.time_provider.advance(large_duration); // Verify time advancement works assert!(scenario.time_provider.now() > Instant::now() - large_duration * 3); } #[test] fn test_maximum_address_values() { let mut scenario = MockScenario::new("TestMaxAddress"); // Test maximum address values scenario.component.set_address_high_nibble(Some(0xFF)); scenario.component.set_address_low_nibble(Some(0xFF)); scenario.component.set_full_address_ready(true); assert_eq!(scenario.component.get_address_high_nibble(), Some(0xFF)); assert_eq!(scenario.component.get_address_low_nibble(), Some(0xFF)); assert_eq!(scenario.component.get_full_address_ready(), true); } #[test] fn test_minimum_address_values() { let mut scenario = MockScenario::new("TestMinAddress"); // Test minimum address values scenario.component.set_address_high_nibble(Some(0x00)); scenario.component.set_address_low_nibble(Some(0x00)); scenario.component.set_full_address_ready(true); assert_eq!(scenario.component.get_address_high_nibble(), Some(0x00)); assert_eq!(scenario.component.get_address_low_nibble(), Some(0x00)); assert_eq!(scenario.component.get_full_address_ready(), true); } #[test] fn test_rapid_state_transitions() { let mut scenario = MockScenario::new("TestRapidTransitions"); // Test rapid state changes let states = vec![ TimingState::Idle, TimingState::AddressPhase, TimingState::WaitLatency, TimingState::DriveData, TimingState::Idle, ]; for state in states { scenario.component.set_timing_state(state); assert_eq!(scenario.component.get_timing_state(), state); } } } #[cfg(test)] mod concurrency_safety_tests { use super::*; use std::sync::Arc; use std::thread; #[test] fn test_mock_component_thread_safety() { let scenario = Arc::new(MockScenario::new("TestThreadSafety")); // Test that our mock can handle concurrent access patterns let handles: Vec<_> = (0..4) .map(|i| { let scenario_clone: Arc = Arc::clone(&scenario); thread::spawn(move || { // Each thread tests different aspects match i { 0 => { // Note: This would need proper Arc> handling for thread safety // For now, we'll skip the mutable operation in this test // Note: Thread safety testing would require proper Arc> handling } 1 => { scenario_clone.component.set_pin_value("D0", PinValue::High); assert_eq!( scenario_clone.component.get_pin_value("D0"), Some(PinValue::High) ); } 2 => { scenario_clone.component.set_address_high_nibble(Some(0x12)); assert_eq!( scenario_clone.component.get_address_high_nibble(), Some(0x12) ); } 3 => { scenario_clone.advance_time(Duration::from_nanos(100)); } _ => unreachable!(), } }) }) .collect(); // Wait for all threads to complete for handle in handles { handle.join().expect("Thread should complete successfully"); } } }