use std::mem::swap;
use std::fmt::Write;
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
thread_local! {
static CURRENT_RUNNER: RefCell<Runner> = RefCell::new(Runner::new());
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
struct Section {
name: &'static str,
file: &'static str,
line: u32,
}
#[derive(Clone, Copy)]
struct Entry {
should_enter: bool,
index: usize,
}
struct Runner {
is_running: bool,
queue: VecDeque<HashMap<Section, Entry>>,
current: HashMap<Section, Entry>,
new: Vec<Section>,
}
impl Runner {
fn new() -> Runner {
Runner {
is_running: false,
queue: vec![HashMap::new()].into(),
current: HashMap::new(),
new: vec![],
}
}
}
pub struct DropHandler {
pub is_top_level: bool,
pub was_success: bool,
}
impl Drop for DropHandler {
fn drop(&mut self) {
if !self.is_top_level {
return;
}
CURRENT_RUNNER.with(|r| {
r.borrow_mut().is_running = false;
if self.was_success {
let mut r = r.borrow_mut();
let mut new = vec![];
swap(&mut r.new, &mut new);
for section in &new {
let mut path = r.current.clone();
let count = r.current.values().filter(|x| x.should_enter).count();
for s in &new {
path.insert(*s, Entry {
should_enter: s == section,
index: count,
});
}
r.queue.push_back(path);
}
}
else {
let mut current: Vec<_> = r.borrow().current.iter()
.map(|(k, v)| (*k, *v))
.filter(|(_, v)| v.should_enter)
.collect();
current.sort_unstable_by(|a, b| a.1.index.cmp(&b.1.index));
if !current.is_empty() {
let mut buffer = "---- the failure was inside these sections ----\n".to_owned();
for (i, (section, _)) in current.iter().enumerate() {
writeln!(&mut buffer, "{: >3}) {:?} at {}:{}",
i, section.name, section.file, section.line).unwrap();
}
eprint!("{}", buffer);
}
}
});
}
}
pub fn enable_sections_start() -> bool {
CURRENT_RUNNER.with(|r| {
if r.borrow().is_running {
false
} else {
r.replace(Runner::new());
true
}
})
}
pub fn enable_sections_step() -> bool {
CURRENT_RUNNER.with(|r| {
let mut r = r.borrow_mut();
if let Some(current) = r.queue.pop_front() {
r.current = current;
r.new.clear();
r.is_running = true;
true
} else {
false
}
})
}
pub fn enter_section(name: &'static str, file: &'static str, line: u32) -> bool {
CURRENT_RUNNER.with(|r| {
let section = Section {name, file, line};
let should_enter = r.borrow().current.get(§ion).map(|x| x.should_enter);
should_enter.unwrap_or_else(|| {
r.borrow_mut().new.push(section);
false
})
})
}
pub fn is_running() -> bool {
CURRENT_RUNNER.with(|r| r.borrow().is_running)
}
#[macro_export]
macro_rules! enable_sections {
(
$(
$(#[$($attrs:tt)*])*
fn $name:ident() {
$($arg:tt)*
}
)*
) => {
$(
$(#[$($attrs)*])*
fn $name() {
let is_top_level = $crate::enable_sections_start();
loop {
if is_top_level && !$crate::enable_sections_step() {
break;
}
let mut scope = $crate::DropHandler {is_top_level, was_success: false};
$($arg)*
scope.was_success = true;
if !is_top_level {
break;
}
}
}
)*
}
}
#[macro_export]
macro_rules! section {
($name:expr) => {{
assert!($crate::is_running(), "{}", "\"section!(...)\" must be called from inside \"enable_sections! { ... }\"");
$crate::enter_section($name, file!(), line!())
}}
}