use std::io;
use std::sync::{Arc, Condvar, Mutex};
use ticklog::{info, Backpressure, Level, LogSink};
const PRODUCED: usize = 200_000;
struct Gate {
open: Mutex<bool>,
ready: Condvar,
}
impl Gate {
fn new() -> Self {
Self {
open: Mutex::new(false),
ready: Condvar::new(),
}
}
fn wait(&self) {
let mut open = self.open.lock().unwrap();
while !*open {
open = self.ready.wait(open).unwrap();
}
}
fn open(&self) {
*self.open.lock().unwrap() = true;
self.ready.notify_all();
}
}
struct GateSink {
lines: Arc<Mutex<Vec<String>>>,
gate: Arc<Gate>,
passed: bool,
}
impl LogSink for GateSink {
fn accept(&mut self, line: &[u8], _level: Level) -> io::Result<()> {
if !self.passed {
self.gate.wait();
self.passed = true;
}
self.lines
.lock()
.unwrap()
.push(String::from_utf8_lossy(line).into_owned());
Ok(())
}
}
fn seq_of(line: &str) -> Option<usize> {
line.rsplit("seq=").next()?.trim().parse().ok()
}
#[test]
fn drop_policy_discards_newest_records_and_never_blocks() {
let lines = Arc::new(Mutex::new(Vec::new()));
let gate = Arc::new(Gate::new());
let sink = GateSink {
lines: Arc::clone(&lines),
gate: Arc::clone(&gate),
passed: false,
};
let guard = ticklog::builder()
.sink(sink)
.max_level(Level::Info)
.backpressure(Backpressure::Drop)
.build()
.expect("first build in a fresh process must succeed");
for i in 0..PRODUCED {
info!("seq={}", i as u64);
}
gate.open();
drop(guard);
let captured = lines.lock().unwrap();
let received = captured.len();
assert!(received > 0, "expected some records to survive");
assert!(
received < PRODUCED,
"Drop must discard under saturation, but all {PRODUCED} survived"
);
let seqs: Vec<usize> = captured
.iter()
.map(|line| seq_of(line).expect("each line ends in seq=<i>"))
.collect();
let expected: Vec<usize> = (0..received).collect();
assert_eq!(seqs, expected, "survivors must be an in-order prefix");
}