use std::error::Error;
use super::*;
#[test]
fn test_log_error_display() {
assert_eq!(LogError::MissingFilePath.to_string(), "file_path required for File output");
assert_eq!(LogError::InvalidFilter("bad".into()).to_string(), "invalid filter: bad");
assert_eq!(
LogError::SetGlobalDefault("already set".into()).to_string(),
"failed to set global subscriber: already set"
);
}
#[test]
fn test_level_to_filter_str() {
assert_eq!(level_to_filter_str(Level::Error), "error");
assert_eq!(level_to_filter_str(Level::Warn), "warn");
assert_eq!(level_to_filter_str(Level::Info), "info");
assert_eq!(level_to_filter_str(Level::Debug), "debug");
assert_eq!(level_to_filter_str(Level::Trace), "trace");
}
#[test]
fn test_log_error_debug() {
let err = LogError::MissingFilePath;
let debug_str = format!("{err:?}");
assert!(debug_str.contains("MissingFilePath"));
}
#[test]
fn test_log_error_from_io() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let log_err = LogError::from(io_err);
assert!(matches!(log_err, LogError::Io(_)));
}
#[test]
fn test_log_error_source() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let log_err = LogError::Io(io_err);
assert!(log_err.source().is_some());
let missing_err = LogError::MissingFilePath;
assert!(missing_err.source().is_none());
}
#[test]
fn test_log_error_io_display() {
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "access denied");
let log_err = LogError::Io(io_err);
let display = log_err.to_string();
assert!(display.contains("I/O error"));
assert!(display.contains("access denied"));
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_log_error_source_all_variants() {
let err = LogError::MissingFilePath;
assert!(err.source().is_none());
let err = LogError::InvalidFilter("bad filter".into());
assert!(err.source().is_none());
let err = LogError::SetGlobalDefault("already set".into());
assert!(err.source().is_none());
let io_err = io::Error::new(io::ErrorKind::NotFound, "gone");
let err = LogError::Io(io_err);
assert!(err.source().is_some());
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn test_log_error_from_io_preserves_error_kind() {
let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "pipe broken");
let log_err = LogError::from(io_err);
match log_err {
LogError::Io(ref e) => assert_eq!(e.kind(), io::ErrorKind::BrokenPipe),
_ => panic!("Expected LogError::Io"),
}
}
#[test]
fn test_log_error_debug_all_variants() {
let variants: Vec<LogError> = vec![
LogError::MissingFilePath,
LogError::InvalidFilter("test".into()),
LogError::SetGlobalDefault("test".into()),
LogError::Io(io::Error::other("test")),
];
for variant in &variants {
let debug_str = format!("{variant:?}");
assert!(!debug_str.is_empty());
}
}
#[test]
fn test_log_error_display_missing_file_path() {
let err = LogError::MissingFilePath;
assert_eq!(err.to_string(), "file_path required for File output");
}
#[test]
fn test_log_error_display_invalid_filter() {
let err = LogError::InvalidFilter("not a valid directive".into());
assert_eq!(err.to_string(), "invalid filter: not a valid directive");
}
#[test]
fn test_log_error_display_set_global_default() {
let err = LogError::SetGlobalDefault("a]ready set".into());
assert_eq!(err.to_string(), "failed to set global subscriber: a]ready set");
}
#[test]
fn test_level_to_filter_str_exhaustive() {
let levels = Level::ALL;
let strs: Vec<&str> = levels.iter().map(|l| level_to_filter_str(*l)).collect();
for s in &strs {
assert!(!s.is_empty());
}
for (i, a) in strs.iter().enumerate() {
for (j, b) in strs.iter().enumerate() {
if i != j {
assert_ne!(a, b, "Filter strings at indices {i} and {j} should differ");
}
}
}
}
#[test]
fn test_build_filter_with_default_config() {
let config = LogConfig::default();
let filter = build_filter(&config);
assert!(filter.is_ok());
}
#[test]
fn test_build_filter_with_all_levels() {
for level in Level::ALL {
let config = LogConfig {
level,
..Default::default()
};
let filter = build_filter(&config);
assert!(filter.is_ok(), "build_filter should succeed for level {level:?}");
}
}
#[test]
fn test_log_error_is_error_trait() {
fn assert_error<T: std::error::Error>() {}
assert_error::<LogError>();
}
#[test]
fn test_init_logging_stderr_plain_builds_subscriber() {
let config = LogConfig {
output: LogOutput::Stderr,
format: LogFormat::Plain,
level: Level::Info,
..Default::default()
};
let filter = build_filter(&config).unwrap();
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(io::stderr)
.with_file(true)
.with_line_number(true)
.with_target(true)
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!("test log from stderr plain");
});
}
#[test]
fn test_init_logging_stderr_json_builds_subscriber() {
let config = LogConfig {
output: LogOutput::Stderr,
format: LogFormat::Json,
level: Level::Debug,
..Default::default()
};
let filter = build_filter(&config).unwrap();
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(io::stderr)
.json()
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::debug!("test log from stderr json");
});
}
#[test]
fn test_init_logging_stderr_pretty_builds_subscriber() {
let config = LogConfig {
output: LogOutput::Stderr,
format: LogFormat::Pretty,
level: Level::Warn,
..Default::default()
};
let filter = build_filter(&config).unwrap();
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(io::stderr)
.pretty()
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::warn!("test log from stderr pretty");
});
}
#[test]
fn test_init_stdout_plain_builds_subscriber() {
let config = LogConfig {
output: LogOutput::Stdout,
format: LogFormat::Plain,
level: Level::Info,
..Default::default()
};
let filter = build_filter(&config).unwrap();
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(io::stdout)
.with_file(true)
.with_line_number(true)
.with_target(true)
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!("test log from stdout plain");
});
}
#[test]
fn test_init_stdout_json_builds_subscriber() {
let config = LogConfig {
output: LogOutput::Stdout,
format: LogFormat::Json,
level: Level::Info,
..Default::default()
};
let filter = build_filter(&config).unwrap();
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(io::stdout)
.json()
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!("test log from stdout json");
});
}
#[test]
fn test_init_stdout_pretty_builds_subscriber() {
let config = LogConfig {
output: LogOutput::Stdout,
format: LogFormat::Pretty,
level: Level::Info,
..Default::default()
};
let filter = build_filter(&config).unwrap();
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(io::stdout)
.pretty()
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!("test log from stdout pretty");
});
}
#[test]
fn test_init_file_json_builds_subscriber() {
let temp_dir = std::env::temp_dir().join("reovim_test_log_file_json");
let _ = std::fs::create_dir_all(&temp_dir);
let log_path = temp_dir.join("test.log");
let config = LogConfig {
output: LogOutput::File,
format: LogFormat::Json,
level: Level::Info,
file_path: Some(log_path.clone()),
rotation: RotationPolicy::Never,
};
let filter = build_filter(&config).unwrap();
let dir = log_path.parent().unwrap_or_else(|| Path::new("."));
let prefix = log_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("reovim");
let appender = rolling::never(dir, prefix);
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(non_blocking)
.json()
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!("test log to file json");
});
drop(guard);
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_init_file_plain_builds_subscriber() {
let temp_dir = std::env::temp_dir().join("reovim_test_log_file_plain");
let _ = std::fs::create_dir_all(&temp_dir);
let log_path = temp_dir.join("test.log");
let config = LogConfig {
output: LogOutput::File,
format: LogFormat::Plain,
level: Level::Info,
file_path: Some(log_path.clone()),
rotation: RotationPolicy::Never,
};
let filter = build_filter(&config).unwrap();
let dir = log_path.parent().unwrap_or_else(|| Path::new("."));
let prefix = log_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("reovim");
let appender = rolling::never(dir, prefix);
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(non_blocking)
.with_file(true)
.with_line_number(true)
.with_target(true)
.with_ansi(false)
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!("test log to file plain");
});
drop(guard);
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_init_file_pretty_builds_as_plain() {
let temp_dir = std::env::temp_dir().join("reovim_test_log_file_pretty");
let _ = std::fs::create_dir_all(&temp_dir);
let log_path = temp_dir.join("test.log");
let config = LogConfig {
output: LogOutput::File,
format: LogFormat::Pretty,
level: Level::Info,
file_path: Some(log_path.clone()),
rotation: RotationPolicy::Never,
};
let filter = build_filter(&config).unwrap();
let dir = log_path.parent().unwrap_or_else(|| Path::new("."));
let prefix = log_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("reovim");
let appender = rolling::never(dir, prefix);
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
let subscriber = fmt::Subscriber::builder()
.with_env_filter(filter)
.with_writer(non_blocking)
.with_file(true)
.with_line_number(true)
.with_target(true)
.with_ansi(false)
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!("test log to file pretty-as-plain");
});
drop(guard);
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_init_file_rotation_daily() {
let temp_dir = std::env::temp_dir().join("reovim_test_log_daily");
let _ = std::fs::create_dir_all(&temp_dir);
let log_path = temp_dir.join("daily.log");
let dir = log_path.parent().unwrap_or_else(|| Path::new("."));
let prefix = log_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("reovim");
let appender = rolling::daily(dir, prefix);
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
drop(non_blocking);
drop(guard);
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_init_file_rotation_hourly() {
let temp_dir = std::env::temp_dir().join("reovim_test_log_hourly");
let _ = std::fs::create_dir_all(&temp_dir);
let log_path = temp_dir.join("hourly.log");
let dir = log_path.parent().unwrap_or_else(|| Path::new("."));
let prefix = log_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("reovim");
let appender = rolling::hourly(dir, prefix);
let (non_blocking, guard) = tracing_appender::non_blocking(appender);
drop(non_blocking);
drop(guard);
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_init_logging_file_missing_path_error() {
let config = LogConfig {
output: LogOutput::File,
format: LogFormat::Plain,
level: Level::Info,
file_path: None,
rotation: RotationPolicy::Never,
};
let result = init_logging(&config);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), LogError::MissingFilePath));
}
#[test]
fn test_init_file_path_without_parent() {
let path = Path::new("justfilename.log");
let dir = path.parent().unwrap_or_else(|| Path::new("."));
assert_eq!(dir, Path::new(""));
}
#[test]
fn test_init_file_path_without_filename() {
let path = Path::new("/");
let prefix = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("reovim");
assert_eq!(prefix, "reovim");
}