use super::*;
use anyhow::{Result, bail};
use std::sync::Mutex as StdMutex;
struct RecordingSessionObserver(StdMutex<Vec<crate::runtime_state::SessionLifecycleEvent>>);
impl Default for RecordingSessionObserver {
fn default() -> Self {
Self(StdMutex::new(Vec::new()))
}
}
impl crate::runtime_state::SessionLifecycleObserver for RecordingSessionObserver {
fn observe(&self, event: crate::runtime_state::SessionLifecycleEvent) {
self.0.lock().unwrap().push(event);
}
}
#[test]
fn admitted_runtime_preserves_session_lifecycle_observer() -> Result<()> {
let observer = Arc::new(RecordingSessionObserver::default());
let runtime = crate::runtime_state::runtime_from_loaded_model(
&skippy_protocol::StageConfig::default(),
skippy_runtime::StageModel::new_dummy(),
Some(observer.clone()),
)?;
runtime
.lock()
.unwrap()
.notify_session_lifecycle(crate::runtime_state::SessionLifecycleEvent::SessionReclaimed);
assert_eq!(
*observer.0.lock().unwrap(),
vec![crate::runtime_state::SessionLifecycleEvent::SessionReclaimed,]
);
Ok(())
}
#[test]
fn dropping_an_absent_session_notifies_nothing() {
let observer = Arc::new(RecordingSessionObserver::default());
let runtime =
RuntimeState::new_modelless_for_test(1).with_session_lifecycle_observer(observer.clone());
let mut runtime = runtime;
let stats = runtime.drop_session_timed("never-existed").unwrap();
assert!(!stats.reset_session);
assert!(!stats.lane_discarded);
assert!(
observer.0.lock().unwrap().is_empty(),
"no real reset/reclaim decision was made, so nothing should be notified"
);
}
#[test]
fn system_one_advertisement_requires_a_single_unsplit_runtime_lane() {
assert!(system_one_endpoint_is_runnable(true, false, false, 1));
assert!(!system_one_endpoint_is_runnable(true, false, false, 4));
assert!(!system_one_endpoint_is_runnable(true, true, false, 1));
assert!(!system_one_endpoint_is_runnable(true, false, true, 1));
assert!(!system_one_endpoint_is_runnable(false, false, false, 1));
}
#[test]
fn notify_wiring_reaches_the_attached_observer() {
let observer = Arc::new(RecordingSessionObserver::default());
let runtime =
RuntimeState::new_modelless_for_test(1).with_session_lifecycle_observer(observer.clone());
runtime.notify_session_lifecycle(crate::runtime_state::SessionLifecycleEvent::SessionReset {
reset_ms: 1.5,
});
runtime.notify_session_lifecycle(crate::runtime_state::SessionLifecycleEvent::SessionReclaimed);
assert_eq!(
*observer.0.lock().unwrap(),
vec![
crate::runtime_state::SessionLifecycleEvent::SessionReset { reset_ms: 1.5 },
crate::runtime_state::SessionLifecycleEvent::SessionReclaimed,
]
);
}
#[test]
fn create_indexed_lane_resource_keeps_index_available_when_creation_fails() {
let mut next_lane_index = 0;
let mut free_lane_indices: Vec<usize> = Vec::new();
let error = create_indexed_lane_resource(
&mut next_lane_index,
&mut free_lane_indices,
2,
|| -> Result<()> { bail!("transient session creation failure") },
)
.expect_err("failed creation should propagate the original error");
assert_eq!(error.to_string(), "transient session creation failure");
assert_eq!(next_lane_index, 0);
assert!(free_lane_indices.is_empty());
let (index, resource) =
create_indexed_lane_resource(&mut next_lane_index, &mut free_lane_indices, 2, || {
Ok("lane")
})
.expect("successful retry should reuse the unconsumed lane index");
assert_eq!(index, 0);
assert_eq!(resource, "lane");
assert_eq!(next_lane_index, 1);
}
#[test]
fn create_indexed_lane_resource_reuses_freed_indices_before_growing() {
let mut next_lane_index = 0;
let mut free_lane_indices: Vec<usize> = Vec::new();
let lane_count = 2;
let (a_idx, _) = create_indexed_lane_resource(
&mut next_lane_index,
&mut free_lane_indices,
lane_count,
|| Ok("a"),
)
.expect("first allocation should succeed");
let (b_idx, _) = create_indexed_lane_resource(
&mut next_lane_index,
&mut free_lane_indices,
lane_count,
|| Ok("b"),
)
.expect("second allocation should succeed");
assert_eq!(a_idx, 0);
assert_eq!(b_idx, 1);
assert_eq!(next_lane_index, 2);
let error = create_indexed_lane_resource(
&mut next_lane_index,
&mut free_lane_indices,
lane_count,
|| Ok("c"),
)
.expect_err("allocating past lane_count should fail when no slots are free");
assert!(error.to_string().contains("all execution lanes are busy"));
free_lane_indices.push(a_idx);
let (reused_idx, _) = create_indexed_lane_resource(
&mut next_lane_index,
&mut free_lane_indices,
lane_count,
|| Ok("c"),
)
.expect("allocation must reuse a freed index, not stay wedged");
assert_eq!(reused_idx, 0);
assert_eq!(next_lane_index, 2);
assert!(free_lane_indices.is_empty());
}
#[test]
fn create_indexed_lane_resource_returns_freed_index_on_create_failure() {
let mut next_lane_index = 1;
let mut free_lane_indices: Vec<usize> = vec![0];
let error = create_indexed_lane_resource(
&mut next_lane_index,
&mut free_lane_indices,
2,
|| -> Result<()> { bail!("create failed mid-reuse") },
)
.expect_err("failed creation should propagate");
assert_eq!(error.to_string(), "create failed mid-reuse");
assert_eq!(next_lane_index, 1);
assert_eq!(free_lane_indices, vec![0]);
let (idx, _) =
create_indexed_lane_resource(&mut next_lane_index, &mut free_lane_indices, 2, || {
Ok("retry")
})
.expect("retry should succeed");
assert_eq!(idx, 0);
assert_eq!(next_lane_index, 1);
assert!(free_lane_indices.is_empty());
}
#[test]
fn capped_target_idle_sessions_clamps_to_the_configured_bound() {
assert_eq!(capped_target_idle_sessions(10, Some(2)), 2);
assert_eq!(capped_target_idle_sessions(1, Some(2)), 1);
}
#[test]
fn capped_target_idle_sessions_is_unbounded_when_unset() {
assert_eq!(capped_target_idle_sessions(10, None), 10);
}
#[test]
fn disabling_idle_sessions_caps_future_retention_at_zero() {
let mut runtime = RuntimeState::new_modelless_for_test(1);
runtime.max_idle_sessions = Some(4);
runtime.disable_idle_sessions();
assert_eq!(runtime.max_idle_sessions, Some(0));
assert!(runtime.idle_sessions.is_empty());
assert_eq!(
capped_target_idle_sessions(10, runtime.max_idle_sessions),
0
);
}