rusty_emu/tests/mock_based_tests.rs
Jan-Peter von Hunnius 6f3295dc5e Created factory for system creation.
Systems are now defined in JSON rather than hard-coded
2025-09-29 12:41:16 +02:00

506 lines
17 KiB
Rust

//! Mock-based tests for Intel 400x data bus and pin operations
//!
//! These tests use the mock implementations to verify the behavior of
//! the intel_400x traits when interacting with hardware components.
use rusty_emu::components::common::intel_400x::*;
use rusty_emu::pin::PinValue;
use std::time::Duration;
#[cfg(test)]
mod data_bus_tests {
use super::*;
#[test]
fn test_data_bus_read_write_operations() {
let mut scenario = MockScenario::new("TestDataBus");
// Test writing data to bus
scenario.set_data_bus_value(0x05); // 0101 in binary
assert_eq!(scenario.get_data_bus_value(), 0x05);
// Verify individual bits
assert_eq!(scenario.component.get_pin_value("D0"), Some(PinValue::High)); // LSB
assert_eq!(scenario.component.get_pin_value("D1"), Some(PinValue::Low));
assert_eq!(scenario.component.get_pin_value("D2"), Some(PinValue::High));
assert_eq!(scenario.component.get_pin_value("D3"), Some(PinValue::Low)); // MSB
// Test writing different value
scenario.set_data_bus_value(0x0A); // 1010 in binary
assert_eq!(scenario.get_data_bus_value(), 0x0A);
assert_eq!(scenario.component.get_pin_value("D0"), Some(PinValue::Low));
assert_eq!(scenario.component.get_pin_value("D1"), Some(PinValue::High));
assert_eq!(scenario.component.get_pin_value("D2"), Some(PinValue::Low));
assert_eq!(scenario.component.get_pin_value("D3"), Some(PinValue::High));
}
#[test]
fn test_data_bus_all_values() {
let mut scenario = MockScenario::new("TestAllDataValues");
// Test all possible 4-bit values
for value in 0u8..16 {
scenario.set_data_bus_value(value);
let read_value = scenario.get_data_bus_value();
assert_eq!(value, read_value, "Failed for value 0x{:02X}", value);
}
}
#[test]
fn test_pin_operation_counting() {
let scenario = MockScenario::new("TestPinCounting");
// Set some pin values and check that operations are counted
scenario.component.set_pin_value("D0", PinValue::High);
scenario.component.set_pin_value("D1", PinValue::Low);
// Note: Our current mock doesn't fully implement the counting
// This test demonstrates the structure for when we enhance the mock
assert_eq!(scenario.component.get_pin_value("D0"), Some(PinValue::High));
assert_eq!(scenario.component.get_pin_value("D1"), Some(PinValue::Low));
}
}
#[cfg(test)]
mod clock_handling_tests {
use super::*;
#[test]
fn test_clock_pin_reading() {
let scenario = MockScenario::new("TestClockReading");
// Test reading clock pins in different states
scenario
.component
.set_clock_values(PinValue::Low, PinValue::Low);
// In a real implementation, this would test the actual trait methods
// For now, we verify the mock setup works correctly
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::Low)
);
assert_eq!(
scenario.component.get_pin_value("PHI2"),
Some(PinValue::Low)
);
}
#[test]
fn test_clock_edge_scenarios() {
let mut scenario = MockScenario::new("TestClockEdges");
// Test PHI1 rising edge
scenario.set_phi1_rising_edge();
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::High)
);
assert_eq!(
scenario.component.get_pin_value("PHI2"),
Some(PinValue::Low)
);
// Test PHI1 falling edge
scenario.set_phi1_falling_edge();
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::Low)
);
assert_eq!(
scenario.component.get_pin_value("PHI2"),
Some(PinValue::Low)
);
// Test PHI2 rising edge
scenario.set_phi2_rising_edge();
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::Low)
);
assert_eq!(
scenario.component.get_pin_value("PHI2"),
Some(PinValue::High)
);
// Test PHI2 falling edge
scenario.set_phi2_falling_edge();
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::Low)
);
assert_eq!(
scenario.component.get_pin_value("PHI2"),
Some(PinValue::Low)
);
}
#[test]
fn test_clock_state_transitions() {
let mut scenario = MockScenario::new("TestClockTransitions");
// Simulate a clock cycle: Low -> High -> Low
scenario.set_clock_low();
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::Low)
);
assert_eq!(
scenario.component.get_pin_value("PHI2"),
Some(PinValue::Low)
);
scenario.set_clock_high();
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::High)
);
assert_eq!(
scenario.component.get_pin_value("PHI2"),
Some(PinValue::High)
);
scenario.set_clock_low();
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::Low)
);
assert_eq!(
scenario.component.get_pin_value("PHI2"),
Some(PinValue::Low)
);
}
}
#[cfg(test)]
mod control_pin_tests {
use super::*;
#[test]
fn test_control_pin_reading() {
let scenario = MockScenario::new("TestControlPins");
// Test SYNC pin
scenario.set_pin_value("SYNC", PinValue::High);
assert_eq!(
scenario.component.get_pin_value("SYNC"),
Some(PinValue::High)
);
scenario.set_pin_value("SYNC", PinValue::Low);
assert_eq!(
scenario.component.get_pin_value("SYNC"),
Some(PinValue::Low)
);
// Test CM-ROM pin
scenario.component.set_pin_value("CM", PinValue::High);
assert_eq!(scenario.component.get_pin_value("CM"), Some(PinValue::High));
scenario.component.set_pin_value("CM", PinValue::Low);
assert_eq!(scenario.component.get_pin_value("CM"), Some(PinValue::Low));
// Test RESET pin
scenario.component.set_pin_value("RESET", PinValue::High);
assert_eq!(
scenario.component.get_pin_value("RESET"),
Some(PinValue::High)
);
scenario.component.set_pin_value("RESET", PinValue::Low);
assert_eq!(
scenario.component.get_pin_value("RESET"),
Some(PinValue::Low)
);
}
#[test]
fn test_reset_functionality() {
let mut scenario = MockScenario::new("TestReset");
// Set up some state
scenario.component.set_timing_state(TimingState::DriveData);
scenario.component.set_full_address_ready(true);
scenario.component.set_address_high_nibble(Some(0x12));
scenario.component.set_address_low_nibble(Some(0x34));
scenario.set_data_bus_value(0x0F);
// Verify state is set
assert_eq!(
scenario.component.get_timing_state(),
TimingState::DriveData
);
assert_eq!(scenario.component.get_full_address_ready(), true);
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12));
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x34));
assert_eq!(scenario.get_data_bus_value(), 0x0F);
// Perform reset
scenario.component.perform_reset();
// Verify reset behavior
assert_eq!(scenario.component.get_timing_state(), TimingState::Idle);
assert_eq!(scenario.component.get_full_address_ready(), false);
assert_eq!(scenario.component.get_address_high_nibble(), None);
assert_eq!(scenario.component.get_address_low_nibble(), None);
// Data bus should be tri-stated (HighZ)
for i in 0..4 {
let pin_name = format!("D{}", i);
assert_eq!(
scenario.component.get_pin_value(&pin_name),
Some(PinValue::HighZ)
);
}
}
}
#[cfg(test)]
mod timing_state_tests {
use super::*;
#[test]
fn test_timing_state_machine_transitions() {
let mut scenario = MockScenario::new("TestTimingStateMachine");
// Start in idle state
assert_eq!(scenario.component.get_timing_state(), TimingState::Idle);
assert!(scenario.component.get_timing_state().is_idle());
assert!(!scenario.component.get_timing_state().is_address_phase());
// Transition to address phase
scenario
.component
.set_timing_state(TimingState::AddressPhase);
assert_eq!(
scenario.component.get_timing_state(),
TimingState::AddressPhase
);
assert!(!scenario.component.get_timing_state().is_idle());
assert!(scenario.component.get_timing_state().is_address_phase());
// Transition to wait latency
scenario
.component
.set_timing_state(TimingState::WaitLatency);
assert_eq!(
scenario.component.get_timing_state(),
TimingState::WaitLatency
);
assert!(scenario.component.get_timing_state().is_waiting_latency());
// Transition to drive data
scenario.component.set_timing_state(TimingState::DriveData);
assert_eq!(
scenario.component.get_timing_state(),
TimingState::DriveData
);
assert!(scenario.component.get_timing_state().is_driving_data());
}
#[test]
fn test_address_latch_timing() {
let mut scenario = MockScenario::new("TestAddressLatchTiming");
// Set up address latching
scenario.component.set_address_high_nibble(Some(0x12));
scenario.component.set_address_low_nibble(Some(0x34));
scenario.component.set_full_address_ready(true);
// Record latch time
let latch_time = scenario.time_provider.now();
scenario.component.set_address_latch_time(Some(latch_time));
// Verify state
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);
assert_eq!(
scenario.component.get_address_latch_time(),
Some(latch_time)
);
// Test clearing the latch
scenario.component.set_address_high_nibble(None);
scenario.component.set_address_low_nibble(None);
scenario.component.set_full_address_ready(false);
scenario.component.set_address_latch_time(None);
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);
assert_eq!(scenario.component.get_address_latch_time(), None);
}
#[test]
fn test_access_time_configuration() {
let scenario = MockScenario::new("TestAccessTime");
// Test default access time
assert_eq!(
scenario.component.get_access_time(),
TimingConstants::DEFAULT_ACCESS_TIME
);
// In a real implementation, we would test changing the access time
// For now, we verify the getter works
let access_time = scenario.component.get_access_time();
assert!(access_time > Duration::from_nanos(0));
}
}
#[cfg(test)]
mod integration_scenarios {
use super::*;
#[test]
fn test_complete_memory_operation_cycle() {
let mut scenario = MockScenario::new("TestMemoryCycle");
// Initial state
assert_eq!(scenario.component.get_timing_state(), TimingState::Idle);
// Phase 1: Address phase - high nibble
scenario
.component
.set_timing_state(TimingState::AddressPhase);
scenario.component.set_address_high_nibble(Some(0x12));
assert_eq!(
scenario.component.get_timing_state(),
TimingState::AddressPhase
);
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12));
// Phase 2: Address phase - low nibble
scenario.component.set_address_low_nibble(Some(0x34));
scenario.component.set_full_address_ready(true);
let latch_time = scenario.time_provider.now();
scenario.component.set_address_latch_time(Some(latch_time));
// Verify address assembly
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);
// Phase 3: Wait for latency
scenario
.component
.set_timing_state(TimingState::WaitLatency);
assert_eq!(
scenario.component.get_timing_state(),
TimingState::WaitLatency
);
// Advance time past access time
scenario.advance_time(scenario.component.get_access_time() + Duration::from_nanos(10));
// Phase 4: Drive data
scenario.component.set_timing_state(TimingState::DriveData);
scenario.set_data_bus_value(0x0F); // Drive some data
assert_eq!(
scenario.component.get_timing_state(),
TimingState::DriveData
);
assert_eq!(scenario.get_data_bus_value(), 0x0F);
// Cycle complete - return to idle
scenario.component.set_timing_state(TimingState::Idle);
assert_eq!(scenario.component.get_timing_state(), TimingState::Idle);
}
#[test]
fn test_bus_contention_avoidance() {
let mut scenario = MockScenario::new("TestBusContention");
// Set up a scenario where bus contention could occur
scenario.set_data_bus_value(0x0F);
// In a real implementation, we would test tri-stating the bus
// to avoid contention with other devices
for i in 0..4 {
let pin_name = format!("D{}", i);
scenario.component.set_pin_value(&pin_name, PinValue::HighZ);
assert_eq!(
scenario.component.get_pin_value(&pin_name),
Some(PinValue::HighZ)
);
}
}
#[test]
fn test_multiple_address_cycles() {
let mut scenario = MockScenario::new("TestMultipleAddresses");
// First address cycle
scenario.component.set_address_high_nibble(Some(0x12));
scenario.component.set_address_low_nibble(Some(0x34));
scenario.component.set_full_address_ready(true);
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12));
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x34));
// Clear for next cycle
scenario.component.set_address_high_nibble(None);
scenario.component.set_address_low_nibble(None);
scenario.component.set_full_address_ready(false);
// Second address cycle
scenario.component.set_address_high_nibble(Some(0x56));
scenario.component.set_address_low_nibble(Some(0x78));
scenario.component.set_full_address_ready(true);
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x56));
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x78));
assert_eq!(scenario.component.get_full_address_ready(), true);
// First address should be cleared
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x56));
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x78));
}
}
#[cfg(test)]
mod error_handling_tests {
use super::*;
#[test]
fn test_missing_pin_handling() {
let scenario = MockScenario::new("TestMissingPins");
// Test accessing non-existent pins
assert_eq!(scenario.component.get_pin_value("NONEXISTENT"), None);
// Standard pins should exist
assert_eq!(
scenario.component.get_pin_value("D0"),
Some(PinValue::HighZ)
);
assert_eq!(
scenario.component.get_pin_value("PHI1"),
Some(PinValue::Low)
);
assert_eq!(
scenario.component.get_pin_value("SYNC"),
Some(PinValue::Low)
);
}
#[test]
fn test_invalid_pin_operations() {
let scenario = MockScenario::new("TestInvalidOperations");
// Test setting pin to HighZ (tri-state)
scenario.component.set_pin_value("D0", PinValue::HighZ);
assert_eq!(
scenario.component.get_pin_value("D0"),
Some(PinValue::HighZ)
);
// Test that HighZ is handled correctly in bus operations
// (This would be more relevant in actual hardware interface tests)
let bus_value = scenario.get_data_bus_value();
// The exact behavior depends on how HighZ pins are interpreted
// This test documents the expected structure
}
}