use super::super::{Config, Entry, Launch, Phase, Shared, State};
use super::{LINE_BYTES, Log, drain, read_lines};
use std::io::{self, Cursor, Write};
use std::sync::{Arc, Condvar, Mutex, atomic::AtomicBool};
type TestResult = Result<(), Box<dyn std::error::Error>>;
struct Writer {
full: bool,
}
impl Write for Writer {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
if self.full {
Err(io::Error::other("fixture log is full"))
} else {
Ok(bytes.len())
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn review_1_log_failure_is_one_incident_across_restarts_until_a_write_succeeds() {
let failed = Arc::new(AtomicBool::new(false));
let mut first = Log {
writer: Writer { full: true },
failed: Arc::clone(&failed),
};
assert!(first.append(b"one").is_some());
assert!(first.append(b"two").is_none());
let mut restarted = Log {
writer: Writer { full: true },
failed,
};
assert!(restarted.append(b"three").is_none());
restarted.writer.full = false;
assert!(restarted.append(b"four").is_none());
restarted.writer.full = true;
assert!(restarted.append(b"five").is_some());
}
#[test]
fn review_7_an_oversized_ready_prefix_is_never_announced() -> TestResult {
let mut bytes = b"fixture ready false".to_vec();
bytes.resize(1_048_576, b'x');
bytes.extend_from_slice(b"\nfixture ready actual\n");
let mut log = Log {
writer: Vec::new(),
failed: Arc::new(AtomicBool::new(false)),
};
let mut seen = Vec::new();
drain(&mut Cursor::new(bytes), &mut log, "fixture", |line| {
seen.push(line.to_vec());
Ok(())
})?;
assert_eq!(seen, vec![b"fixture ready actual\n".to_vec()]);
assert_eq!(
log.writer.len(),
LINE_BYTES + b"fixture ready actual\n".len()
);
Ok(())
}
#[test]
fn review_2_old_generation_keeps_draining_without_marking_the_replacement_ready() -> TestResult {
let shared = Shared {
state: Mutex::new(State {
stopped: false,
entries: vec![Entry {
phase: Phase::NotReady,
generation: 2,
..Entry::default()
}],
}),
changed: Condvar::new(),
};
let config: Config = serde_json::from_value(
serde_json::json!({"name":"fixture", "program":"/unused", "args":[],"ready":"fixture ready", "log":"/unused-log", "give_up_after":3}),
)?;
let mut old = Vec::new();
read_lines(
&shared,
0,
Launch {
generation: 1,
started: std::time::Instant::now(),
},
&config,
Cursor::new(b"fixture ready old\nold stderr still drains\n"),
&mut old,
Arc::new(AtomicBool::new(false)),
)?;
assert_eq!(old, b"fixture ready old\nold stderr still drains\n");
assert!(shared.state.lock().map_err(|e| e.to_string())?.entries[0].phase == Phase::NotReady);
read_lines(
&shared,
0,
Launch {
generation: 2,
started: std::time::Instant::now(),
},
&config,
Cursor::new(b"fixture ready new\n"),
Vec::new(),
Arc::new(AtomicBool::new(false)),
)?;
let mut state = shared.state.lock().map_err(|e| e.to_string())?;
assert!(state.entries[0].phase == Phase::Ready);
state.entries[0].phase = Phase::Exited;
drop(state);
read_lines(
&shared,
0,
Launch {
generation: 2,
started: std::time::Instant::now(),
},
&config,
Cursor::new(b"fixture ready after exit\n"),
Vec::new(),
Arc::new(AtomicBool::new(false)),
)?;
assert!(shared.state.lock().map_err(|e| e.to_string())?.entries[0].phase == Phase::Exited);
Ok(())
}