#![deny(rustdoc::broken_intra_doc_links)]
#![allow(clippy::test_attr_in_doctest)]
#[macro_export]
macro_rules! hit {
($ident:ident) => {
$crate::__rt::hit(stringify!($ident))
};
}
#[macro_export]
macro_rules! check {
($ident:ident) => {
let _guard = $crate::__rt::Guard::new(stringify!($ident), None, $crate::assert!());
};
}
#[macro_export]
macro_rules! check_count {
($ident:ident, $count: literal) => {
let _guard = $crate::__rt::Guard::new(stringify!($ident), Some($count), $crate::assert!());
};
}
pub fn survey() -> __rt::SurveyGuard {
__rt::SurveyGuard::new()
}
#[doc(hidden)]
#[macro_export]
macro_rules! assert {
() => {
|expected_hits, hit_count, mark| match expected_hits {
Some(hits) => assert!(
hit_count == hits,
"mark {mark} was hit {hit_count} times, expected {hits}"
),
None => assert!(hit_count > 0, "mark {mark} was not hit"),
}
};
}
#[doc(hidden)]
pub type AssertCallback = fn(Option<usize>, usize, &'static str);
#[doc(hidden)]
#[cfg(feature = "enable")]
pub mod __rt {
use std::{
cell::RefCell,
sync::atomic::{AtomicUsize, Ordering::Relaxed},
};
use super::AssertCallback;
static LEVEL: AtomicUsize = AtomicUsize::new(0);
const SURVEY_LEVEL: usize = !(usize::MAX >> 1);
thread_local! {
static ACTIVE: RefCell<Vec<GuardInner>> = const { RefCell::new(Vec::new()) };
static SURVEY_RESPONSE: RefCell<Vec<GuardInner>> = const { RefCell::new(Vec::new()) };
}
#[inline(always)]
pub fn hit(key: &'static str) {
let level = LEVEL.load(Relaxed);
if level > 0 {
if level >= SURVEY_LEVEL {
add_to_survey(key);
}
hit_cold(key);
}
#[cold]
fn hit_cold(key: &'static str) {
ACTIVE.with(|it| it.borrow_mut().iter_mut().for_each(|g| g.hit(key)))
}
#[cold]
fn add_to_survey(mark: &'static str) {
SURVEY_RESPONSE.with(|it| {
let mut it = it.borrow_mut();
for survey in it.iter_mut() {
if survey.mark == mark {
survey.hits = survey.hits.saturating_add(1);
return;
}
}
it.push(GuardInner {
mark,
hits: 1,
expected_hits: None,
});
});
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct GuardInner {
mark: &'static str,
hits: usize,
expected_hits: Option<usize>,
}
pub struct Guard {
mark: &'static str,
f: AssertCallback,
}
impl GuardInner {
fn hit(&mut self, key: &'static str) {
if key == self.mark {
self.hits = self.hits.saturating_add(1);
}
}
}
impl Guard {
pub fn new(mark: &'static str, expected_hits: Option<usize>, f: AssertCallback) -> Guard {
LEVEL.fetch_add(1, Relaxed);
ACTIVE.with(|it| {
it.borrow_mut().push(GuardInner {
mark,
hits: 0,
expected_hits,
})
});
Guard { mark, f }
}
}
impl Drop for Guard {
fn drop(&mut self) {
LEVEL.fetch_sub(1, Relaxed);
let last = ACTIVE.with(|it| it.borrow_mut().pop());
if std::thread::panicking() {
return;
}
let last = last.unwrap();
assert_eq!(last.mark, self.mark);
let hit_count = last.hits;
(self.f)(last.expected_hits, hit_count, self.mark)
}
}
pub struct SurveyGuard;
impl SurveyGuard {
#[allow(clippy::new_without_default)]
pub fn new() -> SurveyGuard {
LEVEL.fetch_or(SURVEY_LEVEL, Relaxed);
SurveyGuard
}
}
impl Drop for SurveyGuard {
fn drop(&mut self) {
LEVEL.fetch_and(!SURVEY_LEVEL, Relaxed);
if std::thread::panicking() {
return;
}
SURVEY_RESPONSE.with(|it| {
let mut it = it.borrow_mut();
it.sort();
for g in it.iter() {
let hit_count = g.hits;
if hit_count == 1 {
eprintln!("mark {} ... hit once", g.mark);
} else if 1 < hit_count {
eprintln!("mark {} ... hit {} times", g.mark, hit_count);
}
}
it.clear();
});
}
}
}
#[doc(hidden)]
#[cfg(not(feature = "enable"))]
pub mod __rt {
#[inline(always)]
pub fn hit(_: &'static str) {}
#[non_exhaustive]
pub struct Guard;
impl Guard {
pub fn new(_: &'static str, _: Option<usize>, _: super::AssertCallback) -> Guard {
Guard
}
}
pub struct SurveyGuard;
impl SurveyGuard {
#[allow(clippy::new_without_default)]
pub fn new() -> SurveyGuard {
SurveyGuard
}
}
}