use noxu_log::checksum::ChecksumValidator;
use noxu_log::entry_header::LogEntryHeader;
use noxu_log::entry_type::LogEntryType;
use noxu_log::log_utils;
use noxu_log::provisional::Provisional;
use noxu_util::{Lsn, Vlsn};
use proptest::prelude::*;
use std::io::Cursor;
fn arb_entry_type() -> impl Strategy<Value = LogEntryType> {
prop_oneof![
Just(LogEntryType::FileHeader),
Just(LogEntryType::IN),
Just(LogEntryType::BIN),
Just(LogEntryType::BINDelta),
Just(LogEntryType::InsertLN),
Just(LogEntryType::UpdateLN),
Just(LogEntryType::DeleteLN),
Just(LogEntryType::InsertLNTxn),
Just(LogEntryType::UpdateLNTxn),
Just(LogEntryType::DeleteLNTxn),
Just(LogEntryType::MapLN),
Just(LogEntryType::NameLN),
Just(LogEntryType::NameLNTxn),
Just(LogEntryType::FileSummaryLN),
Just(LogEntryType::TxnCommit),
Just(LogEntryType::TxnAbort),
Just(LogEntryType::TxnPrepare),
Just(LogEntryType::CkptStart),
Just(LogEntryType::CkptEnd),
Just(LogEntryType::DbTree),
Just(LogEntryType::Trace),
Just(LogEntryType::Matchpoint),
]
}
fn arb_provisional() -> impl Strategy<Value = Provisional> {
prop_oneof![
Just(Provisional::No),
Just(Provisional::Yes),
Just(Provisional::BeforeCkptEnd),
]
}
proptest! {
#[test]
fn header_roundtrip_no_vlsn(
entry_type in arb_entry_type(),
item_size in 0u32..100_000_000u32,
provisional in arb_provisional(),
) {
let header = LogEntryHeader::new(
entry_type,
item_size,
provisional,
false, None, );
let mut buf = Vec::new();
header.write_to_log(&mut buf).unwrap();
let lsn = Lsn::new(0, 0);
let decoded = LogEntryHeader::read_from_log(&buf, lsn).unwrap();
prop_assert_eq!(header.entry_type(), decoded.entry_type());
prop_assert_eq!(header.item_size(), decoded.item_size());
prop_assert_eq!(header.provisional(), decoded.provisional());
prop_assert_eq!(header.replicated(), decoded.replicated());
}
#[test]
fn header_roundtrip_with_vlsn(
entry_type in arb_entry_type(),
item_size in 0u32..100_000_000u32,
provisional in arb_provisional(),
vlsn_seq in 1i64..i64::MAX,
) {
let vlsn = Some(Vlsn::new(vlsn_seq));
let header = LogEntryHeader::new(
entry_type,
item_size,
provisional,
true, vlsn,
);
let mut buf = Vec::new();
header.write_to_log(&mut buf).unwrap();
let lsn = Lsn::new(0, 0);
let decoded = LogEntryHeader::read_from_log(&buf, lsn).unwrap();
prop_assert_eq!(header.entry_type(), decoded.entry_type());
prop_assert_eq!(header.item_size(), decoded.item_size());
prop_assert_eq!(header.provisional(), decoded.provisional());
prop_assert!(decoded.replicated());
prop_assert!(decoded.vlsn_present());
prop_assert_eq!(header.vlsn(), decoded.vlsn());
}
#[test]
fn header_size_consistent(
entry_type in arb_entry_type(),
item_size in 0u32..100_000_000u32,
replicated: bool,
) {
let vlsn = if replicated { Some(Vlsn::new(1)) } else { None };
let header = LogEntryHeader::new(
entry_type, item_size, Provisional::No, replicated, vlsn,
);
let mut buf = Vec::new();
header.write_to_log(&mut buf).unwrap();
if replicated {
prop_assert_eq!(buf.len(), noxu_log::entry_header::MAX_HEADER_SIZE);
} else {
prop_assert_eq!(buf.len(), noxu_log::entry_header::MIN_HEADER_SIZE);
}
}
}
proptest! {
#[test]
fn log_utils_i32_roundtrip(val: i32) {
let mut buf = Vec::new();
log_utils::write_i32(&mut buf, val).unwrap();
prop_assert_eq!(buf.len(), log_utils::INT_BYTES);
let result = log_utils::read_i32(&mut Cursor::new(&buf)).unwrap();
prop_assert_eq!(result, val);
}
#[test]
fn log_utils_i64_roundtrip(val: i64) {
let mut buf = Vec::new();
log_utils::write_i64(&mut buf, val).unwrap();
prop_assert_eq!(buf.len(), log_utils::LONG_BYTES);
let result = log_utils::read_i64(&mut Cursor::new(&buf)).unwrap();
prop_assert_eq!(result, val);
}
#[test]
fn log_utils_u32_roundtrip(val: u32) {
let mut buf = Vec::new();
log_utils::write_u32(&mut buf, val).unwrap();
let result = log_utils::read_u32(&mut Cursor::new(&buf)).unwrap();
prop_assert_eq!(result, val);
}
#[test]
fn log_utils_i16_roundtrip(val: i16) {
let mut buf = Vec::new();
log_utils::write_i16(&mut buf, val).unwrap();
let result = log_utils::read_i16(&mut Cursor::new(&buf)).unwrap();
prop_assert_eq!(result, val);
}
#[test]
fn log_utils_byte_array_roundtrip(data in prop::collection::vec(any::<u8>(), 0..256)) {
let mut buf = Vec::new();
log_utils::write_byte_array(&mut buf, Some(&data)).unwrap();
let result = log_utils::read_byte_array(&mut Cursor::new(&buf)).unwrap();
prop_assert_eq!(result, Some(data));
}
#[test]
fn log_utils_string_roundtrip(s in ".*") {
let mut buf = Vec::new();
log_utils::write_string(&mut buf, Some(&s)).unwrap();
let result = log_utils::read_string(&mut Cursor::new(&buf)).unwrap();
prop_assert_eq!(result, Some(s));
}
#[test]
fn log_utils_bool_roundtrip(val: bool) {
let mut buf = Vec::new();
log_utils::write_bool(&mut buf, val).unwrap();
let result = log_utils::read_bool(&mut Cursor::new(&buf)).unwrap();
prop_assert_eq!(result, val);
}
}
proptest! {
#[test]
fn checksum_deterministic(data in prop::collection::vec(any::<u8>(), 0..1024)) {
let c1 = ChecksumValidator::compute(&data);
let c2 = ChecksumValidator::compute(&data);
prop_assert_eq!(c1, c2);
}
#[test]
fn checksum_incremental_matches_oneshot(
part1 in prop::collection::vec(any::<u8>(), 0..512),
part2 in prop::collection::vec(any::<u8>(), 0..512),
) {
let mut full = part1.clone();
full.extend_from_slice(&part2);
let oneshot = ChecksumValidator::compute(&full);
let mut validator = ChecksumValidator::new();
validator.update_all(&part1);
validator.update_all(&part2);
let incremental = validator.value();
prop_assert_eq!(oneshot, incremental);
}
#[test]
fn checksum_differs_on_modification(
data in prop::collection::vec(any::<u8>(), 1..256),
flip_idx in 0usize..256,
) {
let idx = flip_idx % data.len();
let original_checksum = ChecksumValidator::compute(&data);
let mut modified = data.clone();
modified[idx] ^= 0xFF; let modified_checksum = ChecksumValidator::compute(&modified);
if data[idx] != modified[idx] {
prop_assert_ne!(original_checksum, modified_checksum,
"CRC32 collision: modifying byte {} from {:#x} to {:#x}",
idx, data[idx], modified[idx]);
}
}
}