use std::sync::Arc;
use std::time::Duration;
use insomnilog::{
AlreadyStarted, BackendOptions, LogLevel, LoggerAlreadyRegistered, NullSink, ShutdownGuard,
Sink, create_logger, get_logger, get_sink, register_sink, shutdown, start,
};
#[test]
fn start_with_default_options_returns_guard() {
let guard: ShutdownGuard =
start(BackendOptions::default()).expect("first start should succeed");
drop(guard);
}
#[test]
fn start_with_custom_options_succeeds() {
let opts = BackendOptions {
thread_name: "custom-backend".into(),
idle_sleep: Duration::from_millis(1),
..BackendOptions::default()
};
let _guard = start(opts).expect("start with custom options should succeed");
}
#[test]
#[cfg(all(target_os = "linux", not(miri)))]
fn backend_thread_name_is_visible_in_os_thread_list() {
let opts = BackendOptions {
thread_name: "custom-backend".into(),
..BackendOptions::default()
};
let _guard = start(opts).expect("start should succeed");
let deadline = std::time::Instant::now() + Duration::from_secs(1);
let found = loop {
let visible = std::fs::read_dir("/proc/self/task")
.expect("/proc/self/task should be readable")
.filter_map(|entry| std::fs::read_to_string(entry.ok()?.path().join("comm")).ok())
.any(|s| s.trim() == "custom-backend");
if visible || std::time::Instant::now() >= deadline {
break visible;
}
std::thread::sleep(Duration::from_millis(10));
};
assert!(
found,
"expected a thread named 'custom-backend' to appear within 1s"
);
}
#[test]
fn start_called_twice_returns_already_started() {
let _guard = start(BackendOptions::default()).expect("first start should succeed");
let result: Result<ShutdownGuard, AlreadyStarted> = start(BackendOptions::default());
assert!(
matches!(result, Err(AlreadyStarted)),
"second start should return Err(AlreadyStarted)"
);
}
#[test]
fn shutdown_without_start_is_a_noop() {
shutdown();
shutdown();
}
#[test]
fn shutdown_called_twice_after_start_is_idempotent() {
let _guard = start(BackendOptions::default()).expect("start should succeed");
shutdown();
shutdown();
}
#[test]
fn manual_shutdown_then_guard_drop_does_not_panic() {
let guard = start(BackendOptions::default()).expect("start should succeed");
shutdown();
drop(guard); }
#[test]
fn guard_drop_alone_shuts_the_backend_down() {
{
let _guard = start(BackendOptions::default()).expect("start should succeed");
}
shutdown();
}
#[test]
fn concurrent_start_only_one_thread_wins() {
use std::sync::Barrier;
use std::thread;
const N: usize = 8;
let barrier = std::sync::Arc::new(Barrier::new(N));
#[expect(
clippy::needless_collect,
reason = "all threads must be spawned before any is joined; \
a lazy chain would deadlock on the Barrier"
)]
let handles: Vec<_> = (0..N)
.map(|_| {
let barrier = std::sync::Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
start(BackendOptions::default())
})
})
.collect();
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
let ok_count = results.iter().filter(|r| r.is_ok()).count();
let err_count = results
.iter()
.filter(|r| matches!(r, Err(AlreadyStarted)))
.count();
assert_eq!(ok_count, 1, "exactly one start() must succeed");
assert_eq!(err_count, N - 1, "all others must report AlreadyStarted");
}
#[test]
#[should_panic(expected = "insomnilog: call insomnilog::start() before using the logger")]
fn register_sink_panics_when_backend_not_started() {
let _ = register_sink("console", Arc::new(NullSink::new(LogLevel::Info)));
}
#[test]
#[should_panic(expected = "insomnilog: call insomnilog::start() before using the logger")]
fn get_sink_panics_when_backend_not_started() {
let _ = get_sink("console");
}
#[test]
fn register_sink_after_start_inserts_the_sink() {
let _guard = start(BackendOptions::default()).expect("start should succeed");
register_sink("console", Arc::new(NullSink::new(LogLevel::Warning)))
.expect("first registration must succeed");
assert_eq!(
get_sink("console").expect("must be retrievable").level(),
LogLevel::Warning,
);
}
#[test]
fn get_sink_returns_registered_sink_after_start() {
let _guard = start(BackendOptions::default()).expect("start should succeed");
let sink: Arc<dyn Sink> = Arc::new(NullSink::new(LogLevel::Info));
register_sink("console", Arc::clone(&sink)).expect("first registration must succeed");
let fetched = get_sink("console").expect("registered sink must be retrievable");
assert!(Arc::ptr_eq(&sink, &fetched));
let err = register_sink("console", Arc::new(NullSink::new(LogLevel::Error)))
.expect_err("duplicate registration must fail");
assert!(
Arc::ptr_eq(&sink, &err.existing),
"error must carry the originally registered Arc",
);
assert_eq!(err.existing.level(), LogLevel::Info);
}
#[test]
#[should_panic(expected = "insomnilog: call insomnilog::start() before using the logger")]
fn create_logger_panics_when_backend_not_started() {
let _ = create_logger("app", Vec::new(), LogLevel::Info);
}
#[test]
#[should_panic(expected = "insomnilog: call insomnilog::start() before using the logger")]
fn get_logger_panics_when_backend_not_started() {
let _ = get_logger("app");
}
#[test]
fn create_logger_after_start_registers_and_returns() {
let _guard = start(BackendOptions::default()).expect("start should succeed");
let logger = create_logger("app", Vec::new(), LogLevel::Warning)
.expect("first registration must succeed");
assert_eq!(logger.name(), "app");
assert_eq!(logger.level(), LogLevel::Warning);
}
#[test]
fn get_logger_returns_registered_logger_after_start() {
let _guard = start(BackendOptions::default()).expect("start should succeed");
let registered =
create_logger("app", Vec::new(), LogLevel::Info).expect("first registration must succeed");
let fetched = get_logger("app").expect("registered logger must be retrievable");
assert!(Arc::ptr_eq(®istered, &fetched));
}
#[test]
fn create_logger_returns_error_on_duplicate_name() {
let _guard = start(BackendOptions::default()).expect("start should succeed");
let first =
create_logger("app", Vec::new(), LogLevel::Info).expect("first registration must succeed");
let err: LoggerAlreadyRegistered =
create_logger("app", Vec::new(), LogLevel::Error).expect_err("duplicate must fail");
assert!(
Arc::ptr_eq(&first, &err.existing),
"error must carry the originally registered Arc",
);
assert_eq!(
err.existing.level(),
LogLevel::Info,
"existing logger must be unchanged",
);
}
#[test]
fn start_after_shutdown_still_returns_already_started() {
let _guard = start(BackendOptions::default()).expect("first start should succeed");
shutdown();
let result = start(BackendOptions::default());
assert!(
matches!(result, Err(AlreadyStarted)),
"start after shutdown should still report AlreadyStarted"
);
}