//! Comprehensive Edge Case and Boundary Condition Tests //! //! This module provides exhaustive testing of edge cases, boundary conditions, //! overflow/underflow scenarios, and extreme value handling to ensure the //! Foxhunt HFT system remains stable under all conditions. //! //! CRITICAL: These tests prevent system crashes and data corruption when //! processing extreme market conditions and edge cases. use std::f64::{INFINITY, NEG_INFINITY, NAN, MAX, MIN, MIN_POSITIVE, EPSILON}; use std::i64::{MAX as I64_MAX, MIN as I64_MIN}; use std::u64::{MAX as U64_MAX, MIN as U64_MIN}; use chrono::{DateTime, Utc, TimeZone}; // Mock types for comprehensive edge case testing #[derive(Debug, Clone, PartialEq)] /// SafePrice component. pub struct SafePrice { value: f64, } #[derive(Debug, Clone, PartialEq)] /// SafeQuantity component. pub struct SafeQuantity { value: i64, } #[derive(Debug, Clone, PartialEq)] /// SafeVolume component. pub struct SafeVolume { value: u64, } #[derive(Debug, Clone, PartialEq)] /// SafeTimestamp component. pub struct SafeTimestamp { value: i64, } #[derive(Debug, Clone, PartialEq)] /// `OverflowError` component. pub enum OverflowError { PositiveOverflow, NegativeUnderflow, InvalidValue, } // Safe arithmetic implementations impl SafePrice { pub fn new(value: f64) -> Result { if value.is_nan() || value.is_infinite() { Err(OverflowError::InvalidValue) } else if value < 0.0 { Err(OverflowError::NegativeUnderflow) } else if value > 1e12 { // Arbitrary large limit Err(OverflowError::PositiveOverflow) } else { Ok(SafePrice { value }) } } pub fn from_f64_clamped(value: f64) -> Self { let clamped = if value.is_nan() || value.is_infinite() || value < 0.0 { 0.0 } else if value > 1e12 { 1e12 } else { value }; SafePrice { value: clamped } } pub fn to_f64(&self) -> f64 { self.value } pub fn checked_add(&self, other: &SafePrice) -> Result { let result = self.value + other.value; if result.is_finite() && result >= 0.0 && result <= 1e12 { Ok(SafePrice { value: result }) } else if result > 1e12 { Err(OverflowError::PositiveOverflow) } else { Err(OverflowError::InvalidValue) } } pub fn checked_multiply(&self, factor: f64) -> Result { if factor.is_nan() || factor.is_infinite() { return Err(OverflowError::InvalidValue); } let result = self.value * factor; if result.is_finite() && result >= 0.0 && result <= 1e12 { Ok(SafePrice { value: result }) } else if result > 1e12 || result.is_infinite() { Err(OverflowError::PositiveOverflow) } else if result < 0.0 { Err(OverflowError::NegativeUnderflow) } else { Err(OverflowError::InvalidValue) } } } impl SafeQuantity { pub fn new(value: i64) -> Self { SafeQuantity { value } } pub fn to_i64(&self) -> i64 { self.value } pub fn checked_add(&self, other: &SafeQuantity) -> Result { match self.value.checked_add(other.value) { Some(result) => Ok(SafeQuantity { value: result }), None => { if (self.value > 0 && other.value > 0) { Err(OverflowError::PositiveOverflow) } else { Err(OverflowError::NegativeUnderflow) } } } } pub fn saturating_add(&self, other: &SafeQuantity) -> SafeQuantity { SafeQuantity { value: self.value.saturating_add(other.value) } } pub fn checked_multiply(&self, factor: i64) -> Result { match self.value.checked_mul(factor) { Some(result) => Ok(SafeQuantity { value: result }), None => { if (self.value > 0 && factor > 0) || (self.value < 0 && factor < 0) { Err(OverflowError::PositiveOverflow) } else { Err(OverflowError::NegativeUnderflow) } } } } pub fn abs_safe(&self) -> Result { if self.value == I64_MIN { Err(OverflowError::PositiveOverflow) // |MIN| would overflow } else { Ok(SafeQuantity { value: self.value.abs() }) } } } impl SafeVolume { pub fn new(value: u64) -> Self { SafeVolume { value } } pub fn to_u64(&self) -> u64 { self.value } pub fn checked_add(&self, other: &SafeVolume) -> Result { match self.value.checked_add(other.value) { Some(result) => Ok(SafeVolume { value: result }), None => Err(OverflowError::PositiveOverflow) } } pub fn saturating_add(&self, other: &SafeVolume) -> SafeVolume { SafeVolume { value: self.value.saturating_add(other.value) } } } impl SafeTimestamp { pub fn new(value: i64) -> Self { SafeTimestamp { value } } pub fn now() -> Self { SafeTimestamp { value: Utc::now().timestamp_millis() } } pub fn from_datetime(dt: DateTime) -> Self { SafeTimestamp { value: dt.timestamp_millis() } } pub fn to_datetime(&self) -> Option> { Utc.timestamp_millis_opt(self.value).single() } pub fn duration_since(&self, other: &SafeTimestamp) -> Option { self.value.checked_sub(other.value) } } // ============================================================================ // Floating Point Edge Case Tests // ============================================================================ #[test] fn test_price_nan_handling() { // Test NaN input handling let price = SafePrice::new(NAN); assert!(price.is_err()); let clamped_price = SafePrice::from_f64_clamped(NAN); assert_eq!(clamped_price.to_f64(), 0.0); } #[test] fn test_price_infinity_handling() { // Test positive infinity let pos_inf_price = SafePrice::new(INFINITY); assert!(pos_inf_price.is_err()); let clamped_pos_inf = SafePrice::from_f64_clamped(INFINITY); assert_eq!(clamped_pos_inf.to_f64(), 1e12); // Test negative infinity let neg_inf_price = SafePrice::new(NEG_INFINITY); assert!(neg_inf_price.is_err()); let clamped_neg_inf = SafePrice::from_f64_clamped(NEG_INFINITY); assert_eq!(clamped_neg_inf.to_f64(), 0.0); } #[test] fn test_price_extreme_values() { // Test maximum finite value let max_price = SafePrice::new(MAX); assert!(max_price.is_err()); // Should exceed our limit // Test minimum positive value let min_pos_price = SafePrice::new(MIN_POSITIVE); assert!(min_pos_price.is_ok()); assert_eq!(min_pos_price.unwrap().to_f64(), MIN_POSITIVE); // Test zero let zero_price = SafePrice::new(0.0); assert!(zero_price.is_ok()); assert_eq!(zero_price.unwrap().to_f64(), 0.0); // Test negative zero let neg_zero_price = SafePrice::new(-0.0); assert!(neg_zero_price.is_ok()); assert_eq!(neg_zero_price.unwrap().to_f64(), 0.0); } #[test] fn test_price_arithmetic_overflow() { // Test addition overflow let large_price = SafePrice::new(9e11).unwrap(); let other_price = SafePrice::new(5e11).unwrap(); let result = large_price.checked_add(&other_price); assert!(result.is_err()); // Test multiplication overflow let multiplication_result = large_price.checked_multiply(2.0); assert!(multiplication_result.is_err()); } #[test] fn test_price_precision_edge_cases() { // Test very small differences let price1 = SafePrice::new(1.0).unwrap(); let price2 = SafePrice::new(1.0 + EPSILON).unwrap(); let diff = price2.to_f64() - price1.to_f64(); assert!(diff > 0.0); assert!(diff <= EPSILON * 2.0); // Test precision around common trading values let forex_price = SafePrice::new(1.1234).unwrap(); let pip_movement = SafePrice::new(0.0001).unwrap(); let new_price = forex_price.checked_add(&pip_movement).unwrap(); assert!((new_price.to_f64() - 1.1235).abs() < 1e-15); } // ============================================================================ // Integer Overflow/Underflow Tests // ============================================================================ #[test] fn test_quantity_overflow_detection() { // Test positive overflow let large_qty = SafeQuantity::new(I64_MAX); let one_qty = SafeQuantity::new(1); let overflow_result = large_qty.checked_add(&one_qty); assert!(overflow_result.is_err()); // But saturating add should work let saturated_result = large_qty.saturating_add(&one_qty); assert_eq!(saturated_result.to_i64(), I64_MAX); } #[test] fn test_quantity_underflow_detection() { // Test negative underflow let min_qty = SafeQuantity::new(I64_MIN); let neg_one_qty = SafeQuantity::new(-1); let underflow_result = min_qty.checked_add(&neg_one_qty); assert!(underflow_result.is_err()); // But saturating add should work let saturated_result = min_qty.saturating_add(&neg_one_qty); assert_eq!(saturated_result.to_i64(), I64_MIN); } #[test] fn test_quantity_multiplication_overflow() { // Test multiplication that would overflow let qty = SafeQuantity::new(I64_MAX / 2 + 1); let multiply_result = qty.checked_multiply(2); assert!(multiply_result.is_err()); // Test safe multiplication let safe_qty = SafeQuantity::new(1000); let safe_result = safe_qty.checked_multiply(1000); assert!(safe_result.is_ok()); assert_eq!(safe_result.unwrap().to_i64(), 1_000_000); } #[test] fn test_quantity_abs_edge_case() { // Test absolute value of minimum integer (should overflow) let min_qty = SafeQuantity::new(I64_MIN); let abs_result = min_qty.abs_safe(); assert!(abs_result.is_err()); // Test normal absolute value let neg_qty = SafeQuantity::new(-1000); let abs_normal = neg_qty.abs_safe(); assert!(abs_normal.is_ok()); assert_eq!(abs_normal.unwrap().to_i64(), 1000); } #[test] fn test_volume_overflow() { // Test volume overflow let large_volume = SafeVolume::new(U64_MAX); let one_volume = SafeVolume::new(1); let overflow_result = large_volume.checked_add(&one_volume); assert!(overflow_result.is_err()); // Test saturating behavior let saturated_result = large_volume.saturating_add(&one_volume); assert_eq!(saturated_result.to_u64(), U64_MAX); } // ============================================================================ // Timestamp and Time Handling Edge Cases // ============================================================================ #[test] fn test_timestamp_edge_cases() { // Test minimum timestamp let min_timestamp = SafeTimestamp::new(I64_MIN); let datetime = min_timestamp.to_datetime(); assert!(datetime.is_none()); // Should be out of range // Test maximum timestamp let max_timestamp = SafeTimestamp::new(I64_MAX); let datetime = max_timestamp.to_datetime(); assert!(datetime.is_none()); // Should be out of range // Test current timestamp let now = SafeTimestamp::now(); let datetime = now.to_datetime(); assert!(datetime.is_some()); } #[test] fn test_timestamp_duration_overflow() { // Test duration calculation that could overflow let early_time = SafeTimestamp::new(I64_MIN + 1000); let late_time = SafeTimestamp::new(I64_MAX - 1000); let duration = late_time.duration_since(&early_time); assert!(duration.is_none()); // Should overflow // Test normal duration let time1 = SafeTimestamp::new(1000); let time2 = SafeTimestamp::new(2000); let normal_duration = time2.duration_since(&time1); assert!(normal_duration.is_some()); assert_eq!(normal_duration.unwrap(), 1000); } #[test] fn test_unix_epoch_edge_cases() { // Test Unix epoch let epoch = SafeTimestamp::new(0); let epoch_datetime = epoch.to_datetime(); assert!(epoch_datetime.is_some()); // Test negative timestamps (before epoch) let before_epoch = SafeTimestamp::new(-86400000); // 1 day before epoch let before_datetime = before_epoch.to_datetime(); assert!(before_datetime.is_some()); // Test year 2038 problem area (32-bit signed seconds) let y2038_ms = SafeTimestamp::new(2147483647000i64); // 2038-01-19 let y2038_datetime = y2038_ms.to_datetime(); assert!(y2038_datetime.is_some()); } // ============================================================================ // Boundary Value Analysis Tests // ============================================================================ #[test] fn test_zero_boundary_conditions() { // Test operations at zero boundary let zero_price = SafePrice::new(0.0).unwrap(); let zero_qty = SafeQuantity::new(0); let zero_volume = SafeVolume::new(0); // Zero arithmetic should be safe let zero_sum_price = zero_price.checked_add(&zero_price); assert!(zero_sum_price.is_ok()); assert_eq!(zero_sum_price.unwrap().to_f64(), 0.0); let zero_sum_qty = zero_qty.checked_add(&zero_qty); assert!(zero_sum_qty.is_ok()); assert_eq!(zero_sum_qty.unwrap().to_i64(), 0); let zero_sum_volume = zero_volume.checked_add(&zero_volume); assert!(zero_sum_volume.is_ok()); assert_eq!(zero_sum_volume.unwrap().to_u64(), 0); } #[test] fn test_sign_boundary_conditions() { // Test crossing zero boundary let pos_qty = SafeQuantity::new(100); let neg_qty = SafeQuantity::new(-150); let cross_zero = pos_qty.checked_add(&neg_qty); assert!(cross_zero.is_ok()); assert_eq!(cross_zero.unwrap().to_i64(), -50); // Test multiplication sign changes let multiply_pos = pos_qty.checked_multiply(-1); assert!(multiply_pos.is_ok()); assert_eq!(multiply_pos.unwrap().to_i64(), -100); let multiply_neg = neg_qty.checked_multiply(-1); assert!(multiply_neg.is_ok()); assert_eq!(multiply_neg.unwrap().to_i64(), 150); } #[test] fn test_one_off_boundary_conditions() { // Test one-off errors around boundaries // Test around maximum safe price let near_max_price = SafePrice::new(1e12 - 1.0); assert!(near_max_price.is_ok()); let at_max_price = SafePrice::new(1e12); assert!(at_max_price.is_err()); let over_max_price = SafePrice::new(1e12 + 1.0); assert!(over_max_price.is_err()); // Test around integer boundaries let near_max_qty = SafeQuantity::new(I64_MAX - 1); let one_qty = SafeQuantity::new(1); let at_max = near_max_qty.checked_add(&one_qty); assert!(at_max.is_ok()); assert_eq!(at_max.unwrap().to_i64(), I64_MAX); let over_max = at_max.unwrap().checked_add(&one_qty); assert!(over_max.is_err()); } // ============================================================================ // Stress Tests for Edge Conditions // ============================================================================ #[test] fn test_repeated_edge_operations() { // Test repeated operations near boundaries let mut price = SafePrice::new(1.0).unwrap(); let increment = SafePrice::new(1e-10).unwrap(); // Very small increment // Perform many small additions for _ in 0..1_000_000 { match price.checked_add(&increment) { Ok(new_price) => price = new_price, Err(_) => break, // Stop if we hit a boundary } } // Should still be a valid, finite price assert!(price.to_f64().is_finite()); assert!(price.to_f64() > 1.0); } #[test] fn test_alternating_edge_operations() { // Test alternating operations that could accumulate errors let mut qty = SafeQuantity::new(0); let large_add = SafeQuantity::new(1_000_000); let large_sub = SafeQuantity::new(-1_000_000); // Alternate large additions and subtractions for _ in 0..1000 { qty = qty.saturating_add(&large_add); qty = qty.saturating_add(&large_sub); } // Should return to approximately zero assert_eq!(qty.to_i64(), 0); } #[test] fn test_compound_edge_conditions() { // Test multiple edge conditions occurring together let edge_price = SafePrice::new(MIN_POSITIVE).unwrap(); let max_qty = SafeQuantity::new(I64_MAX); let max_volume = SafeVolume::new(U64_MAX); // These should all be handled gracefully let _price_double = edge_price.checked_multiply(2.0); let _qty_increment = max_qty.saturating_add(&SafeQuantity::new(1)); let _volume_increment = max_volume.saturating_add(&SafeVolume::new(1)); // No panics should occur } // ============================================================================ // Financial Edge Case Integration Tests // ============================================================================ #[test] fn test_extreme_market_scenario() { // Test extreme market crash scenario (99% price drop) let pre_crash_price = SafePrice::new(100.0).unwrap(); let crash_factor = 0.01; // 99% drop let post_crash_price = pre_crash_price.checked_multiply(crash_factor); assert!(post_crash_price.is_ok()); assert_eq!(post_crash_price.unwrap().to_f64(), 1.0); // Test extreme volatility (1000% increase) let extreme_factor = 10.0; let extreme_price = pre_crash_price.checked_multiply(extreme_factor); assert!(extreme_price.is_ok()); assert_eq!(extreme_price.unwrap().to_f64(), 1000.0); } #[test] fn test_high_frequency_edge_conditions() { // Test conditions that might occur in high-frequency trading let base_price = SafePrice::new(1.1234).unwrap(); let tick_size = SafePrice::new(0.00001).unwrap(); // 0.1 pip // Simulate rapid small price changes let mut current_price = base_price; let directions = [1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0]; // Random walk for &direction in &directions { let change = tick_size.checked_multiply(direction); if let Ok(change_price) = change { if let Ok(new_price) = current_price.checked_add(&change_price) { current_price = new_price; } } } // Price should remain valid and close to original assert!(current_price.to_f64().is_finite()); assert!((current_price.to_f64() - base_price.to_f64()).abs() < 0.001); } #[test] fn test_position_size_edge_cases() { // Test position sizes at the edge of what's reasonable let micro_position = SafeQuantity::new(1); // 1 unit let standard_position = SafeQuantity::new(100_000); // Standard lot let whale_position = SafeQuantity::new(1_000_000_000); // Billion units // All should be valid assert_eq!(micro_position.to_i64(), 1); assert_eq!(standard_position.to_i64(), 100_000); assert_eq!(whale_position.to_i64(), 1_000_000_000); // Operations should be safe let _micro_double = micro_position.checked_multiply(2); let _standard_double = standard_position.checked_multiply(2); let whale_double = whale_position.checked_multiply(2); // Whale position doubling might overflow if whale_double.is_err() { // This is expected and safe behavior assert!(true); } else { // If it succeeds, result should be valid assert!(whale_double.unwrap().to_i64() > 0); } }