use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use super::*;
mod sparse_store_tests {
use super::*;
#[test]
fn test_create_with_defaults() {
let store = SparseRegisterStore::with_defaults();
assert_eq!(store.entry_count(), 0); assert!(store.memory_usage() > 0); }
#[test]
fn test_coil_operations() {
let store = SparseRegisterStore::with_defaults();
store.write_coil(0, true).unwrap();
let values = store.read_coils(0, 1).unwrap();
assert_eq!(values, vec![true]);
store.write_coils(10, &[true, false, true]).unwrap();
let values = store.read_coils(10, 3).unwrap();
assert_eq!(values, vec![true, false, true]);
assert_eq!(store.entry_count_for(RegisterType::Coil), 4);
}
#[test]
fn test_discrete_input_operations() {
let store = SparseRegisterStore::with_defaults();
store.set_discrete_input(0, true).unwrap();
let values = store.read_discrete_inputs(0, 1).unwrap();
assert_eq!(values, vec![true]);
store.set_discrete_inputs(10, &[true, false]).unwrap();
let values = store.read_discrete_inputs(10, 2).unwrap();
assert_eq!(values, vec![true, false]);
}
#[test]
fn test_holding_register_operations() {
let store = SparseRegisterStore::with_defaults();
store.write_holding_register(0, 12345).unwrap();
let values = store.read_holding_registers(0, 1).unwrap();
assert_eq!(values, vec![12345]);
store.write_holding_registers(10, &[100, 200, 300]).unwrap();
let values = store.read_holding_registers(10, 3).unwrap();
assert_eq!(values, vec![100, 200, 300]);
assert_eq!(store.entry_count_for(RegisterType::HoldingRegister), 4);
}
#[test]
fn test_input_register_operations() {
let store = SparseRegisterStore::with_defaults();
store.set_input_register(0, 54321).unwrap();
let values = store.read_input_registers(0, 1).unwrap();
assert_eq!(values, vec![54321]);
store.set_input_registers(10, &[111, 222]).unwrap();
let values = store.read_input_registers(10, 2).unwrap();
assert_eq!(values, vec![111, 222]);
}
#[test]
fn test_invalid_address() {
let config = RegisterStoreConfig::new(
AddressRange::new(0, 99),
AddressRange::new(0, 99),
AddressRange::new(0, 99),
AddressRange::new(0, 99),
);
let store = SparseRegisterStore::new(config);
let result = store.read_coils(100, 1);
assert!(result.is_err());
let result = store.read_coils(99, 2);
assert!(result.is_err());
let result = store.read_coils(0, 0);
assert!(result.is_err());
}
#[test]
fn test_float_operations() {
let store = SparseRegisterStore::with_defaults();
let f32_value: f32 = 3.14159;
store.write_f32(0, f32_value).unwrap();
let read_value = store.read_f32(0).unwrap();
assert!((read_value - f32_value).abs() < 0.0001);
let f64_value: f64 = 2.718281828459045;
store.write_f64(10, f64_value).unwrap();
let read_value = store.read_f64(10).unwrap();
assert!((read_value - f64_value).abs() < 0.000001);
}
#[test]
fn test_reset() {
let store = SparseRegisterStore::with_defaults();
store.write_holding_register(0, 12345).unwrap();
store.write_coil(0, true).unwrap();
assert!(store.entry_count() > 0);
store.reset();
assert_eq!(store.entry_count(), 0);
let values = store.read_holding_registers(0, 1).unwrap();
assert_eq!(values, vec![0]);
}
#[test]
fn test_snapshot_and_restore() {
let config = RegisterStoreConfig::minimal();
let store1 = SparseRegisterStore::new(config.clone());
store1.write_holding_register(0, 100).unwrap();
store1.write_holding_register(10, 200).unwrap();
store1.write_coil(5, true).unwrap();
let snapshot = store1.snapshot();
let config2 = config.with_initialization(InitializationMode::Snapshot(snapshot.clone()));
let store2 = SparseRegisterStore::new(config2);
assert_eq!(store2.read_holding_registers(0, 1).unwrap(), vec![100]);
assert_eq!(store2.read_holding_registers(10, 1).unwrap(), vec![200]);
assert_eq!(store2.read_coils(5, 1).unwrap(), vec![true]);
}
#[test]
fn test_exists_and_remove() {
let store = SparseRegisterStore::with_defaults();
assert!(!store.exists(RegisterType::HoldingRegister, 0));
store.write_holding_register(0, 100).unwrap();
assert!(store.exists(RegisterType::HoldingRegister, 0));
assert!(store.remove(RegisterType::HoldingRegister, 0));
assert!(!store.exists(RegisterType::HoldingRegister, 0));
assert!(!store.remove(RegisterType::HoldingRegister, 999));
}
#[test]
fn test_memory_usage() {
let store = SparseRegisterStore::with_defaults();
let initial_memory = store.memory_usage();
assert!(initial_memory > 0);
for i in 0..1000u16 {
store.write_holding_register(i, i).unwrap();
}
let after_memory = store.memory_usage();
assert!(after_memory > initial_memory);
let entry_overhead = (after_memory - initial_memory) / 1000;
assert!(entry_overhead >= 20 && entry_overhead <= 50); }
#[test]
fn test_default_values() {
let config = RegisterStoreConfig::new(
AddressRange::new(0, 99),
AddressRange::new(0, 99),
AddressRange::new(0, 99),
AddressRange::new(0, 99),
);
let mut config = config;
config.holding_registers.default_value = DefaultValue::Value(42);
config.coils.default_value = DefaultValue::Value(1);
let store = SparseRegisterStore::new(config);
let values = store.read_holding_registers(50, 1).unwrap();
assert_eq!(values, vec![42]);
let coils = store.read_coils(50, 1).unwrap();
assert_eq!(coils, vec![true]);
}
#[test]
fn test_eager_initialization() {
let mut config = RegisterStoreConfig::new(
AddressRange::new(0, 9),
AddressRange::new(0, 9),
AddressRange::new(0, 9),
AddressRange::new(0, 9),
);
config.initialization = InitializationMode::Eager;
let store = SparseRegisterStore::new(config);
assert_eq!(store.entry_count_for(RegisterType::Coil), 10);
assert_eq!(store.entry_count_for(RegisterType::DiscreteInput), 10);
assert_eq!(store.entry_count_for(RegisterType::HoldingRegister), 10);
assert_eq!(store.entry_count_for(RegisterType::InputRegister), 10);
}
}
mod callback_tests {
use super::*;
#[test]
fn test_write_callback_notification() {
let store = SparseRegisterStore::with_defaults();
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
store.add_write_callback(Arc::new(WriteCallbackFn::new(
"test_counter",
move |_ctx| {
counter_clone.fetch_add(1, Ordering::Relaxed);
},
)));
store.write_holding_register(0, 100).unwrap();
assert_eq!(counter.load(Ordering::Relaxed), 1);
store.write_holding_registers(10, &[1, 2, 3]).unwrap();
assert_eq!(counter.load(Ordering::Relaxed), 4);
store.set_callbacks_enabled(false);
store.write_holding_register(20, 200).unwrap();
assert_eq!(counter.load(Ordering::Relaxed), 4);
store.set_callbacks_enabled(true);
store.write_holding_register(30, 300).unwrap();
assert_eq!(counter.load(Ordering::Relaxed), 5);
}
#[test]
fn test_read_callback_notification() {
let store = SparseRegisterStore::with_defaults();
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
store.add_read_callback(Arc::new(ReadCallbackFn::new(
"test_read",
move |_ctx, _values| {
counter_clone.fetch_add(1, Ordering::Relaxed);
},
)));
store.read_holding_registers(0, 1).unwrap();
assert_eq!(counter.load(Ordering::Relaxed), 1);
store.read_holding_registers(0, 10).unwrap();
assert_eq!(counter.load(Ordering::Relaxed), 2);
}
#[test]
fn test_callback_context() {
let store = SparseRegisterStore::with_defaults();
let captured = Arc::new(parking_lot::RwLock::new(Vec::new()));
let captured_clone = captured.clone();
store.add_write_callback(Arc::new(WriteCallbackFn::new("capture", move |ctx| {
captured_clone.write().push((
ctx.address,
ctx.old_value.as_word(),
ctx.new_value.as_word(),
));
})));
store.write_holding_register(100, 50).unwrap();
store.write_holding_register(100, 75).unwrap();
let events = captured.read();
assert_eq!(events.len(), 2);
assert_eq!(events[0], (100, 0, 50));
assert_eq!(events[1], (100, 50, 75));
}
#[test]
fn test_callback_disabled_by_config() {
let config = RegisterStoreConfig::default().without_callbacks();
let store = SparseRegisterStore::new(config);
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
store.add_write_callback(Arc::new(WriteCallbackFn::new("test", move |_| {
counter_clone.fetch_add(1, Ordering::Relaxed);
})));
store.write_holding_register(0, 100).unwrap();
assert_eq!(counter.load(Ordering::Relaxed), 0);
}
}
mod concurrency_tests {
use super::*;
use std::thread;
#[test]
fn test_concurrent_reads_and_writes() {
let store = Arc::new(SparseRegisterStore::with_defaults());
let mut handles = vec![];
for t in 0..4 {
let store = store.clone();
handles.push(thread::spawn(move || {
for i in 0..1000 {
let addr = ((t * 1000 + i) % 1000) as u16;
let _ = store.read_holding_registers(addr, 1);
}
}));
}
for t in 0..4 {
let store = store.clone();
handles.push(thread::spawn(move || {
for i in 0..1000 {
let addr = ((t * 1000 + i) % 1000) as u16;
store.write_holding_register(addr, i as u16).unwrap();
}
}));
}
for handle in handles {
handle.join().unwrap();
}
assert!(store.entry_count_for(RegisterType::HoldingRegister) <= 1000);
}
#[test]
fn test_concurrent_different_types() {
let store = Arc::new(SparseRegisterStore::with_defaults());
let mut handles = vec![];
let store1 = store.clone();
handles.push(thread::spawn(move || {
for i in 0..1000u16 {
store1.write_coil(i, i % 2 == 0).unwrap();
}
}));
let store2 = store.clone();
handles.push(thread::spawn(move || {
for i in 0..1000u16 {
store2.write_holding_register(i, i).unwrap();
}
}));
let store3 = store.clone();
handles.push(thread::spawn(move || {
for i in 0..1000u16 {
store3.set_input_register(i, i * 2).unwrap();
}
}));
let store4 = store.clone();
handles.push(thread::spawn(move || {
for i in 0..1000u16 {
let _ = store4.read_coils(i % 100, 1);
let _ = store4.read_holding_registers(i % 100, 1);
let _ = store4.read_input_registers(i % 100, 1);
}
}));
for handle in handles {
handle.join().unwrap();
}
}
}
mod config_tests {
use super::*;
#[test]
fn test_address_range_count() {
assert_eq!(AddressRange::new(0, 99).count(), 100);
assert_eq!(AddressRange::new(10, 19).count(), 10);
assert_eq!(AddressRange::new(0, 0).count(), 1);
assert_eq!(AddressRange::new(0, 65534).count(), 65535);
}
#[test]
fn test_config_presets() {
let minimal = RegisterStoreConfig::minimal();
assert_eq!(minimal.coils.range.count(), 100);
let large = RegisterStoreConfig::large();
assert_eq!(large.coils.range.count(), 65535);
let default = RegisterStoreConfig::default();
assert_eq!(default.coils.range.count(), 10000);
}
#[test]
fn test_config_builder_pattern() {
let config = RegisterStoreConfig::default()
.with_initialization(InitializationMode::Eager)
.without_callbacks();
assert_eq!(config.initialization, InitializationMode::Eager);
assert!(!config.callbacks_enabled);
}
}
mod register_type_tests {
use super::*;
#[test]
fn test_function_codes() {
assert_eq!(RegisterType::Coil.read_function_code(), 0x01);
assert_eq!(RegisterType::DiscreteInput.read_function_code(), 0x02);
assert_eq!(RegisterType::HoldingRegister.read_function_code(), 0x03);
assert_eq!(RegisterType::InputRegister.read_function_code(), 0x04);
assert_eq!(RegisterType::Coil.write_single_function_code(), Some(0x05));
assert_eq!(
RegisterType::HoldingRegister.write_single_function_code(),
Some(0x06)
);
assert_eq!(
RegisterType::DiscreteInput.write_single_function_code(),
None
);
assert_eq!(
RegisterType::InputRegister.write_single_function_code(),
None
);
}
#[test]
fn test_type_properties() {
assert!(RegisterType::Coil.is_bit_type());
assert!(RegisterType::DiscreteInput.is_bit_type());
assert!(!RegisterType::HoldingRegister.is_bit_type());
assert!(RegisterType::HoldingRegister.is_word_type());
assert!(RegisterType::InputRegister.is_word_type());
assert!(!RegisterType::Coil.is_word_type());
assert!(RegisterType::Coil.is_writable());
assert!(RegisterType::HoldingRegister.is_writable());
assert!(!RegisterType::DiscreteInput.is_writable());
assert!(!RegisterType::InputRegister.is_writable());
}
}