diff --git a/eyre/Cargo.toml b/eyre/Cargo.toml index 39c0578..ecfaf87 100644 --- a/eyre/Cargo.toml +++ b/eyre/Cargo.toml @@ -26,7 +26,7 @@ pyo3 = { version = "0.20", optional = true, default-features = false } futures = { version = "0.3", default-features = false } rustversion = "1.0" thiserror = "1.0" -trybuild = { version = "1.0.19", features = ["diff"] } +ui_test = "0.21.0" backtrace = "0.3.46" anyhow = "1.0.28" syn = { version = "2.0", features = ["full"] } diff --git a/eyre/tests/compiletest.rs b/eyre/tests/compiletest.rs index 7974a62..1c73b59 100644 --- a/eyre/tests/compiletest.rs +++ b/eyre/tests/compiletest.rs @@ -1,7 +1,19 @@ -#[rustversion::attr(not(nightly), ignore)] +#[cfg_attr(not(backtrace), ignore)] #[cfg_attr(miri, ignore)] #[test] fn ui() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/ui/*.rs"); + let mut test_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + test_dir.push("tests"); + test_dir.push("ui"); + let rust_backtrace_val = "1"; + let mut config = ui_test::Config { + mode: ui_test::Mode::Run { exit_code: 0 }, + filter_files: vec!["ui_test".to_owned()], + + ..ui_test::Config::cargo(test_dir) + }; + config.program.args = vec!["run".into()]; + config.program.envs = vec![("RUST_BACKTRACE".into(), Some(rust_backtrace_val.into()))]; + + let _ = ui_test::run_tests(config); } diff --git a/eyre/tests/test_backtrace.rs b/eyre/tests/test_backtrace.rs new file mode 100644 index 0000000..9094f68 --- /dev/null +++ b/eyre/tests/test_backtrace.rs @@ -0,0 +1,117 @@ +mod test_backtrace { + use eyre::{eyre, Report, WrapErr}; + + #[allow(unused_variables)] + #[allow(dead_code)] + enum FailFrame { + None, + Low, + Med, + High, + } + + fn low(frame: FailFrame) -> Result<(), Report> { + let e: Report = eyre!("This program's goodness is suspect!"); + if let FailFrame::Low = frame { + Err::<(), Report>(e).wrap_err("The low-level code has failed!") + } else { + Ok(()) + } + } + + fn med(frame: FailFrame) -> Result<(), Report> { + let e: Report = eyre!("This program's goodness is suspect!"); + if let FailFrame::Med = frame { + Err(e).wrap_err("The low-level code has failed!") + } else { + low(frame) + } + } + fn high(frame: FailFrame) -> Result<(), Report> { + let e: Report = eyre!("This program's goodness is suspect!"); + if let FailFrame::High = frame { + Err(e).wrap_err("The low-level code has failed!") + } else { + med(frame) + } + } + + use std::panic; + + static BACKTRACE_SNIPPET_HIGH: &str = " +10: test_backtrace::test_backtrace::low + at .\\tests\\test_backtrace.rs:14 +11: test_backtrace::test_backtrace::med + at .\\tests\\test_backtrace.rs:27 +12: test_backtrace::test_backtrace::high + at .\\tests\\test_backtrace.rs:35 +13: test_backtrace::test_backtrace::test_backtrace + "; + + use std::backtrace::Backtrace; + use std::sync::{Arc, Mutex}; + + /* This test does produce a backtrace for panic or error with the standard panic hook, + * but I'm at a loss for how to capture the backtrace and compare it to a snippet. + */ + #[cfg_attr(not(backtrace), ignore)] + // #[test] + // #[should_panic] + fn test_backtrace_simple() { + let report = high(FailFrame::Low).expect_err("Must be error"); + let handler: &eyre::DefaultHandler = report.handler().downcast_ref().unwrap(); + eprintln!("{:?}", handler); + // let backtrace: Backtrace = handler.backtrace.unwrap(); + // let + /* + let backtrace: Option = handler.backtrace; + assert!(backtrace.is_some()); + */ + } + + #[cfg_attr(not(backtrace), ignore)] + // #[test] + fn test_backtrace() { + /* FIXME: check that the backtrace actually happens here + * It's not trivial to compare the *whole* output, + * but we could somehow grep the output for 'stack_backtrace', + * maybe check for this string... though including line numbers is problematic, + * and the frames could change if core changes. + * + */ + + let global_buffer = Arc::new(Mutex::new(String::new())); + let old_hook = panic::take_hook(); + panic::set_hook({ + /* fixme: this panic hook is not working ;( + */ + let global_buffer = global_buffer.clone(); + Box::new(move |info| { + let mut global_buffer = global_buffer.lock().unwrap(); + + if let Some(s) = info.payload().downcast_ref::<&str>() { + println!("PANIC: {}", *s); + *global_buffer = (*s).to_string() + } else { + // panic!("help!"); + } + }) + }); + + panic::catch_unwind(|| { + high(FailFrame::Low).unwrap(); //.unwrap_or(println!("test")); + }) + .expect_err("Backtrace test did not panic."); + let binding = global_buffer.lock().unwrap(); + let panic_output = binding.clone(); + panic::set_hook(old_hook); + if !panic_output.contains(BACKTRACE_SNIPPET_HIGH) { + println!("Backtrace test fail."); + println!("Expected output to contain:"); + println!("{}", BACKTRACE_SNIPPET_HIGH); + println!("Instead, outputted:"); + println!("{}", panic_output); + panic!(); + } + } +} diff --git a/eyre/tests/ui/ui_test_backtrace.rs b/eyre/tests/ui/ui_test_backtrace.rs new file mode 100644 index 0000000..72d85ff --- /dev/null +++ b/eyre/tests/ui/ui_test_backtrace.rs @@ -0,0 +1,14 @@ +use eyre::Report; + +fn fail(fail: bool) -> Result<(), Report> { + let e: Report = eyre!("Internal error message"); + if fail { + Err(e).wrap_err("External error message") + } else { + Ok(()) + } +} + +fn main() { + fail(true); +} diff --git a/eyre/tests/ui/ui_test_backtrace.stderr b/eyre/tests/ui/ui_test_backtrace.stderr new file mode 100644 index 0000000..f8d406b --- /dev/null +++ b/eyre/tests/ui/ui_test_backtrace.stderr @@ -0,0 +1 @@ +error: embedded manifest `$DIR/ui_test_backtrace.rs` requires `-Zscript`