//! Rotation / truncation detection integration tests.
//!
//! These exercise the poll engine directly rather than through the CLI
//! because simulating inotify / kqueue events is too flaky on shared CI.
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::time::Duration;
use ripgrab::tail::engine::{poll, Event};
use tempfile::NamedTempFile;
use tokio::sync::mpsc;
use tokio::time::timeout;
async fn recv_first(rx: &mut mpsc::Receiver<Event>) -> Option<Event> {
timeout(Duration::from_secs(1), rx.recv()).await.ok().flatten()
}
#[tokio::test]
async fn append_emits_grew() {
let mut tmp = NamedTempFile::new().unwrap();
writeln!(tmp, "initial").unwrap();
tmp.flush().unwrap();
let path = tmp.path().to_path_buf();
let (tx, mut rx) = mpsc::channel(8);
let handle = tokio::spawn(poll::run_every(
vec![path.clone()],
tx,
Duration::from_millis(30),
));
{
let mut f = OpenOptions::new().append(true).open(&path).unwrap();
writeln!(f, "second").unwrap();
f.flush().unwrap();
}
let ev = recv_first(&mut rx).await;
handle.abort();
assert!(matches!(ev, Some(Event::Grew(_))));
}
#[tokio::test]
async fn truncate_emits_shrunk() {
let mut tmp = NamedTempFile::new().unwrap();
writeln!(tmp, "bigger content").unwrap();
tmp.flush().unwrap();
let path = tmp.path().to_path_buf();
let (tx, mut rx) = mpsc::channel(8);
let handle = tokio::spawn(poll::run_every(
vec![path.clone()],
tx,
Duration::from_millis(30),
));
{
let f = OpenOptions::new().write(true).open(&path).unwrap();
f.set_len(0).unwrap();
}
let ev = recv_first(&mut rx).await;
handle.abort();
assert!(matches!(ev, Some(Event::Shrunk(_))));
}
#[tokio::test]
async fn rotation_emits_rotated() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("app.log");
{
let mut f = File::create(&path).unwrap();
writeln!(f, "first").unwrap();
}
let (tx, mut rx) = mpsc::channel(8);
let handle = tokio::spawn(poll::run_every(
vec![path.clone()],
tx,
Duration::from_millis(30),
));
// Simulate logrotate: rename away, create fresh file.
let rotated = tmp.path().join("app.log.1");
std::fs::rename(&path, &rotated).unwrap();
let mut fresh = File::create(&path).unwrap();
writeln!(fresh, "post-rotation").unwrap();
fresh.flush().unwrap();
let ev = recv_first(&mut rx).await;
handle.abort();
assert!(matches!(ev, Some(Event::Rotated(_))));
}
#[tokio::test]
async fn vanishing_path_emits_gone() {
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_path_buf();
drop(tmp);
let (tx, mut rx) = mpsc::channel(8);
let handle = tokio::spawn(poll::run_every(
vec![path.clone()],
tx,
Duration::from_millis(30),
));
let ev = recv_first(&mut rx).await;
handle.abort();
assert!(matches!(ev, Some(Event::Gone(_))));
}