swactor/crates/distribution/tests/telemetry.rs
Zachery Aaron Shores-Chmielewski b06583f598 refactor(datastream): cleanup
Trim the datastream crate to its dumb-pipe core: drop the frame-timing sidecar, make mux positions gap-free for accepted frames, and simplify the endpoint fanout.

- mux: defer position assignment from submit to drain so an overflowed submission no longer consumes a position (no synthetic gaps); submit now returns bool and the Mutex<Receiver> is removed since positions are assigned only to accepted frames
- endpoint/mux: switch from std::sync::mpsc to crossbeam-channel and drop per-event event_matches_request filtering — request filters now apply only to the initial catalog snapshot, and future events broadcast to all subscribers
- endpoint (DeliveryFanout): snapshot sender handles under the lock and deliver outside it via FanoutTarget/FanoutReport, so large batches or slow subscribers no longer block subscribe/snapshot control-plane ops
- emit/endpoint/producer: drop set_frame_timing_enabled/frame_timing_enabled and the Position return from submit_record/submit_text/submit_bytes, and add submit_text_owned taking owned String
- timing/lib/spec: delete the timing module and FRAME_TIME_CHANNEL/FRAME_TIME_CHANNEL_ID/FrameTimeSample re-exports (including the auto-registered timing channel in ChannelCatalogState) and renumber the DATASTREAM_SPEC.md section references across frame/ingest/store/mux
- tests: remove the 567-line shared datastream_support/mod.rs harness

Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 13:38:16 +04:00

152 lines
5.6 KiB
Rust

//! Distribution observability contracts.
//!
//! These tests are intentionally narrower than protocol tests: they pin public record/snapshot
//! shapes and assert telemetry remains observational rather than protocol-driving.
//!
//! Behavioral/correctness guarantees:
//! - Distribution telemetry records and snapshot shapes are stable wire contracts.
//! - Telemetry is observational: it reports protocol/cache/registry state without feeding back
//! into behavior.
//! - Transition causes and recent probe targets are externally visible when promised.
mod datastream_records {
//! Distribution-owned datastream records keep their channels and JSON payloads stable through
//! the mux.
use datastream::frame::{Lifetime, NodeId, StreamId};
use datastream::{ChannelId, Mux, Position, Record};
use distribution::telemetry::{
CacheEntryRec, DIST_STATE, DistributionState, MembershipTransition, RegistryEntryRec,
};
#[test]
fn distribution_state_record_round_trips_from_owner_crate() {
let state = DistributionState {
cache_size: 2,
cache_entries: vec![CacheEntryRec {
actor_addr: "actor-a".into(),
node_id: "node-a".into(),
}],
directory_route_count: 7,
registry_size: 3,
registry_tombstones: 1,
registry_entries: vec![RegistryEntryRec {
name: "svc".into(),
actor_addr: "actor-b".into(),
node_id: "node-b".into(),
tombstone: false,
}],
recent_probe_targets: vec!["node-c".into()],
peer_auth_mode: "allow-list".into(),
authorized_peer_count: 4,
};
assert_eq!(DistributionState::CHANNEL, DIST_STATE);
assert_eq!(DistributionState::decode(&state.encode()).unwrap(), state);
}
#[test]
fn distribution_emits_owned_channel_through_datastream_mux() {
let stream = StreamId::new(NodeId::new("dist-node"), Lifetime(1));
let mux = Mux::unbounded(stream);
let state = DistributionState {
registry_size: 9,
..Default::default()
};
let channel = ChannelId(1);
assert!(mux.submit(channel, state.encode()));
let frames = mux.drain();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].channel, channel);
assert_eq!(frames[0].position, Position(0));
assert_eq!(
DistributionState::decode(&frames[0].payload)
.unwrap()
.registry_size,
9
);
}
#[test]
fn membership_transition_record_round_trips_from_owner_crate() {
let transition = MembershipTransition {
peer: "peer-a".into(),
from: "alive".into(),
to: "suspect".into(),
reason: "probe timeout".into(),
};
assert_eq!(
MembershipTransition::decode(&transition.encode()).unwrap(),
transition
);
}
}
mod snapshot_and_swim_telemetry {
//! Snapshot and SWIM telemetry readouts: public empty-state shape, recent probe targets, and
//! transition causes.
use distribution::snapshot::DistributionNodeSnapshot;
use distribution::swim::node::{SwimObservation, SwimObserver};
use distribution::swim::telemetry::SwimTelemetry;
use distribution::types::{MemberState, NodeId};
fn id(byte: u8) -> NodeId {
NodeId([byte; 32])
}
#[test]
fn empty_distribution_snapshot_keeps_the_public_wire_shape_stable() {
// Correctness: external distribution consumers can deserialize the retained
// snapshot shape, and the empty constructor reports explicit zero/open defaults.
let snapshot = DistributionNodeSnapshot::empty(id(0xAB));
let json = serde_json::to_string(&snapshot).unwrap();
let decoded: DistributionNodeSnapshot = serde_json::from_str(&json).unwrap();
assert_eq!(
decoded.node_id,
"abababababababababababababababababababababababababababababababab"
);
assert_eq!(decoded.peer_auth_mode, "open");
assert_eq!(decoded.alive_count, 0);
assert!(decoded.members.is_empty());
assert!(decoded.registry_entries.is_empty());
assert!(decoded.authorized_peer_count.is_none());
}
#[test]
fn swim_telemetry_reports_probe_targets_and_transition_causes_without_state_feedback() {
// Correctness: telemetry is an observer side channel. It records recent probes
// and transition causes for readers, and draining transitions only affects the
// telemetry buffer, not protocol state.
let telemetry = SwimTelemetry::new();
let peer = id(4);
telemetry.observe(SwimObservation::ProbeSent {
target: peer,
sequence: 1,
kind: "direct",
});
telemetry.observe(SwimObservation::Transition {
peer,
from: Some(MemberState::Alive),
to: MemberState::Suspect,
reason: "probe-timeout",
});
assert_eq!(telemetry.rtt_ms_p50(), 0);
assert_eq!(telemetry.recent_targets(), vec![peer]);
assert_eq!(telemetry.last_reasons().get(&peer), Some(&"probe-timeout"));
let drained = telemetry.drain_transitions();
assert_eq!(drained.len(), 1);
assert_eq!(drained[0].peer, peer);
assert_eq!(drained[0].from, Some(MemberState::Alive));
assert_eq!(drained[0].to, MemberState::Suspect);
assert_eq!(drained[0].reason, "probe-timeout");
assert!(telemetry.drain_transitions().is_empty());
}
}