use std::cell::{RefCell, RefMut};
use std::fmt::{Debug, Display, Error, Formatter};
use std::sync::OnceLock;
use std::thread_local;
#[doc(hidden)]
pub enum TestOutcome {
Success,
Failure,
}
thread_local! {
static CURRENT_TEST_OUTCOME: RefCell<Option<TestOutcome>> = const { RefCell::new(None) };
}
impl TestOutcome {
#[doc(hidden)]
pub fn init_current_test_outcome() {
static INSTALL_HOOK: OnceLock<()> = OnceLock::new();
INSTALL_HOOK.get_or_init(|| {
let prev_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let traces = crate::internal::scoped_trace::get_scoped_traces();
if !traces.is_empty() {
#[cfg(test)]
{
use crate::internal::scoped_trace::test_helpers::*;
let use_capture = USE_CAPTURE_HOOK.with(|v| v.get());
if use_capture {
let _ = CAPTURED_TRACES_IN_HOOK
.with(|v| v.try_borrow_mut().map(|mut b| *b = traces.clone()));
prev_hook(info);
return;
}
}
eprintln!("Google Test trace:");
for trace in traces.iter().rev() {
eprintln!(" {}:{}: {}", trace.file, trace.line, trace.message);
}
}
prev_hook(info);
}));
});
Self::with_current_test_outcome(|mut current_test_outcome| {
*current_test_outcome = Some(TestOutcome::Success);
})
}
#[doc(hidden)]
pub fn close_current_test_outcome<E: Display>(
inner_result: Result<(), E>,
) -> Result<(), TestFailure> {
TestOutcome::with_current_test_outcome(|mut outcome| {
let outer_result = match &*outcome {
Some(TestOutcome::Success) => match inner_result {
Ok(()) => Ok(()),
Err(_) => Err(TestFailure),
},
Some(TestOutcome::Failure) => Err(TestFailure),
None => {
panic!("No test context found. This indicates a bug in GoogleTest.")
}
};
if let Err(fatal_assertion_failure) = inner_result {
println!("{fatal_assertion_failure}");
}
*outcome = None;
outer_result
})
}
#[track_caller]
pub(crate) fn get_current_test_outcome() -> Result<(), TestAssertionFailure> {
TestOutcome::with_current_test_outcome(|mut outcome| {
let outcome = outcome
.as_mut()
.expect("No test context found. This indicates a bug in GoogleTest.");
match outcome {
TestOutcome::Success => Ok(()),
TestOutcome::Failure => Err(TestAssertionFailure::create("Test failed".into())),
}
})
}
fn fail_current_test() {
TestOutcome::with_current_test_outcome(|mut outcome| {
let outcome = outcome
.as_mut()
.expect("No test context found. This indicates a bug in GoogleTest.");
*outcome = TestOutcome::Failure;
})
}
fn with_current_test_outcome<T>(action: impl FnOnce(RefMut<Option<TestOutcome>>) -> T) -> T {
CURRENT_TEST_OUTCOME.with(|current_test_outcome| action(current_test_outcome.borrow_mut()))
}
pub(crate) fn ensure_test_context_present() {
if FAILURE_REPORTER_HOOK.get().is_some() {
return;
}
TestOutcome::with_current_test_outcome(|outcome| {
outcome.as_ref().expect(
"
No test context found.
* Did you annotate the test with gtest?
* Is the assertion running in the original test thread?
",
);
})
}
}
pub struct TestFailure;
impl std::error::Error for TestFailure {}
impl std::fmt::Debug for TestFailure {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
writeln!(f, "See failure output above")?;
Ok(())
}
}
impl std::fmt::Display for TestFailure {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
writeln!(f, "See failure output above")?;
Ok(())
}
}
#[doc(hidden)]
#[derive(Clone)]
pub struct TestAssertionFailure {
pub description: String,
pub custom_message: Option<String>,
pub traces: Vec<crate::internal::scoped_trace::TraceInfo>,
location: Location,
}
#[doc(hidden)]
#[derive(Clone)]
enum Location {
Real(&'static std::panic::Location<'static>),
Fake { file: &'static str, line: u32, column: u32 },
}
impl Display for Location {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Location::Real(l) => write!(f, "{l}"),
Location::Fake { file, line, column } => write!(f, "{file}:{line}:{column}"),
}
}
}
#[doc(hidden)]
static FAILURE_REPORTER_HOOK: OnceLock<fn(&TestAssertionFailure)> = OnceLock::new();
#[doc(hidden)]
pub fn set_failure_reporter_hook_if_not_set(capture_fn: fn(&TestAssertionFailure)) -> bool {
FAILURE_REPORTER_HOOK.set(capture_fn).is_ok()
}
impl TestAssertionFailure {
#[track_caller]
pub fn create(description: String) -> Self {
Self {
description,
custom_message: None,
traces: crate::internal::scoped_trace::get_scoped_traces(),
location: Location::Real(std::panic::Location::caller()),
}
}
pub fn with_fake_location(mut self, file: &'static str, line: u32, column: u32) -> Self {
self.location = Location::Fake { file, line, column };
self
}
pub(crate) fn log(&self) {
println!("{self}");
if let Some(capture_fn) = FAILURE_REPORTER_HOOK.get() {
capture_fn(self);
return;
}
TestOutcome::fail_current_test();
}
pub fn file(&self) -> &'static str {
match self.location {
Location::Real(l) => l.file(),
Location::Fake { file, .. } => file,
}
}
pub fn line(&self) -> u32 {
match self.location {
Location::Real(l) => l.line(),
Location::Fake { line, .. } => line,
}
}
}
impl Display for TestAssertionFailure {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
writeln!(f, "{}", self.description)?;
if let Some(custom_message) = &self.custom_message {
writeln!(f, "{custom_message}")?;
}
if !self.traces.is_empty() {
writeln!(f, "Google Test trace:")?;
for trace in self.traces.iter().rev() {
writeln!(f, " {}:{}: {}", trace.file, trace.line, trace.message)?;
}
}
writeln!(f, " at {}", self.location)
}
}
impl Debug for TestAssertionFailure {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
Display::fmt(self, f)
}
}
impl<T: std::error::Error> From<T> for TestAssertionFailure {
#[track_caller]
fn from(value: T) -> Self {
TestAssertionFailure::create(format!("{value}"))
}
}
#[cfg(feature = "proptest")]
impl From<TestAssertionFailure> for proptest::test_runner::TestCaseError {
fn from(value: TestAssertionFailure) -> Self {
proptest::test_runner::TestCaseError::Fail(format!("{value}").into())
}
}