use crate::testing::test_context::{TestCase, TestResult, TestSuite};
use rayon::prelude::*;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct ParallelConfig {
pub num_threads: Option<usize>,
pub preserve_order: bool,
pub chunk_size: usize,
}
impl Default for ParallelConfig {
fn default() -> Self {
ParallelConfig {
num_threads: None, preserve_order: true,
chunk_size: 1,
}
}
}
pub struct ParallelExecutor {
pub config: ParallelConfig,
}
impl ParallelExecutor {
pub fn new(config: ParallelConfig) -> Self {
ParallelExecutor { config }
}
pub fn run_tests_parallel(
&self,
suite_name: &str,
tests: &[TestCase],
timeout: Duration,
) -> Vec<TestResult> {
if tests.is_empty() {
return Vec::new();
}
let results = Arc::new(Mutex::new(Vec::with_capacity(tests.len())));
let results_clone: Arc<Mutex<Vec<TestResult>>> = Arc::clone(&results);
tests
.par_iter()
.chunks(self.config.chunk_size)
.for_each(|chunk| {
let chunk_results: Vec<TestResult> = chunk
.into_par_iter()
.map(|test| self.run_single_test(suite_name, test, timeout))
.collect();
let mut locked: MutexGuard<'_, Vec<TestResult>> = results_clone.lock().unwrap();
if self.config.preserve_order {
locked.extend(chunk_results);
} else {
locked.extend(chunk_results);
}
});
let locked: _ = results.lock().unwrap();
locked.clone()
}
pub fn run_suites_parallel(
&self,
suites: &[TestSuite],
global_timeout: Duration,
) -> Vec<TestResult> {
if suites.is_empty() {
return Vec::new();
}
let all_results: Vec<Vec<TestResult>> = suites
.par_iter()
.map(|suite| self.run_tests_parallel(&suite.name, &suite.tests, global_timeout))
.collect();
all_results.into_iter().flatten().collect()
}
fn run_single_test(&self, suite_name: &str, test: &TestCase, timeout: Duration) -> TestResult {
let start: _ = Instant::now();
let mut result = TestResult::new(suite_name.to_string(), test.name.clone());
if test.skip {
let duration: _ = start.elapsed();
result.duration = duration;
return result;
}
let test_timeout: _ = if test.timeout > Duration::from_secs(0) {
test.timeout
} else {
timeout
};
let duration: _ = start.elapsed();
result.passed = false;
result.error = Some(
"parallel V8 test execution is not implemented; refusing to report an unexecuted test as passed"
.to_string(),
);
if duration > test_timeout {
result.error = Some(format!("Test timeout: exceeded {:?}", test_timeout));
}
result.duration = duration;
result
}
}
#[derive(Debug, Clone)]
pub struct ThreadPoolConfig {
pub size: usize,
pub stack_size: usize,
pub name_prefix: String,
}
impl Default for ThreadPoolConfig {
fn default() -> Self {
ThreadPoolConfig {
size: num_cpus::get(),
stack_size: 2 * 1024 * 1024, name_prefix: "amberjs-test".to_string(),
}
}
}