tidaldb/tidal/tests/wal_integration_basic.rs
jx12n ad4134e280 chore: doc consolidation, seven-dimension review fixes, and commit hooks
- Eliminate the tidal/ self-contained doc mirror; docs now have two canonical
  homes (root *.md and docs/), with planning/specs/research/reviews moved up
- Remove stale .agents/skills and .ai mirrors; canonicalize skills under .claude/
- Add pre-commit hook + scripts/check-docs.sh doc-guard + scripts/install-hooks.sh
- Implement M0-M10 seven-dimension review findings across engine, net, server,
  and tidalctl (durability, replication, query, WAL, storage, CLI hardening)
2026-06-08 22:46:28 -06:00

209 lines
6.7 KiB
Rust

#![allow(
clippy::too_many_lines,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::missing_const_for_fn
)]
use std::{sync::Arc, time::Duration};
use tidaldb::{
replication::ShardId,
wal::{SignalEvent, WalConfig, WalHandle, segment},
};
fn test_config(dir: &std::path::Path) -> WalConfig {
WalConfig {
wal_dir_override: None,
dir: dir.to_path_buf(),
segment_size: 16 * 1024 * 1024,
batch_size: 100,
batch_timeout: Duration::from_millis(1),
dedup_window: Duration::from_secs(30),
}
}
fn make_event(id: u64) -> SignalEvent {
SignalEvent {
entity_id: id,
signal_type: 1,
weight: 1.0,
timestamp_nanos: id * 1_000_000_000,
}
}
// -- AC-1, AC-2: Wire format byte-level tests are in format.rs unit tests.
// These integration tests validate the full pipeline.
#[test]
fn wal_basic_round_trip() {
let dir = tempfile::tempdir().expect("tempdir creation should succeed");
let config = test_config(dir.path());
// Write events
let (handle, replayed, _) = WalHandle::open(config).expect("open should succeed");
assert!(replayed.is_empty());
for i in 1..=10 {
handle.append(make_event(i)).expect("append should succeed");
}
handle.shutdown().expect("shutdown should succeed");
// Reopen and verify replay
let config = test_config(dir.path());
let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");
assert_eq!(replayed.len(), 10);
for (i, event) in replayed.iter().enumerate() {
assert_eq!(event.entity_id, (i + 1) as u64);
assert_eq!(event.signal_type, 1);
assert_eq!(event.weight.to_bits(), 1.0_f32.to_bits());
}
handle.shutdown().expect("shutdown should succeed");
}
// -- AC-10, AC-11: Deduplication
#[test]
fn wal_dedup_silent() {
let dir = tempfile::tempdir().expect("tempdir creation should succeed");
let config = test_config(dir.path());
let (handle, _, _) = WalHandle::open(config).expect("open should succeed");
let event = make_event(42);
let seq1 = handle
.append(event.clone())
.expect("first append should succeed");
let seq2 = handle
.append(event.clone())
.expect("second append should succeed");
let seq3 = handle.append(event).expect("third append should succeed");
assert!(seq1 > 0, "first event should get real sequence number");
assert_eq!(seq2, 0, "duplicate should return seq=0");
assert_eq!(seq3, 0, "duplicate should return seq=0");
handle.shutdown().expect("shutdown should succeed");
// Verify only one event on disk
let config = test_config(dir.path());
let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");
assert_eq!(replayed.len(), 1, "only one unique event should be on disk");
handle.shutdown().expect("shutdown should succeed");
}
// -- AC-12: No false positives
#[test]
fn wal_dedup_no_false_positives() {
let dir = tempfile::tempdir().expect("tempdir creation should succeed");
// Use a large batch size so batches fill quickly from concurrent writers.
let config = WalConfig {
wal_dir_override: None,
dir: dir.path().to_path_buf(),
segment_size: 16 * 1024 * 1024,
batch_size: 256,
batch_timeout: Duration::from_millis(1),
dedup_window: Duration::from_secs(60),
};
let (handle, _, _) = WalHandle::open(config).expect("open should succeed");
let handle = Arc::new(handle);
let total_events: u64 = 1_000;
let num_threads = 10u64;
let per_thread = total_events / num_threads;
let mut threads = Vec::new();
for t in 0..num_threads {
let handle = Arc::clone(&handle);
threads.push(std::thread::spawn(move || {
let mut count = 0u64;
for i in 0..per_thread {
let entity_id = t * per_thread + i;
let event = SignalEvent {
entity_id,
signal_type: (entity_id % 256) as u8,
weight: entity_id as f32,
timestamp_nanos: entity_id * 1_000_000,
};
let seq = handle.append(event).expect("append should succeed");
if seq > 0 {
count += 1;
}
}
count
}));
}
let mut real_seqs = 0u64;
for thread in threads {
real_seqs += thread.join().expect("thread should join");
}
let handle = Arc::try_unwrap(handle).expect("should be sole owner of WalHandle Arc");
handle.shutdown().expect("shutdown should succeed");
assert_eq!(
real_seqs, total_events,
"all {total_events} unique events must be accepted (no false positives)"
);
}
// -- AC-5, AC-6: Segment rotation
#[test]
fn wal_segment_rotation() {
let dir = tempfile::tempdir().expect("tempdir creation should succeed");
// Use very small segment size to force rotation
let config = WalConfig {
wal_dir_override: None,
dir: dir.path().to_path_buf(),
segment_size: 256, // tiny: one batch exceeds this
batch_size: 10,
batch_timeout: Duration::from_millis(10),
dedup_window: Duration::from_secs(30),
};
let (handle, _, _) = WalHandle::open(config).expect("open should succeed");
// Write enough events to trigger multiple rotations
for i in 1..=100 {
handle.append(make_event(i)).expect("append should succeed");
}
handle.shutdown().expect("shutdown should succeed");
// Check segment files exist
let wal_dir = dir.path().join("wal");
let segments = segment::list_segments(&wal_dir).expect("list should succeed");
assert!(
segments.len() > 1,
"expected multiple segments, got {}",
segments.len()
);
// Verify segment naming: all should match wal-{seq:020}.seg pattern
for (seq, path) in &segments {
let filename = path
.file_name()
.expect("should have filename")
.to_str()
.expect("should be valid UTF-8");
assert_eq!(
filename,
segment::segment_filename(ShardId::SINGLE, *seq),
"segment filename mismatch"
);
}
// Verify replay gets all events
let config = WalConfig {
wal_dir_override: None,
dir: dir.path().to_path_buf(),
segment_size: 256,
batch_size: 10,
batch_timeout: Duration::from_millis(10),
dedup_window: Duration::from_secs(30),
};
let (handle, replayed, _) = WalHandle::open(config).expect("reopen should succeed");
assert_eq!(replayed.len(), 100, "all events should be replayed");
handle.shutdown().expect("shutdown should succeed");
}