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>
93 lines
3.0 KiB
Rust
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);
|
|
}
|