#![cfg(feature = "hot-reload")]
#![allow(deprecated)]
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use config_lib::hot_reload::{ConfigChangeEvent, HotReloadConfig};
use std::fs::File;
use std::io::Write;
use std::time::Duration;
use tempfile::TempDir;
fn atomic_replace(target: &std::path::Path, body: &str) {
let tmp = target.with_extension("conf.tmp");
let mut f = File::create(&tmp).unwrap();
f.write_all(body.as_bytes()).unwrap();
f.flush().unwrap();
f.sync_all().unwrap();
drop(f);
std::fs::rename(&tmp, target).unwrap();
}
#[test]
fn atomic_rename_save_emits_single_reloaded_event() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("app.conf");
{
let mut f = File::create(&path).unwrap();
f.write_all(b"key=value1\n").unwrap();
f.sync_all().unwrap();
}
let (hot, rx) = HotReloadConfig::from_file(&path)
.unwrap()
.with_debounce(Duration::from_millis(100))
.with_change_notifications();
let handle = hot.start_watching();
std::thread::sleep(Duration::from_millis(200));
atomic_replace(&path, "key=value2\n");
let collect_deadline = std::time::Instant::now() + Duration::from_millis(500);
let mut reloaded_count = 0;
while std::time::Instant::now() < collect_deadline {
if let Ok(event) = rx.recv_timeout(Duration::from_millis(50)) {
if matches!(event, ConfigChangeEvent::Reloaded { .. }) {
reloaded_count += 1;
}
}
}
assert!(
reloaded_count >= 1,
"expected at least one Reloaded event from the atomic rename"
);
assert!(
reloaded_count <= 2,
"expected the debounce window to collapse the event burst; \
got {reloaded_count} Reloaded events (>=2 indicates the debounce \
is not collapsing the rename burst as designed)"
);
handle.stop().unwrap();
}