use std::sync::Arc;
use std::time::Duration;
use beamr::module::{ModuleRegistry, ResolvedImportTarget};
use beamr::namespace::NamespaceId;
use beamr::scheduler::{SchedulerConfig, SchedulerServices};
use frame_core::component::{
ChildArgument, ChildSpec, ComponentId, ComponentMeta, ServiceCapability, SupervisionPolicy,
};
use frame_core::event::LifecycleState;
use frame_core::registry::ComponentRegistry;
use frame_core::supervision::LifecycleConfig;
const FAILING_VARIANT_BEAM: &[u8] = include_bytes!("fixtures/gcbif/graph_view_presence.beam");
const CHILD: &str = "presence";
const LIVENESS_MESSAGE: i64 = 0;
const STOP_MESSAGE: i64 = 2;
const OPERATION_TIMEOUT: Duration = Duration::from_secs(3);
const SCHEDULER_THREADS: usize = 2;
fn component_id() -> ComponentId {
ComponentId::derive("frame.tests.bif-composition", "gcbif-regression")
}
fn meta() -> ComponentMeta {
ComponentMeta {
id: component_id(),
name: "gcbif regression witness (attribution failing-variant bytecode)".to_owned(),
version: "0.1.0".to_owned(),
requires: Vec::new(),
provides: vec![ServiceCapability {
id: "frame.tests:gcbif-regression@v1".to_owned(),
description: "real-arithmetic child over the once-failing bytecode".to_owned(),
}],
needs: Vec::new(),
actions: Vec::new(),
fragments: Vec::new(),
children: vec![ChildSpec {
name: CHILD.to_owned(),
module: "graph_view_presence".to_owned(),
function: "run".to_owned(),
arguments: vec![ChildArgument::SupervisorPid],
liveness_message: LIVENESS_MESSAGE,
stop_message: STOP_MESSAGE,
}],
supervision: Some(SupervisionPolicy {
max_restarts: 3,
window: Duration::from_secs(30),
}),
}
}
#[test]
fn real_arithmetic_child_survives_news_on_the_once_failing_bytecode()
-> Result<(), Box<dyn std::error::Error>> {
let scheduler = Arc::new(frame_core::composition::compose_scheduler(
SchedulerConfig {
thread_count: Some(SCHEDULER_THREADS),
..SchedulerConfig::default()
},
SchedulerServices::minimal(),
Arc::new(ModuleRegistry::new()),
)?);
let registry = ComponentRegistry::new(
Arc::clone(&scheduler),
LifecycleConfig {
operation_timeout: OPERATION_TIMEOUT,
max_fragment_bytes: None,
},
);
let id = component_id();
registry.register(meta(), FAILING_VARIANT_BEAM.to_vec())?;
registry.start(id)?;
let module_atom = scheduler.atom_table().intern("graph_view_presence");
let committed = scheduler
.lookup_module_in(NamespaceId::DEFAULT, module_atom)
.ok_or("committed module missing after start")?;
let erlang = scheduler.atom_table().intern("erlang");
let deferred_erlang: Vec<_> = committed
.resolved_imports
.iter()
.filter(|import| {
import.module == erlang
&& matches!(import.target, ResolvedImportTarget::Deferred { .. })
})
.collect();
assert!(
deferred_erlang.is_empty(),
"load left erlang:* imports deferred: {deferred_erlang:?}"
);
let pid_before = child_pid(®istry)?;
assert_eq!(registry.probe_child(id, CHILD)?, 0);
registry.send_child(id, CHILD, 41)?;
assert_eq!(registry.probe_child(id, CHILD)?, 1, "first news must count");
registry.send_child(id, CHILD, 43)?;
assert_eq!(
registry.probe_child(id, CHILD)?,
2,
"second news must count"
);
assert_eq!(
child_pid(®istry)?,
pid_before,
"the child must survive arithmetic — a pid change means supervision replaced a dead child"
);
let status = registry
.status(id)?
.ok_or("component status missing after news")?;
assert_eq!(status.state, LifecycleState::Running);
assert!(status.failure.is_none());
registry.remove(id)?;
assert_eq!(scheduler.process_count(), 0);
scheduler.shutdown();
Ok(())
}
fn child_pid(registry: &ComponentRegistry) -> Result<u64, Box<dyn std::error::Error>> {
Ok(registry
.status(component_id())?
.ok_or("component status missing")?
.children
.first()
.ok_or("component has no child")?
.pid)
}