use super::*;
use crate::opslog::{self, Activity, YOG_DRIFT};
use tempfile::tempdir;
fn failure() -> OpRow {
OpRow::from(&OpEntry::synthetic_failure(
"1".into(),
vec!["bl".into(), "close".into(), "bl-4db6".into()],
"/proj".into(),
"gate refused\n".into(),
Origin::Balls,
crate::registry::Client::default(),
))
}
fn drift() -> OpRow {
OpRow::from(&OpEntry::drift(
"2".into(),
"unannounced",
"/state".into(),
"/root\n".into(),
))
}
fn ack_row() -> OpRow {
OpRow::from(&entry(
"3",
Path::new("/state"),
ACK_STEP,
crate::registry::Client::default(),
))
}
#[test]
fn with_no_ack_every_row_is_still_live() {
let rows = vec![failure(), drift()];
assert_eq!(since_ack(&rows).len(), 2);
assert_eq!(
opslog::activity(&rows),
Activity {
total: 2,
errors: 1,
drifts: 1
}
);
}
#[test]
fn an_ack_quiets_every_alarm_before_it_and_hides_no_row() {
let rows = vec![failure(), drift(), ack_row()];
assert!(
since_ack(&rows).is_empty(),
"nothing after the ack is unacknowledged"
);
assert_eq!(
opslog::activity(&rows),
Activity {
total: 3,
errors: 0,
drifts: 0
}
);
assert_eq!(
opslog::activity(&rows).chip(),
"activity · 3 ops",
"no ⚠, no drift word — and the op count is the trail's, undiminished"
);
}
#[test]
fn a_new_failure_after_an_ack_re_alarms() {
let rows = vec![failure(), ack_row(), failure()];
assert_eq!(since_ack(&rows).len(), 1);
assert_eq!(opslog::activity(&rows).errors, 1);
let rows = vec![failure(), ack_row(), failure(), ack_row()];
assert_eq!(opslog::activity(&rows).errors, 0);
}
#[test]
fn only_a_real_ack_line_moves_the_watermark() {
let other_step = OpRow::from(&OpEntry::step_done(
"9".into(),
"mint",
"/state".into(),
Origin::World,
crate::registry::Client::default(),
));
let foreign = OpRow::from(&OpEntry::drift(
"9".into(),
ACK_STEP,
"/state".into(),
String::new(),
));
assert_eq!(foreign.argv, format!("{YOG_DRIFT} {ACK_STEP}"));
let rows = vec![failure(), other_step, foreign];
assert_eq!(since_ack(&rows).len(), 3);
assert_eq!(opslog::activity(&rows).errors, 1);
}
#[test]
fn ack_appends_a_clean_world_step_line_that_banners_nowhere() {
let dir = tempdir().unwrap();
ack(dir.path(), "17", crate::registry::Client::default()).unwrap();
let rows: Vec<OpRow> = opslog::tail(dir.path(), 8)
.iter()
.map(OpRow::from)
.collect();
let row = rows.first().unwrap();
assert_eq!(row.argv, format!("{YOG_STEP} {ACK_STEP}"));
assert_eq!(row.ts, "17");
assert_eq!(row.origin, Origin::World);
assert!(!row.failed(), "an ack is not an alarm about itself");
assert!(!row.drift());
assert_eq!(row.exit_label(), "exit 0");
assert!(!row.has_output(), "and it carries no captured streams");
}
#[test]
fn clear_leaves_a_one_row_trail_whose_row_is_the_clear() {
let dir = tempdir().unwrap();
for n in 0..3 {
opslog::append(
dir.path(),
&OpEntry::synthetic_failure(
n.to_string(),
vec!["bl".into(), "close".into()],
"/proj".into(),
"boom".into(),
Origin::Balls,
crate::registry::Client::default(),
),
)
.unwrap();
}
assert_eq!(opslog::tail(dir.path(), 8).len(), 3);
clear(dir.path(), "42", crate::registry::Client::default()).unwrap();
let rows: Vec<OpRow> = opslog::tail(dir.path(), 8)
.iter()
.map(OpRow::from)
.collect();
assert_eq!(rows.len(), 1, "the trail is exactly its own first row");
let row = rows.first().unwrap();
assert_eq!(row.argv, format!("{YOG_STEP} {CLEAR_STEP}"));
assert_eq!(row.ts, "42");
assert_eq!(row.origin, Origin::World);
assert!(!row.failed());
assert_eq!(
opslog::activity(&rows),
Activity {
total: 1,
errors: 0,
drifts: 0
}
);
}
#[test]
fn clear_founds_a_missing_state_root() {
let dir = tempdir().unwrap();
let root = dir.path().join("yog").join("state");
clear(&root, "1", crate::registry::Client::default()).unwrap();
assert_eq!(opslog::tail(&root, 8).len(), 1);
}
#[test]
fn a_concurrent_append_after_the_truncate_is_not_clobbered() {
let dir = tempdir().unwrap();
let mut handle = fs::OpenOptions::new()
.create(true)
.append(true)
.open(dir.path().join(FILENAME))
.unwrap();
handle.set_len(0).unwrap();
opslog::append(
dir.path(),
&OpEntry::step_done(
"7".into(),
"mint",
"/state".into(),
Origin::World,
crate::registry::Client::default(),
),
)
.unwrap();
handle
.write_all(&build_line(&entry(
"8",
dir.path(),
CLEAR_STEP,
crate::registry::Client::default(),
)))
.unwrap();
let tail = opslog::tail(dir.path(), 8);
assert_eq!(tail.len(), 2);
assert_eq!(tail.first().unwrap().ts, "7");
assert_eq!(
tail.last().unwrap().argv,
vec![YOG_STEP.to_owned(), CLEAR_STEP.to_owned()]
);
}
#[test]
fn both_shapes_are_bounded_and_inside_the_pipe_buf_cap() {
for step in [ACK_STEP, CLEAR_STEP] {
let e = entry(
"1785630266",
Path::new("/home/u/.local/state/yog"),
step,
crate::registry::Client::default(),
);
assert!(e.stdout.is_empty() && e.stderr.is_empty());
assert_eq!(e.argv.len(), 2);
assert!(build_line(&e).len() <= opslog::CAP);
}
}