use crate::types::TestCategory;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParallelStrategy {
Sequential,
CategoryLevel,
TestLevel,
}
#[derive(Debug, Clone)]
pub struct Workload {
pub num_categories: usize,
pub estimated_tests_per_category: usize,
pub num_cpus: usize,
}
impl Workload {
pub fn new(
categories: &[TestCategory],
num_global_options: usize,
num_subcommands: usize,
) -> Self {
let num_categories = categories.len();
let estimated_tests_per_category =
estimate_tests_per_category(num_global_options, num_subcommands, num_categories);
let num_cpus = num_cpus::get();
Self {
num_categories,
estimated_tests_per_category,
num_cpus,
}
}
pub fn total_estimated_tests(&self) -> usize {
self.num_categories * self.estimated_tests_per_category
}
}
fn estimate_tests_per_category(
num_global_options: usize,
num_subcommands: usize,
num_categories: usize,
) -> usize {
if num_categories == 0 {
return 0;
}
let complexity_score = num_global_options + num_subcommands;
let avg_tests_per_category = complexity_score.max(1) * 2 / num_categories.max(1);
avg_tests_per_category.clamp(5, 50)
}
pub fn choose_strategy(workload: &Workload) -> ParallelStrategy {
let total_tests = workload.total_estimated_tests();
if total_tests < 20 || workload.num_categories <= 1 {
log::debug!(
"Choosing Sequential strategy (total_tests={}, num_categories={})",
total_tests,
workload.num_categories
);
return ParallelStrategy::Sequential;
}
if total_tests < 100 || workload.num_cpus < 4 {
log::debug!(
"Choosing CategoryLevel strategy (total_tests={}, num_cpus={})",
total_tests,
workload.num_cpus
);
return ParallelStrategy::CategoryLevel;
}
log::debug!(
"Choosing TestLevel strategy (total_tests={}, num_cpus={})",
total_tests,
workload.num_cpus
);
ParallelStrategy::TestLevel
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_workload(
num_categories: usize,
num_global_options: usize,
num_subcommands: usize,
) -> Workload {
let categories: Vec<TestCategory> = (0..num_categories)
.map(|i| match i % 3 {
0 => TestCategory::Basic,
1 => TestCategory::Security,
_ => TestCategory::Help,
})
.collect();
Workload::new(&categories, num_global_options, num_subcommands)
}
#[test]
fn test_choose_strategy_sequential_small_workload() {
let workload = create_test_workload(1, 5, 0);
assert_eq!(choose_strategy(&workload), ParallelStrategy::Sequential);
}
#[test]
fn test_choose_strategy_sequential_single_category() {
let workload = create_test_workload(1, 50, 10);
assert_eq!(choose_strategy(&workload), ParallelStrategy::Sequential);
}
#[test]
fn test_choose_strategy_category_level_medium_workload() {
let workload = create_test_workload(3, 10, 5);
assert_eq!(choose_strategy(&workload), ParallelStrategy::CategoryLevel);
}
#[test]
fn test_choose_strategy_test_level_large_workload() {
let workload = create_test_workload(6, 30, 50);
let total_tests = workload.total_estimated_tests();
assert!(
total_tests >= 100,
"Expected large workload (>=100 tests), got {}",
total_tests
);
let strategy = choose_strategy(&workload);
if workload.num_cpus >= 4 {
assert_eq!(
strategy,
ParallelStrategy::TestLevel,
"Expected TestLevel with {} CPUs and {} tests",
workload.num_cpus,
total_tests
);
} else {
assert_eq!(
strategy,
ParallelStrategy::CategoryLevel,
"Expected CategoryLevel with {} CPUs and {} tests",
workload.num_cpus,
total_tests
);
}
}
#[test]
fn test_estimate_tests_per_category() {
let result = estimate_tests_per_category(10, 5, 3);
assert_eq!(result, 10);
}
#[test]
fn test_estimate_tests_per_category_clamping() {
let result = estimate_tests_per_category(1, 0, 5);
assert_eq!(result, 5);
let result = estimate_tests_per_category(100, 50, 1);
assert_eq!(result, 50);
}
#[test]
fn test_workload_total_estimated_tests() {
let workload = create_test_workload(4, 10, 5);
let total = workload.total_estimated_tests();
assert!(total > 0);
assert!(total <= 200); }
}