use std::collections::BTreeSet;
use crate::instrumentation::{
Capability, EventDelivery, EventKind, InstrumentFilter, InstrumentMode, InstrumentRegistration,
InstrumentationError, InstrumentationHub, ProjectionRequest, RuntimeBackend, TargetDescriptor,
TargetKind,
};
fn set<T: Ord>(values: impl IntoIterator<Item = T>) -> BTreeSet<T> {
values.into_iter().collect()
}
fn interpreter_target(id: &str, session: &str) -> TargetDescriptor {
TargetDescriptor {
target_id: id.into(),
session_id: session.into(),
kind: TargetKind::Interpreter,
backend: RuntimeBackend::new("rust").expect("test backend"),
capabilities: set([Capability::EventLifecycle]),
}
}
fn passive(id: &str, session: &str) -> InstrumentRegistration {
InstrumentRegistration {
instrument_id: id.into(),
session_id: session.into(),
mode: InstrumentMode::Passive,
capabilities: set([Capability::EventLifecycle]),
events: set([EventKind::ExecutionTerminal]),
filter: InstrumentFilter::default(),
projection: ProjectionRequest::default(),
delivery: EventDelivery::Queue { capacity: 8 },
}
}
fn control_reg(id: &str, session: &str) -> InstrumentRegistration {
InstrumentRegistration {
instrument_id: id.into(),
session_id: session.into(),
mode: InstrumentMode::Control,
capabilities: set([Capability::EventLifecycle, Capability::ControlPause]),
events: set([EventKind::ExecutionTerminal]),
filter: InstrumentFilter::default(),
projection: ProjectionRequest::default(),
delivery: EventDelivery::Queue { capacity: 8 },
}
}
#[test]
fn fresh_hub_has_zero_registered_state() {
let hub = InstrumentationHub::new();
assert_eq!(hub.registration_count(), 0);
assert_eq!(hub.target_count(), 0);
assert_eq!(hub.attachment_count(), 0);
assert!(hub.enabled_events().is_empty());
}
#[test]
fn attachments_follow_registration_order() {
let mut hub = InstrumentationHub::new();
let target = hub
.register_target(interpreter_target("t", "s"))
.expect("target");
let first = hub.register(passive("first", "s")).expect("first");
let second = hub.register(passive("second", "s")).expect("second");
hub.attach(&first, &target).expect("first attachment");
hub.attach(&second, &target).expect("second attachment");
let ids: Vec<&str> = hub
.attachments_for_target(&target)
.expect("live target")
.iter()
.map(|a| a.instrument.instrument_id())
.collect();
assert_eq!(ids, ["first", "second"]);
}
#[test]
fn unsupported_capability_produces_exact_evidence() {
let mut hub = InstrumentationHub::new();
let target = hub
.register_target(TargetDescriptor {
target_id: "execution".into(),
session_id: "s".into(),
kind: TargetKind::Interpreter,
backend: RuntimeBackend::new("rust").expect("test backend"),
capabilities: BTreeSet::new(),
})
.expect("target");
let trace = hub.register(passive("trace", "s")).expect("instrument");
let error = hub.attach(&trace, &target).expect_err("must fail");
assert!(
matches!(
error,
InstrumentationError::UnsupportedCapabilities {
ref target_id,
ref missing,
..
} if target_id == "execution" && missing.contains(&Capability::EventLifecycle)
),
"unexpected error: {error:?}"
);
}
#[test]
fn exclusive_control_lease_conflict() {
let mut hub = InstrumentationHub::new();
let target = hub
.register_target(TargetDescriptor {
target_id: "execution".into(),
session_id: "s".into(),
kind: TargetKind::Hbc,
backend: RuntimeBackend::new("rust").expect("test backend"),
capabilities: set([Capability::EventLifecycle, Capability::ControlPause]),
})
.expect("target");
let first = hub.register(control_reg("debugger-a", "s")).expect("first");
let second = hub
.register(control_reg("debugger-b", "s"))
.expect("second");
hub.attach(&first, &target).expect("first attach");
hub.attach(&second, &target).expect("second attach");
hub.acquire_control(&first, &target).expect("first lease");
let conflict = hub
.acquire_control(&second, &target)
.expect_err("second must conflict");
assert!(
matches!(
conflict,
InstrumentationError::ControlLeaseHeld {
ref holder,
..
} if holder == "debugger-a"
),
"unexpected error: {conflict:?}"
);
}
#[test]
fn stale_handle_after_id_reuse() {
let mut hub = InstrumentationHub::new();
let original = hub.register(passive("trace", "s")).expect("original");
hub.detach(&original).expect("detach original");
let replacement = hub.register(passive("trace", "s")).expect("replacement");
assert_eq!(original.generation(), 0);
assert_eq!(replacement.generation(), 1);
let stale = hub.detach(&original).expect_err("old handle must be stale");
assert!(
matches!(
stale,
InstrumentationError::StaleInstrumentHandle {
ref instrument_id,
generation: 0,
} if instrument_id == "trace"
),
"unexpected error: {stale:?}"
);
}
#[test]
fn session_cleanup_removes_all_state() {
let mut hub = InstrumentationHub::new();
let target = hub
.register_target(TargetDescriptor {
target_id: "execution".into(),
session_id: "s".into(),
kind: TargetKind::Hbc,
backend: RuntimeBackend::new("rust").expect("test backend"),
capabilities: set([Capability::EventLifecycle, Capability::ControlPause]),
})
.expect("target");
let ctrl = hub.register(control_reg("d", "s")).expect("controller");
hub.attach(&ctrl, &target).expect("attachment");
hub.acquire_control(&ctrl, &target).expect("lease");
assert_eq!(hub.registration_count(), 1);
assert_eq!(hub.target_count(), 1);
assert_eq!(hub.attachment_count(), 1);
hub.detach_session("s");
assert_eq!(hub.registration_count(), 0);
assert_eq!(hub.target_count(), 0);
assert_eq!(hub.attachment_count(), 0);
assert!(hub.enabled_events().is_empty());
}
#[test]
fn zero_attachment_produces_no_events() {
let mut hub = InstrumentationHub::new();
let target = hub
.register_target(interpreter_target("t", "s"))
.expect("target");
hub.register(passive("trace", "s"))
.expect("instrument without attachment");
assert_eq!(
hub.enabled_for_target(&target, EventKind::ExecutionTerminal),
Ok(false)
);
assert!(hub.enabled_events().is_empty());
}