nobug 0.7.0

Assertions and active code annotations
Documentation
#[allow(unused_imports)]
use crate::*;

/// Aborts the program with a message.
///
/// Takes the same arguments as [`CRITICAL!()`].
///
/// # Semantics
///
/// * Will print a critical message to stderr.
/// * Will abort the program, except when running tests in debug builds, then it will panic.
///
/// Panicking in tests is necessary because the testsuite runs the tests as threads. We do
/// not want to abort the whole test suite when a test fails.
///
/// # Example
///
/// ```rust,ignore
/// # use nobug::DIE;
/// DIE!("this is a test {}", 1 + 2);
/// DIE!("this is a test");
/// DIE!();
/// ```
#[macro_export]
macro_rules! DIE {
    (($($fmt:tt)*) $(,$($args:expr),*)?) => {{
        $crate::TRACE_NOBUG!(($($fmt)*) $(,$($args),*)?);
        $crate::CFG_IF! {
            if #[cfg(all(not(feature = "abort_on_fail"), not(any(test, debug_assertions))))] {
                compile_error!("abort_on_fail is not enabled");
            } else if #[cfg(debug_assertions)] {
                if $crate::get_testing() {
                    panic!(concat!(file!(), ":", line!(), ": ", $($fmt)*) $(,$($args),*)?)
                }
            }
        }
        $crate::CRITICAL!(($($fmt)*) $(,$($args),*)?);
        $crate::CFG_IF! {
            if #[cfg(feature = "backtrace_on_abort")] {
                eprintln!("{:?}", std::backtrace::Backtrace::force_capture());
            }
        };
        std::process::abort()
    }};
    ($fmt:literal $(,$($args:expr),*)?) => {{
        $crate::TRACE_NOBUG!(($fmt) $(,$($args),*)?);
        $crate::CFG_IF! {
            if #[cfg(all(not(feature = "abort_on_fail"), not(any(test, debug_assertions))))] {
                compile_error!("abort_on_fail is not enabled");
            } else if #[cfg(debug_assertions)] {
                if $crate::get_testing() {
                    panic!(concat!($fmt) $(,$($args),*)?)
                }
            }
        }
        $crate::CRITICAL!(($fmt) $(,$($args),*)?);
        $crate::CFG_IF! {
            if #[cfg(feature = "backtrace_on_abort")] {
                eprintln!("{:?}", std::backtrace::Backtrace::force_capture());
            }
        };
        std::process::abort()
    }};
    () => {{
        $crate::TRACE_NOBUG!("EXPLICIT DIE");
        $crate::CFG_IF! {
            if #[cfg(all(not(feature = "abort_on_fail"), not(any(test, debug_assertions))))] {
                compile_error!("abort_on_fail is not enabled");
            } else if #[cfg(debug_assertions)] {
                if $crate::get_testing() {
                    panic!(concat!(file!(), ":", line!(), ": EXPLICIT DIE"))
                }
            }
        }
        $crate::CRITICAL!("EXPLICIT DIE");
        $crate::CFG_IF! {
            if #[cfg(feature = "backtrace_on_abort")] {
                eprintln!("{:?}", std::backtrace::Backtrace::force_capture());
            }
        };
        std::process::abort()
    }}
}

#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "this is a test")]
fn test_die() {
    DIE!(("this is a test"));
}

#[cfg(test)]
#[cfg(debug_assertions)]
fn testfn() {
    DIE!(("this is a test"));
}

#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "this is a test")]
fn test_diefn() {
    testfn();
}

#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "this is a test 123")]
fn test_die2() {
    DIE!("this is a test {}{}{}", 1, 2, 3);
}

/// Calls some code only once on first call.
///
/// # Example
///
/// ```rust
/// # use nobug::*;
/// ONCE! {
///     println!("This will be printed only once.");
/// }
/// ```
#[macro_export]
macro_rules! ONCE {
    ($($code:tt)*) => {{
        static ONCE: std::sync::Once = std::sync::Once::new();
        ONCE.call_once(|| {$($code)*});
    }};
}

/// Mock some result/side-effect in debug builds.
///
/// Sometimes incomplete code can be tested by mocking a result or side-effect.
/// Will be a `compile_error()` in release mode.
///
/// This macro can be used in 2 forms:
/// 1. With some code. Will call this code unconditionally and return its result.
/// 2. With an `IF` and `THEN` block and optional `ELSE` block.
///    * Will call the `THEN` block if the `IF` block returns `true`.
///    * Will call the `ELSE` block if the `IF` block returns `false`.
///
/// # Examples
///
/// ```rust
/// # use nobug::*;
/// # CFG_IF! { if #[cfg(debug_assertions)] {
/// let r = MOCK!{true};
/// CHECK!(r);
/// let r = MOCK!{false};
/// CHECK!(!r);
/// let r = MOCK!(IF {true} THEN {true} ELSE {false});
/// CHECK!(r);
/// let r = MOCK!(IF {false} THEN {true} ELSE {false});
/// CHECK!(!r);
/// # }}
/// ```
#[macro_export]
macro_rules! MOCK {
    (IF {$($pred:tt)*} THEN {$($then:tt)*} $(ELSE {$($else:tt)*})?) => {{
        $crate::TRACE_NOBUG!();
        $crate::CFG_IF! {
            if #[cfg(debug_assertions)] {
                if {$($pred)*} {
                    $crate::NOTICE!(
                        ("MOCK: {{{}}}"),
                        stringify!($($then)*)
                    );
                    {$($then)*}
                } $(else {
                    $crate::NOTICE!(
                        ("MOCK: {{{}}}"),
                        stringify!($($else)*)
                    );
                    {$($else)*}
                })?
            } else {
                compile_error!(
                    concat!(
                        "MOCK: {", stringify!($($then)*), "}"
                        $(, " {", stringify!($($else)*), "}")?
                    )
                )
            }
        }
    }};
    ($($code:tt)*) => {{
        $crate::TRACE_NOBUG!();
        $crate::CFG_IF! {
            if #[cfg(debug_assertions)] {
                $crate::NOTICE!(
                    ("MOCK: {{{}}}"),
                    stringify!($($code)*)
                );
                {$($code)*}
            } else {
                compile_error!(
                    concat!(
                        "MOCK: {", stringify!($($code)*), "}"
                    )
                )
            }
        }
    }};
}

#[test]
#[cfg(debug_assertions)]
fn test_mock_simple() {
    CHECK!(MOCK! {true});
    CHECK!(!MOCK! {false});
}

#[test]
#[cfg(debug_assertions)]
fn test_mock_conditional() {
    let mut r: bool = false;
    MOCK!(IF {true} THEN {r = true;});
    CHECK!(r);
    CHECK!(MOCK!(IF {false} THEN {false} ELSE {true}));
}