cargo hollywood
Diffstat (limited to 'src/test/mod.rs')
| -rw-r--r-- | src/test/mod.rs | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/src/test/mod.rs b/src/test/mod.rs new file mode 100644 index 0000000..7b7d0bb --- /dev/null +++ b/src/test/mod.rs @@ -0,0 +1,170 @@ +mod ui; +use anyhow::Result; +use crossbeam::channel::{unbounded, Receiver}; +use crossterm::event::{self, Event, KeyCode}; +use ratatui::prelude::*; +use ratatui::Terminal; +use std::path::Path; +use std::time::{Duration, Instant}; + +use crate::cargo; +use crate::cargo::{test, TestEvent, TestMessage, TestResult}; +use crate::test::ui::stdout::Stdout; + +#[derive(Default, PartialEq, Eq)] +pub enum Screen { + #[default] + Main, + Stdout, +} + +enum Test { + InProgress { name: String }, + Succeeded(TestResult), + Failed(TestResult), + Ignored { name: String }, +} + +impl Test { + fn name(&self) -> &str { + let (Self::InProgress { name } + | Self::Succeeded(TestResult { name, .. }) + | Self::Failed(TestResult { name, .. }) + | Self::Ignored { name }) = self; + name + } + + fn stdout(&self) -> Option<&str> { + match self { + Self::Succeeded(TestResult { stdout, .. }) + | Self::Failed(TestResult { stdout, .. }) => stdout.as_deref(), + _ => None, + } + } +} + +trait VAt { + fn at(&mut self, which: &str) -> Option<&mut Test>; +} + +impl VAt for Vec<Test> { + fn at(&mut self, which: &str) -> Option<&mut Test> { + let p = self.iter().position(|t| t.name() == which)?; + Some(&mut self[p]) + } +} + +pub struct TestState { + tests: Vec<Test>, + test_list: ui::test_list::TestList, + rx: Receiver<TestMessage>, + screen: Screen, + test_count: usize, + stdout: Stdout, + time: f32, + done: bool, +} + +impl TestState { + pub fn new(dir: Option<&Path>) -> Result<Self> { + log::info!("initializing test state"); + let (tx, rx) = unbounded(); + let TestEvent::SuiteStart { test_count } = test(tx, dir)? else { + panic!("first ev should be suite start") + }; + Ok(Self { + test_list: ui::test_list::TestList::default(), + tests: vec![], + rx, + screen: Screen::default(), + done: false, + test_count, + time: 0., + stdout: Stdout::default(), + }) + } + + pub fn recv(&mut self) { + if self.done { + return; + } + let deadline = Instant::now() + Duration::from_millis(50); + while let Ok(event) = self.rx.recv_deadline(deadline) { + log::debug!("got event {event:?}"); + let event = match event { + TestMessage::Event(e) => e, + TestMessage::Finished => { + self.done = true; + return; + } + }; + match event { + TestEvent::TestStart { name } => { + self.tests.push(Test::InProgress { name }); + } + TestEvent::TestOk(r) => { + let pre = self.tests.at(&r.name).unwrap(); + *pre = Test::Succeeded(r); + } + TestEvent::TestFail(r) => { + let pre = self.tests.at(&r.name).unwrap(); + *pre = Test::Failed(r); + } + TestEvent::TestIgnore { name } => { + let pre = self.tests.at(&name).unwrap(); + *pre = Test::Ignored { name }; + } + TestEvent::SuiteOk { exec_time, .. } | TestEvent::SuiteFail { exec_time, .. } => { + self.time += exec_time; + } + TestEvent::SuiteStart { test_count } => { + log::trace!("have {test_count} tests"); + self.test_count += test_count; + } + }; + } + } +} + +pub fn run<B: Backend>( + terminal: &mut Terminal<B>, + dir: Option<&Path>, + meta: &cargo::Metadata, +) -> Result<()> { + let mut state = TestState::new(dir)?; + loop { + terminal.draw(|f| ui::ui(f, &mut state, &meta))?; + if event::poll(Duration::from_millis(5))? { + if let Event::Key(key) = event::read()? { + match state.screen { + Screen::Main => match key.code { + KeyCode::Char('q') => return Ok(()), + KeyCode::Down | KeyCode::Char('s') => state.test_list.next(), + KeyCode::Up | KeyCode::Char('w') => state.test_list.prev(), + KeyCode::Right | KeyCode::Char('d') + if state.test_list.stdout(&state).is_some() => + { + state.screen = Screen::Stdout; + state.stdout.scroll = 0; + state.stdout.lines = u16::try_from( + state.test_list.stdout(&state).unwrap().lines().count(), + )?; + } + _ => {} + }, + Screen::Stdout => match key.code { + KeyCode::Char('q') => return Ok(()), + KeyCode::Down | KeyCode::Char('s') => state.stdout.incr(), + KeyCode::Up | KeyCode::Char('w') => state.stdout.decr(), + KeyCode::Left | KeyCode::Char('a') => { + state.screen = Screen::Main; + state.stdout.scroll = 0; + } + _ => {} + }, + } + } + } + state.recv(); + } +} |