use std::collections::HashSet;
use std::num::NonZeroUsize;
use std::time::Duration;
use frame_core::event::{LifecycleEventKind, LifecycleState};
use frame_core::runtime::RuntimePolicy;
use frame_host::manifest;
use frame_host::runtime::HostRuntime;
use frame_host::spec::ComponentInstall;
const EVENT_DEADLINE: Duration = Duration::from_secs(10);
const TEST_EVENT_BUFFER: usize = 64;
const fn test_policy() -> RuntimePolicy {
RuntimePolicy {
scheduler_threads: 2,
operation_timeout: Duration::from_secs(3),
max_fragment_bytes: None,
}
}
#[test]
fn boot_emits_lifecycle_events_and_shutdown_removes_cleanly()
-> Result<(), Box<dyn std::error::Error>> {
let mut runtime = HostRuntime::new(test_policy())?;
let subscription = runtime
.registry()
.subscribe(NonZeroUsize::new(TEST_EVENT_BUFFER).ok_or("zero buffer")?)?;
let logger = runtime.spawn_event_logger(HashSet::from([manifest::component_id()]))?;
let statuses = runtime.install(vec![ComponentInstall {
meta: manifest::component_meta(),
bytecode: manifest::PRESENCE_BYTECODE.to_vec(),
support_modules: Vec::new(),
}])?;
assert_eq!(statuses.len(), 1);
let status = &statuses[0];
assert_eq!(status.state, LifecycleState::Running);
assert_eq!(status.children.len(), 1);
assert_eq!(status.children[0].name, manifest::PRESENCE_CHILD);
assert!(status.failure.is_none());
assert_eq!(status.id, manifest::component_id());
let grants = runtime
.registry()
.host_capabilities()
.grants_for(manifest::component_id())?;
assert!(grants.is_empty());
let id = manifest::component_id();
assert_eq!(
runtime
.registry()
.probe_child(id, manifest::PRESENCE_CHILD)?,
0
);
runtime
.registry()
.send_child(id, manifest::PRESENCE_CHILD, manifest::NEWS_MESSAGE)?;
assert_eq!(
runtime
.registry()
.probe_child(id, manifest::PRESENCE_CHILD)?,
1
);
let boot_transitions = drain_transitions(&subscription, LifecycleState::Running)?;
assert_eq!(
boot_transitions,
vec![
(None, LifecycleState::Registered),
(Some(LifecycleState::Registered), LifecycleState::Starting),
(Some(LifecycleState::Starting), LifecycleState::Running),
]
);
runtime.shutdown(logger)?;
let teardown_transitions = drain_transitions(&subscription, LifecycleState::Removed)?;
assert_eq!(
teardown_transitions,
vec![
(Some(LifecycleState::Running), LifecycleState::Stopping),
(Some(LifecycleState::Stopping), LifecycleState::Stopped),
(Some(LifecycleState::Stopped), LifecycleState::Removed),
]
);
Ok(())
}
type Transition = (Option<LifecycleState>, LifecycleState);
fn drain_transitions(
subscription: &frame_core::event::LifecycleSubscription,
until: LifecycleState,
) -> Result<Vec<Transition>, Box<dyn std::error::Error>> {
let mut transitions = Vec::new();
loop {
let event = subscription.recv_timeout(EVENT_DEADLINE)?;
if event.component_id != manifest::component_id() {
continue;
}
if let LifecycleEventKind::Transition { from, to } = event.kind {
transitions.push((from, to));
if to == until {
return Ok(transitions);
}
}
}
}