1pub use af_core_macros::test_main as main;
10
11use crate::test::prelude::*;
12use crate::util::defer;
13use console::style;
14use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
15use std::io;
16
17#[derive(Debug, Error)]
19pub enum Error {
20 #[error("{}.", fmt::count(*.0, "test failure", "test failures"))]
22 Failures(usize),
23 #[error("IO error. {0}")]
25 Io(#[from] io::Error),
26}
27
28pub struct Output {
30 pub elapsed: Duration,
31 pub failures: usize,
32 pub tests: usize,
33}
34
35pub type Result<T = Output, E = Error> = std::result::Result<T, E>;
37
38pub async fn run(build: impl FnOnce(&mut test::Context)) -> Result {
40 let style_initial = ProgressStyle::default_bar()
41 .template("[{elapsed_precise}] {bar:40} {pos:>7}/{len:7} {msg}")
42 .progress_chars("##-");
43
44 let style_ok = style_initial
45 .clone()
46 .template("[{elapsed_precise}] {bar:40.green.bright/.green} {pos:>7}/{len:7} {msg}");
47
48 let style_err = style_ok
49 .clone()
50 .template("[{elapsed_precise}] {bar:40.red.bright/.red} {pos:>7}/{len:7} {msg}");
51
52 let mut term = console::Term::buffered_stdout();
53 let pb = ProgressBar::with_draw_target(0, ProgressDrawTarget::to_term(term.clone(), None));
54
55 pb.set_message("Starting…");
56 pb.set_style(style_initial);
57
58 let mut ctx = test::Context::new();
59
60 build(&mut ctx);
61
62 let panic_hook = panic::take_hook();
63 let _guard = defer(|| panic::set_hook(panic_hook));
64
65 panic::set_hook(Box::new(|_| ()));
66
67 let started_at = Time::now();
68 let mut output = ctx.start();
69
70 let tests = output.len();
71 let mut failures = 0;
72
73 pb.set_length(tests as u64);
74 pb.set_message("Running…");
75 pb.set_style(style_ok);
76
77 while let Some(test::Output { path, result }) = output.next().await {
78 if let Err(err) = result {
79 failures += 1;
80
81 term.clear_last_lines(1)?;
82
83 pb.set_style(style_err.clone());
84
85 writeln!(
86 term,
87 "{} {} — {:#}\n",
88 path,
89 style("failed").bright().red(),
90 fmt::indent("", " ", err)
91 )?;
92 }
93
94 pb.set_position((tests - output.len()) as u64);
95 }
96
97 pb.finish_and_clear();
98
99 let elapsed = started_at.elapsed();
100
101 let (count, status) = match failures {
102 0 => (fmt::count(tests, "test", "tests"), style("passed").bright().green()),
103 n => (fmt::count(n, "test", "tests"), style("failed").bright().red()),
104 };
105
106 if failures > 0 {
107 writeln!(term)?;
108 }
109
110 writeln!(term, "{} {} in {}.", count, status, style(elapsed).bright().white())?;
111
112 term.flush()?;
113
114 Ok(Output { elapsed, failures, tests })
115}