use std::{any::Any, fmt::Debug, num::NonZeroUsize, sync::atomic::Ordering, thread::Scope};
use kitest::{
Whatever,
capture::DefaultPanicHookProvider,
outcome::TestOutcome,
runner::{DefaultRunner, SimpleRunner, scope::NoScopeFactory},
test::{TestMeta, TestResult},
};
use nu_experimental::ExperimentalOption;
use nu_utils::downcast;
use crate::{harness::group::RUN_TEST_GROUP_IN_SERIAL, tester::TestError};
#[derive(Debug)]
pub struct Extra {
pub run_in_serial: bool,
pub experimental_options: &'static [(&'static ExperimentalOption, bool)],
pub environment_variables: &'static [(&'static str, &'static str)],
}
#[derive(Debug, Default)]
pub struct TestRunner {
parallel: DefaultRunner<DefaultPanicHookProvider, NoScopeFactory>,
serial: SimpleRunner<DefaultPanicHookProvider, NoScopeFactory>,
exact: bool,
}
impl TestRunner {
pub fn with_thread_count(self, thread_count: NonZeroUsize) -> Self {
Self {
parallel: self.parallel.with_thread_count(thread_count),
..self
}
}
pub fn with_exact(self, exact: bool) -> Self {
Self { exact, ..self }
}
}
enum NuTestRunnerIterator<IP, IS> {
Parallel(IP),
Serial(IS),
}
impl<'t, IP, IS> Iterator for NuTestRunnerIterator<IP, IS>
where
IP: Iterator<Item = (&'t TestMeta<Extra>, TestOutcome)>,
IS: Iterator<Item = (&'t TestMeta<Extra>, TestOutcome)>,
Extra: 't,
{
type Item = (&'t TestMeta<Extra>, TestOutcome);
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Parallel(iter) => iter.next(),
Self::Serial(iter) => iter.next(),
}
}
}
impl<'t> kitest::runner::TestRunner<'t, Extra> for TestRunner {
fn run<'s, I, F>(
&self,
tests: I,
scope: &'s Scope<'s, 't>,
) -> impl Iterator<Item = (&'t TestMeta<Extra>, kitest::outcome::TestOutcome)>
where
I: ExactSizeIterator<Item = (F, &'t TestMeta<Extra>)>,
F: (Fn() -> kitest::outcome::TestStatus) + Send + 's,
Extra: 't,
{
match self.exact || RUN_TEST_GROUP_IN_SERIAL.load(Ordering::Relaxed) {
false => NuTestRunnerIterator::Parallel(
<DefaultRunner<_, _> as kitest::runner::TestRunner<Extra>>::run(
&self.parallel,
tests,
scope,
),
),
true => {
NuTestRunnerIterator::Serial(<SimpleRunner<_, _> as kitest::runner::TestRunner<
Extra,
>>::run(&self.serial, tests, scope))
}
}
}
fn worker_count(&self, tests_count: usize) -> NonZeroUsize {
match RUN_TEST_GROUP_IN_SERIAL.load(Ordering::Relaxed) {
true => const { NonZeroUsize::new(1).unwrap() },
false => <DefaultRunner<_, _> as kitest::runner::TestRunner<Extra>>::worker_count(
&self.parallel,
tests_count,
),
}
}
}
pub trait IntoTestResult {
fn into_test_result(self) -> TestResult;
}
impl IntoTestResult for () {
fn into_test_result(self) -> TestResult {
self.into()
}
}
impl<E: Debug + Any> IntoTestResult for Result<(), E> {
fn into_test_result(self) -> TestResult {
let Err(err) = self else {
return TestResult(Ok(None));
};
match downcast::<E, TestError>(err) {
Ok(test_error) => TestResult(Err(Whatever::from(test_error))),
Err(err) => Err(err).into(),
}
}
}