use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, Mutex};
use crate::pal::PlatformFacade;
use crate::{ERR_POISONED_LOCK, Operation, OperationMetrics, Report};
#[derive(Debug)]
pub struct Session {
operations: Arc<Mutex<HashMap<String, Arc<Mutex<OperationMetrics>>>>>,
platform: PlatformFacade,
}
impl Session {
#[expect(
clippy::new_without_default,
reason = "to avoid ambiguity with the notion of a 'default session' that is not actually a default session"
)]
#[must_use]
pub fn new() -> Self {
Self {
operations: Arc::new(Mutex::new(HashMap::new())),
platform: PlatformFacade::real(),
}
}
#[cfg(test)]
pub(crate) fn with_platform(platform: PlatformFacade) -> Self {
Self {
operations: Arc::new(Mutex::new(HashMap::new())),
platform,
}
}
pub fn operation(&self, name: impl Into<String>) -> Operation {
let name = name.into();
let operation_data = {
let mut operations = self.operations.lock().expect(ERR_POISONED_LOCK);
Arc::clone(
operations
.entry(name.clone())
.or_insert_with(|| Arc::new(Mutex::new(OperationMetrics::default()))),
)
};
Operation::new(name, operation_data, self.platform.clone())
}
#[must_use]
pub fn to_report(&self) -> Report {
let operations_snapshot: HashMap<String, OperationMetrics> = self
.operations
.lock()
.expect(ERR_POISONED_LOCK)
.iter()
.map(|(name, data_ref)| {
(
name.clone(),
data_ref.lock().expect(ERR_POISONED_LOCK).clone(),
)
})
.collect();
Report::from_operation_data(&operations_snapshot)
}
#[cfg_attr(test, mutants::skip)] pub fn print_to_stdout(&self) {
self.to_report().print_to_stdout();
}
#[must_use]
pub fn is_empty(&self) -> bool {
let operations = self.operations.lock().expect(ERR_POISONED_LOCK);
operations.is_empty()
|| operations
.values()
.all(|data| data.lock().expect(ERR_POISONED_LOCK).total_iterations == 0)
}
}
impl fmt::Display for Session {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_report())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::pal::{FakePlatform, PlatformFacade};
fn create_test_session() -> Session {
let fake_platform = FakePlatform::new();
let platform_facade = PlatformFacade::fake(fake_platform);
Session::with_platform(platform_facade)
}
#[test]
fn is_empty_returns_true_for_no_operations() {
let session = create_test_session();
assert!(session.is_empty());
}
#[test]
fn is_empty_returns_true_for_operations_with_no_spans() {
let session = create_test_session();
let _operation1 = session.operation("test1");
let _operation2 = session.operation("test2");
assert!(session.is_empty());
}
#[test]
fn is_empty_returns_false_for_operations_with_spans() {
let session = create_test_session();
{
let operation = session.operation("test");
{
let _span = operation.measure_thread();
}
}
assert!(!session.is_empty());
}
#[test]
fn is_empty_mixed_operations_some_with_spans() {
let session = create_test_session();
let _operation1 = session.operation("no_spans1");
let _operation2 = session.operation("no_spans2");
{
let operation_with_spans = session.operation("with_spans");
{
let _span = operation_with_spans.measure_process();
}
}
assert!(!session.is_empty());
}
#[test]
fn is_empty_multiple_operations_all_empty() {
let session = create_test_session();
for i in 0..5 {
let _operation = session.operation(format!("test_{i}"));
}
assert!(session.is_empty());
}
#[test]
fn is_empty_multiple_operations_all_with_spans() {
let session = create_test_session();
for i in 0..3 {
let operation = session.operation(format!("test_{i}"));
let _span = operation.measure_thread();
}
assert!(!session.is_empty());
}
#[test]
fn report_is_empty_matches_session_is_empty() {
let session = create_test_session();
let report = session.to_report();
assert_eq!(session.is_empty(), report.is_empty());
assert!(session.is_empty());
assert!(report.is_empty());
let _operation = session.operation("test");
let report = session.to_report();
assert_eq!(session.is_empty(), report.is_empty());
assert!(session.is_empty());
assert!(report.is_empty());
{
let operation = session.operation("test_with_spans");
let _span = operation.measure_thread();
}
let report = session.to_report();
assert_eq!(session.is_empty(), report.is_empty());
assert!(!session.is_empty());
assert!(!report.is_empty());
}
use std::panic::RefUnwindSafe;
use std::panic::UnwindSafe;
static_assertions::assert_impl_all!(Session: Send, Sync);
static_assertions::assert_impl_all!(Session: UnwindSafe, RefUnwindSafe);
#[test]
fn session_display_includes_operation_name() {
let session = create_test_session();
{
let operation = session.operation("display_test_operation");
let _span = operation.measure_thread();
}
let display_output = session.to_string();
assert!(display_output.contains("display_test_operation"));
}
}