Files
foxhunt/testing/integration/unit/edge_case_boundary_tests.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

617 lines
20 KiB
Rust

//! 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<Self, OverflowError> {
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<SafePrice, OverflowError> {
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<SafePrice, OverflowError> {
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<SafeQuantity, OverflowError> {
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<SafeQuantity, OverflowError> {
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<SafeQuantity, OverflowError> {
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<SafeVolume, OverflowError> {
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<Utc>) -> Self {
SafeTimestamp {
value: dt.timestamp_millis()
}
}
pub fn to_datetime(&self) -> Option<DateTime<Utc>> {
Utc.timestamp_millis_opt(self.value).single()
}
pub fn duration_since(&self, other: &SafeTimestamp) -> Option<i64> {
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);
}
}