#![allow(dead_code)]
#![cfg(test)]
use super::*;
use crate::health::{_reset_health_for_tests, get_health_snapshot};
use crate::resilience::{_reset_resilience_for_tests, set_exporter_policy};
use crate::testing::acquire_test_state_lock;
use opentelemetry::InstrumentationScope;
use opentelemetry_sdk::logs::SdkLogRecord;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("runtime")
}
#[derive(Debug, Clone)]
struct AlwaysFailSpan(Arc<AtomicU32>);
impl SpanExporter for AlwaysFailSpan {
async fn export(&self, _batch: Vec<SpanData>) -> OTelSdkResult {
self.0.fetch_add(1, Ordering::SeqCst);
Err(OTelSdkError::InternalFailure("stub failure".into()))
}
}
#[derive(Debug, Clone)]
struct AlwaysFailLog(Arc<AtomicU32>);
impl LogExporter for AlwaysFailLog {
async fn export(&self, _batch: LogBatch<'_>) -> OTelSdkResult {
self.0.fetch_add(1, Ordering::SeqCst);
Err(OTelSdkError::InternalFailure("stub failure".into()))
}
}
#[derive(Debug)]
struct AlwaysFailMetric(Arc<AtomicU32>);
impl PushMetricExporter for AlwaysFailMetric {
async fn export(&self, _metrics: &ResourceMetrics) -> OTelSdkResult {
self.0.fetch_add(1, Ordering::SeqCst);
Err(OTelSdkError::InternalFailure("stub failure".into()))
}
fn force_flush(&self) -> OTelSdkResult {
Ok(())
}
fn shutdown_with_timeout(&self, _t: Duration) -> OTelSdkResult {
Ok(())
}
fn temporality(&self) -> Temporality {
Temporality::Cumulative
}
}
fn fail_open_zero_retry(signal: Signal) {
set_exporter_policy(
signal,
ExporterPolicy {
retries: 0,
backoff_seconds: 0.0,
timeout_seconds: 0.0,
fail_open: true,
..ExporterPolicy::default()
},
)
.unwrap();
}
fn fail_open_two_retries(signal: Signal) {
set_exporter_policy(
signal,
ExporterPolicy {
retries: 2,
backoff_seconds: 0.0,
timeout_seconds: 0.0,
fail_open: true,
..ExporterPolicy::default()
},
)
.unwrap();
}
#[test]
fn span_runtime_failure_increments_export_failures_counter() {
let _g = acquire_test_state_lock();
_reset_resilience_for_tests();
_reset_health_for_tests();
fail_open_zero_retry(Signal::Traces);
let calls = Arc::new(AtomicU32::new(0));
let w = ResilientSpanExporter::new(AlwaysFailSpan(calls.clone()));
rt().block_on(async move {
w.export(vec![]).await.expect("fail-open returns Ok");
});
let snap = get_health_snapshot();
assert_eq!(calls.load(Ordering::SeqCst), 1);
assert_eq!(snap.export_failures_traces, 1);
assert_eq!(snap.export_failures_logs, 0);
assert_eq!(snap.export_failures_metrics, 0);
}
#[test]
fn log_runtime_failure_increments_export_failures_counter() {
let _g = acquire_test_state_lock();
_reset_resilience_for_tests();
_reset_health_for_tests();
fail_open_zero_retry(Signal::Logs);
let calls = Arc::new(AtomicU32::new(0));
let w = ResilientLogExporter::new(AlwaysFailLog(calls.clone()));
let data: Vec<(SdkLogRecord, InstrumentationScope)> = vec![];
let refs: Vec<_> = data.iter().map(|(r, s)| (r, s)).collect();
let batch = LogBatch::new(&refs);
rt().block_on(async move {
w.export(batch).await.expect("fail-open returns Ok");
});
let snap = get_health_snapshot();
assert_eq!(calls.load(Ordering::SeqCst), 1);
assert_eq!(snap.export_failures_logs, 1);
assert_eq!(snap.export_failures_traces, 0);
assert_eq!(snap.export_failures_metrics, 0);
}
#[test]
fn metric_runtime_failure_increments_export_failures_counter() {
let _g = acquire_test_state_lock();
_reset_resilience_for_tests();
_reset_health_for_tests();
fail_open_zero_retry(Signal::Metrics);
let calls = Arc::new(AtomicU32::new(0));
let w = ResilientMetricExporter::new(AlwaysFailMetric(calls.clone()));
let rm = ResourceMetrics::default();
rt().block_on(async move {
w.export(&rm).await.expect("fail-open returns Ok");
});
let snap = get_health_snapshot();
assert_eq!(calls.load(Ordering::SeqCst), 1);
assert_eq!(snap.export_failures_metrics, 1);
assert_eq!(snap.export_failures_logs, 0);
assert_eq!(snap.export_failures_traces, 0);
}
#[test]
fn span_retries_increment_retry_counter_per_attempt_after_first() {
let _g = acquire_test_state_lock();
_reset_resilience_for_tests();
_reset_health_for_tests();
fail_open_two_retries(Signal::Traces);
let calls = Arc::new(AtomicU32::new(0));
let w = ResilientSpanExporter::new(AlwaysFailSpan(calls.clone()));
rt().block_on(async move {
w.export(vec![]).await.expect("fail-open returns Ok");
});
let snap = get_health_snapshot();
assert_eq!(calls.load(Ordering::SeqCst), 3);
assert_eq!(snap.retries_traces, 2);
assert_eq!(snap.export_failures_traces, 3);
assert_eq!(snap.retries_logs, 0);
assert_eq!(snap.retries_metrics, 0);
}
#[test]
fn log_retries_increment_retry_counter_per_attempt_after_first() {
let _g = acquire_test_state_lock();
_reset_resilience_for_tests();
_reset_health_for_tests();
fail_open_two_retries(Signal::Logs);
let calls = Arc::new(AtomicU32::new(0));
let w = ResilientLogExporter::new(AlwaysFailLog(calls.clone()));
let data: Vec<(SdkLogRecord, InstrumentationScope)> = vec![];
let refs: Vec<_> = data.iter().map(|(r, s)| (r, s)).collect();
let batch = LogBatch::new(&refs);
rt().block_on(async move {
w.export(batch).await.expect("fail-open returns Ok");
});
let snap = get_health_snapshot();
assert_eq!(calls.load(Ordering::SeqCst), 3);
assert_eq!(snap.retries_logs, 2);
assert_eq!(snap.export_failures_logs, 3);
}