Compare commits

...

5 Commits

Author SHA1 Message Date
Bel LaPointe be632d3da3 testing reveals the stage as source of truth IS angry 2025-11-11 17:31:44 -07:00
Bel LaPointe 90de4a0cfd tests pass 2025-11-11 17:29:31 -07:00
Bel LaPointe 3c64bb27de some tests 2025-11-11 17:18:03 -07:00
Bel LaPointe bcbcd6fbb7 test helpers 2025-11-11 17:09:42 -07:00
Bel LaPointe b5b794817c more test 2025-11-11 16:40:38 -07:00
1 changed files with 223 additions and 37 deletions

View File

@ -104,18 +104,19 @@ mod test_flags {
#[test] #[test]
fn test_flags_files_unhidden_only() { fn test_flags_files_unhidden_only() {
let d = tempdir::TempDir::new("").unwrap(); tests::with_dir(|d| {
std::fs::File::create(d.path().join("plain")).unwrap(); std::fs::File::create(d.path().join("plain")).unwrap();
std::fs::File::create(d.path().join(".hidden")).unwrap(); std::fs::File::create(d.path().join(".hidden")).unwrap();
let flags = Flags { let flags = Flags {
path: d.path().to_str().unwrap().to_string(), path: d.path().to_str().unwrap().to_string(),
add: None, add: None,
edit: false, edit: false,
dry_run: true, dry_run: true,
}; };
let files = flags.files().expect("failed to files from dir"); let files = flags.files().expect("failed to files from dir");
assert_eq!(1, files.files.len()); assert_eq!(1, files.files.len());
});
} }
} }
@ -158,15 +159,15 @@ impl File {
pub fn persist_stage(&self) -> Result<(), String> { pub fn persist_stage(&self) -> Result<(), String> {
let persisted = self.events()?.snapshot()?; let persisted = self.events()?.snapshot()?;
let snapshot = serde_json::to_string(&persisted).unwrap(); let persisted = serde_json::to_string(&persisted).unwrap();
let snapshot = snapshot.as_str(); let persisted = persisted.as_str();
let snapshot: serde_json::Value = serde_json::from_str(&snapshot).unwrap(); let persisted: serde_json::Value = serde_json::from_str(&persisted).unwrap();
let stage = self.stage()?; let stage = self.stage()?;
let stage = serde_json::to_string(&stage).unwrap(); let stage = serde_json::to_string(&stage).unwrap();
let stage: serde_json::Value = serde_json::from_str(stage.as_str()).unwrap(); let stage: serde_json::Value = serde_json::from_str(stage.as_str()).unwrap();
let patches = json_patch::diff(&snapshot, &stage); let patches = json_patch::diff(&persisted, &stage);
let deltas: Vec<Delta> = patches let deltas: Vec<Delta> = patches
.iter() .iter()
.map(|patch| patch.clone()) .map(|patch| patch.clone())
@ -233,12 +234,125 @@ mod test_file {
use super::*; use super::*;
#[test] #[test]
fn test_file() { fn test_file_empty_empty() {
let d = tempdir::TempDir::new("").unwrap(); tests::with_dir(|d| {
let mut f = std::fs::File::create(d.path().join("plain")).unwrap(); tests::write_file(&d, "plain", "[]");
writeln!(f, "hello world").unwrap();
assert!(false, "not impl"); let f = File::new(&d.path().join("plain").to_str().unwrap().to_string());
assert_eq!(0, f.events().unwrap().0.len());
assert_eq!(0, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "[]");
f.persist_stage().unwrap();
assert_eq!(0, f.events().unwrap().0.len());
assert_eq!(0, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "[]");
f.stage_persisted().unwrap();
assert_eq!(0, f.events().unwrap().0.len());
assert_eq!(0, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "[]");
});
}
#[test]
fn test_file_empty_stage_fills_events() {
tests::with_dir(|d| {
tests::write_file(&d, "plain", "[hello, world]");
let f = File::new(&d.path().join("plain").to_str().unwrap().to_string());
assert_eq!(0, f.events().unwrap().0.len());
assert_eq!(2, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "[hello, world]");
f.persist_stage().unwrap();
assert_eq!(2, f.events().unwrap().0.len());
assert_eq!(2, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "[hello, world]");
f.stage_persisted().unwrap();
assert_eq!(2, f.events().unwrap().0.len());
assert_eq!(2, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "- hello\n- world");
});
}
#[test]
fn test_file_persist_empty_drains_events() {
tests::with_dir(|d| {
tests::write_file(&d, "plain", "[]");
tests::write_file(
&d,
".plain.host_a",
r#"
{"ts":1, "patch":{"op":"replace", "path":"", "value": ["initial"]}}
{"ts":3, "patch":{"op":"add", "path":"/-", "value": {"k":"v"}}}
"#,
);
tests::write_file(
&d,
".plain.host_b",
r#"
{"ts":2, "patch":{"op":"add", "path":"/-", "value": 1}}
"#,
);
let f = File::new(&d.path().join("plain").to_str().unwrap().to_string());
assert_eq!(3, f.events().unwrap().0.len());
assert_eq!(0, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "[]");
f.persist_stage().unwrap();
assert_eq!(6, f.events().unwrap().0.len());
assert_eq!(0, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "[]");
f.stage_persisted().unwrap();
assert_eq!(6, f.events().unwrap().0.len());
assert_eq!(0, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "[]");
});
}
#[test]
fn test_file_deletion_to_persist() {
tests::with_dir(|d| {
tests::write_file(&d, "plain", "- initial\n- 1");
tests::write_file(
&d,
".plain.host_a",
r#"
{"ts":1, "patch":{"op":"replace", "path":"", "value": ["initial"]}}
{"ts":3, "patch":{"op":"add", "path":"/-", "value": {"k":"v"}}}
"#,
);
tests::write_file(
&d,
".plain.host_b",
r#"
{"ts":2, "patch":{"op":"add", "path":"/-", "value": 1}}
"#,
);
let f = File::new(&d.path().join("plain").to_str().unwrap().to_string());
assert_eq!(3, f.events().unwrap().0.len());
assert_eq!(2, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "- initial\n- 1");
f.persist_stage().unwrap();
assert_eq!(4, f.events().unwrap().0.len());
assert_eq!(2, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "- initial\n- 1");
f.stage_persisted().unwrap();
assert_eq!(4, f.events().unwrap().0.len());
assert_eq!(2, f.stage().unwrap().len());
tests::file_contains(&d, "plain", "- initial\n- 1");
});
} }
} }
@ -294,6 +408,7 @@ impl Events {
Ok(f) => { Ok(f) => {
for line in std::io::BufReader::new(f).lines() { for line in std::io::BufReader::new(f).lines() {
let line = line.unwrap(); let line = line.unwrap();
let line = line.trim();
if line.len() > 0 { if line.len() > 0 {
let delta = match serde_json::from_str(&line) { let delta = match serde_json::from_str(&line) {
Ok(v) => Ok(v), Ok(v) => Ok(v),
@ -307,7 +422,9 @@ impl Events {
Err(msg) => Err(format!("failed to read {}: {}", &log, msg)), Err(msg) => Err(format!("failed to read {}: {}", &log, msg)),
}?; }?;
} }
result.sort_by(|a, b| a.ts.cmp(&b.ts)); result.sort_by(|a, b| a.ts.cmp(&b.ts));
Ok(Events(result)) Ok(Events(result))
} }
@ -356,26 +473,95 @@ mod test_events {
use super::*; use super::*;
#[test] #[test]
fn test_events() { fn test_events_oplog_to_snapshot_one() {
let d = tempdir::TempDir::new("").unwrap(); tests::with_dir(|d| {
test_file(&d, "plain", "- persisted\n- stage only"); tests::write_file(&d, "plain", "- persisted\n- stage only");
test_file( tests::write_file(
&d, &d,
".plain.log", ".plain.some_host",
r#" r#"
{"ts":1, "patch":{"op":"replace", "path":"", "value":["persisted"]}} {"ts":1, "patch":{"op":"replace", "path":"", "value":["persisted"]}}
"#, "#,
); );
let events = Events::new(&d.path().join("plain").to_str().unwrap().to_string()).unwrap(); let events =
assert_eq!(1, events.0.len(), "events: {:?}", events); Events::new(&d.path().join("plain").to_str().unwrap().to_string()).unwrap();
assert_eq!(1, events.0.len(), "events: {:?}", events);
assert!(false, "not impl"); let snapshot = events.snapshot().unwrap();
assert_eq!(1, snapshot.len());
assert_eq!(
serde_yaml::Value::String("persisted".to_string()),
snapshot[0].0
);
});
}
#[test]
fn test_events_oplog_to_snapshot_complex() {
tests::with_dir(|d| {
tests::write_file(&d, "plain", "- ignored");
tests::write_file(
&d,
".plain.host_a",
r#"
{"ts":1, "patch":{"op":"replace", "path":"", "value":["persisted"]}}
{"ts":3, "patch":{"op":"add", "path":"/-", "value":"persisted 3"}}
{"ts":2, "patch":{"op":"add", "path":"/-", "value":"persisted 2"}}
{"ts":6, "patch":{"op":"replace", "path":"/4", "value":"persisted 5'"}}
{"ts":7, "patch":{"op":"remove", "path":"/3"}}
"#,
);
tests::write_file(
&d,
".plain.host_b",
r#"
{"ts":4, "patch":{"op":"add", "path":"/-", "value":"persisted 4"}}
{"ts":5, "patch":{"op":"add", "path":"/-", "value":"persisted 5"}}
"#,
);
let events =
Events::new(&d.path().join("plain").to_str().unwrap().to_string()).unwrap();
let snapshot = events.snapshot().unwrap();
assert_eq!(4, snapshot.len());
assert_eq!(
serde_yaml::Value::String("persisted".to_string()),
snapshot[0].0
);
assert_eq!(
serde_yaml::Value::String("persisted 2".to_string()),
snapshot[1].0
);
assert_eq!(
serde_yaml::Value::String("persisted 3".to_string()),
snapshot[2].0
);
assert_eq!(
serde_yaml::Value::String("persisted 5'".to_string()),
snapshot[3].0
);
});
} }
} }
fn test_file(d: &tempdir::TempDir, fname: &str, content: &str) { mod tests {
let mut f = std::fs::File::create(d.path().join("plain")).unwrap(); use super::*;
writeln!(f, "- persisted\n- stage only").unwrap();
f.sync_all().unwrap(); pub fn with_dir(mut foo: impl FnMut(tempdir::TempDir)) {
foo(tempdir::TempDir::new("").unwrap());
}
pub fn write_file(d: &tempdir::TempDir, fname: &str, content: &str) {
let mut f = std::fs::File::create(d.path().join(&fname)).unwrap();
writeln!(f, "{}", &content).unwrap();
f.sync_all().unwrap();
}
pub fn file_contains(d: &tempdir::TempDir, fname: &str, content: &str) {
let p = d.path().join(&fname);
let file_content = std::fs::read_to_string(p).unwrap();
assert!(file_content.contains(content));
}
} }