use anyhow::Result;
use kerf::{ArcEvent, Config, Event, Kerf, Level, Match};
use std::sync::{Arc, Mutex};
use tokio::time::{Duration, sleep};
fn create_test_event(level: Level, message: &str) -> ArcEvent {
create_test_event_with_module(level, message, "test::module")
}
fn create_test_event_with_module(level: Level, message: &str, module_path: &str) -> ArcEvent {
Arc::new(Event {
id: 1,
timestamp: chrono::Local::now(),
level,
target: "test".to_string(),
name: "test_event".to_string(),
module_path: Some(module_path.to_string()),
file: Some("test.rs".to_string()),
line: Some(42),
message: message.to_string(),
fields: std::collections::HashMap::new(),
span_name: None,
span_hierarchy: None,
})
}
#[tokio::test]
async fn test_per_tab_callbacks() -> Result<()> {
let console_events = Arc::new(Mutex::new(Vec::new()));
let error_events = Arc::new(Mutex::new(Vec::new()));
let debug_events = Arc::new(Mutex::new(Vec::new()));
let config = Config::empty()
.with_tab("console", Match::info().module_pattern("console::*"))
.with_tab("errors", Match::error().module_pattern("error::*"))
.with_tab("debug", Match::debug().module_pattern("debug::*"));
let console_events_clone = Arc::clone(&console_events);
let error_events_clone = Arc::clone(&error_events);
let debug_events_clone = Arc::clone(&debug_events);
let kerf = Kerf::new_with_config(config)
.with_tab_callback("console", move |event| {
console_events_clone
.lock()
.unwrap()
.push(event.message.clone());
})
.with_tab_callback("errors", move |event| {
error_events_clone
.lock()
.unwrap()
.push(event.message.clone());
})
.with_tab_callback("debug", move |event| {
debug_events_clone
.lock()
.unwrap()
.push(event.message.clone());
})
.build()
.await?;
let sender = kerf._get_sender_for_testing();
sender.send(create_test_event_with_module(
Level::INFO,
"This is an info message",
"console::main",
))?;
sender.send(create_test_event_with_module(
Level::ERROR,
"This is an error message",
"error::handler",
))?;
sender.send(create_test_event_with_module(
Level::DEBUG,
"This is a debug message",
"debug::trace",
))?;
sender.send(create_test_event_with_module(
Level::WARN,
"This is a warning message",
"other::module",
))?;
sleep(Duration::from_millis(100)).await;
let console_msgs = console_events.lock().unwrap();
let error_msgs = error_events.lock().unwrap();
let debug_msgs = debug_events.lock().unwrap();
assert_eq!(console_msgs.len(), 1);
assert!(console_msgs[0].contains("This is an info message"));
assert_eq!(error_msgs.len(), 1);
assert!(error_msgs[0].contains("This is an error message"));
assert_eq!(debug_msgs.len(), 1);
assert!(debug_msgs[0].contains("This is a debug message"));
Ok(())
}
#[tokio::test]
async fn test_mixed_callbacks() -> Result<()> {
let tab_events = Arc::new(Mutex::new(Vec::new()));
#[allow(clippy::type_complexity)]
let global_events: Arc<Mutex<Vec<(String, Vec<String>)>>> = Arc::new(Mutex::new(Vec::new()));
let config = Config::empty()
.with_tab("with_callback", Match::info().module_pattern("info::*"))
.with_tab(
"without_callback",
Match::error().module_pattern("error::*"),
);
let tab_events_clone = Arc::clone(&tab_events);
let global_events_clone = Arc::clone(&global_events);
let kerf = Kerf::new_with_config(config)
.with_tab_callback("with_callback", move |event| {
tab_events_clone.lock().unwrap().push(event.message.clone());
})
.build()
.await?;
kerf.set_callback(move |event, tab_names| {
let tab_names_owned: Vec<String> = tab_names.iter().map(|s| s.to_string()).collect();
global_events_clone
.lock()
.unwrap()
.push((event.message.clone(), tab_names_owned));
})?
.await??;
let sender = kerf._get_sender_for_testing();
sender.send(create_test_event_with_module(
Level::INFO,
"Info message",
"info::test",
))?; sender.send(create_test_event_with_module(
Level::ERROR,
"Error message",
"error::test",
))?;
sleep(Duration::from_millis(100)).await;
let tab_msgs = tab_events.lock().unwrap();
let global_msgs = global_events.lock().unwrap();
assert_eq!(tab_msgs.len(), 1);
assert!(tab_msgs[0].contains("Info message"));
assert_eq!(global_msgs.len(), 1);
assert!(global_msgs[0].0.contains("Error message"));
assert_eq!(global_msgs[0].1, vec!["without_callback".to_string()]);
Ok(())
}
#[tokio::test]
async fn test_dynamic_tab_callbacks() -> Result<()> {
let events = Arc::new(Mutex::new(Vec::new()));
let config = Config::empty().with_tab("dynamic", Match::info().all_modules());
let kerf = Kerf::new_with_config(config);
let sender = kerf._get_sender_for_testing();
sender.send(create_test_event(Level::INFO, "Message 1"))?;
sleep(Duration::from_millis(50)).await;
let events_clone = Arc::clone(&events);
kerf.set_tab_callback("dynamic", move |event| {
events_clone.lock().unwrap().push(event.message.clone());
})
.await?;
sender.send(create_test_event(Level::INFO, "Message 2"))?;
sleep(Duration::from_millis(50)).await;
kerf.remove_tab_callback("dynamic").await?;
sender.send(create_test_event(Level::INFO, "Message 3"))?;
sleep(Duration::from_millis(50)).await;
let captured_events = events.lock().unwrap();
assert_eq!(captured_events.len(), 1);
assert!(captured_events[0].contains("Message 2"));
Ok(())
}
#[tokio::test]
async fn test_multiple_tabs_same_event() -> Result<()> {
let tab1_events = Arc::new(Mutex::new(Vec::new()));
let tab2_events = Arc::new(Mutex::new(Vec::new()));
let config = Config::empty()
.with_tab("all_info", Match::info().all_modules())
.with_tab("all_levels", Match::trace().all_modules());
let tab1_events_clone = Arc::clone(&tab1_events);
let tab2_events_clone = Arc::clone(&tab2_events);
let kerf = Kerf::new_with_config(config)
.with_tab_callback("all_info", move |event| {
tab1_events_clone
.lock()
.unwrap()
.push(format!("TAB1: {}", event.message));
})
.with_tab_callback("all_levels", move |event| {
tab2_events_clone
.lock()
.unwrap()
.push(format!("TAB2: {}", event.message));
})
.build()
.await?;
let sender = kerf._get_sender_for_testing();
sender.send(create_test_event(Level::INFO, "Shared message"))?;
sleep(Duration::from_millis(100)).await;
let tab1_msgs = tab1_events.lock().unwrap();
let tab2_msgs = tab2_events.lock().unwrap();
assert_eq!(tab1_msgs.len(), 1);
assert!(tab1_msgs[0].contains("TAB1: Shared message"));
assert_eq!(tab2_msgs.len(), 1);
assert!(tab2_msgs[0].contains("TAB2: Shared message"));
Ok(())
}
#[tokio::test]
async fn test_error_handling() -> Result<()> {
let config = Config::empty().with_tab("test", Match::info().all_modules());
let kerf = Kerf::new_with_config(config);
let result = kerf.set_tab_callback("nonexistent", |_event| {}).await;
assert!(result.is_err());
let result = kerf.remove_tab_callback("nonexistent").await;
assert!(result.is_err());
Ok(())
}
#[tokio::test]
async fn test_serializable_config() -> Result<()> {
let config = Config::empty()
.with_tab("test1", Match::info().all_modules())
.with_tab("test2", Match::error().all_modules());
let serialized = serde_json::to_string(&config)?;
let deserialized: Config = serde_json::from_str(&serialized)?;
assert_eq!(config.tabs.len(), deserialized.tabs.len());
assert_eq!(config.tabs[0].name, deserialized.tabs[0].name);
assert_eq!(config.tabs[1].name, deserialized.tabs[1].name);
Ok(())
}