Skip to content

Commit f7562e0

Browse files
Merge pull request #15 from antithesishq/improve-assertion-macros
Improve assertion macros
2 parents a5dcc55 + 046928d commit f7562e0

File tree

4 files changed

+167
-41
lines changed

4 files changed

+167
-41
lines changed

lib/src/assert/guidance.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ macro_rules! impl_diff_signed {
165165

166166
fn diff(&self, other: Self) -> Self::Output {
167167
if *self < other {
168+
// For correctness, see
169+
// https://github.com/rust-lang/rust/blob/11e760b7f4e4aaa11bf51a64d4bb7f1171f6e466/library/core/src/num/int_macros.rs#L3443-L3456
168170
-((other as $unsigned_t).wrapping_sub(*self as $unsigned_t) as f64)
169171
} else {
170172
(*self as $unsigned_t).wrapping_sub(other as $unsigned_t) as f64

lib/src/assert/macros.rs

Lines changed: 162 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ macro_rules! function {
3535
macro_rules! assert_helper {
3636
// The handling of this pattern-arm of assert_helper
3737
// is wrapped in a block {} to avoid name collisions
38-
(condition = $condition:expr, $message:literal, $details:expr, $assert_type:path, $display_type:literal, must_hit = $must_hit:literal) => {{
38+
(condition = $condition:expr, $message:literal, $(details = $details:expr)?, $assert_type:path, $display_type:literal, must_hit = $must_hit:literal) => {{
3939
// Force evaluation of expressions.
4040
let condition = $condition;
41-
let details = $details;
41+
let details = &$crate::serde_json::json!({});
42+
$(let details = $details;)?
4243

4344
$crate::function!(FUN_NAME);
4445

@@ -109,16 +110,24 @@ macro_rules! assert_helper {
109110
/// ```
110111
#[macro_export]
111112
macro_rules! assert_always {
112-
($condition:expr, $message:literal, $details:expr) => {
113+
($condition:expr, $message:literal$(, $details:expr)?) => {
113114
$crate::assert_helper!(
114115
condition = $condition,
115116
$message,
116-
$details,
117+
$(details = $details)?,
117118
$crate::assert::AssertType::Always,
118119
"Always",
119120
must_hit = true
120121
)
121122
};
123+
($($rest:tt)*) => {
124+
::std::compile_error!(
125+
r#"Invalid syntax when calling macro `assert_always`.
126+
Example usage:
127+
`assert_always!(condition_expr, "assertion message (static literal)", &details_json_value_expr)`
128+
"#
129+
);
130+
};
122131
}
123132

124133
/// Assert that ``condition`` is true every time this function is called. The corresponding test property will pass even if the assertion is never encountered.
@@ -137,16 +146,24 @@ macro_rules! assert_always {
137146
/// ```
138147
#[macro_export]
139148
macro_rules! assert_always_or_unreachable {
140-
($condition:expr, $message:literal, $details:expr) => {
149+
($condition:expr, $message:literal$(, $details:expr)?) => {
141150
$crate::assert_helper!(
142151
condition = $condition,
143152
$message,
144-
$details,
153+
$(details = $details)?,
145154
$crate::assert::AssertType::Always,
146155
"AlwaysOrUnreachable",
147156
must_hit = false
148157
)
149158
};
159+
($($rest:tt)*) => {
160+
::std::compile_error!(
161+
r#"Invalid syntax when calling macro `assert_always_or_unreachable`.
162+
Example usage:
163+
`assert_always_or_unreachable!(condition_expr, "assertion message (static literal)", &details_json_value_expr)`
164+
"#
165+
);
166+
};
150167
}
151168

152169
/// Assert that ``condition`` is true at least one time that this function was called.
@@ -166,16 +183,24 @@ macro_rules! assert_always_or_unreachable {
166183
/// ```
167184
#[macro_export]
168185
macro_rules! assert_sometimes {
169-
($condition:expr, $message:literal, $details:expr) => {
186+
($condition:expr, $message:literal$(, $details:expr)?) => {
170187
$crate::assert_helper!(
171188
condition = $condition,
172189
$message,
173-
$details,
190+
$(details = $details)?,
174191
$crate::assert::AssertType::Sometimes,
175192
"Sometimes",
176193
must_hit = true
177194
)
178195
};
196+
($($rest:tt)*) => {
197+
::std::compile_error!(
198+
r#"Invalid syntax when calling macro `assert_sometimes`.
199+
Example usage:
200+
`assert_sometimes!(condition_expr, "assertion message (static literal)", &details_json_value_expr)`
201+
"#
202+
);
203+
};
179204
}
180205

181206
/// Assert that a line of code is reached at least once.
@@ -197,16 +222,24 @@ macro_rules! assert_sometimes {
197222
/// ```
198223
#[macro_export]
199224
macro_rules! assert_reachable {
200-
($message:literal, $details:expr) => {
225+
($message:literal$(, $details:expr)?) => {
201226
$crate::assert_helper!(
202227
condition = true,
203228
$message,
204-
$details,
229+
$(details = $details)?,
205230
$crate::assert::AssertType::Reachability,
206231
"Reachable",
207232
must_hit = true
208233
)
209234
};
235+
($($rest:tt)*) => {
236+
::std::compile_error!(
237+
r#"Invalid syntax when calling macro `assert_reachable`.
238+
Example usage:
239+
`assert_reachable!("assertion message (static literal)", &details_json_value_expr)`
240+
"#
241+
);
242+
};
210243
}
211244

212245
/// Assert that a line of code is never reached.
@@ -229,16 +262,24 @@ macro_rules! assert_reachable {
229262
/// ```
230263
#[macro_export]
231264
macro_rules! assert_unreachable {
232-
($message:literal, $details:expr) => {
265+
($message:literal$(, $details:expr)?) => {
233266
$crate::assert_helper!(
234267
condition = false,
235268
$message,
236-
$details,
269+
$(details = $details)?,
237270
$crate::assert::AssertType::Reachability,
238271
"Unreachable",
239272
must_hit = false
240273
)
241274
};
275+
($($rest:tt)*) => {
276+
::std::compile_error!(
277+
r#"Invalid syntax when calling macro `assert_unreachable`.
278+
Example usage:
279+
`assert_unreachable!("assertion message (static literal)", &details_json_value_expr)`
280+
"#
281+
);
282+
};
242283
}
243284

244285
#[cfg(feature = "full")]
@@ -283,10 +324,12 @@ macro_rules! guidance_helper {
283324
#[doc(hidden)]
284325
#[macro_export]
285326
macro_rules! numeric_guidance_helper {
286-
($assert:path, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:literal, $details:expr) => {{
327+
($assert:path, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:literal$(, $details:expr)?) => {{
287328
let left = $left;
288329
let right = $right;
289-
let mut details = $details.clone();
330+
let details = &$crate::serde_json::json!({});
331+
$(let details = $details;)?
332+
let mut details = details.clone();
290333
details["left"] = left.into();
291334
details["right"] = right.into();
292335
$assert!(left $op right, $message, &details);
@@ -324,22 +367,24 @@ macro_rules! numeric_guidance_helper {
324367
#[doc(hidden)]
325368
#[macro_export]
326369
macro_rules! numeric_guidance_helper {
327-
($assert:ident, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:literal, $details:expr) => {
328-
assert!($left $op $right, $message, $details);
370+
($assert:ident, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
371+
assert!($left $op $right, $message$(, $details)?);
329372
};
330373
}
331374

332375
#[cfg(feature = "full")]
333376
#[doc(hidden)]
334377
#[macro_export]
335378
macro_rules! boolean_guidance_helper {
336-
($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:literal, $details:expr) => {{
337-
let mut details = $details.clone();
379+
($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:literal$(, $details:expr)?) => {{
380+
let details = &$crate::serde_json::json!({});
381+
$(let details = $details;)?
382+
let mut details = details.clone();
338383
let (cond, guidance_data) = {
339384
$(let $name = $cond;)*
340385
$(details[::std::stringify!($name)] = $name.into();)*
341386
(
342-
if $all { $($name)&&* } else { $($name)||* },
387+
if $all { true $(&& $name)* } else { false $(|| $name)* },
343388
$crate::serde_json::json!({$(::std::stringify!($name): $name),*})
344389
)
345390
};
@@ -352,74 +397,138 @@ macro_rules! boolean_guidance_helper {
352397
#[doc(hidden)]
353398
#[macro_export]
354399
macro_rules! boolean_guidance_helper {
355-
($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:literal, $details:expr) => {{
356-
let cond = if $all { $($name)&&* } else { $($name)||* };
357-
$assert!(cond, $message, &details);
400+
($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:literal$(, $details:expr)?) => {{
401+
let cond = if $all { true $(&& $name)* } else { false $(|| $name)* },
402+
$assert!(cond, $message$(, &$details)?);
358403
}};
359404
}
360405

361406
/// `assert_always_greater_than(x, y, ...)` is mostly equivalent to `assert_always!(x > y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
362407
#[macro_export]
363408
macro_rules! assert_always_greater_than {
364-
($left:expr, $right:expr, $message:literal, $details:expr) => {
365-
$crate::numeric_guidance_helper!($crate::assert_always, >, false, $left, $right, $message, $details)
409+
($left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
410+
$crate::numeric_guidance_helper!($crate::assert_always, >, false, $left, $right, $message$(, $details)?)
411+
};
412+
($($rest:tt)*) => {
413+
::std::compile_error!(
414+
r#"Invalid syntax when calling macro `assert_always_greater_than`.
415+
Example usage:
416+
`assert_always_greater_than!(left_expr, right_expr, "assertion message (static literal)", &details_json_value_expr)`
417+
"#
418+
);
366419
};
367420
}
368421

369422
/// `assert_always_greater_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_always!(x >= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
370423
#[macro_export]
371424
macro_rules! assert_always_greater_than_or_equal_to {
372-
($left:expr, $right:expr, $message:literal, $details:expr) => {
425+
($left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
373426
$crate::numeric_guidance_helper!($crate::assert_always, >=, false, $left, $right, $message, $details)
374427
};
428+
($($rest:tt)*) => {
429+
::std::compile_error!(
430+
r#"Invalid syntax when calling macro `assert_always_greater_than_or_equal_to`.
431+
Example usage:
432+
`assert_always_greater_than_or_equal_to!(left_expr, right_expr, "assertion message (static literal)", &details_json_value_expr)`
433+
"#
434+
);
435+
};
375436
}
376437

377438
/// `assert_always_less_than(x, y, ...)` is mostly equivalent to `assert_always!(x < y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
378439
#[macro_export]
379440
macro_rules! assert_always_less_than {
380-
($left:expr, $right:expr, $message:literal, $details:expr) => {
441+
($left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
381442
$crate::numeric_guidance_helper!($crate::assert_always, <, true, $left, $right, $message, $details)
382443
};
444+
($($rest:tt)*) => {
445+
::std::compile_error!(
446+
r#"Invalid syntax when calling macro `assert_always_less_than`.
447+
Example usage:
448+
`assert_always_less_than!(left_expr, right_expr, "assertion message (static literal)", &details_json_value_expr)`
449+
"#
450+
);
451+
};
383452
}
384453

385454
/// `assert_always_less_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_always!(x <= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
386455
#[macro_export]
387456
macro_rules! assert_always_less_than_or_equal_to {
388-
($left:expr, $right:expr, $message:literal, $details:expr) => {
457+
($left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
389458
$crate::numeric_guidance_helper!($crate::assert_always, <=, true, $left, $right, $message, $details)
390459
};
460+
($($rest:tt)*) => {
461+
::std::compile_error!(
462+
r#"Invalid syntax when calling macro `assert_always_less_than_or_equal_to`.
463+
Example usage:
464+
`assert_always_less_than_or_equal_to!(left_expr, right_expr, "assertion message (static literal)", &details_json_value_expr)`
465+
"#
466+
);
467+
};
391468
}
392469

393470
/// `assert_sometimes_greater_than(x, y, ...)` is mostly equivalent to `assert_sometimes!(x > y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
394471
#[macro_export]
395472
macro_rules! assert_sometimes_greater_than {
396-
($left:expr, $right:expr, $message:literal, $details:expr) => {
473+
($left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
397474
$crate::numeric_guidance_helper!($crate::assert_sometimes, >, true, $left, $right, $message, $details)
398475
};
476+
($($rest:tt)*) => {
477+
::std::compile_error!(
478+
r#"Invalid syntax when calling macro `assert_sometimes_greater_than`.
479+
Example usage:
480+
`assert_sometimes_greater_than!(left_expr, right_expr, "assertion message (static literal)", &details_json_value_expr)`
481+
"#
482+
);
483+
};
399484
}
400485

401486
/// `assert_sometimes_greater_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_sometimes!(x >= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
402487
#[macro_export]
403488
macro_rules! assert_sometimes_greater_than_or_equal_to {
404-
($left:expr, $right:expr, $message:literal, $details:expr) => {
489+
($left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
405490
$crate::numeric_guidance_helper!($crate::assert_sometimes, >=, true, $left, $right, $message, $details)
406491
};
492+
($($rest:tt)*) => {
493+
::std::compile_error!(
494+
r#"Invalid syntax when calling macro `assert_sometimes_greater_than_or_equal_to`.
495+
Example usage:
496+
`assert_sometimes_greater_than_or_equal_to!(left_expr, right_expr, "assertion message (static literal)", &details_json_value_expr)`
497+
"#
498+
);
499+
};
407500
}
408501

409502
/// `assert_sometimes_less_than(x, y, ...)` is mostly equivalent to `assert_sometimes!(x < y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
410503
#[macro_export]
411504
macro_rules! assert_sometimes_less_than {
412-
($left:expr, $right:expr, $message:literal, $details:expr) => {
505+
($left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
413506
$crate::numeric_guidance_helper!($crate::assert_sometimes, <, false, $left, $right, $message, $details)
414507
};
508+
($($rest:tt)*) => {
509+
::std::compile_error!(
510+
r#"Invalid syntax when calling macro `assert_sometimes_less_than`.
511+
Example usage:
512+
`assert_sometimes_less_than!(left_expr, right_expr, "assertion message (static literal)", &details_json_value_expr)`
513+
"#
514+
);
515+
};
415516
}
416517

417518
/// `assert_sometimes_less_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_sometimes!(x <= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
418519
#[macro_export]
419520
macro_rules! assert_sometimes_less_than_or_equal_to {
420-
($left:expr, $right:expr, $message:literal, $details:expr) => {
521+
($left:expr, $right:expr, $message:literal$(, $details:expr)?) => {
421522
$crate::numeric_guidance_helper!($crate::assert_sometimes, <=, false, $left, $right, $message, $details)
422523
};
524+
($($rest:tt)*) => {
525+
::std::compile_error!(
526+
r#"Invalid syntax when calling macro `assert_sometimes_less_than_or_equal_to`.
527+
Example usage:
528+
`assert_sometimes_less_than_or_equal_to!(left_expr, right_expr, "assertion message (static literal)", &details_json_value_expr)`
529+
"#
530+
);
531+
};
423532
}
424533

425534
/// `assert_always_some({a: x, b: y, ...})` is similar to `assert_always(x || y || ...)`, except:
@@ -428,9 +537,17 @@ macro_rules! assert_sometimes_less_than_or_equal_to {
428537
/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
429538
#[macro_export]
430539
macro_rules! assert_always_some {
431-
({$($name:ident: $cond:expr),*}, $message:literal, $details:expr) => {
432-
$crate::boolean_guidance_helper!($crate::assert_always, false, {$($name: $cond),*}, $message, $details);
433-
}
540+
({$($($name:ident: $cond:expr),+ $(,)?)?}, $message:literal$(, $details:expr)?) => {
541+
$crate::boolean_guidance_helper!($crate::assert_always, false, {$($($name: $cond),+)?}, $message$(, $details)?);
542+
};
543+
($($rest:tt)*) => {
544+
::std::compile_error!(
545+
r#"Invalid syntax when calling macro `assert_always_some`.
546+
Example usage:
547+
`assert_always_some!({field1: cond1, field2: cond2, ...}, "assertion message (static literal)", &details_json_value_expr)`
548+
"#
549+
);
550+
};
434551
}
435552

436553
/// `assert_sometimes_all({a: x, b: y, ...})` is similar to `assert_sometimes(x && y && ...)`, except:
@@ -439,7 +556,15 @@ macro_rules! assert_always_some {
439556
/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
440557
#[macro_export]
441558
macro_rules! assert_sometimes_all {
442-
({$($name:ident: $cond:expr),*}, $message:literal, $details:expr) => {
443-
$crate::boolean_guidance_helper!($crate::assert_sometimes, true, {$($name: $cond),*}, $message, $details);
444-
}
559+
({$($($name:ident: $cond:expr),+ $(,)?)?}, $message:literal$(, $details:expr)?) => {
560+
$crate::boolean_guidance_helper!($crate::assert_sometimes, true, {$($($name: $cond),+)?}, $message$(, $details)?);
561+
};
562+
($($rest:tt)*) => {
563+
::std::compile_error!(
564+
r#"Invalid syntax when calling macro `assert_sometimes_all`.
565+
Example usage:
566+
`assert_sometimes_all!({field1: cond1, field2: cond2, ...}, "assertion message (static literal)", &details_json_value_expr)`
567+
"#
568+
);
569+
};
445570
}

0 commit comments

Comments
 (0)