#[cfg(test)]
mod tests {
use crate::wal::tests::helpers::*;
use crate::wal::{Wal, WalError, WalHeader};
use std::fs::{self, OpenOptions};
use std::io::{Seek, SeekFrom, Write};
use tempfile::TempDir;
#[test]
fn wal_header_accessors() {
let hdr = WalHeader::new(2048, 7);
assert_eq!(hdr.wal_seq(), 7);
assert_eq!(hdr.max_record_size(), 2048);
assert_eq!(hdr.version(), WalHeader::VERSION);
}
#[test]
fn wal_accessors() {
init_tracing();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("000000.log");
let wal: Wal<MemTableRecord> = Wal::open(&path, Some(4096)).unwrap();
assert_eq!(wal.wal_seq(), 0);
assert_eq!(wal.max_record_size(), 4096);
assert_eq!(wal.path(), path);
let size = wal.file_size().unwrap();
assert!(size > 0, "file should contain at least the header");
}
#[test]
fn wal_iter_debug() {
init_tracing();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("000000.log");
let wal: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
let iter = wal.replay_iter().unwrap();
let dbg = format!("{:?}", iter);
assert!(dbg.contains("WalIter"));
assert!(dbg.contains("offset"));
}
#[test]
fn append_record_too_large() {
init_tracing();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("000000.log");
let wal: Wal<MemTableRecord> = Wal::open(&path, Some(1)).unwrap();
let record = MemTableRecord {
key: b"big".to_vec(),
value: Some(b"value_that_exceeds_1_byte_limit".to_vec()),
timestamp: 1,
deleted: false,
};
let err = wal.append(&record).unwrap_err();
assert!(matches!(err, WalError::RecordTooLarge(_)));
}
#[test]
fn truncated_payload_during_replay() {
init_tracing();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("000000.log");
let wal: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
let record = MemTableRecord {
key: b"trunc".to_vec(),
value: Some(b"payload".to_vec()),
timestamp: 1,
deleted: false,
};
wal.append(&record).unwrap();
drop(wal);
let file_len = fs::metadata(&path).unwrap().len();
let new_len = file_len - 10;
{
let f = OpenOptions::new().write(true).open(&path).unwrap();
f.set_len(new_len).unwrap();
}
let wal2: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
let mut iter = wal2.replay_iter().unwrap();
let result = iter.next();
assert!(result.is_some());
let err = result.unwrap().unwrap_err();
assert!(
matches!(err, WalError::UnexpectedEof | WalError::ChecksumMismatch),
"expected UnexpectedEof or ChecksumMismatch, got {:?}",
err
);
}
#[test]
fn truncated_checksum_during_replay() {
init_tracing();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("000000.log");
let wal: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
let record = MemTableRecord {
key: b"cs".to_vec(),
value: Some(b"val".to_vec()),
timestamp: 1,
deleted: false,
};
wal.append(&record).unwrap();
drop(wal);
let file_len = fs::metadata(&path).unwrap().len();
{
let f = OpenOptions::new().write(true).open(&path).unwrap();
f.set_len(file_len - 2).unwrap();
}
let wal2: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
let mut iter = wal2.replay_iter().unwrap();
let result = iter.next().unwrap();
assert!(
result.is_err(),
"truncated checksum should cause replay error"
);
}
#[test]
fn bad_magic_byte_in_header() {
init_tracing();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("000000.log");
{
let _wal: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
}
{
let mut f = OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap();
f.seek(SeekFrom::Start(0)).unwrap();
f.write_all(b"XYZW").unwrap();
f.sync_all().unwrap();
}
let err = Wal::<MemTableRecord>::open(&path, None).unwrap_err();
assert!(matches!(err, WalError::InvalidHeader(_)));
}
#[test]
fn wal_seq_mismatch_on_reopen() {
init_tracing();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("000000.log");
{
let _wal: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
}
let wrong_path = tmp.path().join("000005.log");
fs::rename(&path, &wrong_path).unwrap();
let err = Wal::<MemTableRecord>::open(&wrong_path, None).unwrap_err();
assert!(
matches!(err, WalError::InvalidHeader(_)),
"expected InvalidHeader for seq mismatch, got {:?}",
err
);
}
#[test]
fn record_crc_mismatch_during_replay() {
init_tracing();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("000000.log");
let wal: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
let record = MemTableRecord {
key: b"data".to_vec(),
value: Some(b"value".to_vec()),
timestamp: 1,
deleted: false,
};
wal.append(&record).unwrap();
drop(wal);
{
let hdr_size = WAL_HDR_SIZE + WAL_CRC32_SIZE;
let data_offset = hdr_size + 4 + 2; let mut f = OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap();
f.seek(SeekFrom::Start(data_offset as u64)).unwrap();
f.write_all(&[0xFF]).unwrap();
f.sync_all().unwrap();
}
let wal2: Wal<MemTableRecord> = Wal::open(&path, None).unwrap();
let mut iter = wal2.replay_iter().unwrap();
let result = iter.next().unwrap();
assert!(
matches!(
result,
Err(WalError::ChecksumMismatch) | Err(WalError::Encoding(_))
),
"expected ChecksumMismatch or Encoding error, got {:?}",
result
);
}
}