cargo hollywood
Diffstat (limited to 'src/test/mod.rs')
-rw-r--r--src/test/mod.rs170
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();
+ }
+}