Skip to content

Commit f605d54

Browse files
Googlercopybara-github
authored andcommitted
Refactor verify_pred into a proc macro helps with subsequent improvements to its functionality.
Also supports function/method arguments that don't implement `Debug` using inherent method specialization. PiperOrigin-RevId: 690021288
1 parent 61b92b0 commit f605d54

File tree

6 files changed

+228
-116
lines changed

6 files changed

+228
-116
lines changed

googletest/src/assertions.rs

Lines changed: 49 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,10 @@ macro_rules! verify_that {
183183
/// ```text
184184
/// equals_modulo(a, b(b(2)), 2 + 3) was false with
185185
/// a = 1,
186-
/// b(v) = 7,
187-
/// 2 + 3 = 5
186+
/// b(b(2)) = 7,
187+
/// 2 + 3 = 5,
188188
/// ```
189189
///
190-
/// The function passed to this macro must return `bool`. Each of the arguments
191-
/// must evaluate to a type implementing [`std::fmt::Debug`]. The debug output
192-
/// is used to construct the failure message.
193-
///
194190
/// The predicate can also be a method on a struct, e.g.:
195191
///
196192
/// ```ignore
@@ -204,46 +200,12 @@ macro_rules! verify_that {
204200
/// ```
205201
///
206202
/// **Warning:** This macro assumes that the arguments passed to the predicate
207-
/// are pure so that two subsequent invocations to any of the expresssions
208-
/// passed as arguments result in different values, then the output message of a
209-
/// test failure will deviate from the values actually passed to the predicate.
210-
/// For this reason, *always assign the outputs of non-pure functions to
211-
/// variables before using them in this macro. For example:
212-
///
213-
/// ```ignore
214-
/// let output = generate_random_number(); // Assigned outside of verify_pred.
215-
/// verify_pred!(is_sufficiently_random(output))?;
216-
/// ```
203+
/// cause no mutations, or else the output message of a test failure will
204+
/// deviate from the values actually passed to the predicate.
217205
#[macro_export]
218206
macro_rules! verify_pred {
219-
(@internal [$($predicate:tt)+] $(,)?) => {
220-
if !$($predicate)* {
221-
$crate::assertions::internal::report_failed_predicate(
222-
stringify!($($predicate)*),
223-
vec![],
224-
)
225-
} else {
226-
Ok(())
227-
}
228-
};
229-
230-
(@internal [$($predicate:tt)+]($($arg:expr),* $(,)?)) => {
231-
if !$($predicate)*($($arg),*) {
232-
$crate::assertions::internal::report_failed_predicate(
233-
concat!(stringify!($($predicate)*), stringify!(($($arg),*))),
234-
vec![$((format!(concat!(stringify!($arg), " = {:?}"), $arg))),*],
235-
)
236-
} else {
237-
Ok(())
238-
}
239-
};
240-
241-
(@internal [$($predicate:tt)+] $first:tt $($rest:tt)*) => {
242-
$crate::verify_pred!(@internal [$($predicate)* $first] $($rest)*)
243-
};
244-
245-
($first:tt $($rest:tt)*) => {
246-
$crate::verify_pred!(@internal [$first] $($rest)*)
207+
($expr:expr $(,)?) => {
208+
$crate::assertions::internal::__googletest_macro_verify_pred!($expr)
247209
};
248210
}
249211

@@ -1414,6 +1376,8 @@ pub mod internal {
14141376
};
14151377
use std::fmt::Debug;
14161378

1379+
pub use ::googletest_macro::__googletest_macro_verify_pred;
1380+
14171381
/// Extension trait to perform autoref through method lookup in the
14181382
/// assertion macros. With this trait, the subject can be either a value
14191383
/// or a reference. For example, this trait makes the following code
@@ -1453,25 +1417,6 @@ pub mod internal {
14531417

14541418
impl<T: Copy + Debug> Subject for T {}
14551419

1456-
/// Constructs a `Result::Err(TestAssertionFailure)` for a predicate failure
1457-
/// as produced by the macro [`crate::verify_pred`].
1458-
///
1459-
/// This intended only for use by the macro [`crate::verify_pred`].
1460-
///
1461-
/// **For internal use only. API stablility is not guaranteed!**
1462-
#[track_caller]
1463-
#[must_use = "The assertion result must be evaluated to affect the test result."]
1464-
pub fn report_failed_predicate(
1465-
actual_expr: &'static str,
1466-
formatted_arguments: Vec<String>,
1467-
) -> Result<(), TestAssertionFailure> {
1468-
Err(TestAssertionFailure::create(format!(
1469-
"{} was false with\n {}",
1470-
actual_expr,
1471-
formatted_arguments.join(",\n ")
1472-
)))
1473-
}
1474-
14751420
/// Creates a failure at specified location.
14761421
///
14771422
/// **For internal use only. API stability is not guaranteed!**
@@ -1480,4 +1425,45 @@ pub mod internal {
14801425
pub fn create_fail_result(message: String) -> crate::Result<()> {
14811426
Err(crate::internal::test_outcome::TestAssertionFailure::create(message))
14821427
}
1428+
1429+
/// Trait that defaults to not rendering values. Used for autoref
1430+
/// specialization to conditionally render only values that implement
1431+
/// `Debug`. See also [`FormatDebug`].
1432+
pub trait FormatNonDebug {
1433+
fn __googletest_format_as_line(&self, output: &mut String, expr_label: &str) {
1434+
use ::std::fmt::Write as _;
1435+
write!(output, "\n {} does not implement Debug,", expr_label)
1436+
.expect("Formatting to String should never fail");
1437+
}
1438+
}
1439+
1440+
impl<T> FormatNonDebug for &T {}
1441+
1442+
/// Trait to render values that implement `Debug` to a format string. Used
1443+
/// for autoref specialization to conditionally render only values that
1444+
/// implement `Debug`. See also [`FormatNonDebug`].
1445+
pub trait FormatDebug {
1446+
fn __googletest_format_as_line(&self, output: &mut String, expr_label: &str);
1447+
}
1448+
1449+
impl<T: Debug> FormatDebug for T {
1450+
#[track_caller]
1451+
fn __googletest_format_as_line(&self, output: &mut String, expr_label: &str) {
1452+
use ::std::fmt::Write as _;
1453+
write!(output, "\n {} = {:?},", expr_label, self)
1454+
.expect("Formatting to String should never fail");
1455+
}
1456+
}
1457+
1458+
#[macro_export]
1459+
macro_rules! __googletest__format_as_line(
1460+
($output_str:expr, $expr_str:expr, $value:expr $(,)?) => {
1461+
{
1462+
use $crate::assertions::internal::FormatNonDebug as _;
1463+
use $crate::assertions::internal::FormatDebug as _;
1464+
(&$value).__googletest_format_as_line($output_str, $expr_str)
1465+
}
1466+
}
1467+
);
1468+
pub use __googletest__format_as_line;
14831469
}

googletest/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ pub mod matcher;
3030
pub mod matcher_support;
3131
pub mod matchers;
3232

33+
pub use googletest_macro::__googletest_macro_verify_pred;
34+
3335
/// Re-exports of the symbols in this crate which are most likely to be used.
3436
///
3537
/// This includes:

googletest/tests/assertions_test.rs

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,28 @@ mod verify_pred {
33
use indoc::indoc;
44

55
#[test]
6-
fn supports_function_call() -> Result<()> {
7-
fn f(_a: u32, _b: u32, _c: u32) -> bool {
6+
fn supports_function_call_with_non_debug_types() -> Result<()> {
7+
// Non-Debug - cannot be printed.
8+
struct Apple;
9+
fn f(_a: &Apple, _b: u32, _c: u32) -> bool {
810
false
911
}
1012
fn g(_a: u32) -> u32 {
1113
5
1214
}
1315

14-
let a = 1;
16+
let a = &Apple;
1517
let res = verify_pred!(f(a, g(g(3)), 1 + 2));
1618
verify_that!(
1719
res,
18-
err(displays_as(contains_substring(indoc! {
19-
"
20+
err(displays_as(contains_substring(indoc! {"
2021
f(a, g(g(3)), 1 + 2) was false with
21-
a = 1,
22+
a does not implement Debug,
2223
g(g(3)) = 5,
23-
1 + 2 = 3
24-
"
24+
1 + 2 = 3,
25+
at"
2526
})))
26-
)?;
27-
28-
Ok(())
27+
)
2928
}
3029

3130
#[test]
@@ -40,20 +39,32 @@ mod verify_pred {
4039
}
4140

4241
#[test]
43-
fn supports_method_calls() -> Result<()> {
44-
struct Foo {
45-
b: Bar,
42+
fn supports_method_calls_with_non_debug_types() -> Result<()> {
43+
struct Apple {
44+
b: Banana,
4645
}
47-
struct Bar;
48-
impl Bar {
49-
fn c(&self) -> bool {
46+
struct Banana;
47+
impl Banana {
48+
fn c(&self, _c: &Cherry, _d: u32) -> bool {
5049
false
5150
}
5251
}
52+
// Non-Debug - cannot be printed.
53+
struct Cherry;
5354

54-
let a = Foo { b: Bar };
55-
let res = verify_pred!(a.b.c());
56-
verify_that!(res, err(displays_as(contains_substring("a.b.c() was false"))))
55+
let a = Apple { b: Banana };
56+
let c = &Cherry;
57+
let d = 3;
58+
let res = verify_pred!(a.b.c(c, d));
59+
verify_that!(
60+
res,
61+
err(displays_as(contains_substring(indoc! {"
62+
a.b.c(c, d) was false with
63+
c does not implement Debug,
64+
d = 3,
65+
at"
66+
})))
67+
)
5768
}
5869

5970
#[test]
@@ -73,8 +84,12 @@ mod verify_pred {
7384

7485
let a = Foo;
7586
let res = verify_pred!(a.b().c());
76-
verify_that!(res, err(displays_as(contains_substring("a.b().c() was false"))))?;
77-
78-
Ok(())
87+
verify_that!(
88+
res,
89+
err(displays_as(contains_substring(indoc! {"
90+
a.b().c() was false with
91+
at"
92+
})))
93+
)
7994
}
8095
}

googletest_macro/src/lib.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,11 @@ impl Fixture {
256256

257257
fn parameter(&self) -> proc_macro2::TokenStream {
258258
let Self { identifier, mutability, consumable, .. } = self;
259-
if *consumable { quote!(#identifier) } else { quote!(& #mutability #identifier) }
259+
if *consumable {
260+
quote!(#identifier)
261+
} else {
262+
quote!(& #mutability #identifier)
263+
}
260264
}
261265

262266
fn wrap_call(&self, inner_call: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
@@ -327,3 +331,14 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
327331
}
328332
.into()
329333
}
334+
335+
mod verify_pred;
336+
337+
/// This is an implementation detail of `verify_pred!`.
338+
///
339+
/// It's not intended to be used directly.
340+
#[doc(hidden)]
341+
#[proc_macro]
342+
pub fn __googletest_macro_verify_pred(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
343+
verify_pred::verify_pred_impl(input)
344+
}

googletest_macro/src/verify_pred.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use quote::quote;
16+
use syn::{parse_macro_input, punctuated::Punctuated, token::Comma, Expr, Ident};
17+
18+
struct AccumulatePartsState {
19+
error_message_ident: Ident,
20+
formats: Vec<proc_macro2::TokenStream>,
21+
}
22+
23+
fn expr_to_string(expr: &Expr) -> String {
24+
quote!(#expr).to_string()
25+
}
26+
27+
impl AccumulatePartsState {
28+
fn new() -> Self {
29+
Self {
30+
error_message_ident: Ident::new(
31+
"__googletest__verify_pred__error_message",
32+
::proc_macro2::Span::call_site(),
33+
),
34+
formats: vec![],
35+
}
36+
}
37+
38+
/// Accumulates error message formating parts for various parts of the
39+
/// expression.
40+
fn accumulate_parts(&mut self, expr: &Expr) {
41+
let expr_string = expr_to_string(expr);
42+
match expr {
43+
Expr::Group(group) => {
44+
// This is an invisible group added for correct precedence in the AST. Just pass
45+
// through without having a separate printing result.
46+
return self.accumulate_parts(&group.expr);
47+
}
48+
Expr::Call(call) => {
49+
// Format the args into the error message.
50+
self.format_args(&call.args);
51+
}
52+
Expr::MethodCall(method_call) => {
53+
// Format the args into the error message.
54+
self.format_args(&method_call.args);
55+
}
56+
_ => {}
57+
}
58+
let error_message_ident = &self.error_message_ident;
59+
self.formats.push(quote! {
60+
::googletest::fmt::internal::__googletest__write_expr_value!(
61+
&mut #error_message_ident,
62+
#expr_string,
63+
#expr,
64+
);
65+
});
66+
}
67+
68+
// Formats each argument expression into the error message.
69+
fn format_args(&mut self, args: &Punctuated<Expr, Comma>) {
70+
for pair in args.pairs() {
71+
let error_message_ident = &self.error_message_ident;
72+
let expr_string = expr_to_string(pair.value());
73+
let expr = pair.value();
74+
self.formats.push(quote! {
75+
::googletest::fmt::internal::__googletest__write_expr_value!(
76+
&mut #error_message_ident,
77+
#expr_string,
78+
#expr,
79+
);
80+
});
81+
}
82+
}
83+
}
84+
85+
pub fn verify_pred_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
86+
let parsed = parse_macro_input!(input as Expr);
87+
let error_message = quote!(#parsed).to_string() + " was false with";
88+
89+
let mut state = AccumulatePartsState::new();
90+
state.accumulate_parts(&parsed);
91+
let AccumulatePartsState { error_message_ident, mut formats, .. } = state;
92+
93+
let _ = formats.pop(); // The last one is the full expression itself.
94+
quote! {
95+
{
96+
if (#parsed) {
97+
Ok(())
98+
} else {
99+
let mut #error_message_ident = #error_message.to_string();
100+
#(#formats)*
101+
::core::result::Result::Err(
102+
::googletest::internal::test_outcome::TestAssertionFailure::create(
103+
#error_message_ident))
104+
}
105+
}
106+
}
107+
.into()
108+
}

0 commit comments

Comments
 (0)