mod common;
use common::{counter_reducer, stats_reducer, StatsState};
use eventfold::{Event, EventLog};
use proptest::prelude::*;
use serde_json::json;
use tempfile::tempdir;
fn arb_event_type() -> impl Strategy<Value = String> {
prop_oneof![
Just("type_a".to_string()),
Just("type_b".to_string()),
Just("type_c".to_string()),
Just("type_d".to_string()),
]
}
fn arb_event() -> impl Strategy<Value = Event> {
(arb_event_type(), any::<u64>()).prop_map(|(t, ts)| {
let mut e = Event::new(&t, json!({"value": ts % 100}));
e.ts = ts;
e
})
}
fn arb_event_sequence() -> impl Strategy<Value = Vec<Event>> {
proptest::collection::vec(arb_event(), 0..50)
}
proptest! {
#[test]
fn prop_reducer_determinism(events in arb_event_sequence()) {
let dir = tempdir().unwrap();
let mut manual_state = 0u64;
for event in &events {
manual_state = counter_reducer(manual_state, event);
}
let mut log = EventLog::builder(dir.path())
.view::<u64>("counter", counter_reducer)
.open()
.unwrap();
for event in &events {
log.append(event).unwrap();
}
log.refresh_all().unwrap();
let view_state = *log.view::<u64>("counter").unwrap();
prop_assert_eq!(manual_state, view_state);
}
}
proptest! {
#[test]
fn prop_rotation_invariance(
events in arb_event_sequence(),
rotation_points in proptest::collection::vec(0..50usize, 0..5)
) {
let dir_a = tempdir().unwrap();
let mut log_a = EventLog::builder(dir_a.path())
.view::<u64>("counter", counter_reducer)
.open()
.unwrap();
for event in &events {
log_a.append(event).unwrap();
}
log_a.refresh_all().unwrap();
let state_a = *log_a.view::<u64>("counter").unwrap();
let dir_b = tempdir().unwrap();
let mut log_b = EventLog::builder(dir_b.path())
.view::<u64>("counter", counter_reducer)
.open()
.unwrap();
let mut sorted_points: Vec<usize> = rotation_points
.iter()
.filter(|&&p| p < events.len())
.copied()
.collect();
sorted_points.sort();
sorted_points.dedup();
for (i, event) in events.iter().enumerate() {
log_b.append(event).unwrap();
if sorted_points.contains(&i) {
log_b.rotate().unwrap();
}
}
log_b.refresh_all().unwrap();
let state_b = *log_b.view::<u64>("counter").unwrap();
prop_assert_eq!(state_a, state_b);
}
}
proptest! {
#[test]
fn prop_snapshot_equivalence(events in arb_event_sequence()) {
let dir = tempdir().unwrap();
let mut log = EventLog::builder(dir.path())
.view::<u64>("counter", counter_reducer)
.open()
.unwrap();
for event in &events {
log.append(event).unwrap();
}
log.refresh_all().unwrap();
let state_a = *log.view::<u64>("counter").unwrap();
drop(log);
let snap_path = dir.path().join("views/counter.snapshot.json");
let _ = std::fs::remove_file(&snap_path);
let mut log = EventLog::builder(dir.path())
.view::<u64>("counter", counter_reducer)
.open()
.unwrap();
log.refresh_all().unwrap();
let state_b = *log.view::<u64>("counter").unwrap();
prop_assert_eq!(state_a, state_b);
}
}
proptest! {
#[test]
fn prop_event_ordering(
events in proptest::collection::vec(arb_event(), 1..30),
rotation_points in proptest::collection::vec(0..30usize, 0..3)
) {
let dir = tempdir().unwrap();
let mut log = EventLog::builder(dir.path())
.view::<u64>("counter", counter_reducer)
.open()
.unwrap();
let mut sorted_points: Vec<usize> = rotation_points
.iter()
.filter(|&&p| p < events.len())
.copied()
.collect();
sorted_points.sort();
sorted_points.dedup();
for (i, event) in events.iter().enumerate() {
log.append(event).unwrap();
if sorted_points.contains(&i) {
log.rotate().unwrap();
}
}
let read_events: Vec<_> = log
.read_full()
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
prop_assert_eq!(read_events.len(), events.len());
for (original, (read, _hash)) in events.iter().zip(read_events.iter()) {
prop_assert_eq!(&original.event_type, &read.event_type);
prop_assert_eq!(original.ts, read.ts);
}
}
}
proptest! {
#[test]
fn prop_multi_view_consistency(events in arb_event_sequence()) {
let dir = tempdir().unwrap();
let mut log = EventLog::builder(dir.path())
.view::<u64>("counter", counter_reducer)
.view::<StatsState>("stats", stats_reducer)
.open()
.unwrap();
for event in &events {
log.append(event).unwrap();
}
log.refresh_all().unwrap();
let counter_state = *log.view::<u64>("counter").unwrap();
let stats_state = log.view::<StatsState>("stats").unwrap().clone();
let mut manual_counter = 0u64;
let mut manual_stats = StatsState::default();
for event in &events {
manual_counter = counter_reducer(manual_counter, event);
manual_stats = stats_reducer(manual_stats, event);
}
prop_assert_eq!(counter_state, manual_counter);
prop_assert_eq!(stats_state, manual_stats);
}
}