Files
foxhunt/trading_engine/tests/simd_and_lockfree_tests.rs
jgrusewski 6093eac7bf 🔧 Tonic 0.14 Upgrade: Auto-generated and build system changes
Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade

Files updated:
- Cargo.lock: Dependency resolution for Tonic 0.14.2
- All build.rs: Updated for tonic-prost-build
- Proto files: Regenerated with tonic-prost 0.14
- Examples/tests: Updated for new gRPC API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 07:34:26 +02:00

447 lines
13 KiB
Rust

#![allow(unused_crate_dependencies)]
//! Comprehensive tests for SIMD fallback paths and lock-free data structures
//!
//! This test suite ensures proper fallback behavior when CPU features are unavailable
//! and tests edge cases in concurrent data structures.
// use trading_engine::types::simd_optimizations::{CacheAlignedPriceArray, SIMDFinancialOps};
// use common::{Price, Quantity}; // Unused in current tests
use std::sync::Arc;
use std::thread;
use trading_engine::lockfree::mpsc_queue::{AtomicCounter, MPSCQueue};
// ============================================================================
// SIMD Fallback Path Tests
// ============================================================================
// Note: SIMD tests are disabled as simd_optimizations module is not implemented yet
/*
#[test]
fn test_simd_portfolio_value_empty_arrays() {
let positions: Vec<Quantity> = vec![];
let prices: Vec<Price> = vec![];
let result = SIMDFinancialOps::portfolio_value_simd(&positions, &prices);
assert_eq!(result, Price::ZERO);
}
#[test]
fn test_simd_portfolio_value_single_element() {
let positions = vec![Quantity(100)];
let prices = vec![Price::from_f64(50000.0).unwrap()];
let result = SIMDFinancialOps::portfolio_value_simd(&positions, &prices);
let expected = Price::from_f64(5_000_000.0).unwrap();
// Use approximate comparison for floating point
assert!((result.to_f64() - expected.to_f64()).abs() < 0.01);
}
#[test]
fn test_simd_portfolio_value_misaligned_length() {
// Test with length that's not a multiple of 8
let positions = vec![
Quantity(100),
Quantity(200),
Quantity(150),
Quantity(75),
Quantity(300),
];
let prices = vec![
Price::from_f64(50000.0).unwrap(),
Price::from_f64(3000.0).unwrap(),
Price::from_f64(100.0).unwrap(),
Price::from_f64(1.0).unwrap(),
Price::from_f64(200.0).unwrap(),
];
let result = SIMDFinancialOps::portfolio_value_simd(&positions, &prices);
// Manual calculation: 100*50000 + 200*3000 + 150*100 + 75*1 + 300*200
// = 5,000,000 + 600,000 + 15,000 + 75 + 60,000 = 5,675,075
let expected = Price::from_f64(5_675_075.0).unwrap();
assert!((result.to_f64() - expected.to_f64()).abs() < 0.01);
}
#[test]
fn test_simd_portfolio_value_exactly_8_elements() {
// Test with exactly 8 elements (one SIMD register worth)
let positions = vec![
Quantity(100), Quantity(200), Quantity(150), Quantity(75),
Quantity(300), Quantity(50), Quantity(125), Quantity(175),
];
let prices: Vec<Price> = vec![
Price::from_f64(1000.0).unwrap(),
Price::from_f64(2000.0).unwrap(),
Price::from_f64(3000.0).unwrap(),
Price::from_f64(4000.0).unwrap(),
Price::from_f64(5000.0).unwrap(),
Price::from_f64(6000.0).unwrap(),
Price::from_f64(7000.0).unwrap(),
Price::from_f64(8000.0).unwrap(),
];
let result = SIMDFinancialOps::portfolio_value_simd(&positions, &prices);
// Manual: 100*1000 + 200*2000 + 150*3000 + 75*4000 + 300*5000 + 50*6000 + 125*7000 + 175*8000
// = 100000 + 400000 + 450000 + 300000 + 1500000 + 300000 + 875000 + 1400000 = 5,325,000
let expected = Price::from_f64(5_325_000.0).unwrap();
assert!((result.to_f64() - expected.to_f64()).abs() < 0.01);
}
#[test]
fn test_simd_portfolio_value_large_array() {
// Test with a large array (multiple SIMD registers)
let mut positions = Vec::new();
let mut prices = Vec::new();
for i in 0..1000 {
positions.push(Quantity(i + 1));
prices.push(Price::from_f64((i + 1) as f64 * 100.0).unwrap());
}
let result = SIMDFinancialOps::portfolio_value_simd(&positions, &prices);
// Verify result is reasonable (should be sum of i * i * 100 for i=1 to 1000)
// Sum formula: sum(i^2 * 100) = 100 * (n * (n+1) * (2n+1) / 6)
// For n=1000: 100 * (1000 * 1001 * 2001 / 6) = 333,833,500
let expected = Price::from_f64(333_833_500.0).unwrap();
assert!((result.to_f64() - expected.to_f64()).abs() < 100.0); // Allow some FP error
}
#[test]
fn test_simd_portfolio_value_zero_prices() {
let positions = vec![Quantity(100), Quantity(200), Quantity(150)];
let prices = vec![
Price::from_f64(0.0).unwrap(),
Price::from_f64(0.0).unwrap(),
Price::from_f64(0.0).unwrap(),
];
let result = SIMDFinancialOps::portfolio_value_simd(&positions, &prices);
assert_eq!(result, Price::ZERO);
}
#[test]
fn test_simd_portfolio_value_negative_positions() {
// Short positions (negative quantities)
let positions = vec![Quantity(-100), Quantity(-200)];
let prices = vec![
Price::from_f64(50000.0).unwrap(),
Price::from_f64(3000.0).unwrap(),
];
let result = SIMDFinancialOps::portfolio_value_simd(&positions, &prices);
// Result should be negative: -100*50000 + -200*3000 = -5,600,000
let expected = Price::from_f64(-5_600_000.0).unwrap();
assert!((result.to_f64() - expected.to_f64()).abs() < 0.01);
}
#[test]
fn test_cache_aligned_price_array() {
let prices = [
Price::from_f64(1.0).unwrap(),
Price::from_f64(2.0).unwrap(),
Price::from_f64(3.0).unwrap(),
Price::from_f64(4.0).unwrap(),
Price::from_f64(5.0).unwrap(),
Price::from_f64(6.0).unwrap(),
Price::from_f64(7.0).unwrap(),
Price::from_f64(8.0).unwrap(),
];
let aligned = CacheAlignedPriceArray::new(prices);
let slice = aligned.as_slice();
assert_eq!(slice.len(), 8);
assert_eq!(slice[0], prices[0]);
assert_eq!(slice[7], prices[7]);
}
#[test]
fn test_cache_aligned_price_array_mutation() {
let prices = [Price::ZERO; 8];
let mut aligned = CacheAlignedPriceArray::new(prices);
let mut_slice = aligned.as_mut_slice();
mut_slice[0] = Price::from_f64(100.0).unwrap();
mut_slice[7] = Price::from_f64(200.0).unwrap();
let slice = aligned.as_slice();
assert_eq!(slice[0], Price::from_f64(100.0).unwrap());
assert_eq!(slice[7], Price::from_f64(200.0).unwrap());
}
*/
// ============================================================================
// Lock-free MPSC Queue Edge Cases
// ============================================================================
#[test]
fn test_mpsc_queue_concurrent_push_pop() {
let queue = Arc::new(MPSCQueue::<u64>::new());
let num_producers = 8;
let items_per_producer = 10000;
// Spawn multiple producers
let mut producer_handles = Vec::new();
for producer_id in 0..num_producers {
let queue_clone = Arc::clone(&queue);
let handle = thread::spawn(move || {
for i in 0..items_per_producer {
let value = (producer_id as u64) * items_per_producer + i;
queue_clone.push(value);
}
});
producer_handles.push(handle);
}
// Consumer thread
let queue_consumer = Arc::clone(&queue);
let consumer_handle = thread::spawn(move || {
let mut received = Vec::new();
let expected_total = (num_producers * items_per_producer) as usize;
while received.len() < expected_total {
if let Some(item) = queue_consumer.try_pop() {
received.push(item);
} else {
thread::yield_now();
}
}
received
});
// Wait for producers
for handle in producer_handles {
handle.join().expect("Producer thread failed");
}
// Wait for consumer
let received = consumer_handle.join().expect("Consumer thread failed");
// Verify all items received
assert_eq!(
received.len(),
(num_producers * items_per_producer) as usize
);
// Verify queue is empty
assert!(queue.is_empty());
}
#[test]
fn test_mpsc_queue_pop_from_empty() {
let queue = MPSCQueue::<u64>::new();
// Multiple pops from empty queue should return None
assert_eq!(queue.try_pop(), None);
assert_eq!(queue.try_pop(), None);
assert_eq!(queue.try_pop(), None);
assert!(queue.is_empty());
}
#[test]
fn test_mpsc_queue_push_pop_interleaved() {
let queue = MPSCQueue::<u64>::new();
// Interleave push and pop operations
queue.push(1);
assert_eq!(queue.try_pop(), Some(1));
queue.push(2);
queue.push(3);
assert_eq!(queue.try_pop(), Some(2));
queue.push(4);
assert_eq!(queue.try_pop(), Some(3));
assert_eq!(queue.try_pop(), Some(4));
assert!(queue.is_empty());
}
#[test]
fn test_mpsc_queue_rapid_push() {
let queue = Arc::new(MPSCQueue::<u64>::new());
let queue_clone = Arc::clone(&queue);
// Rapidly push 100,000 items
let handle = thread::spawn(move || {
for i in 0..100_000 {
queue_clone.push(i);
}
});
handle.join().expect("Producer thread failed");
// Verify count
assert_eq!(queue.len(), 100_000);
// Pop all items and verify order
let mut prev = None;
let mut count = 0;
while let Some(item) = queue.try_pop() {
if let Some(p) = prev {
assert_eq!(item, p + 1, "Items should be in order");
}
prev = Some(item);
count += 1;
}
assert_eq!(count, 100_000);
}
#[test]
fn test_mpsc_queue_size_tracking() {
let queue = MPSCQueue::<u64>::new();
assert_eq!(queue.len(), 0);
assert!(queue.is_empty());
queue.push(1);
assert_eq!(queue.len(), 1);
assert!(!queue.is_empty());
queue.push(2);
queue.push(3);
assert_eq!(queue.len(), 3);
queue.try_pop();
assert_eq!(queue.len(), 2);
queue.try_pop();
queue.try_pop();
assert_eq!(queue.len(), 0);
assert!(queue.is_empty());
}
// ============================================================================
// Atomic Counter Edge Cases
// ============================================================================
#[test]
fn test_atomic_counter_custom_increment() {
let counter = AtomicCounter::new_with(100, 5);
assert_eq!(counter.get(), 100);
assert_eq!(counter.next(), 100);
assert_eq!(counter.next(), 105);
assert_eq!(counter.next(), 110);
assert_eq!(counter.get(), 115);
}
#[test]
fn test_atomic_counter_reset() {
let counter = AtomicCounter::new();
counter.next();
counter.next();
counter.next();
assert_eq!(counter.get(), 3);
counter.reset(0);
assert_eq!(counter.get(), 0);
assert_eq!(counter.next(), 0);
}
#[test]
fn test_atomic_counter_add() {
let counter = AtomicCounter::new();
assert_eq!(counter.add(10), 0); // Returns old value
assert_eq!(counter.get(), 10);
assert_eq!(counter.add(5), 10); // Returns old value
assert_eq!(counter.get(), 15);
}
#[test]
fn test_atomic_counter_concurrent_increments() {
let counter = Arc::new(AtomicCounter::new());
let num_threads = 16;
let increments_per_thread = 10000;
let mut handles = Vec::new();
for _ in 0..num_threads {
let counter_clone: Arc<AtomicCounter> = Arc::clone(&counter);
let handle = thread::spawn(move || {
for _ in 0..increments_per_thread {
counter_clone.next();
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread failed");
}
// Final value should be num_threads * increments_per_thread
assert_eq!(counter.get(), (num_threads * increments_per_thread) as u64);
}
#[test]
fn test_atomic_counter_wraparound() {
let counter = AtomicCounter::new_with(u64::MAX - 5, 1);
assert_eq!(counter.next(), u64::MAX - 5);
assert_eq!(counter.next(), u64::MAX - 4);
assert_eq!(counter.next(), u64::MAX - 3);
assert_eq!(counter.next(), u64::MAX - 2);
assert_eq!(counter.next(), u64::MAX - 1);
assert_eq!(counter.next(), u64::MAX);
// Next increment will wrap around to 0
assert_eq!(counter.next(), 0);
}
#[test]
fn test_mpsc_queue_debug_output() {
let queue = MPSCQueue::<u64>::new();
queue.push(1);
queue.push(2);
let debug_str = format!("{:?}", queue);
assert!(debug_str.contains("MPSCQueue"));
assert!(debug_str.contains("size"));
}
#[test]
fn test_atomic_counter_debug_output() {
let counter = AtomicCounter::new_with(42, 5);
let debug_str = format!("{:?}", counter);
assert!(debug_str.contains("AtomicCounter"));
assert!(debug_str.contains("42"));
}
#[test]
fn test_mpsc_queue_with_large_items() {
let queue = MPSCQueue::<Vec<u64>>::new();
// Push large vectors
for i in 0..100 {
let vec = vec![i; 1000]; // 1000 elements each
queue.push(vec);
}
// Pop and verify
let mut count = 0;
while let Some(vec) = queue.try_pop() {
assert_eq!(vec.len(), 1000);
assert_eq!(vec[0], count);
count += 1;
}
assert_eq!(count, 100);
}