swactor/crates/iroh-driver/tests/telemetry_transport.rs
Zachery Aaron Shores-Chmielewski 23c829a9ba Enforce actor-owned Myelin control flow
Add a repository-owned rustc wrapper that enforces execution ownership and dependency boundaries during ordinary Cargo commands, with compile-pass and compile-fail policy contracts.

Move scheduling, timers, provider polling, provisioning, recovery, supervision, and shutdown decisions behind engine and actor APIs. Add deterministic component properties, stateful Myelin lifecycle coverage, persisted regression cases, and the bounded CI workflow.

Tighten resource ownership by cancelling telemetry collectors, terminating reply observers, bounding dashboard projections, and releasing process file descriptors, child observers, and inode-verified Unix socket paths on every exit path.
2026-08-20 01:38:14 +04:00

198 lines
7 KiB
Rust

use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use std::time::{Duration, Instant};
use iroh::{Endpoint, EndpointAddr, RelayMode};
use iroh_driver::{
TELEMETRY_ALPN, TelemetryQuicHeader, read_next_uni_from_connection, spawn_pull_collector,
write_available_subscription,
};
use swactor::config::RuntimeConfig;
use swactor::runtime::RuntimeParts;
use swactor_engine::{Engine, TokioBackend, TokioConfig};
use telemetry::frame::TelemetryEvent;
use telemetry::{
ChannelContent, DeliveryFanout, Lifetime, NodeId, Position, StreamId, SubscriptionRequest,
TelemetryEndpoint,
};
/// Telemetry transport test scheduled through `EngineHandle`, not an ambient
/// `#[tokio::test]` runtime (ENGINE_SPEC.md).
#[test]
fn iroh_telemetry_alpn_carries_catalog_and_numeric_frames() {
let parts = RuntimeParts::new(RuntimeConfig::default());
let engine = Engine::new(
parts,
TokioBackend::new(TokioConfig::default()).expect("test backend"),
)
.expect("test engine");
let handle = engine.handle();
let (done_tx, done_rx) = std::sync::mpsc::channel::<Result<(), String>>();
let h = handle.clone();
handle.spawn(async move {
let source = test_endpoint().await;
let collector = test_endpoint().await;
let collector_addr = endpoint_addr(&collector);
// Accept the incoming connection through an engine-hosted task + oneshot,
// since EngineHandle::spawn is fire-and-forget (no JoinHandle).
let (accept_tx, accept_rx) = tokio::sync::oneshot::channel();
{
let collector = collector.clone();
h.spawn(async move {
let conn = collector
.accept()
.await
.expect("incoming connection")
.await
.expect("accepted connection");
let _ = accept_tx.send(conn);
});
}
let stream = StreamId::new(NodeId::new("source-node"), Lifetime(1));
let endpoint = TelemetryEndpoint::with_capacity(stream.clone(), 8, 8);
let producer = endpoint.producer();
let runtime_log = producer.register_channel("runtime.log", ChannelContent::TextStream);
let subscription = endpoint.subscribe_all("iroh");
producer.submit_text(runtime_log, "alpha");
producer.submit_text(runtime_log, "beta");
endpoint.tick();
let conn = source
.connect(collector_addr, TELEMETRY_ALPN)
.await
.expect("connect telemetry ALPN");
let send = conn.open_uni().await.expect("open uni stream");
let header =
TelemetryQuicHeader::from_snapshot([7; 16], b"token".to_vec(), subscription.snapshot())
.expect("header from subscription snapshot");
let wrote = write_available_subscription(&h, send, &header, &subscription)
.await
.expect("write subscription");
assert_eq!(wrote.events, 2);
let accepted = accept_rx.await.expect("collector accept task");
let read = read_next_uni_from_connection(&accepted)
.await
.expect("read telemetry uni stream");
assert_eq!(read.header, header);
assert_eq!(read.header.stream.stream, stream);
assert!(
read.header
.channels
.iter()
.any(|descriptor| descriptor.id == runtime_log && descriptor.name == "runtime.log")
);
assert_eq!(read.events.len(), 2);
match &read.events[0] {
TelemetryEvent::Frame(frame) => {
assert_eq!(frame.channel.stream, stream);
assert_eq!(frame.channel.channel, runtime_log);
assert_eq!(frame.position, Position(0));
assert_eq!(frame.payload, b"alpha");
}
other => panic!("expected frame event, got {other:?}"),
}
match &read.events[1] {
TelemetryEvent::Frame(frame) => {
assert_eq!(frame.position, Position(1));
assert_eq!(frame.payload, b"beta");
}
other => panic!("expected frame event, got {other:?}"),
}
source.close().await;
collector.close().await;
let _ = done_tx.send(Ok(()));
});
match done_rx.recv() {
Ok(Ok(())) => {}
Ok(Err(e)) => panic!("test failed: {e}"),
Err(_) => panic!("test task dropped"),
}
}
#[test]
fn pull_collector_cancellation_interrupts_inflight_io() {
let parts = RuntimeParts::new(RuntimeConfig::default());
let engine = Engine::new(
parts,
TokioBackend::new(TokioConfig::default()).expect("test backend"),
)
.expect("test engine");
let handle = engine.handle();
let (resource_tx, resource_rx) = std::sync::mpsc::channel();
let setup_handle = handle.clone();
handle.spawn(async move {
let collector_endpoint = test_endpoint().await;
let silent_peer = test_endpoint().await;
let (header_tx, header_rx) = std::sync::mpsc::channel();
let collector = spawn_pull_collector(
&setup_handle,
collector_endpoint.clone(),
endpoint_addr(&silent_peer),
[3; 16],
Vec::new(),
SubscriptionRequest::all(),
Arc::new(DeliveryFanout::new(8)),
header_tx,
);
resource_tx
.send((collector, collector_endpoint, silent_peer, header_rx))
.expect("return collector resources");
});
let (collector, collector_endpoint, silent_peer, _header_rx) = resource_rx
.recv_timeout(Duration::from_secs(5))
.expect("collector setup");
std::thread::sleep(Duration::from_millis(100));
assert!(
!collector.is_finished(),
"collector was not retained in silent-peer network I/O"
);
collector.cancel();
let stopped_deadline = Instant::now() + Duration::from_secs(2);
while !collector.is_finished() && Instant::now() < stopped_deadline {
std::thread::sleep(Duration::from_millis(10));
}
assert!(
collector.is_finished(),
"cancelled collector remained blocked in network I/O"
);
drop((collector_endpoint, silent_peer));
}
async fn test_endpoint() -> Endpoint {
Endpoint::builder(iroh::endpoint::presets::Minimal)
.relay_mode(RelayMode::Disabled)
.alpns(vec![TELEMETRY_ALPN.to_vec()])
.bind()
.await
.expect("bind test endpoint")
}
fn endpoint_addr(endpoint: &Endpoint) -> EndpointAddr {
let mut addr = EndpointAddr::new(endpoint.id());
for socket in endpoint.bound_sockets() {
addr = addr.with_ip_addr(loopback_if_unspecified(socket));
}
addr
}
fn loopback_if_unspecified(socket: SocketAddr) -> SocketAddr {
match socket.ip() {
IpAddr::V4(ip) if ip.is_unspecified() => {
SocketAddr::new(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), socket.port())
}
IpAddr::V6(ip) if ip.is_unspecified() => {
SocketAddr::new(IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), socket.port())
}
_ => socket,
}
}