swactor/crates/datastream/tests/t_datastream_endpoint.rs

180 lines
5.8 KiB
Rust
Raw Normal View History

2026-06-25 12:30:18 +00:00
use std::time::Duration;
use datastream::{
ChannelId, DatastreamEndpoint, DeliveryFanout, Frame, Lifetime, NodeId, Position, StreamId,
};
use serde_json::Value;
use swactor::actor::ActorAddress;
use swactor::stats::ActorSnapshot;
fn stream() -> StreamId {
StreamId::new(NodeId::new("node-endpoint"), Lifetime(7))
}
#[test]
fn endpoint_without_subscribers_drains_to_bitbucket() {
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
let producer = endpoint.producer();
producer.submit_text("runtime.log", "before");
let tick = endpoint.tick();
assert_eq!(tick.drained, 1);
assert_eq!(tick.subscribers, 0);
assert_eq!(tick.delivered, 0);
assert_eq!(endpoint.assigned(), 1);
assert_eq!(endpoint.drained(), 1);
assert_eq!(endpoint.bitbucketed(), 1);
}
#[test]
fn subscription_receives_only_future_frames() {
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
let producer = endpoint.producer();
producer.submit_text("runtime.log", "pre-subscription");
endpoint.tick();
let subscription = endpoint.subscribe_all("dashboard");
producer.submit_text("runtime.log", "visible");
endpoint.tick();
let deliveries = subscription.drain_available();
assert_eq!(deliveries.len(), 1);
assert_eq!(deliveries[0].stream, stream());
assert_eq!(deliveries[0].frame.position, Position(1));
assert_eq!(deliveries[0].frame.channel, ChannelId::new("runtime.log"));
assert_eq!(deliveries[0].frame.payload, b"visible");
}
#[test]
fn endpoint_fans_out_ordered_frames_to_multiple_subscribers() {
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
let producer = endpoint.producer();
let left = endpoint.subscribe_all("left");
let right = endpoint.subscribe_all("right");
for n in 0..3 {
producer.submit_text("runtime.log", format!("line-{n}"));
}
let tick = endpoint.tick();
assert_eq!(tick.drained, 3);
assert_eq!(tick.subscribers, 2);
assert_eq!(tick.delivered, 6);
assert_eq!(positions(&left.drain_available()), vec![0, 1, 2]);
assert_eq!(positions(&right.drain_available()), vec![0, 1, 2]);
}
#[test]
fn slow_subscriber_drops_without_blocking_fast_subscriber() {
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 8);
let producer = endpoint.producer();
let slow = endpoint.subscribe_all_with_capacity("slow", 1);
let fast = endpoint.subscribe_all_with_capacity("fast", 8);
for n in 0..4 {
producer.submit_text("runtime.log", format!("line-{n}"));
}
let tick = endpoint.tick();
assert_eq!(tick.drained, 4);
assert_eq!(tick.delivered, 5);
assert_eq!(tick.dropped_for_subscribers, 3);
assert_eq!(positions(&slow.drain_available()), vec![0]);
assert_eq!(positions(&fast.drain_available()), vec![0, 1, 2, 3]);
let slow_snapshot = endpoint
.subscriber_snapshots()
.into_iter()
.find(|snapshot| snapshot.name == "slow")
.expect("slow subscriber snapshot");
assert_eq!(slow_snapshot.dropped, 3);
}
#[test]
fn delivery_fanout_can_publish_collector_deliveries_without_a_mux() {
let fanout = DeliveryFanout::new(4);
let sub = fanout.subscribe_all("dashboard");
let delivery = datastream::Delivery::new(
stream(),
Frame::new("remote.lifecycle", Position(9), b"ready".to_vec()),
);
let tick = fanout.publish(delivery.clone());
assert_eq!(tick.drained, 1);
assert_eq!(tick.delivered, 1);
assert_eq!(
sub.recv_timeout(Duration::from_millis(50)).unwrap(),
delivery
);
}
#[test]
fn process_observer_adapter_submits_configured_channels() {
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
let producer = endpoint.producer();
let observer = producer.process_observer_with(|label, is_stderr| {
ChannelId::new(format!(
"proc.{label}.{}",
if is_stderr { "stderr" } else { "stdout" }
))
});
let sub = endpoint.subscribe_all("test");
observer.on_output("trainer", false, b"hello\n");
observer.on_output("trainer", true, b"warn\n");
endpoint.tick();
let deliveries = sub.drain_available();
assert_eq!(deliveries.len(), 2);
assert_eq!(
deliveries[0].frame.channel,
ChannelId::new("proc.trainer.stdout")
);
assert_eq!(deliveries[0].frame.payload, b"hello\n");
assert_eq!(
deliveries[1].frame.channel,
ChannelId::new("proc.trainer.stderr")
);
assert_eq!(deliveries[1].frame.payload, b"warn\n");
}
#[test]
fn stats_hook_adapter_submits_worker_snapshot_json() {
let endpoint = DatastreamEndpoint::with_capacity(stream(), 8, 4);
let producer = endpoint.producer();
let hook = producer.stats_hook_on("runtime.actors");
let sub = endpoint.subscribe_all("test");
let actor = ActorAddress::new_random();
let snapshots = [ActorSnapshot {
address: actor,
mailbox_depth: 3,
last_msg_type: Some("Ping"),
messages_processed: 5,
poisoned: false,
message_type_counts: vec![("Ping", 5)],
}];
hook.on_tick(2, &snapshots);
endpoint.tick();
let delivery = sub.recv_timeout(Duration::from_millis(50)).unwrap();
assert_eq!(delivery.frame.channel, ChannelId::new("runtime.actors"));
let json: Value = serde_json::from_slice(&delivery.frame.payload).unwrap();
assert_eq!(json["worker_id"], 2);
assert_eq!(json["actors"][0]["address"], actor.to_string());
assert_eq!(json["actors"][0]["mailbox_depth"], 3);
assert_eq!(json["actors"][0]["last_msg_type"], "Ping");
assert_eq!(json["actors"][0]["messages_processed"], 5);
assert_eq!(json["actors"][0]["message_type_counts"][0]["ty"], "Ping");
}
fn positions(deliveries: &[datastream::Delivery]) -> Vec<u64> {
deliveries
.iter()
.map(|delivery| delivery.frame.position.0)
.collect()
}