1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// 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 in the LICENSE file at the
// root of this repository, or online 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.

//! Assertion utilities.
//!
//! # Soft assertions
//!
//! Soft assertions are like debug assertions, but they can be toggled on and
//! off at runtime in a release build.
//!
//! They are useful in two scenarios:
//!
//!   * When a failed assertion should result in a log message rather a process
//!     crash.
//!
//!   * When evaluating the condition is too expensive to evaluate in production
//!     deployments.
//!
//! When soft assertions are disabled, the performance cost of each assertion is
//! one branch on an atomic.
//!
//! Ore provides the following macros to make soft assertions:
//!
//!   * [`soft_assert`](crate::soft_assert)
//!   * [`soft_assert_eq`](crate::soft_assert_eq)
//!   * [`soft_assert_or_log`](crate::soft_assert_or_log)
//!   * [`soft_panic_or_log`](crate::soft_panic_or_log)
//!
//! Due to limitations in Rust, these macros are exported at the crate root.

use std::sync::atomic::AtomicBool;

use crate::env;

/// Whether to enable soft assertions.
///
/// This value defaults to `true` when `debug_assertions` are enabled, or to the
/// value of the `MZ_SOFT_ASSERTIONS` environment variable otherwise.
// The rules about what you can do in a `ctor` function are somewhat fuzzy,
// because Rust does not explicitly support constructors. But a scan of the
// stdlib suggests that reading environment variables is safe enough.
#[ctor::ctor]
pub static SOFT_ASSERTIONS: AtomicBool = {
    let default = cfg!(debug_assertions) || env::is_var_truthy("MZ_SOFT_ASSERTIONS");
    AtomicBool::new(default)
};

/// Asserts that a condition is true if soft assertions are enabled.
///
/// Soft assertions have a small runtime cost even when disabled. See
/// [`ore::assert`](crate::assert#Soft-assertions) for details.
#[macro_export]
macro_rules! soft_assert {
    ($cond:expr $(, $($arg:tt)+)?) => {{
        if $crate::assert::SOFT_ASSERTIONS.load(::std::sync::atomic::Ordering::Relaxed) {
            assert!($cond$(, $($arg)+)?);
        }
    }}
}

/// Asserts that two values are equal if soft assertions are enabled.
///
/// Soft assertions have a small runtime cost even when disabled. See
/// [`ore::assert`](crate::assert#Soft-assertions) for details.
#[macro_export]
macro_rules! soft_assert_eq {
    ($cond:expr, $($arg:tt)+) => {{
        if $crate::assert::SOFT_ASSERTIONS.load(::std::sync::atomic::Ordering::Relaxed) {
            assert_eq!($cond, $($arg)+);
        }
    }}
}

/// Asserts that a condition is true if soft assertions are enabled, or logs
/// an error if soft assertions are disabled and the condition is false.
#[macro_export]
macro_rules! soft_assert_or_log {
    ($cond:expr, $($arg:tt)+) => {{
        if $crate::assert::SOFT_ASSERTIONS.load(::std::sync::atomic::Ordering::Relaxed) {
            assert!($cond, $($arg)+);
        } else if !$cond {
            ::tracing::error!($($arg)+)
        }
    }}
}

/// Panics if soft assertions are enabled, or logs an error if soft
/// assertions are disabled.
#[macro_export]
macro_rules! soft_panic_or_log {
    ($($arg:tt)+) => {{
        if $crate::assert::SOFT_ASSERTIONS.load(::std::sync::atomic::Ordering::Relaxed) {
            panic!($($arg)+);
        } else {
            ::tracing::error!($($arg)+)
        }
    }}
}

/// Asserts that the left expression contains the right expression.
///
/// Containment is determined by the `contains` method on the left type. If the
/// left expression does not contain the right expression, the macro will panic
/// with a descriptive message that includes both the left and right
/// expressions.
///
/// # Motivation
///
/// The standard pattern for asserting containment uses the [`assert!`] macro
///
/// ```
/// # let left = &[()];
/// # let right = ();
/// assert!(left.contains(&right))
/// ```
///
/// but this pattern panics with a message that only displays `false` as the
/// cause. This hampers determination of the true cause of the assertion
/// failure.
///
/// # Examples
///
/// Check whether a string contains a substring:
///
/// ```
/// use ore::assert_contains;
/// assert_contains!("hello", "ello");
/// ```
///
/// Check whether a slice contains an element:
///
/// ```
/// use ore::assert_contains;
/// assert_contains!(&[1, 2, 3], 2);
/// ```
///
/// Failed assertions panic:
///
/// ```should_panic
/// use ore::assert_contains;
/// assert_contains!("hello", "yellow");
/// ```
#[macro_export]
macro_rules! assert_contains {
    ($left:expr, $right:expr $(,)?) => {{
        let left = $left;
        let right = $right;
        if !left.contains(&$right) {
            panic!(
                r#"assertion failed: `left.contains(right)`:
  left: `{:?}`
 right: `{:?}`"#,
                left, right
            );
        }
    }};
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_assert_contains_str() {
        assert_contains!("hello", "ello");
    }

    #[test]
    fn test_assert_contains_slice() {
        assert_contains!(&[1, 2, 3], 2);
    }

    #[test]
    #[should_panic(expected = "assertion failed: `left.contains(right)`:
  left: `\"hello\"`
 right: `\"yellow\"`")]
    fn test_assert_contains_fail() {
        assert_contains!("hello", "yellow");
    }
}