summaryrefslogtreecommitdiff
path: root/tests/src/logger.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-04-13 10:39:45 +0200
committerGitHub <noreply@github.com>2024-04-13 08:39:45 +0000
commit020294fca9a7065d4b9cf4e677f606ebaaa29b00 (patch)
treec0027ad22046e2726c22298461327823d6b88d53 /tests/src/logger.rs
parent72dd79210602ecc799726fc096b078afbb47f299 (diff)
Better test runner (#3922)
Diffstat (limited to 'tests/src/logger.rs')
-rw-r--r--tests/src/logger.rs141
1 files changed, 141 insertions, 0 deletions
diff --git a/tests/src/logger.rs b/tests/src/logger.rs
new file mode 100644
index 00000000..c48650a7
--- /dev/null
+++ b/tests/src/logger.rs
@@ -0,0 +1,141 @@
+use std::io::{self, IsTerminal, StderrLock, Write};
+use std::time::{Duration, Instant};
+
+use crate::collect::Test;
+use crate::run::TestResult;
+
+/// Receives status updates by individual test runs.
+pub struct Logger<'a> {
+ filtered: usize,
+ passed: usize,
+ failed: usize,
+ skipped: usize,
+ mismatched_image: bool,
+ active: Vec<&'a Test>,
+ last_change: Instant,
+ temp_lines: usize,
+ terminal: bool,
+}
+
+impl<'a> Logger<'a> {
+ /// Create a new logger.
+ pub fn new(filtered: usize, skipped: usize) -> Self {
+ Self {
+ filtered,
+ passed: 0,
+ failed: 0,
+ skipped,
+ mismatched_image: false,
+ active: vec![],
+ temp_lines: 0,
+ last_change: Instant::now(),
+ terminal: std::io::stderr().is_terminal(),
+ }
+ }
+
+ /// Register the start of a test.
+ pub fn start(&mut self, test: &'a Test) {
+ self.active.push(test);
+ self.last_change = Instant::now();
+ self.refresh();
+ }
+
+ /// Register a finished test.
+ pub fn end(&mut self, test: &'a Test, result: std::thread::Result<TestResult>) {
+ self.active.retain(|t| t.name != test.name);
+
+ let result = match result {
+ Ok(result) => result,
+ Err(_) => {
+ self.failed += 1;
+ self.temp_lines = 0;
+ self.print(move |out| {
+ writeln!(out, "❌ {test} panicked")?;
+ Ok(())
+ })
+ .unwrap();
+ return;
+ }
+ };
+
+ if result.is_ok() {
+ self.passed += 1;
+ } else {
+ self.failed += 1;
+ }
+
+ self.mismatched_image |= result.mismatched_image;
+ self.last_change = Instant::now();
+
+ self.print(move |out| {
+ if !result.errors.is_empty() {
+ writeln!(out, "❌ {test}")?;
+ for line in result.errors.lines() {
+ writeln!(out, " {line}")?;
+ }
+ } else if crate::ARGS.verbose || !result.infos.is_empty() {
+ writeln!(out, "✅ {test}")?;
+ }
+ for line in result.infos.lines() {
+ writeln!(out, " {line}")?;
+ }
+ Ok(())
+ })
+ .unwrap();
+ }
+
+ /// Prints a summary and returns whether the test suite passed.
+ pub fn finish(&self) -> bool {
+ let Self { filtered, passed, failed, skipped, .. } = *self;
+
+ eprintln!("{passed} passed, {failed} failed, {skipped} skipped");
+ assert_eq!(filtered, passed + failed, "not all tests were executed succesfully");
+
+ if self.mismatched_image {
+ eprintln!(" pass the --update flag to update the reference images");
+ }
+
+ self.failed == 0
+ }
+
+ /// Refresh the status.
+ pub fn refresh(&mut self) {
+ self.print(|_| Ok(())).unwrap();
+ }
+
+ /// Refresh the status print.
+ fn print(
+ &mut self,
+ inner: impl FnOnce(&mut StderrLock<'_>) -> io::Result<()>,
+ ) -> io::Result<()> {
+ let mut out = std::io::stderr().lock();
+
+ // Clear the status lines.
+ for _ in 0..self.temp_lines {
+ write!(out, "\x1B[1F\x1B[0J")?;
+ self.temp_lines = 0;
+ }
+
+ // Print the result of a finished test.
+ inner(&mut out)?;
+
+ // Print the status line.
+ let done = self.failed + self.passed;
+ if done < self.filtered {
+ if self.last_change.elapsed() > Duration::from_secs(2) {
+ for test in &self.active {
+ writeln!(out, "⏰ {test} is taking a long time ...")?;
+ if self.terminal {
+ self.temp_lines += 1;
+ }
+ }
+ }
+ if self.terminal {
+ writeln!(out, "💨 {done} / {}", self.filtered)?;
+ self.temp_lines += 1;
+ }
+ }
+
+ Ok(())
+ }
+}