blob: 2171cc7b945056b3763b7fa0b3bd908b385a01c3 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::cell::{RefCell, RefMut};
use std::fmt::{Debug, Display, Error, Formatter};
use std::thread_local;
/// The outcome hitherto of running a test.
///
/// This is kept as a running record as the test progresses. One can access it
/// with `TestOutcome::with_current_test_outcome`.
///
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub enum TestOutcome {
/// The test ran or is currently running and no assertions have failed.
Success,
/// The test ran or is currently running and at least one assertion has
/// failed.
Failure,
}
thread_local! {
static CURRENT_TEST_OUTCOME: RefCell<Option<TestOutcome>> = RefCell::new(None);
}
impl TestOutcome {
/// Resets the current test's [`TestOutcome`].
///
/// This is intended only for use by the attribute macro
/// `#[googletest::test]`.
///
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub fn init_current_test_outcome() {
Self::with_current_test_outcome(|mut current_test_outcome| {
*current_test_outcome = Some(TestOutcome::Success);
})
}
/// Evaluates the current test's [`TestOutcome`], producing a suitable
/// `Result`.
///
/// The parameter `result` is the value returned by the test function
/// itself. This returns `Result::Err` with a `Display`-formatted string of
/// the error if `result` is `Result::Err`.
///
/// Otherwise, this returns `Result::Err` precisely when a test failure has
/// been recorded with
/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure).
///
/// **For internal use only. API stablility is not guaranteed!**
#[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
})
}
/// Returns a `Result` corresponding to the outcome of the currently running
/// test.
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())),
}
})
}
/// Records that the currently running test has failed.
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;
})
}
/// Runs `action` with the [`TestOutcome`] for the currently running test.
///
/// This is primarily intended for use by assertion macros like
/// `expect_that!`.
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()))
}
/// Ensure that there is a test context present and panic if there is not.
pub(crate) fn ensure_text_context_present() {
TestOutcome::with_current_test_outcome(|outcome| {
outcome.as_ref().expect(
"
No test context found.
* Did you annotate the test with googletest::test?
* Is the assertion running in the original test thread?
",
);
})
}
}
/// A marking struct indicating that a test has failed.
///
/// This exists to implement the [Error][std::error::Error] trait. It displays
/// to a message indicating that the actual test assertion failure messages are
/// in the text above.
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(())
}
}
/// A report that a single test assertion failed.
///
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
#[derive(Clone)]
pub struct TestAssertionFailure {
/// A human-readable formatted string describing the error.
pub description: String,
pub custom_message: Option<String>,
}
impl TestAssertionFailure {
/// Creates a new instance with the given `description`.
///
/// **For internal use only. API stablility is not guaranteed!**
pub fn create(description: String) -> Self {
Self { description, custom_message: None }
}
pub(crate) fn log(&self) {
TestOutcome::fail_current_test();
print!("{}", self);
}
}
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)?;
}
Ok(())
}
}
// The standard Rust test harness outputs the TestAssertionFailure with the
// Debug trait. We want the output to be formatted, so we use a custom Debug
// implementation which defers to Display.
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 {
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())
}
}