Files
alkssh/tests/common/event_capture.rs
T
glm-5.3-flash 3d3bb5e9bf test(mapping): land the channel-not-held guard's wire rows on all three served rows (w4-channel-not-held-rows)
- git row: a second exec on a served git channel answers channel_failure
  inline — the serve rides on, no second serving path; race-free by
  construction (advertisement drained, serve parked at the gated list_refs)
- tty rows: the pipe row (second exec) and the shell row (second shell
  request) — the same inline decline shape, channel still usable
- the rows assert the decline event's exact request vocabulary (class
  request, reason channel-not-held, route git/pipe/absent, debug level,
  self-describing build) — the class's vocabulary coverage
- tests/common/event_capture.rs: the observe-suite's capture convention
  shared across the mapping binaries
- test-only: no production change; the guard held its documented shape
  in every featured build (verified default / git / session /
  session,git / tunnel / sftp / --all-features)

Verified: cargo test (+ per-feature + --all-features) all green ·
cargo clippy --all-targets -- -D warnings ✓ · cargo clippy
--all-features --all-targets -- -D warnings ✓ · cargo fmt --check ✓ ·
cargo doc --no-deps 0 warnings · taskgraph validate ✓ (35 tasks)
2026-10-29 07:27:59 +00:00

196 lines
6.9 KiB
Rust

//! The decline-row tracing capture (`w4-channel-not-held-rows`): a
//! minimal hand-rolled global [`tracing::Subscriber`] recording every
//! event's level + field pairs, the mapping binaries' decline-class
//! rows assert the [`crate`'s event vocabulary](alkssh's observe
//! module) against. The recorded test convention (see
//! `tests/observe.rs`'s module docs) applies verbatim: no
//! `tracing-subscriber` dependency (the hand-rolled shape needs none),
//! and the **global** default is the only capture shape that works —
//! the server side runs on spawned tasks, so a thread-local default
//! never sees their events.
//!
//! Rows install once per binary with [`install`], then match events by
//! field pairs ([`await_event`]) — parallel rows never order against
//! each other — and pin the decline event's exact vocabulary with
//! [`assert_request_decline`] (the exact-field-set assertion is the
//! safety posture: no dynamic extra fields exist).
#![allow(dead_code)]
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{Duration, Instant};
/// One captured event: the level and the field pairs, in emit order.
#[derive(Debug, Clone)]
pub struct Captured {
pub level: String,
pub fields: Vec<(String, String)>,
}
impl Captured {
fn field(&self, name: &str) -> Option<&str> {
self.fields
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| v.as_str())
}
fn has(&self, want: &[(&str, &str)]) -> bool {
want.iter()
.all(|(n, v)| self.field(n).is_some_and(|got| got == *v))
}
}
struct FieldVisitor(Vec<(String, String)>);
impl tracing::field::Visit for FieldVisitor {
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
self.0.push((field.name().to_owned(), value.to_owned()));
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
self.0.push((field.name().to_owned(), format!("{value:?}")));
}
}
#[derive(Default)]
struct GlobalCapture(Mutex<Vec<Captured>>);
impl tracing::Subscriber for GlobalCapture {
fn enabled(&self, _metadata: &tracing::Metadata<'_>) -> bool {
true
}
fn new_span(&self, _span: &tracing::span::Attributes<'_>) -> tracing::Id {
tracing::Id::from_u64(1)
}
fn record(&self, _span: &tracing::Id, _values: &tracing::span::Record<'_>) {}
fn record_follows_from(&self, _span: &tracing::Id, _follows: &tracing::Id) {}
fn event(&self, event: &tracing::Event<'_>) {
let mut visitor = FieldVisitor(Vec::new());
event.record(&mut visitor);
self.0
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(Captured {
level: event.metadata().level().to_string(),
fields: visitor.0,
});
}
fn enter(&self, _span: &tracing::Id) {}
fn exit(&self, _span: &tracing::Id) {}
fn clone_span(&self, _id: &tracing::Id) -> tracing::Id {
tracing::Id::from_u64(1)
}
}
/// The installed capture — one per binary (the [`OnceLock`] makes the
/// exactly-once shape explicit).
fn capture() -> &'static Arc<GlobalCapture> {
static CAPTURE: OnceLock<Arc<GlobalCapture>> = OnceLock::new();
CAPTURE.get_or_init(|| {
let capture = Arc::new(GlobalCapture::default());
tracing::subscriber::set_global_default(Arc::clone(&capture))
.expect("the capture suite installs the global capture exactly once");
capture
})
}
/// Installs the global capture once per binary. Call it before a row's
/// first wire exchange, so no event is emitted below the
/// not-yet-installed global.
pub fn install() {
let _ = capture();
}
/// Waits until an event matching every field pair appears, then
/// returns it — the spawned-task surfaces land their events
/// asynchronously (the deadline loop is the observe-suite convention).
pub async fn await_event(want: &[(&str, &str)]) -> Captured {
let capture = capture();
let deadline = Instant::now() + Duration::from_secs(10);
loop {
let events = capture.0.lock().unwrap_or_else(|e| e.into_inner()).clone();
if let Some(event) = events.into_iter().find(|e| e.has(want)) {
return event;
}
if Instant::now() > deadline {
panic!("no tracing event matching {want:?} within the window");
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
/// The expected `feature_build` value, derived from `cfg!`
/// independently of the crate's match — divergence breaks this loudly.
pub fn expected_build() -> &'static str {
match (
cfg!(feature = "session"),
cfg!(feature = "git"),
cfg!(feature = "tunnel"),
cfg!(feature = "sftp"),
) {
(false, false, false, false) => "none",
(true, false, false, false) => "session",
(false, true, false, false) => "git",
(false, false, true, false) => "tunnel",
(false, false, false, true) => "sftp",
(true, true, false, false) => "session+git",
(true, false, true, false) => "session+tunnel",
(true, false, false, true) => "session+sftp",
(false, true, true, false) => "git+tunnel",
(false, true, false, true) => "git+sftp",
(false, false, true, true) => "tunnel+sftp",
(true, true, true, false) => "session+git+tunnel",
(true, true, false, true) => "session+git+sftp",
(true, false, true, true) => "session+tunnel+sftp",
(false, true, true, true) => "git+tunnel+sftp",
(true, true, true, true) => "session+git+tunnel+sftp",
}
}
/// The request-decline vocabulary assertion: a captured request-level
/// decline carries exactly the vocabulary (sorted-name-set comparison —
/// nothing more; the dynamic-free shape is the safety posture), the
/// build self-describing, the message shape, and the `debug` level (a
/// decline is the door's normal, honest posture). `exec_route` present
/// iff the row was a routed exec.
pub fn assert_request_decline(event: &Captured, exec_route: bool) {
assert_eq!(
event.level, "DEBUG",
"a decline emits at debug — the vocabulary's level posture"
);
assert_eq!(
event.field("feature_build"),
Some(expected_build()),
"every event is self-describing per build"
);
assert_eq!(
event.field("message"),
Some("decline: channel request rejected"),
"the request-level decline message shape"
);
let mut names: Vec<&str> = event.fields.iter().map(|(n, _)| n.as_str()).collect();
names.sort_unstable();
let mut expected: Vec<&str> = vec![
"channel_id",
"decline_class",
"feature_build",
"message",
"reason",
"row",
];
if exec_route {
expected.push("exec_route");
}
expected.sort_unstable();
assert_eq!(
names, expected,
"a request decline carries exactly the vocabulary, nothing else"
);
}