diff --git a/src/ghci/parse/ghc_message/exception.rs b/src/ghci/parse/ghc_message/exception.rs new file mode 100644 index 00000000..b09906da --- /dev/null +++ b/src/ghci/parse/ghc_message/exception.rs @@ -0,0 +1,54 @@ +use winnow::PResult; +use winnow::Parser; + +use crate::ghci::parse::lines::until_newline; + +use super::GhcMessage; + +/// Parse an "Exception" message like this: +/// +/// ```plain +/// *** Exception: /Users/.../dist-newstyle/ghc82733_tmp_1/ghc_tmp_34657.h: withFile: does not exist (No such file or directory) +/// ``` +pub fn exception(input: &mut &str) -> PResult { + let _ = "*** Exception: ".parse_next(input)?; + + let message = until_newline.parse_next(input)?; + + Ok(GhcMessage::Exception(message.to_owned())) +} + +#[cfg(test)] +mod tests { + use super::*; + + use indoc::indoc; + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_exception() { + assert_eq!( + exception.parse("*** Exception: Uh oh!\n").unwrap(), + GhcMessage::Exception("Uh oh!".into()) + ); + + assert_eq!( + exception.parse("*** Exception: /Users/.../dist-newstyle/ghc82733_tmp_1/ghc_tmp_34657.h: withFile: does not exist (No such file or directory)\n").unwrap(), + GhcMessage::Exception("/Users/.../dist-newstyle/ghc82733_tmp_1/ghc_tmp_34657.h: withFile: does not exist (No such file or directory)".into()) + ); + + // Doesn't parse subsequent lines (even if they're relevant, unfortunately). + assert_eq!( + exception + .parse(indoc!( + " + *** Exception: puppy doggy + CallStack (from HasCallStack): + error, called at :3:1 in interactive:Ghci1 + " + )) + .unwrap(), + GhcMessage::Exception("puppy doggy".into()) + ); + } +} diff --git a/src/ghci/parse/ghc_message/mod.rs b/src/ghci/parse/ghc_message/mod.rs index 59733df9..6036a1df 100644 --- a/src/ghci/parse/ghc_message/mod.rs +++ b/src/ghci/parse/ghc_message/mod.rs @@ -44,6 +44,9 @@ use module_import_cycle_diagnostic::module_import_cycle_diagnostic; mod no_location_info_diagnostic; use no_location_info_diagnostic::no_location_info_diagnostic; +mod exception; +use exception::exception; + use super::rest_of_line; use super::CompilingModule; @@ -64,6 +67,15 @@ pub enum GhcMessage { /// Foo.hs:81:1: Warning: Defined but not used: `bar' /// ``` Diagnostic(GhcDiagnostic), + /// An exception while running a command or similar. + /// + /// These may be multiple lines, but there's no good way to determine where the message ends, + /// so we just parse the first line. + /// + /// ```text + /// *** Exception: /Users/.../dist-newstyle/ghc82733_tmp_1/ghc_tmp_34657.h: withFile: does not exist (No such file or directory) + /// ``` + Exception(String), /// A configuration file being loaded. /// /// ```text @@ -181,6 +193,7 @@ fn parse_messages_inner(input: &mut &str) -> PResult> { .map(Item::One), module_import_cycle_diagnostic.map(Item::Many), loaded_configuration.map(Item::One), + exception.map(Item::One), rest_of_line.map(|line| { tracing::debug!(line, "Ignoring GHC output line"); Item::Ignore @@ -290,6 +303,7 @@ mod tests { | 4 | example = "example" | ^^^^^^^^^ + *** Exception: /Users/.../dist-newstyle/ghc82733_tmp_1/ghc_tmp_34657.h: withFile: does not exist (No such file or directory) Failed, two modules loaded. "# )) @@ -322,6 +336,7 @@ mod tests { ].join("\n"), }, ), + GhcMessage::Exception("/Users/.../dist-newstyle/ghc82733_tmp_1/ghc_tmp_34657.h: withFile: does not exist (No such file or directory)".to_owned()), GhcMessage::Summary( CompilationSummary { result: CompilationResult::Err, diff --git a/tests/all_good.rs b/tests/all_good.rs index 395c7a8c..0e125832 100644 --- a/tests/all_good.rs +++ b/tests/all_good.rs @@ -43,3 +43,33 @@ async fn can_detect_compilation_failure() { .await .unwrap(); } + +/// Test that `ghciwatch` can detect an `*** Exception` diagnostic. +/// +/// Regression test for DUX-3144. +#[test] +async fn can_detect_exception() { + let mut session = GhciWatchBuilder::new("tests/data/simple") + .start() + .await + .expect("ghciwatch starts"); + session.wait_until_ready().await.expect("ghciwatch loads"); + + let module_path = session.path("src/MyModule.hs"); + + session + .fs() + .prepend(&module_path, "{-# OPTIONS_GHC -F -pgmF false #-}\n") + .await + .unwrap(); + + session + .wait_for_log(BaseMatcher::compilation_failed()) + .await + .unwrap(); + + session + .wait_for_log(BaseMatcher::reload_completes().but_not(BaseMatcher::message("All good!"))) + .await + .unwrap(); +} diff --git a/tests/data/simple/src/MyModule.hs b/tests/data/simple/src/MyModule.hs index 87a6839d..12cf7f21 100644 --- a/tests/data/simple/src/MyModule.hs +++ b/tests/data/simple/src/MyModule.hs @@ -1,3 +1,5 @@ +{-# OPTIONS_GHC -F -pgmF false #-} + module MyModule (example) where example :: String