use super::{results, suite, Test};
use crate::{
cli,
errors::{self, RuntError},
};
use futures::{
io::{AllowStdIo, AsyncWriteExt},
stream, StreamExt,
};
pub struct Executor {
tests: Vec<Test>,
max_futures: usize,
}
impl Executor {
pub fn execute_all(
self,
) -> impl stream::Stream<Item = Result<results::Test, RuntError>> {
stream::iter(self.tests.into_iter().map(|test| test.execute_test()))
.buffer_unordered(self.max_futures)
}
}
pub struct Status {
pub miss: u64,
pub pass: u64,
pub remain: u64,
pub skip: u64,
pub fail: u64,
pub timeout: u64,
handle: AllowStdIo<std::io::BufWriter<std::io::Stdout>>,
istty: bool,
}
impl Status {
pub fn new(total: u64) -> Self {
let handle =
AllowStdIo::new(std::io::BufWriter::new(std::io::stdout()));
let istty = atty::is(atty::Stream::Stdout);
Self {
remain: total,
miss: 0,
pass: 0,
skip: 0,
fail: 0,
timeout: 0,
handle,
istty,
}
}
fn summary(&self) -> String {
use colored::*;
format!(
" {} {} / {} {} / {} {} / {} {} / {} {}",
self.pass.to_string().green().bold(),
&"passing".green().bold(),
(self.fail + self.timeout).to_string().red().bold(),
&"failing".red().bold(),
self.miss.to_string().yellow().bold(),
&"missing".yellow().bold(),
self.skip.to_string().yellow().dimmed().bold(),
&"skipped".yellow().dimmed().bold(),
self.remain.to_string().dimmed().bold(),
&"remaining".dimmed().bold(),
)
}
#[inline]
pub async fn stream_summary(&mut self) -> Result<(), errors::RuntError> {
if self.istty {
self.print_summary().await?;
}
Ok(())
}
#[inline]
pub async fn print_summary(&mut self) -> Result<(), errors::RuntError> {
self.handle.write_all(self.summary().as_bytes()).await?;
self.handle.flush().await?;
Ok(())
}
#[inline]
pub async fn clear(&mut self) -> Result<(), errors::RuntError> {
if self.istty {
self.handle.write_all("\r\x1B[K".as_bytes()).await?;
}
Ok(())
}
pub async fn print<S: AsRef<[u8]>>(
&mut self,
msg: S,
) -> Result<(), errors::RuntError> {
self.handle.write_all(msg.as_ref()).await?;
self.handle.write_all("\n".as_ref()).await?;
self.handle.flush().await?;
Ok(())
}
pub fn update(&mut self, state: &results::State) {
match state {
results::State::Skip => {
self.skip += 1;
}
results::State::Correct => {
self.pass += 1;
}
results::State::Mismatch(..) => {
self.fail += 1;
}
results::State::Timeout => {
self.timeout += 1;
}
results::State::Missing(..) => {
self.miss += 1;
}
}
self.remain -= 1;
}
}
pub struct Context {
configs: Vec<suite::Config>,
pub exec: Executor,
}
impl Context {
pub fn from(suites: Vec<suite::Suite>, max_futures: usize) -> Self {
let mut configs = Vec::with_capacity(suites.len());
let mut tests = Vec::with_capacity(suites.len());
for (idx, suite) in suites.into_iter().enumerate() {
let suite::Suite { config, paths } = suite;
tests.extend(paths.into_iter().map(|path| Test {
path,
cmd: config.cmd.clone(),
expect_dir: config.expect_dir.clone(),
test_suite: idx as u64,
timeout: config.timeout,
}));
configs.push(config);
}
Context {
exec: Executor { tests, max_futures },
configs,
}
}
pub async fn execute_and_summarize(
self,
opts: &cli::Opts,
) -> Result<i32, errors::RuntError> {
let mut st = Status::new(self.exec.tests.len() as u64);
let mut tasks = self.exec.execute_all();
st.stream_summary().await?;
while let Some(result) = tasks.next().await {
let mut res = result?;
if res.should_save(opts) {
res.save_results().await?;
}
st.update(&res.state);
st.clear().await?;
if res.should_print(opts) {
let suite_name = &self.configs[res.test_suite as usize].name;
st.print(res.report_str(Some(suite_name), opts.diff))
.await?;
}
st.stream_summary().await?;
}
st.clear().await?;
st.print_summary().await?;
println!();
match opts.post_filter {
Some(cli::OnlyOpt::Fail) => Ok((st.fail + st.timeout) as i32),
Some(cli::OnlyOpt::Missing) => Ok((st.miss) as i32),
Some(cli::OnlyOpt::Pass) => Ok(0),
None => Ok((st.fail + st.timeout + st.miss) as i32)
}
}
}