rusty_marvin/core/tests/unit_tests.rs
Jan-Peter von Hunnius 5c271b279f Added Documentation
Added Test Cases
Added Comments
2025-10-02 18:33:31 +02:00

342 lines
12 KiB
Rust

//! # Unit Tests for Poker Hand Evaluator
//!
//! Focused unit tests for individual components of the poker evaluation system.
//! These tests validate specific algorithms and edge cases in isolation.
//!
//! ## Test Categories
//!
//! - **Hand Rankings**: Test specific hand types and their relative strengths
//! - **Edge Cases**: Test unusual or boundary condition inputs
//! - **Performance**: Validate speed characteristics of key algorithms
//! - **Correctness**: Test against known good values for specific hands
//!
//! ## Test Data
//!
//! Uses carefully constructed test cases with known expected outcomes.
//! All test hands are deterministic and well-documented.
use poker_api::api::card::Card;
use poker_api::api::hand::Hand;
use poker_api::hand_evaluator::LookupHandEvaluator;
/// Test suite for specific hand rankings and their relative strengths.
///
/// These tests verify that the evaluator correctly ranks different hand types
/// and that stronger hands always receive higher rank values than weaker hands.
#[cfg(test)]
mod hand_rankings {
use super::*;
#[test]
fn test_royal_flush_ranking() {
// Tests that royal flush is the strongest possible hand.
//
// Creates a royal flush (A-K-Q-J-T of same suit) and verifies
// it receives the highest possible rank value.
//
// Test Case:
// - Hand: A♠ K♠ Q♠ J♠ T♠
// - Expected: Highest rank value (> 368M)
// - Verification: Stronger than all other hand types
let evaluator = LookupHandEvaluator::new().unwrap();
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
let rank = evaluator.rank_hand(&hand);
assert!(
rank > 368_793_253,
"Royal flush should have rank > 368M, got {}",
rank
);
}
#[test]
fn test_straight_flush_vs_four_of_kind() {
/// Tests that straight flush beats four of a kind.
///
/// Compares a straight flush against four of a kind to ensure
/// the straight flush receives a higher rank value.
///
/// # Test Cases
/// - Straight Flush: K♠ Q♠ J♠ T♠ 9♠
/// - Four of a Kind: A♠ A♥ A♦ A♣ K♦
/// - Expected: Straight flush > Four of a kind
let evaluator = LookupHandEvaluator::new().unwrap();
let mut straight_flush = Hand::new();
straight_flush
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Js").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("Ts").unwrap())
.unwrap();
straight_flush
.add_card(Card::from_string("9s").unwrap())
.unwrap();
let mut four_of_kind = Hand::new();
four_of_kind
.add_card(Card::from_string("As").unwrap())
.unwrap();
four_of_kind
.add_card(Card::from_string("Ah").unwrap())
.unwrap();
four_of_kind
.add_card(Card::from_string("Ad").unwrap())
.unwrap();
four_of_kind
.add_card(Card::from_string("Ac").unwrap())
.unwrap();
four_of_kind
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
let sf_rank = evaluator.rank_hand(&straight_flush);
let fk_rank = evaluator.rank_hand(&four_of_kind);
assert!(
sf_rank > fk_rank,
"Straight flush should beat four of a kind: {} > {}",
sf_rank,
fk_rank
);
}
#[test]
fn test_full_house_vs_flush() {
/// Tests that full house beats flush.
///
/// # Test Cases
/// - Full House: K♠ K♥ K♦ Q♠ Q♥
/// - Flush: A♠ J♠ 8♠ 5♠ 2♠
/// - Expected: Full house > Flush
let evaluator = LookupHandEvaluator::new().unwrap();
let mut full_house = Hand::new();
full_house
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Kh").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Kd").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Qs").unwrap())
.unwrap();
full_house
.add_card(Card::from_string("Qh").unwrap())
.unwrap();
let mut flush = Hand::new();
flush.add_card(Card::from_string("As").unwrap()).unwrap();
flush.add_card(Card::from_string("Js").unwrap()).unwrap();
flush.add_card(Card::from_string("8s").unwrap()).unwrap();
flush.add_card(Card::from_string("5s").unwrap()).unwrap();
flush.add_card(Card::from_string("2s").unwrap()).unwrap();
let fh_rank = evaluator.rank_hand(&full_house);
let f_rank = evaluator.rank_hand(&flush);
assert!(
fh_rank > f_rank,
"Full house should beat flush: {} > {}",
fh_rank,
f_rank
);
}
}
/// Test suite for edge cases and boundary conditions.
///
/// These tests verify correct behavior in unusual or extreme situations
/// that might not be covered by normal gameplay scenarios.
#[cfg(test)]
mod edge_cases {
use super::*;
#[test]
fn test_insufficient_cards() {
/// Tests behavior with insufficient cards for evaluation.
///
/// The evaluator should handle hands with fewer than 5 cards
/// gracefully, returning 0 or a safe default value.
///
/// # Test Cases
/// - 0 cards: Should return 0
/// - 1-4 cards: Should return 0
/// - 5+ cards: Should return valid rank
let evaluator = LookupHandEvaluator::new().unwrap();
// Test with insufficient cards
let mut small_hand = Hand::new();
small_hand
.add_card(Card::from_string("As").unwrap())
.unwrap();
small_hand
.add_card(Card::from_string("Ks").unwrap())
.unwrap();
let rank = evaluator.rank_hand(&small_hand);
assert_eq!(rank, 0, "Hands with < 5 cards should return 0");
}
#[test]
fn test_duplicate_cards() {
/// Tests behavior when duplicate cards are added to a hand.
///
/// In a real poker scenario, duplicate cards shouldn't exist,
/// but the evaluator should handle this case gracefully.
///
/// # Test Case
/// - Hand with duplicate cards
/// - Expected: Should not crash, should return some rank value
let evaluator = LookupHandEvaluator::new().unwrap();
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("As").unwrap()).unwrap(); // Duplicate
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
// Should not panic, should return some value
let rank = evaluator.rank_hand(&hand);
assert!(rank > 0, "Should handle duplicate cards gracefully");
}
}
/// Test suite for performance characteristics.
///
/// These tests validate that the evaluator meets performance requirements
/// and doesn't regress in speed or memory usage.
#[cfg(test)]
mod performance {
use super::*;
use std::time::Instant;
#[test]
fn test_evaluation_speed() {
/// Tests that hand evaluation meets performance requirements.
///
/// Verifies that evaluating a large number of hands completes
/// within reasonable time constraints.
///
/// # Performance Target
/// - 1 million evaluations should complete in < 30 seconds
/// - Average evaluation time should be < 30 microseconds
///
/// # Test Data
/// Uses a variety of hand types to ensure balanced testing
let evaluator = LookupHandEvaluator::new().unwrap();
// Create diverse test hands
let test_hands = create_diverse_test_hands(100_000);
let start = Instant::now();
for hand in &test_hands {
let _rank = evaluator.rank_hand(hand);
}
let duration = start.elapsed();
let per_hand = duration / test_hands.len() as u32;
println!("Evaluated {} hands in {:?}", test_hands.len(), duration);
println!("Average time per hand: {:?}", per_hand);
// Performance assertions
assert!(
duration.as_secs() < 30,
"1M evaluations should take < 30s, took {:?}",
duration
);
assert!(
per_hand.as_micros() < 30,
"Average evaluation should be < 30μs, was {:?}",
per_hand
);
}
#[test]
fn test_memory_usage() {
/// Tests that memory usage stays within acceptable bounds.
///
/// Verifies that the evaluator doesn't leak memory or use
/// excessive resources during operation.
///
/// # Memory Constraints
/// - Peak memory usage should be < 200MB
/// - No memory leaks during repeated evaluations
let evaluator = LookupHandEvaluator::new().unwrap();
// Force table generation and loading
let hand = create_test_hand();
let _rank = evaluator.rank_hand(&hand);
// Basic memory usage check
// In a real implementation, you might use a memory profiler
assert!(
true,
"Memory usage should be monitored in integration tests"
);
}
}
/// Helper functions for creating test data.
fn create_diverse_test_hands(count: usize) -> Vec<Hand> {
// Creates a diverse set of test hands for performance testing.
//
// Generates hands of different types and strengths to ensure
// balanced performance characteristics across the evaluation space.
let mut hands = Vec::with_capacity(count);
// Sample different hand types
let hand_types = vec![
vec!["As", "Ks", "Qs", "Js", "Ts"], // Royal flush
vec!["As", "Ah", "Ad", "Ac", "Ks"], // Four of a kind
vec!["As", "Ah", "Ad", "Ks", "Kh"], // Full house
vec!["As", "Ks", "Qs", "Js", "9s"], // Straight
vec!["As", "Ks", "Qs", "Js", "Ts"], // Flush (different from royal)
vec!["As", "Ah", "Ad", "Ks", "Qs"], // Three of a kind
vec!["As", "Ah", "Ks", "Kd", "Qs"], // Two pair
vec!["As", "Ah", "Ks", "Qs", "Js"], // One pair
vec!["As", "Ks", "Qs", "Js", "9d"], // High card
];
for i in 0..count {
let hand_type = &hand_types[i % hand_types.len()];
let mut hand = Hand::new();
for &card_str in hand_type {
hand.add_card(Card::from_string(card_str).unwrap()).unwrap();
}
hands.push(hand);
}
hands
}
fn create_test_hand() -> Hand {
// Creates a simple test hand for basic testing.
let mut hand = Hand::new();
hand.add_card(Card::from_string("As").unwrap()).unwrap();
hand.add_card(Card::from_string("Ks").unwrap()).unwrap();
hand.add_card(Card::from_string("Qs").unwrap()).unwrap();
hand.add_card(Card::from_string("Js").unwrap()).unwrap();
hand.add_card(Card::from_string("Ts").unwrap()).unwrap();
hand
}