cargo hollywood
Diffstat (limited to 'src/cargo.rs')
| -rw-r--r-- | src/cargo.rs | 228 |
1 files changed, 31 insertions, 197 deletions
diff --git a/src/cargo.rs b/src/cargo.rs index 35007af..d0ff7fa 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -1,92 +1,25 @@ -use anyhow::{bail, Result}; -use crossbeam::channel::Sender; +use anyhow::Result; +pub use cargo_metadata::{ + libtest::SuiteEvent, libtest::TestEvent, Message, TestMessage as RawTestMessage, +}; +use crossbeam::channel::bounded; +use crossbeam::channel::Receiver; use serde_derive::Deserialize; use std::path::Path; use std::{ - error::Error, io::Read, process::{Command, Stdio}, }; -#[derive(Deserialize, Debug)] -#[serde(rename_all = "lowercase")] -enum Type { - Test, - Suite, -} -#[derive(Deserialize, Debug)] -#[serde(rename_all = "lowercase")] -enum Event { - Ok, - #[serde(rename = "started")] - Start, - #[serde(rename = "failed")] - Fail, - #[serde(rename = "ignored")] - Ignore, -} - -#[derive(Deserialize, Debug)] -// todo: figure out if theres a cool serde trick -struct TestRaw { - #[serde(rename = "type")] - ty: Type, - event: Event, - name: Option<String>, - passed: Option<usize>, - failed: Option<usize>, - ignored: Option<usize>, - measured: Option<usize>, - filtered_out: Option<usize>, - test_count: Option<usize>, - stdout: Option<String>, - exec_time: Option<f32>, -} - -#[derive(Debug, PartialEq)] -pub struct TestResult { - pub name: String, - pub exec_time: f32, - pub stdout: Option<String>, -} - -#[derive(Debug, PartialEq)] -pub enum TestEvent { - SuiteStart { - test_count: usize, - }, - SuiteOk { - failed: usize, - passed: usize, - ignored: usize, - measured: usize, - filtered_out: usize, - exec_time: f32, - }, - SuiteFail { - passed: usize, - failed: usize, - ignored: usize, - measured: usize, - filtered_out: usize, - exec_time: f32, - }, - TestStart { - name: String, - }, - TestOk(TestResult), - TestFail(TestResult), - TestIgnore { - name: String, - }, -} #[derive(Debug)] pub enum TestMessage { - Event(TestEvent), + CompilerEvent(Message), + Event(RawTestMessage), Finished, } -pub fn test(to: Sender<TestMessage>, at: Option<&Path>) -> Result<TestEvent> { +pub fn test(at: Option<&Path>) -> Result<Receiver<TestMessage>> { + let (tx, rx) = bounded(10); let mut proc = Command::new("cargo"); if let Some(at) = at { proc.arg("-C"); @@ -95,6 +28,8 @@ pub fn test(to: Sender<TestMessage>, at: Option<&Path>) -> Result<TestEvent> { proc.args([ "-Zunstable-options", "test", + "--message-format", + "json", "--", "-Zunstable-options", "--report-time", @@ -107,108 +42,34 @@ pub fn test(to: Sender<TestMessage>, at: Option<&Path>) -> Result<TestEvent> { let mut out = proc.stdout.take().unwrap(); let mut tmp = Vec::with_capacity(32); let mut stdout = [0; 4096]; - loop { - let n = out.read(&mut stdout)?; + + std::thread::spawn(move || loop { + let n = out.read(&mut stdout).unwrap(); for &byte in &stdout[..n] { match byte { b'\n' => { - log::debug!("got first event, returning"); - let event = parse_test(std::str::from_utf8(&tmp).unwrap()).unwrap(); + let val = serde_json::from_slice::<serde_json::Value>(&tmp).unwrap(); + log::debug!("got val: {}", serde_json::to_string_pretty(&val).unwrap()); + let event = match serde_json::value::from_value::<Message>(val.clone()) { + Err(_) => TestMessage::Event( + serde_json::value::from_value::<RawTestMessage>(val).unwrap(), + ), + Ok(v) => TestMessage::CompilerEvent(v), + }; tmp.clear(); - log::debug!("spawning thread"); - std::thread::spawn(move || loop { - let n = out.read(&mut stdout).unwrap(); - for &byte in &stdout[..n] { - match byte { - b'\n' => { - let event = - parse_test(std::str::from_utf8(&tmp).unwrap()).unwrap(); - tmp.clear(); - to.send(TestMessage::Event(event)).unwrap(); - } - b => tmp.push(b), - } - } - if let Ok(Some(_)) = proc.try_wait() { - to.send(TestMessage::Finished).unwrap(); - log::debug!("proc exited, joining thread"); - break; - } - std::thread::sleep(std::time::Duration::from_millis(50)); - }); - return Ok(event); + tx.send(event).unwrap(); } b => tmp.push(b), } } - if let Some(exit) = proc.try_wait()? { - log::trace!("process died, we die"); - bail!("process exited too early ({exit})"); + if let Ok(Some(_)) = proc.try_wait() { + tx.send(TestMessage::Finished).unwrap(); + log::debug!("proc exited, joining thread"); + break; } - std::thread::sleep(std::time::Duration::from_millis(10)); - } -} - -#[derive(Debug)] -struct Should(&'static str); -impl std::fmt::Display for Should { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "should have had a {}, dang", self.0) - } -} -impl Error for Should {} - -fn parse_test(s: &str) -> Result<TestEvent> { - let raw = serde_json::from_str::<TestRaw>(s)?; - log::trace!("got raw event {raw:?}"); - macro_rules! take { - ($thing:ident { $($holds:ident),+ $(?= $($opt:ident),+)?}) => { - $thing { - $($holds: raw.$holds.ok_or(Should(stringify!($holds)))?,)+ - $($($opt: raw.$opt),+)? - } - }; - ($thing:ident($inner:ident { $($holds:ident),+ $(?= $($opt:ident),+)? })) => { - $thing(take!($inner { $($holds),+ $(?= $($opt),+)? })) - } - } - use TestEvent::*; - Ok(match raw.ty { - Type::Test => match raw.event { - Event::Start => take!(TestStart { name }), - Event::Ok => take!(TestOk(TestResult { - name, - exec_time ?= stdout - })), - Event::Fail => take!(TestFail(TestResult { - name, - exec_time ?= stdout - })), - Event::Ignore => take!(TestIgnore { name }), - }, - Type::Suite => match raw.event { - Event::Start => take!(SuiteStart { test_count }), - Event::Ok => take!(SuiteOk { - failed, - passed, - ignored, - measured, - filtered_out, - exec_time - }), - Event::Fail => { - take!(SuiteFail { - failed, - passed, - ignored, - measured, - filtered_out, - exec_time - }) - } - Event::Ignore => panic!("ignore suite???"), - }, - }) + std::thread::sleep(std::time::Duration::from_millis(50)); + }); + return Ok(rx); } #[derive(Deserialize)] @@ -226,30 +87,3 @@ pub fn meta(at: &Path) -> Result<Metadata> { at.join("Cargo.toml"), )?)?) } - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_output() { - macro_rules! run { - ($($input:literal parses to $output:expr),+) => { - $(assert_eq!(parse_test($input).unwrap(), $output);)+ - }; - } - run![ - r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestEvent::SuiteStart { test_count: 2}, - r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestEvent::TestStart { name: "fail".into() }, - r#"{ "type": "test", "name": "fail", "event": "ok", "exec_time": 0.000003428, "stdout": "hello world" }"# parses to TestEvent::TestOk(TestResult { name: "fail".into(), exec_time: 0.000003428, stdout: Some("hello world".into()) }), - r#"{ "type": "test", "event": "started", "name": "nope" }"# parses to TestEvent::TestStart { name: "nope".into() }, - r#"{ "type": "test", "name": "nope", "event": "ignored" }"# parses to TestEvent::TestIgnore { name: "nope".into() }, - r#"{ "type": "suite", "event": "ok", "passed": 1, "failed": 0, "ignored": 1, "measured": 0, "filtered_out": 0, "exec_time": 0.000684028 }"# parses to TestEvent::SuiteOk { passed: 1, failed: 0, ignored: 1, measured: 0, filtered_out: 0, exec_time: 0.000684028 } - ]; - r#" - { "type": "suite", "event": "started", "test_count": 1 } - { "type": "test", "event": "started", "name": "fail" } - { "type": "test", "name": "fail", "event": "failed", "exec_time": 0.000081092, "stdout": "thread 'fail' panicked at src/main.rs:3:5:\nexplicit panic\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" } - { "type": "suite", "event": "failed", "passed": 0, "failed": 1, "ignored": 0, "measured": 0, "filtered_out": 0, "exec_time": 0.000731068 } - "#; - } -} |