src/tail/engine/poll_test.rs

#[cfg(test)]
mod tests {
    use std::io::Write;
    use std::time::Duration;

    use tempfile::NamedTempFile;
    use tokio::sync::mpsc;
    use tokio::time::timeout;

    use crate::tail::engine::{poll, Event};

    async fn next_event(
        rx: &mut mpsc::Receiver<Event>,
        max: Duration,
    ) -> Option<Event> {
        timeout(max, rx.recv()).await.ok().flatten()
    }

    #[tokio::test]
    async fn detects_growth() {
        let mut tmp = NamedTempFile::new().unwrap();
        writeln!(tmp, "first").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),
        ));
        writeln!(tmp, "second").unwrap();
        tmp.flush().unwrap();
        let event = next_event(&mut rx, Duration::from_secs(1)).await;
        handle.abort();
        assert!(matches!(event, Some(Event::Grew(p)) if p == path));
    }

    #[tokio::test]
    async fn detects_truncation() {
        let mut tmp = NamedTempFile::new().unwrap();
        writeln!(tmp, "hello").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),
        ));
        tmp.as_file().set_len(0).unwrap();
        let event = next_event(&mut rx, Duration::from_secs(1)).await;
        handle.abort();
        assert!(matches!(event, Some(Event::Shrunk(p)) if p == path));
    }

    #[tokio::test]
    async fn missing_path_reports_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 event = next_event(&mut rx, Duration::from_secs(1)).await;
        handle.abort();
        assert!(matches!(event, Some(Event::Gone(p)) if p == path));
    }
}