Files
foxhunt/ml/tests/ppo_circuit_breaker_tests.rs
jgrusewski c645e6222d Wave 11: Rainbow DQN integration + 23/23 tests passing
CRITICAL FINDINGS from 3-trial validation:
- 85,120 gradient clipping warnings (81.6% of logs) - REGRESSION
- Rainbow features DISABLED: use_dueling=false, use_distributional=false, use_noisy_nets=false
- Negative Q-values confirmed: HOLD -1000 to -3250
- Performance: Sharpe 0.29 (target 0.77)

Changes:
- Fixed N-Step compilation (7/7 tests passing)
- Fixed Distributional compilation (6/6 tests passing)
- Fixed Dueling CUDA errors (10/10 tests passing)
- Added TDD validation for state_dim=225
- Total: 23/23 Wave 11 tests passing (100%)

Issues requiring investigation:
1. Why are Dueling/Distributional/Noisy disabled in hyperopt?
2. Why gradient explosion despite previous fixes?
3. Test coverage gaps - unit tests pass but integration fails

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 13:53:59 +01:00

93 lines
3.0 KiB
Rust

//! Test suite for PPO CircuitBreaker
//!
//! TDD Red Phase: Tests written FIRST before implementation
//! These tests define the expected behavior of the CircuitBreaker module
use std::thread;
use std::time::Duration;
// Import from PPO module (will fail until implementation exists)
use ml::ppo::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig, CircuitState};
#[test]
fn test_circuit_breaker_closed_to_open() {
// Test that circuit opens after exceeding failure threshold
let config = CircuitBreakerConfig {
failure_threshold: 3,
success_threshold: 2,
timeout_duration: Duration::from_secs(60),
half_open_max_calls: 2,
};
let breaker = CircuitBreaker::new(config);
// Initial state: Closed
assert_eq!(breaker.current_state(), CircuitState::Closed);
assert!(breaker.allow_request());
// Record 2 failures - should stay closed
breaker.record_failure();
breaker.record_failure();
assert_eq!(breaker.current_state(), CircuitState::Closed);
assert!(breaker.allow_request());
// Third failure - should open
breaker.record_failure();
assert_eq!(breaker.current_state(), CircuitState::Open);
assert!(!breaker.allow_request()); // Blocked when open
}
#[test]
fn test_circuit_breaker_half_open_recovery() {
// Test transition from HalfOpen to Closed after successful recoveries
let config = CircuitBreakerConfig {
failure_threshold: 2,
success_threshold: 2,
timeout_duration: Duration::from_millis(100),
half_open_max_calls: 3,
};
let breaker = CircuitBreaker::new(config);
// Open the circuit
breaker.record_failure();
breaker.record_failure();
assert_eq!(breaker.current_state(), CircuitState::Open);
// Wait for timeout to transition to half-open
thread::sleep(Duration::from_millis(150));
assert!(breaker.allow_request());
assert_eq!(breaker.current_state(), CircuitState::HalfOpen);
// Record successes to close the circuit
breaker.record_success();
assert_eq!(breaker.current_state(), CircuitState::HalfOpen);
breaker.record_success();
assert_eq!(breaker.current_state(), CircuitState::Closed);
assert!(breaker.allow_request());
}
#[test]
fn test_circuit_breaker_rejects_in_open() {
// Test that training requests are rejected when circuit is open
let config = CircuitBreakerConfig {
failure_threshold: 2,
success_threshold: 2,
timeout_duration: Duration::from_secs(10), // Long timeout
half_open_max_calls: 2,
};
let breaker = CircuitBreaker::new(config);
// Open the circuit
breaker.record_failure();
breaker.record_failure();
assert_eq!(breaker.current_state(), CircuitState::Open);
// Repeated requests should be rejected (timeout not elapsed)
assert!(!breaker.allow_request());
assert!(!breaker.allow_request());
assert!(!breaker.allow_request());
// Circuit should still be open
assert_eq!(breaker.current_state(), CircuitState::Open);
}