test_that/result.rs
1// Copyright 2022 Google LLC
2// Copyright 2026 Bradford Hovinen <bradford@hovinen.me>
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::internal::test_outcome::{TestAssertionFailure, TestOutcome};
17use alloc::string::{String, ToString as _};
18
19/// A `Result` whose `Err` variant indicates a test failure.
20///
21/// The assertions [`verify_that!`][crate::verify_that],
22/// [`verify_pred!`][crate::verify_pred], and [`fail!`][crate::fail] evaluate
23/// to `TestResult<()>`. A test function may return `TestResult<()>` in
24/// combination with those macros to abort immediately on assertion failure.
25///
26/// This can be used with subroutines which may cause the test to fatally fail
27/// and which return some value needed by the caller. For example:
28///
29/// ```
30/// # use test_that::prelude::*;
31/// fn load_file_content_as_string() -> TestResult<String> {
32/// let file_stream = load_file().or_fail()?;
33/// Ok(file_stream.to_string())
34/// }
35/// # fn load_file() -> Option<String> { None }
36/// ```
37///
38/// The `Err` variant contains a [`TestAssertionFailure`] which carries the data
39/// of the (fatal) assertion failure which generated this result. Non-fatal
40/// assertion failures, which log the failure and report the test as having
41/// failed but allow it to continue running, are not encoded in this type.
42pub type TestResult<T> = core::result::Result<T, TestAssertionFailure>;
43
44/// Alias for [TestResult] to ease porting from [googletest](https://docs.rs/googletest).
45#[cfg(feature = "googletest-compat")]
46#[cfg_attr(feature = "googletest-migrate", deprecated(note = "Use TestResult instead"))]
47pub type Result<T> = TestResult<T>;
48
49/// Returns a [`Result`] corresponding to the outcome of the currently running
50/// test.
51///
52/// This returns `Result::Err` precisely if the current test has recorded at
53/// least one test assertion failure via [`expect_that!`][crate::expect_that],
54/// [`expect_pred!`][crate::expect_pred], or [`TestResultExt::and_log_failure`].
55/// It can be used in concert with the `?` operator to continue execution of the
56/// test conditionally on there not having been any failure yet.
57///
58/// This requires the use of the [`#[test_that::test]`][crate::test] attribute
59/// macro.
60///
61/// ```
62/// # use test_that::prelude::*;
63/// # #[cfg(feature = "non-fatal-assertions")] {
64/// # /* Make sure this also compiles as a doctest.
65/// #[test_that::test]
66/// # */
67/// # fn foo() -> u32 { 1 }
68/// # fn bar() -> u32 { 2 }
69/// fn should_fail_and_not_execute_last_assertion() -> TestResult<()> {
70/// # test_that::internal::test_outcome::TestOutcome::init_current_test_outcome();
71/// expect_that!(foo(), eq(2)); // May fail, but will not abort the test.
72/// expect_that!(bar(), gt(1)); // May fail, but will not abort the test.
73/// verify_current_test_outcome()?; // Aborts the test if one of the previous assertions failed.
74/// verify_that!(foo(), gt(0)) // Does not execute if the line above aborts.
75/// }
76/// # verify_that!(should_fail_and_not_execute_last_assertion(), err(displays_as(contains_substring("Test failed")))).unwrap();
77/// # }
78/// ```
79#[cfg(feature = "std")]
80pub fn verify_current_test_outcome() -> TestResult<()> {
81 TestOutcome::get_current_test_outcome()
82}
83
84/// Adds to `Result` support for Test That! functionality.
85pub trait TestResultExt {
86 /// If `self` is a `Result::Err`, writes to `stdout` a failure report
87 /// and marks the test failed. Otherwise, does nothing.
88 ///
89 /// This can be used for non-fatal test assertions, for example:
90 ///
91 /// ```
92 /// # use test_that::prelude::*;
93 /// # use test_that::internal::test_outcome::TestOutcome;
94 /// # #[cfg(feature = "std")]
95 /// # TestOutcome::init_current_test_outcome();
96 /// let actual = 42;
97 /// verify_that!(actual, eq(42)).and_log_failure();
98 /// // Test still passing; nothing happens
99 /// verify_that!(actual, eq(10)).and_log_failure();
100 /// // Test now fails and failure output to stdout
101 /// verify_that!(actual, eq(100)).and_log_failure();
102 /// // Test still fails and new failure also output to stdout
103 /// # #[cfg(feature = "std")]
104 /// # TestOutcome::close_current_test_outcome::<&str>(Ok(())).unwrap_err();
105 /// ```
106 fn and_log_failure(self);
107
108 /// Adds `message` to the logged failure message if `self` is a
109 /// `Result::Err`. Otherwise, does nothing.
110 ///
111 /// If this method is called more than once, only `message` from the last
112 /// invocation is output.
113 ///
114 /// For example:
115 ///
116 /// ```
117 /// # use test_that::prelude::*;
118 /// # fn should_fail() -> TestResult<()> {
119 /// let actual = 0;
120 /// verify_that!(actual, eq(42)).failure_message("Actual was wrong!")?;
121 /// # Ok(())
122 /// # }
123 /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual was wrong"))))
124 /// # .unwrap();
125 /// ```
126 ///
127 /// results in the following failure message:
128 ///
129 /// ```text
130 /// Expected: actual equal to 42
131 /// but was: 0
132 /// Actual was wrong!
133 /// ```
134 ///
135 /// One can pass a `String` too:
136 ///
137 /// ```
138 /// # use test_that::prelude::*;
139 /// # fn should_fail() -> TestResult<()> {
140 /// let actual = 0;
141 /// verify_that!(actual, eq(42))
142 /// .failure_message(format!("Actual {} was wrong!", actual))?;
143 /// # Ok(())
144 /// # }
145 /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
146 /// # .unwrap();
147 /// ```
148 ///
149 /// However, consider using [`TestResultExt::with_failure_message`]
150 /// instead in that case to avoid unnecessary memory allocation when the
151 /// message is not needed.
152 fn failure_message(self, message: impl Into<String>) -> Self;
153
154 /// Adds the output of the closure `provider` to the logged failure message
155 /// if `self` is a `Result::Err`. Otherwise, does nothing.
156 ///
157 /// This is analogous to [`TestResultExt::failure_message`] but
158 /// only executes the closure `provider` if it actually produces the
159 /// message, thus saving possible memory allocation.
160 ///
161 /// ```
162 /// # use test_that::prelude::*;
163 /// # fn should_fail() -> TestResult<()> {
164 /// let actual = 0;
165 /// verify_that!(actual, eq(42))
166 /// .with_failure_message(|| format!("Actual {} was wrong!", actual))?;
167 /// # Ok(())
168 /// # }
169 /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
170 /// # .unwrap();
171 /// ```
172 fn with_failure_message(self, provider: impl FnOnce() -> String) -> Self;
173}
174
175impl<T> TestResultExt for core::result::Result<T, TestAssertionFailure> {
176 fn and_log_failure(self) {
177 TestOutcome::ensure_text_context_present();
178 if let Err(failure) = self {
179 failure.log();
180 }
181 }
182
183 fn failure_message(mut self, message: impl Into<String>) -> Self {
184 if let Err(ref mut failure) = self {
185 failure.custom_message = Some(message.into());
186 }
187 self
188 }
189
190 fn with_failure_message(mut self, provider: impl FnOnce() -> String) -> Self {
191 if let Err(ref mut failure) = self {
192 failure.custom_message = Some(provider());
193 }
194 self
195 }
196}
197
198/// Provides an extension method for converting an arbitrary type into a
199/// [`TestResult`].
200///
201/// A type can implement this trait to provide an easy way to return immediately
202/// from a test in conjunction with the `?` operator. This is useful for
203/// [`Option`] as well as [`Result`] types whose `Result::Err` variant does not
204/// implement [`std::error::Error`].
205///
206/// There is an implementation of this trait for [`anyhow::Error`] (which does
207/// not implement `std::error::Error`) when the `anyhow` feature is enabled.
208/// Importing this trait allows one to easily map [`anyhow::Error`] to a test
209/// failure.
210///
211/// This is also implemented for [`Option`].
212///
213/// See [`or_fail`][OrFailExt::or_fail] for usage examples.
214pub trait OrFailExt<T> {
215 /// Converts this instance into a [`TestResult`].
216 ///
217 /// Invoking this method allows direct use of the `?` operator in tests.
218 /// For example, in the case of [`Option`]:
219 ///
220 /// ```
221 /// use test_that::prelude::*;
222 ///
223 /// # /* So that this compiles in the doctest context
224 /// #[test]
225 /// # */
226 /// fn fails_due_to_missing_element() -> TestResult<()> {
227 /// let empty_hash_map = std::collections::HashMap::<u32, u32>::new();
228 /// let value = empty_hash_map.get(&0).or_fail()?;
229 /// Ok(())
230 /// }
231 ///
232 /// fails_due_to_missing_element().unwrap_err();
233 /// ```
234 ///
235 /// In the case of [`anyhow::Error`]:
236 ///
237 /// ```
238 /// use test_that::prelude::*;
239 ///
240 /// # /* So that this compiles in the doctest context
241 /// #[test]
242 /// # */
243 /// # #[cfg(feature = "anyhow")]
244 /// fn fails_due_to_anyhow_error() -> TestResult<()> {
245 /// something_which_can_fail().or_fail()?;
246 /// Ok(())
247 /// }
248 ///
249 /// # #[cfg(feature = "anyhow")]
250 /// fn something_which_can_fail() -> anyhow::Result<()> {
251 /// anyhow::bail!("An error")
252 /// }
253 ///
254 /// # #[cfg(feature = "anyhow")]
255 /// fails_due_to_anyhow_error().unwrap_err();
256 /// ```
257 ///
258 /// Typically, the `Self` type is itself a [`Result`] or an [`Option`]. This
259 /// method should then map `None` or the `Err` variant to a
260 /// [`TestAssertionFailure`] and leave the `Some` or `Ok` variant unchanged.
261 fn or_fail(self) -> TestResult<T>;
262}
263
264#[cfg(feature = "anyhow")]
265impl<T> OrFailExt<T> for core::result::Result<T, anyhow::Error> {
266 fn or_fail(self) -> core::result::Result<T, TestAssertionFailure> {
267 self.map_err(|e| TestAssertionFailure::create(alloc::format!("{e:#}")))
268 }
269}
270
271#[cfg(feature = "proptest")]
272impl<OkT, CaseT: core::fmt::Debug> OrFailExt<OkT>
273 for core::result::Result<OkT, proptest::test_runner::TestError<CaseT>>
274{
275 fn or_fail(self) -> core::result::Result<OkT, TestAssertionFailure> {
276 self.map_err(|e| TestAssertionFailure::create(alloc::format!("{e}")))
277 }
278}
279
280impl<T> OrFailExt<T> for core::option::Option<T> {
281 fn or_fail(self) -> core::result::Result<T, TestAssertionFailure> {
282 self.ok_or_else(|| {
283 TestAssertionFailure::create("Expected Option to be Some but was None".to_string())
284 })
285 }
286}