mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-08-05 15:13:06 -07:00
273 lines
7.5 KiB
Rust
273 lines
7.5 KiB
Rust
//! Tests that write() makes data visible to other vecs without fsync.
|
|
//!
|
|
//! This verifies the key property that enables fast per-compute operations:
|
|
//! after vec_a.write(), vec_b can immediately read the new data because
|
|
//! both share the same mmap. No fsync is needed for in-process visibility.
|
|
|
|
use rawdb::Database;
|
|
use tempfile::TempDir;
|
|
use vecdb::{Result, StoredVec, Version};
|
|
|
|
fn setup_test_db() -> Result<(Database, TempDir)> {
|
|
let temp_dir = TempDir::new()?;
|
|
let db = Database::open(temp_dir.path())?;
|
|
Ok((db, temp_dir))
|
|
}
|
|
|
|
/// Tests that after write() on vec_a, a separate vec_b instance
|
|
/// can read the new data (simulating derived vec computation).
|
|
fn run_write_visibility_test<V>() -> Result<(), Box<dyn std::error::Error>>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let version = Version::ZERO;
|
|
let (database, _temp) = setup_test_db()?;
|
|
|
|
// Create and populate vec_a
|
|
let mut vec_a: V = V::forced_import(&database, "vec_a", version)?;
|
|
for i in 0..100u32 {
|
|
vec_a.push(i);
|
|
}
|
|
|
|
// Write without flush - just mmap, no fsync
|
|
vec_a.write()?;
|
|
|
|
// Now create vec_b that will read from vec_a
|
|
// This simulates the pattern: compute vec_b derived from vec_a
|
|
let vec_a_reader: V = V::forced_import(&database, "vec_a", version)?;
|
|
|
|
// vec_b should see all 100 values written by vec_a
|
|
assert_eq!(
|
|
vec_a_reader.len(),
|
|
100,
|
|
"vec_b should see vec_a's written data"
|
|
);
|
|
|
|
for i in 0..100usize {
|
|
assert_eq!(
|
|
vec_a_reader.collect_one(i),
|
|
Some(i as u32),
|
|
"vec_b should read correct value at index {}",
|
|
i
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Tests the full compute chain pattern:
|
|
/// 1. Compute and write vec_a
|
|
/// 2. Compute vec_b derived from vec_a, write it
|
|
/// 3. Compute vec_c derived from vec_b, write it
|
|
/// 4. Final flush for durability
|
|
fn run_compute_chain_test<V>() -> Result<(), Box<dyn std::error::Error>>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let version = Version::ZERO;
|
|
let (database, _temp) = setup_test_db()?;
|
|
|
|
// Step 1: Compute vec_a (source data)
|
|
let mut vec_a: V = V::forced_import(&database, "chain_a", version)?;
|
|
for i in 0..50u32 {
|
|
vec_a.push(i * 2); // Even numbers: 0, 2, 4, ...
|
|
}
|
|
vec_a.write()?; // No fsync, just mmap write
|
|
|
|
// Step 2: Compute vec_b derived from vec_a (sum of consecutive pairs)
|
|
let vec_a_for_read: V = V::forced_import(&database, "chain_a", version)?;
|
|
let mut vec_b: V = V::forced_import(&database, "chain_b", version)?;
|
|
|
|
for i in 0..25usize {
|
|
let a1 = vec_a_for_read.collect_one(i * 2).unwrap();
|
|
let a2 = vec_a_for_read.collect_one(i * 2 + 1).unwrap();
|
|
vec_b.push(a1 + a2);
|
|
}
|
|
vec_b.write()?; // No fsync, just mmap write
|
|
|
|
// Step 3: Compute vec_c derived from vec_b (cumulative sum)
|
|
let vec_b_for_read: V = V::forced_import(&database, "chain_b", version)?;
|
|
let mut vec_c: V = V::forced_import(&database, "chain_c", version)?;
|
|
|
|
let mut cumsum = 0u32;
|
|
for val in vec_b_for_read.collect() {
|
|
cumsum += val;
|
|
vec_c.push(cumsum);
|
|
}
|
|
vec_c.write()?; // No fsync, just mmap write
|
|
|
|
// Verify the chain computed correctly
|
|
// vec_a: [0, 2, 4, 6, 8, 10, ...]
|
|
// vec_b: [0+2, 4+6, 8+10, ...] = [2, 10, 18, ...]
|
|
// vec_c: cumsum of vec_b
|
|
|
|
let vec_c_verify: V = V::forced_import(&database, "chain_c", version)?;
|
|
assert_eq!(vec_c_verify.len(), 25);
|
|
|
|
let expected_b: Vec<u32> = (0..25).map(|i| (i * 4) + (i * 4 + 2)).collect();
|
|
let expected_c: Vec<u32> = expected_b
|
|
.iter()
|
|
.scan(0u32, |acc, &x| {
|
|
*acc += x;
|
|
Some(*acc)
|
|
})
|
|
.collect();
|
|
|
|
let actual_c: Vec<u32> = vec_c_verify.collect();
|
|
assert_eq!(
|
|
actual_c, expected_c,
|
|
"compute chain should produce correct results"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Tests that write() returns the correct boolean:
|
|
/// - true if data was written
|
|
/// - false if nothing to write
|
|
fn run_write_returns_bool_test<V>() -> Result<(), Box<dyn std::error::Error>>
|
|
where
|
|
V: StoredVec<I = usize, T = u32>,
|
|
{
|
|
let version = Version::ZERO;
|
|
let (database, _temp) = setup_test_db()?;
|
|
|
|
let mut vec: V = V::forced_import(&database, "bool_test", version)?;
|
|
|
|
// First write with no data should return false
|
|
assert!(!vec.write()?, "write() with no data should return false");
|
|
|
|
// Push data
|
|
vec.push(42);
|
|
|
|
// Write with data should return true
|
|
assert!(vec.write()?, "write() with data should return true");
|
|
|
|
// Second write with no new data should return false
|
|
assert!(
|
|
!vec.write()?,
|
|
"write() after already written should return false"
|
|
);
|
|
|
|
// Push more data
|
|
vec.push(43);
|
|
|
|
// Write should return true again
|
|
assert!(vec.write()?, "write() with new data should return true");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ============================================================================
|
|
// Test instantiation for each vec type
|
|
// ============================================================================
|
|
|
|
mod bytes {
|
|
use super::*;
|
|
use vecdb::BytesVec;
|
|
type V = BytesVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn test_write_visibility() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_visibility_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_compute_chain() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_compute_chain_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_returns_bool() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_returns_bool_test::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "pco")]
|
|
mod pco {
|
|
use super::*;
|
|
use vecdb::PcoVec;
|
|
type V = PcoVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn test_write_visibility() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_visibility_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_compute_chain() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_compute_chain_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_returns_bool() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_returns_bool_test::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "lz4")]
|
|
mod lz4 {
|
|
use super::*;
|
|
use vecdb::LZ4Vec;
|
|
type V = LZ4Vec<usize, u32>;
|
|
|
|
#[test]
|
|
fn test_write_visibility() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_visibility_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_compute_chain() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_compute_chain_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_returns_bool() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_returns_bool_test::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "zstd")]
|
|
mod zstd {
|
|
use super::*;
|
|
use vecdb::ZstdVec;
|
|
type V = ZstdVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn test_write_visibility() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_visibility_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_compute_chain() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_compute_chain_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_returns_bool() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_returns_bool_test::<V>()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "zerocopy")]
|
|
mod zerocopy {
|
|
use super::*;
|
|
use vecdb::ZeroCopyVec;
|
|
type V = ZeroCopyVec<usize, u32>;
|
|
|
|
#[test]
|
|
fn test_write_visibility() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_visibility_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_compute_chain() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_compute_chain_test::<V>()
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_returns_bool() -> Result<(), Box<dyn std::error::Error>> {
|
|
run_write_returns_bool_test::<V>()
|
|
}
|
|
}
|