1use std::io::{Stdout, Write};
2use std::time::{Duration, Instant};
3
4use crossterm::{cursor, terminal, ExecutableCommand};
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8#[derive(Error, Debug)]
10pub enum ResultsError {
11 #[error("terminal error: {0}")]
12 TerminalError(std::io::Error),
13}
14
15pub type Result<T> = std::result::Result<T, ResultsError>;
17
18#[derive(Clone, Default, Debug, Serialize, Deserialize)]
20pub enum State {
21 #[default]
23 NotRun,
24
25 Running,
28
29 Passed,
31
32 Failed(String),
34}
35
36impl std::fmt::Display for State {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 State::NotRun => write!(f, "⏸"),
40 State::Running => write!(f, "🏃"),
41 State::Passed => write!(f, "✅"),
42 State::Failed(_) => write!(f, "❌"),
43 }
44 }
45}
46
47#[derive(Debug)]
48pub struct Results {
49 pub name: String,
50 pub state: State,
51 pub duration: Duration,
52 pub children: Vec<Results>,
53}
54
55impl Results {
56 pub fn new(name: &str) -> Self {
57 Self {
58 name: name.to_string(),
59 state: State::NotRun,
60 duration: Duration::default(),
61 children: Vec::new(),
62 }
63 }
64
65 pub fn add(&mut self, name: &str) {
66 self.children.push(Self::new(name));
67 }
68
69 pub fn add_results(&mut self, results: Results) {
70 self.children.push(results);
71 }
72
73 pub fn from_test(name: &str, test: &crate::Test) -> Self {
74 Self {
75 name: name.to_string(),
76 state: State::NotRun,
77 duration: Duration::default(),
78 children: test
79 .steps
80 .iter()
81 .map(|s| Self {
82 name: s.name.clone(),
83 state: State::NotRun,
84 duration: Duration::default(),
85 children: s
86 .asserts
87 .iter()
88 .map(|a| Self {
89 name: format!("{}", a),
90 state: State::NotRun,
91 duration: Duration::default(),
92 children: Vec::new(),
93 })
94 .collect(),
95 })
96 .collect(),
97 }
98 }
99
100 pub fn is_empty(&self) -> bool {
101 self.children.is_empty()
102 }
103
104 pub fn len(&self) -> usize {
105 let mut len = 1;
106 for child in &self.children {
107 len += child.len();
108 }
109 len
110 }
111
112 pub fn update(&mut self, names: &[String], state: State, start: Instant) {
113 if names.len() == 1 && self.name == names[0] {
114 self.duration = start.elapsed();
115 self.state = state;
116 } else if !names.is_empty() && self.name == names[0] {
117 let child = self
118 .children
119 .iter_mut()
120 .find(|c| c.name == names[1])
121 .unwrap();
122 child.update(&names[1..], state, start);
123 }
124 }
125
126 pub fn print(&self, s: &mut Stdout, prefix: &str) -> Result<()> {
127 writeln!(
128 s,
129 "{}{} ({:?}) {}",
130 prefix, self.state, self.duration, self.name
131 )
132 .map_err(ResultsError::TerminalError)?;
133 for child in &self.children {
134 child.print(s, &format!("{} ", prefix))?;
135 }
136 Ok(())
137 }
138
139 pub fn output(&self, s: &mut Stdout, prefix: &str) -> Result<()> {
140 self.clear(s)?;
141 writeln!(
142 s,
143 "{}{} ({:?}) {}",
144 prefix, self.state, self.duration, self.name
145 )
146 .map_err(ResultsError::TerminalError)?;
147 for child in &self.children {
148 child.print(s, &format!("{} ", prefix))?;
149 }
150 Ok(())
151 }
152
153 pub fn clear(&self, s: &mut Stdout) -> Result<()> {
154 s.execute(cursor::MoveUp(self.len() as u16))
155 .map_err(ResultsError::TerminalError)?;
156 s.execute(terminal::Clear(terminal::ClearType::FromCursorDown))
157 .map_err(ResultsError::TerminalError)?;
158 Ok(())
159 }
160}