use std::cell::Cell;
use std::fmt;
use std::time::{Duration, Instant};
thread_local! {
static INDENT_LEVEL: Cell<usize> = const { Cell::new(0) };
}
#[derive(Debug, Clone, PartialEq)]
pub struct ScopeError(pub &'static str);
pub struct ScopeGuard {
name: &'static str,
start: Instant,
indent: usize,
exited: bool,
}
impl ScopeGuard {
pub fn enter(name: &'static str) -> Self {
let indent = INDENT_LEVEL.with(|lvl| {
let current = lvl.get();
lvl.set(current + 1);
current + 1
});
let start = Instant::now();
ScopeGuard {
name,
start,
indent,
exited: false,
}
}
pub fn elapsed(&self) -> Duration {
self.start.elapsed()
}
pub fn indent_level() -> usize {
INDENT_LEVEL.with(|lvl| lvl.get())
}
pub fn exit(mut self) {
self.exited = true;
}
}
impl Drop for ScopeGuard {
fn drop(&mut self) {
INDENT_LEVEL.with(|lvl| {
let current = lvl.get();
if current > 0 {
lvl.set(current - 1);
}
});
if !self.exited {
self.exited = true;
}
}
}
impl fmt::Debug for ScopeGuard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ScopeGuard")
.field("name", &self.name)
.field("indent", &self.indent)
.field("elapsed", &self.elapsed())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::panic;
use std::thread;
use std::time::Duration;
#[test]
fn test_scope_basic_timing() {
let start = Instant::now();
let guard = ScopeGuard::enter("test_scope");
thread::sleep(Duration::from_millis(10));
let elapsed = guard.elapsed();
assert!(elapsed >= Duration::from_millis(10));
drop(guard);
let total = start.elapsed();
assert!(total >= Duration::from_millis(10));
}
#[test]
fn test_scope_indentation() {
assert_eq!(ScopeGuard::indent_level(), 0);
{
let _g1 = ScopeGuard::enter("outer");
assert_eq!(ScopeGuard::indent_level(), 1);
{
let _g2 = ScopeGuard::enter("inner");
assert_eq!(ScopeGuard::indent_level(), 2);
}
assert_eq!(ScopeGuard::indent_level(), 1);
}
assert_eq!(ScopeGuard::indent_level(), 0);
}
#[test]
fn test_scope_exit_explicit() {
let guard = ScopeGuard::enter("explicit_exit");
guard.exit();
assert_eq!(ScopeGuard::indent_level(), 0);
}
#[test]
fn test_scope_panic_handling() {
let result = panic::catch_unwind(|| {
let _guard = ScopeGuard::enter("panic_scope");
panic!("test panic");
});
assert!(result.is_err());
assert_eq!(ScopeGuard::indent_level(), 0);
}
}