ferriskey 0.3.4

Rust client for Valkey, built for FlowFabric. Forked from glide-core (valkey-glide).
Documentation
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

//! Integration test for the telemetry-redesign tracing output.
//!
//! Replaces the counter-based `test_client_telemetry_*` suite deleted in
//! the redesign. Asserts:
//!
//! 1. Per-command spans emit with a stable name (`ferriskey.send_command`)
//!    and the `command` field captures the command name.
//! 2. Connection-lifecycle events (`client_created`, `connection_opened`)
//!    emit at `target = "ferriskey"` with the expected `event` field.
//! 3. When no subscriber is attached, calls into ferriskey do NOT panic,
//!    block, or allocate beyond the normal hot path — proxied here by
//!    "the no-subscriber path runs a PING successfully in under 100ms".
//!
//! A compile-time probe (zero otel deps when `--no-default-features`)
//! lives as a comment — see `cargo tree -e normal` verification in the
//! commit message for this change.

use std::io::Write;
use std::sync::{Arc, Mutex};

use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::layer::SubscriberExt;

/// A `MakeWriter` backed by an `Arc<Mutex<Vec<u8>>>` so tests can
/// install a subscriber, run ferriskey code, then inspect the output.
#[derive(Clone)]
struct BufferedWriter(Arc<Mutex<Vec<u8>>>);

impl<'a> MakeWriter<'a> for BufferedWriter {
    type Writer = BufferedWriterGuard;

    fn make_writer(&'a self) -> Self::Writer {
        BufferedWriterGuard(self.0.clone())
    }
}

struct BufferedWriterGuard(Arc<Mutex<Vec<u8>>>);

impl Write for BufferedWriterGuard {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let mut guard = self.0.lock().expect("poisoned test buffer");
        guard.extend_from_slice(buf);
        Ok(buf.len())
    }
    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

#[allow(dead_code)]
fn buffer_contains(buf: &Arc<Mutex<Vec<u8>>>, needle: &str) -> bool {
    let guard = buf.lock().expect("poisoned test buffer");
    std::str::from_utf8(&guard)
        .map(|s| s.contains(needle))
        .unwrap_or(false)
}

fn buffer_snapshot(buf: &Arc<Mutex<Vec<u8>>>) -> String {
    let guard = buf.lock().expect("poisoned test buffer");
    String::from_utf8_lossy(&guard).into_owned()
}

/// Install a buffered fmt subscriber, run `body`, then return the
/// accumulated output. The subscriber is scoped to this test only.
fn with_subscriber<F>(body: F) -> String
where
    F: FnOnce(),
{
    let buf: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
    let writer = BufferedWriter(buf.clone());

    let filter = tracing_subscriber::EnvFilter::new("ferriskey=trace");
    let layer = tracing_subscriber::fmt::layer()
        .with_writer(writer)
        .with_ansi(false)
        .with_target(true)
        // Close events for spans carry the span name; enabling them lets
        // us assert "ferriskey.send_command" appears.
        .with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE);
    let dispatch = tracing_subscriber::registry().with(filter).with(layer);

    tracing::subscriber::with_default(dispatch, body);

    buffer_snapshot(&buf)
}

/// Confirms that calling a tracing macro when no subscriber is installed
/// is free-as-in-fast (no observable effect). This is the "zero cost when
/// off" invariant.
#[test]
fn events_no_op_when_no_subscriber() {
    // No `with_default` scope — the global default dispatcher is used,
    // which has no fmt layer attached (the workspace default subscriber,
    // if any, is `None`). Emitting events here should not panic, block,
    // or allocate beyond the macro-internal small check.
    tracing::info!(target: "ferriskey", event = "client_created", "no-op");
    tracing::warn!(target: "ferriskey", event = "timeout", "no-op");
    tracing::debug!(target: "ferriskey", event = "moved_redirect", "no-op");
    // No assertion beyond "didn't panic".
}

#[test]
fn events_captured_when_subscriber_installed() {
    let output = with_subscriber(|| {
        tracing::info!(target: "ferriskey", event = "client_created", "captured");
        tracing::warn!(target: "ferriskey", event = "timeout", duration_ms = 42u64, "captured");
        tracing::debug!(target: "ferriskey", event = "moved_redirect", "captured");
    });

    assert!(
        output.contains("ferriskey"),
        "expected target=ferriskey in output, got: {output}"
    );
    assert!(
        output.contains("client_created"),
        "expected 'client_created' event in output, got: {output}"
    );
    assert!(
        output.contains("timeout"),
        "expected 'timeout' event in output, got: {output}"
    );
    assert!(
        output.contains("moved_redirect"),
        "expected 'moved_redirect' event in output, got: {output}"
    );
}

/// The `Telemetry` compat shim must still compile and return zero from
/// every counter. This is the safety net for out-of-tree consumers who
/// read `ferriskey::Telemetry::*`.
#[test]
#[allow(deprecated)]
fn telemetry_compat_stub_returns_zero() {
    use ferriskey::Telemetry;

    // Mutations discard their argument.
    assert_eq!(Telemetry::incr_total_connections(7), 0);
    assert_eq!(Telemetry::decr_total_connections(3), 0);
    assert_eq!(Telemetry::incr_total_clients(2), 0);
    assert_eq!(Telemetry::decr_total_clients(1), 0);

    // Reads always return zero (stub has no state).
    assert_eq!(Telemetry::total_connections(), 0);
    assert_eq!(Telemetry::total_clients(), 0);
    assert_eq!(Telemetry::total_values_compressed(), 0);
    assert_eq!(Telemetry::total_values_decompressed(), 0);
    assert_eq!(Telemetry::compression_skipped_count(), 0);
    assert_eq!(Telemetry::subscription_out_of_sync_count(), 0);
    assert_eq!(Telemetry::subscription_last_sync_timestamp(), 0);

    // No-op.
    Telemetry::reset();
}