use crate::metrics::*;
#[test]
fn test_recall_at_k_perfect() {
let ground_truth = vec![1u64, 2, 3, 4, 5];
let results = vec![1u64, 2, 3, 4, 5];
let recall = recall_at_k(&ground_truth, &results);
assert!(
(recall - 1.0).abs() < f64::EPSILON,
"Expected 1.0, got {recall}"
);
}
#[test]
fn test_recall_at_k_partial() {
let ground_truth = vec![1u64, 2, 3, 4, 5];
let results = vec![1u64, 3, 6, 2, 7];
let recall = recall_at_k(&ground_truth, &results);
assert!(
(recall - 0.6).abs() < f64::EPSILON,
"Expected 0.6, got {recall}"
);
}
#[test]
fn test_recall_at_k_zero() {
let ground_truth = vec![1u64, 2, 3];
let results = vec![10u64, 20, 30];
let recall = recall_at_k(&ground_truth, &results);
assert!(
(recall - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {recall}"
);
}
#[test]
fn test_recall_at_k_empty_ground_truth() {
let ground_truth: Vec<u64> = vec![];
let results = vec![1u64, 2, 3];
let recall = recall_at_k(&ground_truth, &results);
assert!(
(recall - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {recall}"
);
}
#[test]
fn test_recall_at_k_empty_results() {
let ground_truth = vec![1u64, 2, 3];
let results: Vec<u64> = vec![];
let recall = recall_at_k(&ground_truth, &results);
assert!(
(recall - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {recall}"
);
}
#[test]
fn test_precision_at_k_perfect() {
let ground_truth = vec![1u64, 2, 3, 4, 5];
let results = vec![1u64, 2, 3, 4, 5];
let precision = precision_at_k(&ground_truth, &results);
assert!(
(precision - 1.0).abs() < f64::EPSILON,
"Expected 1.0, got {precision}"
);
}
#[test]
fn test_precision_at_k_partial() {
let ground_truth = vec![1u64, 2, 3, 4, 5];
let results = vec![1u64, 3, 6, 2, 7];
let precision = precision_at_k(&ground_truth, &results);
assert!(
(precision - 0.6).abs() < f64::EPSILON,
"Expected 0.6, got {precision}"
);
}
#[test]
fn test_precision_at_k_zero() {
let ground_truth = vec![1u64, 2, 3];
let results = vec![10u64, 20, 30];
let precision = precision_at_k(&ground_truth, &results);
assert!(
(precision - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {precision}"
);
}
#[test]
fn test_precision_at_k_empty_results() {
let ground_truth = vec![1u64, 2, 3];
let results: Vec<u64> = vec![];
let precision = precision_at_k(&ground_truth, &results);
assert!(
(precision - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {precision}"
);
}
#[test]
fn test_precision_different_k() {
let ground_truth = vec![1u64, 2, 3];
let results = vec![1u64, 2, 3, 10, 20, 30, 40, 50, 60, 70];
let precision = precision_at_k(&ground_truth, &results);
assert!(
(precision - 0.3).abs() < f64::EPSILON,
"Expected 0.3, got {precision}"
);
}
#[test]
fn test_mrr_first_relevant() {
let ground_truth = vec![1u64, 2, 3];
let results = vec![1u64, 10, 20, 30];
let mrr_val = mrr(&ground_truth, &results);
assert!(
(mrr_val - 1.0).abs() < f64::EPSILON,
"Expected 1.0, got {mrr_val}"
);
}
#[test]
fn test_mrr_second_relevant() {
let ground_truth = vec![1u64, 2, 3];
let results = vec![10u64, 1, 20, 30];
let mrr_val = mrr(&ground_truth, &results);
assert!(
(mrr_val - 0.5).abs() < f64::EPSILON,
"Expected 0.5, got {mrr_val}"
);
}
#[test]
fn test_mrr_third_relevant() {
let ground_truth = vec![1u64, 2, 3];
let results = vec![10u64, 20, 2, 30];
let mrr_val = mrr(&ground_truth, &results);
let expected = 1.0 / 3.0;
assert!(
(mrr_val - expected).abs() < f64::EPSILON,
"Expected {expected}, got {mrr_val}"
);
}
#[test]
fn test_mrr_no_relevant() {
let ground_truth = vec![1u64, 2, 3];
let results = vec![10u64, 20, 30, 40];
let mrr_val = mrr(&ground_truth, &results);
assert!(
(mrr_val - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {mrr_val}"
);
}
#[test]
fn test_mrr_empty_results() {
let ground_truth = vec![1u64, 2, 3];
let results: Vec<u64> = vec![];
let mrr_val = mrr(&ground_truth, &results);
assert!(
(mrr_val - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {mrr_val}"
);
}
#[test]
fn test_average_metrics_perfect() {
let ground_truths = vec![vec![1u64, 2, 3], vec![4u64, 5, 6]];
let results_list = vec![vec![1u64, 2, 3], vec![4u64, 5, 6]];
let (avg_recall, avg_precision, avg_mrr) = average_metrics(&ground_truths, &results_list);
assert!(
(avg_recall - 1.0).abs() < f64::EPSILON,
"Expected recall 1.0, got {avg_recall}"
);
assert!(
(avg_precision - 1.0).abs() < f64::EPSILON,
"Expected precision 1.0, got {avg_precision}"
);
assert!(
(avg_mrr - 1.0).abs() < f64::EPSILON,
"Expected MRR 1.0, got {avg_mrr}"
);
}
#[test]
fn test_average_metrics_mixed() {
let ground_truths = vec![
vec![1u64, 2, 3, 4, 5], vec![10u64, 20, 30, 40, 50], ];
let results_list = vec![
vec![1u64, 2, 3, 10, 20], vec![10u64, 11, 12, 13, 14], ];
let (avg_recall, avg_precision, avg_mrr) = average_metrics(&ground_truths, &results_list);
assert!(
(avg_recall - 0.4).abs() < f64::EPSILON,
"Expected recall 0.4, got {avg_recall}"
);
assert!(
(avg_precision - 0.4).abs() < f64::EPSILON,
"Expected precision 0.4, got {avg_precision}"
);
assert!(
(avg_mrr - 1.0).abs() < f64::EPSILON,
"Expected MRR 1.0, got {avg_mrr}"
);
}
#[test]
fn test_average_metrics_empty() {
let ground_truths: Vec<Vec<u64>> = vec![];
let results_list: Vec<Vec<u64>> = vec![];
let (avg_recall, avg_precision, avg_mrr) = average_metrics(&ground_truths, &results_list);
assert!((avg_recall - 0.0).abs() < f64::EPSILON);
assert!((avg_precision - 0.0).abs() < f64::EPSILON);
assert!((avg_mrr - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_exact_search_has_100_percent_recall() {
let ground_truth: Vec<u64> = (0..100).collect();
let exact_results: Vec<u64> = (0..100).collect();
let recall = recall_at_k(&ground_truth, &exact_results);
let precision = precision_at_k(&ground_truth, &exact_results);
assert!(
(recall - 1.0).abs() < f64::EPSILON,
"Exact search must have 100% recall"
);
assert!(
(precision - 1.0).abs() < f64::EPSILON,
"Exact search must have 100% precision"
);
}
#[test]
fn test_recall_at_10() {
let ground_truth: Vec<u64> = (0..10).collect();
let results = vec![0u64, 1, 2, 3, 4, 5, 6, 7, 100, 101];
let recall = recall_at_k(&ground_truth, &results);
assert!(
(recall - 0.8).abs() < f64::EPSILON,
"Expected 0.8, got {recall}"
);
}
#[test]
fn test_recall_at_100() {
let ground_truth: Vec<u64> = (0..100).collect();
let mut results: Vec<u64> = (0..90).collect(); results.extend(200..210);
let recall = recall_at_k(&ground_truth, &results);
assert!(
(recall - 0.9).abs() < f64::EPSILON,
"Expected 0.9, got {recall}"
);
}
#[test]
fn test_ndcg_perfect_ranking() {
let relevances = vec![3.0, 2.0, 1.0, 0.0];
let ndcg = ndcg_at_k(&relevances, 4);
assert!((ndcg - 1.0).abs() < 1e-10, "Expected 1.0, got {ndcg}");
}
#[test]
fn test_ndcg_worst_ranking() {
let relevances = vec![0.0, 1.0, 2.0, 3.0];
let ndcg = ndcg_at_k(&relevances, 4);
assert!(
ndcg < 1.0,
"NDCG should be < 1.0 for bad ranking, got {ndcg}"
);
assert!(ndcg > 0.0, "NDCG should be > 0.0, got {ndcg}");
}
#[test]
fn test_ndcg_empty() {
let relevances: Vec<f64> = vec![];
let ndcg = ndcg_at_k(&relevances, 10);
assert!(
(ndcg - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {ndcg}"
);
}
#[test]
fn test_ndcg_k_greater_than_list() {
let relevances = vec![3.0, 2.0];
let ndcg = ndcg_at_k(&relevances, 10);
assert!((ndcg - 1.0).abs() < 1e-10, "Expected 1.0, got {ndcg}");
}
#[test]
fn test_ndcg_all_zeros() {
let relevances = vec![0.0, 0.0, 0.0];
let ndcg = ndcg_at_k(&relevances, 3);
assert!(
(ndcg - 0.0).abs() < f64::EPSILON,
"Expected 0.0, got {ndcg}"
);
}
#[test]
fn test_hit_rate_all_hits() {
let query_results = vec![
(vec![1u64, 2, 3], vec![1u64, 10, 20]), (vec![4u64, 5, 6], vec![4u64, 5, 30]), ];
let hr = hit_rate(&query_results, 3);
assert!((hr - 1.0).abs() < f64::EPSILON, "Expected 1.0, got {hr}");
}
#[test]
fn test_hit_rate_no_hits() {
let query_results = vec![
(vec![1u64, 2, 3], vec![10u64, 20, 30]), (vec![4u64, 5, 6], vec![40u64, 50, 60]), ];
let hr = hit_rate(&query_results, 3);
assert!((hr - 0.0).abs() < f64::EPSILON, "Expected 0.0, got {hr}");
}
#[test]
fn test_hit_rate_partial() {
let query_results = vec![
(vec![1u64, 2, 3], vec![1u64, 10, 20]), (vec![4u64, 5, 6], vec![40u64, 50, 60]), ];
let hr = hit_rate(&query_results, 3);
assert!((hr - 0.5).abs() < f64::EPSILON, "Expected 0.5, got {hr}");
}
#[test]
fn test_hit_rate_empty() {
let query_results: Vec<(Vec<u64>, Vec<u64>)> = vec![];
let hr = hit_rate(&query_results, 3);
assert!((hr - 0.0).abs() < f64::EPSILON, "Expected 0.0, got {hr}");
}
#[test]
fn test_map_perfect() {
let relevance_lists = vec![
vec![true, true, true], vec![true, true, true], ];
let map = mean_average_precision(&relevance_lists);
assert!((map - 1.0).abs() < f64::EPSILON, "Expected 1.0, got {map}");
}
#[test]
fn test_map_no_relevant() {
let relevance_lists = vec![vec![false, false, false], vec![false, false, false]];
let map = mean_average_precision(&relevance_lists);
assert!((map - 0.0).abs() < f64::EPSILON, "Expected 0.0, got {map}");
}
#[test]
fn test_map_mixed() {
let relevance_lists = vec![vec![true, false, true], vec![false, true, false]];
let map = mean_average_precision(&relevance_lists);
let q1_ap = 1.0_f64.midpoint(2.0_f64 / 3.0);
let expected = q1_ap.midpoint(0.5);
assert!(
(map - expected).abs() < 1e-10,
"Expected {expected}, got {map}"
);
}
#[test]
fn test_map_empty() {
let relevance_lists: Vec<Vec<bool>> = vec![];
let map = mean_average_precision(&relevance_lists);
assert!((map - 0.0).abs() < f64::EPSILON, "Expected 0.0, got {map}");
}
#[test]
fn test_latency_stats_basic() {
use std::time::Duration;
let samples: Vec<Duration> = vec![
Duration::from_micros(100),
Duration::from_micros(200),
Duration::from_micros(300),
Duration::from_micros(400),
Duration::from_micros(500),
];
let stats = compute_latency_percentiles(&samples);
assert_eq!(stats.min, Duration::from_micros(100));
assert_eq!(stats.max, Duration::from_micros(500));
assert_eq!(stats.p50, Duration::from_micros(300)); }
#[test]
fn test_latency_stats_single_sample() {
use std::time::Duration;
let samples = vec![Duration::from_micros(100)];
let stats = compute_latency_percentiles(&samples);
assert_eq!(stats.min, Duration::from_micros(100));
assert_eq!(stats.max, Duration::from_micros(100));
assert_eq!(stats.p50, Duration::from_micros(100));
assert_eq!(stats.p95, Duration::from_micros(100));
assert_eq!(stats.p99, Duration::from_micros(100));
}
#[test]
fn test_latency_stats_empty() {
use std::time::Duration;
let samples: Vec<Duration> = vec![];
let stats = compute_latency_percentiles(&samples);
assert_eq!(stats.min, Duration::ZERO);
assert_eq!(stats.max, Duration::ZERO);
assert_eq!(stats.p50, Duration::ZERO);
}
#[test]
fn test_latency_stats_p99() {
use std::time::Duration;
let samples: Vec<Duration> = (1..=100).map(|i| Duration::from_micros(i * 10)).collect();
let stats = compute_latency_percentiles(&samples);
assert!(stats.p99 >= Duration::from_micros(990));
assert!(stats.p99 <= Duration::from_millis(1));
}
#[test]
fn test_latency_stats_mean() {
use std::time::Duration;
let samples = vec![
Duration::from_micros(100),
Duration::from_micros(200),
Duration::from_micros(300),
];
let stats = compute_latency_percentiles(&samples);
assert_eq!(stats.mean, Duration::from_micros(200));
}