use serial_test::serial;
use std::env;
use crate::build_filter;
fn set_rust_log(value: &str) {
unsafe {
env::set_var("RUST_LOG", value);
}
}
fn remove_rust_log() {
unsafe {
env::remove_var("RUST_LOG");
}
}
#[test]
#[serial]
fn test_build_filter_rust_log_override() {
set_rust_log("debug");
let filter = build_filter(None, "warn");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("DEBUG"),
"Expected filter to use RUST_LOG value 'debug' (DEBUG), got: {filter_str}"
);
remove_rust_log();
}
#[test]
#[serial]
fn test_build_filter_explicit_filter() {
remove_rust_log();
let filter = build_filter(Some("warn"), "info");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("WARN"),
"Expected filter to use explicit value 'warn' (WARN), got: {filter_str}"
);
}
#[test]
#[serial]
fn test_build_filter_default_fallback() {
remove_rust_log();
let filter = build_filter(None, "error");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("ERROR"),
"Expected filter to use default value 'error' (ERROR), got: {filter_str}"
);
}
#[test]
#[serial]
fn test_build_filter_invalid_fallback() {
remove_rust_log();
let filter = build_filter(Some("invalid[[[filter"), "info");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("INFO"),
"Expected filter to fall back to default 'info' (INFO), got: {filter_str}"
);
}
#[test]
#[serial]
fn test_build_filter_complex_directives() {
remove_rust_log();
let filter = build_filter(Some("mx_logging=debug,tower=warn"), "info");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("mx_logging") || filter_str.contains("DEBUG"),
"Expected filter to contain module directive, got: {filter_str}"
);
}
#[test]
#[serial]
fn test_build_filter_all_log_levels() {
remove_rust_log();
for (level, expected) in [
("trace", "TRACE"),
("debug", "DEBUG"),
("info", "INFO"),
("warn", "WARN"),
("error", "ERROR"),
("off", "OFF"),
] {
let filter = build_filter(Some(level), "info");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains(expected),
"Expected filter to contain '{expected}' for level '{level}', got: {filter_str}"
);
}
}
#[test]
#[serial]
fn test_build_filter_case_insensitive() {
remove_rust_log();
for level in ["DEBUG", "Debug", "debug", "dEbUg"] {
let filter = build_filter(Some(level), "info");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("DEBUG"),
"Expected filter to parse '{level}' as DEBUG, got: {filter_str}"
);
}
}
#[test]
#[serial]
fn test_build_filter_rust_log_complex() {
set_rust_log("mx_logging=trace,tower_http=debug,hyper=warn");
let filter = build_filter(None, "info");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("mx_logging") || filter_str.contains("TRACE"),
"Expected RUST_LOG directives to be applied, got: {filter_str}"
);
remove_rust_log();
}
#[test]
#[serial]
fn test_build_filter_empty_string() {
remove_rust_log();
let filter = build_filter(Some(""), "warn");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("OFF") || filter_str.contains("statics"),
"Expected empty filter to create valid empty filter, got: {filter_str}"
);
}
#[test]
#[serial]
fn test_build_filter_whitespace_only() {
remove_rust_log();
let filter = build_filter(Some(" "), "error");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("ERROR"),
"Expected whitespace filter to fall back to default 'error', got: {filter_str}"
);
}
#[test]
#[serial]
fn test_build_filter_rust_log_empty() {
set_rust_log("");
let filter = build_filter(Some("debug"), "info");
let _filter_str = format!("{filter:?}");
remove_rust_log();
}
#[test]
#[serial]
fn test_build_filter_span_directives() {
remove_rust_log();
let filter = build_filter(Some("info,[span_name]=debug"), "warn");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("INFO") || filter_str.contains("span"),
"Expected filter to parse span directive, got: {filter_str}"
);
}
#[test]
#[serial]
fn test_init_idempotent() {
use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};
static INIT_COUNT: AtomicUsize = AtomicUsize::new(0);
static TEST_LOCK: OnceLock<()> = OnceLock::new();
TEST_LOCK.get_or_init(|| {
INIT_COUNT.fetch_add(1, Ordering::SeqCst);
});
TEST_LOCK.get_or_init(|| {
INIT_COUNT.fetch_add(1, Ordering::SeqCst);
});
assert_eq!(
INIT_COUNT.load(Ordering::SeqCst),
1,
"OnceLock should only execute initialization once"
);
}
#[test]
#[serial]
fn test_init_with_explicit_filter() {
remove_rust_log();
let filter = build_filter(Some("trace"), "info");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("TRACE"),
"Explicit filter 'trace' (TRACE) should be respected, got: {filter_str}"
);
}
#[test]
#[serial]
fn test_init_default_filter() {
remove_rust_log();
let filter = build_filter(None, "warn");
let filter_str = format!("{filter:?}");
assert!(
filter_str.contains("WARN"),
"Default filter 'warn' (WARN) should be used, got: {filter_str}"
);
}
#[test]
fn test_build_fmt_layer_creates_layer() {
use crate::build_fmt_layer;
let _layer = build_fmt_layer::<tracing_subscriber::Registry>();
}
#[test]
fn test_is_initialized_behavior() {
use crate::is_initialized;
let _result = is_initialized();
}
#[test]
fn test_tracing_oncelock_initialized_check() {
use std::sync::OnceLock;
static TEST_TRACING: OnceLock<()> = OnceLock::new();
assert!(TEST_TRACING.get().is_none());
TEST_TRACING.get_or_init(|| ());
assert!(TEST_TRACING.get().is_some());
}
#[test]
fn test_tracing_oncelock_multiple_init_attempts() {
use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
static TEST_TRACING_2: OnceLock<()> = OnceLock::new();
TEST_TRACING_2.get_or_init(|| {
CALL_COUNT.fetch_add(1, Ordering::SeqCst);
});
TEST_TRACING_2.get_or_init(|| {
CALL_COUNT.fetch_add(1, Ordering::SeqCst);
});
TEST_TRACING_2.get_or_init(|| {
CALL_COUNT.fetch_add(1, Ordering::SeqCst);
});
assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1);
}
#[cfg(feature = "opentelemetry")]
mod otel_tests {
use crate::otel::{OtelConfig, OtelError};
#[test]
fn test_otel_config_default() {
let config = OtelConfig::default();
assert_eq!(config.service_name, "mx-service");
assert_eq!(config.otlp_endpoint, "http://localhost:4317");
assert!((config.sampling_ratio - 1.0).abs() < f64::EPSILON);
assert_eq!(config.batch_size, 512);
assert_eq!(config.max_queue_size, 2048);
assert_eq!(config.export_timeout_ms, 30_000);
assert_eq!(config.connect_timeout_ms, 10_000);
}
#[test]
fn test_otel_config_custom() {
let config = OtelConfig {
service_name: "my-custom-service".to_string(),
otlp_endpoint: "http://collector.example.com:4317".to_string(),
sampling_ratio: 0.5,
batch_size: 1024,
max_queue_size: 4096,
export_timeout_ms: 60_000,
connect_timeout_ms: 5_000,
};
assert_eq!(config.service_name, "my-custom-service");
assert_eq!(config.otlp_endpoint, "http://collector.example.com:4317");
assert!((config.sampling_ratio - 0.5).abs() < f64::EPSILON);
assert_eq!(config.batch_size, 1024);
assert_eq!(config.max_queue_size, 4096);
assert_eq!(config.export_timeout_ms, 60_000);
assert_eq!(config.connect_timeout_ms, 5_000);
}
#[test]
fn test_otel_config_clone() {
let original = OtelConfig {
service_name: "test-service".to_string(),
otlp_endpoint: "http://test:4317".to_string(),
sampling_ratio: 0.75,
batch_size: 256,
max_queue_size: 1024,
export_timeout_ms: 15_000,
connect_timeout_ms: 3_000,
};
let cloned = original.clone();
assert_eq!(cloned.service_name, original.service_name);
assert_eq!(cloned.otlp_endpoint, original.otlp_endpoint);
assert!((cloned.sampling_ratio - original.sampling_ratio).abs() < f64::EPSILON);
assert_eq!(cloned.batch_size, original.batch_size);
assert_eq!(cloned.max_queue_size, original.max_queue_size);
assert_eq!(cloned.export_timeout_ms, original.export_timeout_ms);
assert_eq!(cloned.connect_timeout_ms, original.connect_timeout_ms);
}
#[test]
fn test_otel_config_debug() {
let config = OtelConfig::default();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("OtelConfig"));
assert!(debug_str.contains("service_name"));
assert!(debug_str.contains("mx-service"));
assert!(debug_str.contains("otlp_endpoint"));
assert!(debug_str.contains("localhost:4317"));
assert!(debug_str.contains("sampling_ratio"));
assert!(debug_str.contains("batch_size"));
assert!(debug_str.contains("max_queue_size"));
assert!(debug_str.contains("export_timeout_ms"));
assert!(debug_str.contains("connect_timeout_ms"));
}
#[test]
fn test_otel_config_edge_values() {
let min_config = OtelConfig {
service_name: String::new(),
otlp_endpoint: String::new(),
sampling_ratio: 0.0,
batch_size: 0,
max_queue_size: 0,
export_timeout_ms: 0,
connect_timeout_ms: 0,
};
assert!(min_config.service_name.is_empty());
assert!(min_config.sampling_ratio <= 0.0);
let max_config = OtelConfig {
service_name: "a".repeat(1000),
otlp_endpoint: "http://very-long-hostname.example.com:4317".to_string(),
sampling_ratio: 1.0,
batch_size: usize::MAX,
max_queue_size: usize::MAX,
export_timeout_ms: u64::MAX,
connect_timeout_ms: u64::MAX,
};
assert_eq!(max_config.service_name.len(), 1000);
assert!((max_config.sampling_ratio - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_init_with_otel_sampler_always_on() {
let config = OtelConfig {
sampling_ratio: 1.0,
..Default::default()
};
let is_always_on = (config.sampling_ratio - 1.0).abs() < f64::EPSILON;
assert!(
is_always_on,
"sampling_ratio=1.0 should trigger AlwaysOn sampler"
);
}
#[test]
fn test_init_with_otel_sampler_always_on_near_one() {
let config = OtelConfig {
sampling_ratio: 1.0 - f64::EPSILON / 2.0,
..Default::default()
};
let is_always_on = (config.sampling_ratio - 1.0).abs() < f64::EPSILON;
assert!(
is_always_on,
"sampling_ratio very close to 1.0 should trigger AlwaysOn sampler"
);
}
#[test]
fn test_init_with_otel_sampler_always_off() {
let config = OtelConfig {
sampling_ratio: 0.0,
..Default::default()
};
let is_always_off = config.sampling_ratio <= 0.0;
assert!(
is_always_off,
"sampling_ratio=0.0 should trigger AlwaysOff sampler"
);
}
#[test]
fn test_init_with_otel_sampler_always_off_negative() {
let config = OtelConfig {
sampling_ratio: -0.5,
..Default::default()
};
let is_always_off = config.sampling_ratio <= 0.0;
assert!(
is_always_off,
"negative sampling_ratio should trigger AlwaysOff sampler"
);
}
#[test]
fn test_init_with_otel_sampler_ratio() {
let config = OtelConfig {
sampling_ratio: 0.5,
..Default::default()
};
let is_ratio_based =
config.sampling_ratio > 0.0 && (config.sampling_ratio - 1.0).abs() >= f64::EPSILON;
assert!(
is_ratio_based,
"sampling_ratio=0.5 should trigger TraceIdRatioBased sampler"
);
}
#[test]
fn test_init_with_otel_sampler_ratio_edge_cases() {
for ratio in [0.001, 0.1, 0.25, 0.33, 0.5, 0.75, 0.9, 0.999] {
let config = OtelConfig {
sampling_ratio: ratio,
..Default::default()
};
let is_ratio_based =
config.sampling_ratio > 0.0 && (config.sampling_ratio - 1.0).abs() >= f64::EPSILON;
assert!(
is_ratio_based,
"sampling_ratio={ratio} should trigger TraceIdRatioBased sampler"
);
}
}
#[test]
fn test_otel_error_display() {
let err = OtelError::ExporterCreation("connection refused".to_string());
let display = err.to_string();
assert!(
display.contains("Failed to create OTLP exporter"),
"ExporterCreation should contain 'Failed to create OTLP exporter', got: {display}"
);
assert!(
display.contains("connection refused"),
"ExporterCreation should contain the error message, got: {display}"
);
let err = OtelError::TracerInstallation("runtime error".to_string());
let display = err.to_string();
assert!(
display.contains("Failed to install tracer"),
"TracerInstallation should contain 'Failed to install tracer', got: {display}"
);
assert!(
display.contains("runtime error"),
"TracerInstallation should contain the error message, got: {display}"
);
let err = OtelError::AlreadyInitialized;
let display = err.to_string();
assert!(
display.contains("already initialized"),
"AlreadyInitialized should contain 'already initialized', got: {display}"
);
}
#[test]
fn test_otel_error_debug() {
let err = OtelError::ExporterCreation("test error".to_string());
let debug = format!("{:?}", err);
assert!(debug.contains("ExporterCreation"));
assert!(debug.contains("test error"));
let err = OtelError::TracerInstallation("install error".to_string());
let debug = format!("{:?}", err);
assert!(debug.contains("TracerInstallation"));
assert!(debug.contains("install error"));
let err = OtelError::AlreadyInitialized;
let debug = format!("{:?}", err);
assert!(debug.contains("AlreadyInitialized"));
}
#[test]
fn test_otel_error_is_std_error() {
fn assert_error<T: std::error::Error>(_: &T) {}
let err = OtelError::ExporterCreation("test".to_string());
assert_error(&err);
let err = OtelError::TracerInstallation("test".to_string());
assert_error(&err);
let err = OtelError::AlreadyInitialized;
assert_error(&err);
}
#[test]
fn test_otel_error_source() {
use std::error::Error;
let err = OtelError::ExporterCreation("test".to_string());
assert!(err.source().is_none());
let err = OtelError::TracerInstallation("test".to_string());
assert!(err.source().is_none());
let err = OtelError::AlreadyInitialized;
assert!(err.source().is_none());
}
#[test]
fn test_otel_error_pattern_matching() {
let err = OtelError::ExporterCreation("test".to_string());
assert!(matches!(err, OtelError::ExporterCreation(_)));
let err = OtelError::TracerInstallation("test".to_string());
assert!(matches!(err, OtelError::TracerInstallation(_)));
let err = OtelError::AlreadyInitialized;
assert!(matches!(err, OtelError::AlreadyInitialized));
}
#[test]
fn test_otel_error_with_empty_message() {
let err = OtelError::ExporterCreation(String::new());
let display = err.to_string();
assert!(display.contains("Failed to create OTLP exporter"));
let err = OtelError::TracerInstallation(String::new());
let display = err.to_string();
assert!(display.contains("Failed to install tracer"));
}
#[test]
fn test_otel_error_with_long_message() {
let long_msg = "x".repeat(10000);
let err = OtelError::ExporterCreation(long_msg.clone());
let display = err.to_string();
assert!(display.contains(&long_msg));
let err = OtelError::TracerInstallation(long_msg.clone());
let display = err.to_string();
assert!(display.contains(&long_msg));
}
#[test]
fn test_init_with_otel_idempotent() {
let err = OtelError::AlreadyInitialized;
assert!(matches!(err, OtelError::AlreadyInitialized));
}
#[test]
fn test_shutdown_otel_not_initialized() {
use std::sync::OnceLock;
static TEST_PROVIDER: OnceLock<()> = OnceLock::new();
assert!(TEST_PROVIDER.get().is_none());
}
#[test]
fn test_tracer_provider_oncelock_behavior() {
use std::sync::OnceLock;
static TEST_LOCK: OnceLock<String> = OnceLock::new();
let result = TEST_LOCK.set("first".to_string());
assert!(result.is_ok());
let result = TEST_LOCK.set("second".to_string());
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "second");
assert_eq!(TEST_LOCK.get(), Some(&"first".to_string()));
}
}