apictl/
results.rs

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/// ResultsError is the error type for results.
9#[derive(Error, Debug)]
10pub enum ResultsError {
11    #[error("terminal error: {0}")]
12    TerminalError(std::io::Error),
13}
14
15/// Result is the result type for tests.
16pub type Result<T> = std::result::Result<T, ResultsError>;
17
18/// State is the current state of a result.
19#[derive(Clone, Default, Debug, Serialize, Deserialize)]
20pub enum State {
21    /// NotRun indicates that the result has not been run.
22    #[default]
23    NotRun,
24
25    // TODO: (?) we could potentially do running later.
26    /// Running indicates that the result is currently running.
27    Running,
28
29    /// Passed indicates that the result has passed.
30    Passed,
31
32    /// Failed indicates that the result has failed.
33    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}