use std::path::PathBuf;
use crate::common::universal_io::{MmapFile, MmapFs, UniversalFlush as _};
use rstest::rstest;
use tempfile::Builder;
use super::*;
type TestTracker = Tracker<MmapFile>;
impl TestTracker {
#[allow(clippy::unnecessary_wraps)]
pub fn mapping_len(&self) -> Result<usize> {
let mut count = 0;
for i in 0..self.next_pointer_offset {
if self.get(i).ok().flatten().is_some() {
count += 1;
}
}
Ok(count)
}
pub fn is_empty(&self) -> bool {
self.mapping_len().unwrap_or(0) == 0
}
pub fn mmap_file_size(&self) -> Result<usize> {
self.storage
.len::<u8>()
.map(|u| u as usize)
.map_err(Into::into)
}
pub fn write_pending_and_flush_internal(&mut self) -> Result<Vec<ValuePointer>> {
let pending_updates = std::mem::take(&mut self.pending_updates);
let res = self.write_pending(pending_updates)?;
self.storage.flusher()()?;
Ok(res)
}
}
#[test]
fn test_file_name() {
let path: PathBuf = "/tmp/test".into();
let file_name = TestTracker::tracker_file_name(&path);
assert_eq!(file_name, path.join(TestTracker::FILE_NAME));
}
#[test]
fn test_page_tracker_files() {
let file = Builder::new().prefix("test-tracker").tempdir().unwrap();
let path = file.path();
let tracker = TestTracker::new(&MmapFs, path, None).unwrap();
let files = tracker.files();
assert_eq!(files.len(), 1);
assert_eq!(files[0], path.join(TestTracker::FILE_NAME));
}
#[test]
fn test_new_tracker() {
let file = Builder::new().prefix("test-tracker").tempdir().unwrap();
let path = file.path();
let tracker = TestTracker::new(&MmapFs, path, None).unwrap();
assert!(tracker.is_empty());
assert_eq!(tracker.mapping_len().unwrap(), 0);
assert_eq!(tracker.pointer_count(), 0);
}
#[rstest]
#[case(10)]
#[case(100)]
#[case(1000)]
fn test_mapping_len_tracker(#[case] initial_tracker_size: usize) {
let file = Builder::new().prefix("test-tracker").tempdir().unwrap();
let path = file.path();
let mut tracker = TestTracker::new(&MmapFs, path, Some(initial_tracker_size)).unwrap();
assert!(tracker.is_empty());
tracker.set(0, ValuePointer::new(1, 1, 1));
tracker.write_pending_and_flush_internal().unwrap();
assert!(!tracker.is_empty());
assert_eq!(tracker.mapping_len().unwrap(), 1);
tracker.set(100, ValuePointer::new(2, 2, 2));
tracker.write_pending_and_flush_internal().unwrap();
assert_eq!(tracker.pointer_count(), 101);
assert_eq!(tracker.mapping_len().unwrap(), 2);
}
#[rstest]
#[case(10)]
#[case(100)]
#[case(1000)]
fn test_set_get_clear_tracker(#[case] initial_tracker_size: usize) {
let file = Builder::new().prefix("test-tracker").tempdir().unwrap();
let path = file.path();
let mut tracker = TestTracker::new(&MmapFs, path, Some(initial_tracker_size)).unwrap();
tracker.set(0, ValuePointer::new(1, 1, 1));
tracker.set(1, ValuePointer::new(2, 2, 2));
tracker.set(2, ValuePointer::new(3, 3, 3));
tracker.set(10, ValuePointer::new(10, 10, 10));
tracker.write_pending_and_flush_internal().unwrap();
assert!(!tracker.is_empty());
assert_eq!(tracker.mapping_len().unwrap(), 4);
assert_eq!(tracker.pointer_count(), 11);
assert_eq!(
tracker.get_raw(0).unwrap(),
Some(ValuePointer::new(1, 1, 1))
);
assert_eq!(
tracker.get_raw(1).unwrap(),
Some(ValuePointer::new(2, 2, 2))
);
assert_eq!(
tracker.get_raw(2).unwrap(),
Some(ValuePointer::new(3, 3, 3))
);
assert_eq!(tracker.get_raw(3).unwrap(), None); assert_eq!(
tracker.get_raw(10).unwrap(),
Some(ValuePointer::new(10, 10, 10))
);
assert_eq!(tracker.get_raw(100_000).unwrap(), None);
tracker.unset(1).unwrap();
tracker.write_pending_and_flush_internal().unwrap();
assert_eq!(tracker.get_raw(1).unwrap(), None);
assert_eq!(tracker.get(1).unwrap(), None);
assert_eq!(tracker.mapping_len().unwrap(), 3);
assert_eq!(tracker.pointer_count(), 11);
tracker.set(0, ValuePointer::new(10, 10, 10));
tracker.set(2, ValuePointer::new(30, 30, 30));
tracker.write_pending_and_flush_internal().unwrap();
assert_eq!(tracker.get(0).unwrap(), Some(ValuePointer::new(10, 10, 10)));
assert_eq!(tracker.get(2).unwrap(), Some(ValuePointer::new(30, 30, 30)));
}
#[rstest]
#[case(10)]
#[case(100)]
#[case(1000)]
fn test_persist_and_open_tracker(#[case] initial_tracker_size: usize) {
let file = Builder::new().prefix("test-tracker").tempdir().unwrap();
let path = file.path();
let value_count: usize = 1000;
let mut tracker = TestTracker::new(&MmapFs, path, Some(initial_tracker_size)).unwrap();
for i in 0..value_count {
if i % 2 == 0 {
tracker.set(i as u32, ValuePointer::new(i as u32, i as u32, i as u32));
}
}
tracker.write_pending_and_flush_internal().unwrap();
assert_eq!(tracker.mapping_len().unwrap(), value_count / 2);
assert_eq!(tracker.pointer_count(), value_count as u32 - 1);
drop(tracker);
let tracker = TestTracker::open(&MmapFs, path, Populate::No, true).unwrap();
assert_eq!(tracker.mapping_len().unwrap(), value_count / 2);
assert_eq!(tracker.pointer_count(), value_count as u32 - 1);
for i in 0..value_count {
if i % 2 == 0 {
assert_eq!(
tracker.get(i as u32).unwrap(),
Some(ValuePointer::new(i as u32, i as u32, i as u32))
);
} else {
assert_eq!(tracker.get(i as u32).unwrap(), None);
}
}
}
#[rstest]
#[case(10, 16)]
#[case(100, 128)]
#[case(1000, 1024)]
#[case(1024, 1024)]
fn test_page_tracker_resize(
#[case] desired_tracker_size: usize,
#[case] actual_tracker_size: usize,
) {
let file = Builder::new().prefix("test-tracker").tempdir().unwrap();
let path = file.path();
let mut tracker = TestTracker::new(&MmapFs, path, Some(desired_tracker_size)).unwrap();
assert_eq!(tracker.mapping_len().unwrap(), 0);
assert_eq!(tracker.mmap_file_size().unwrap(), actual_tracker_size);
for i in 0..100_000 {
tracker.set(i, ValuePointer::new(i, i, i));
}
tracker.write_pending_and_flush_internal().unwrap();
assert_eq!(tracker.mapping_len().unwrap(), 100_000);
assert!(tracker.mmap_file_size().unwrap() > actual_tracker_size);
}
#[test]
fn test_track_non_sequential_large_offset() {
let file = Builder::new().prefix("test-tracker").tempdir().unwrap();
let path = file.path();
let mut tracker = TestTracker::new(&MmapFs, path, None).unwrap();
assert_eq!(tracker.mapping_len().unwrap(), 0);
let page_pointer = ValuePointer::new(1, 1, 1);
let key = 1_000_000;
tracker.set(key, page_pointer);
assert_eq!(tracker.get(key).unwrap(), Some(page_pointer));
}
#[test]
fn test_value_pointer_drain() {
let mut updates = PointerUpdates::default();
updates.set(ValuePointer::new(1, 1, 1));
assert!(updates.clone().drain_persisted(&updates), "must drop entry");
updates.set(ValuePointer::new(1, 2, 1));
assert!(updates.clone().drain_persisted(&updates), "must drop entry");
let persisted = updates.clone();
updates.set(ValuePointer::new(1, 3, 1));
{
let mut updates = updates.clone();
assert!(!updates.drain_persisted(&persisted));
assert_eq!(updates.current, Some(ValuePointer::new(1, 3, 1))); assert_eq!(
updates.to_free.as_slice(),
&[
ValuePointer::new(1, 2, 1), ]
);
}
updates.set(ValuePointer::new(1, 4, 1));
{
let mut updates = updates.clone();
assert!(!updates.drain_persisted(&persisted));
assert_eq!(updates.current, Some(ValuePointer::new(1, 4, 1))); assert_eq!(
updates.to_free.as_slice(),
&[
ValuePointer::new(1, 2, 1), ValuePointer::new(1, 3, 1), ]
);
}
let persisted = updates.clone();
updates.unset(ValuePointer::new(1, 4, 1));
{
let mut updates = updates.clone();
assert!(!updates.drain_persisted(&persisted));
assert_eq!(updates.current, None);
assert_eq!(updates.to_free.as_slice(), &[ValuePointer::new(1, 4, 1)]);
}
{
let mut updates = updates.clone();
let mut persisted = updates.clone();
persisted.to_free.swap(0, 1);
persisted.to_free.swap(1, 3);
assert!(updates.drain_persisted(&persisted));
}
}
#[test]
fn test_value_pointer_drain_bug_7741() {
let mut updates = PointerUpdates::default();
updates.set(ValuePointer::new(1, 1, 1));
updates.unset(ValuePointer::new(1, 1, 1));
let persisted = updates.clone();
updates.set(ValuePointer::new(1, 2, 1));
let do_drop = updates.drain_persisted(&persisted);
assert!(!do_drop, "must not drop entry");
let expected = {
let mut expected = PointerUpdates::default();
expected.set(ValuePointer::new(1, 2, 1));
expected
};
assert_eq!(
updates, expected,
"must have one pending update to set block offset 2",
);
}
#[test]
fn test_option_value_pointer_layout() {
#[repr(align(4))]
struct AlignedData([u8; std::mem::size_of::<OptionalPointer>()]);
assert_eq!(
std::mem::size_of::<OptionalPointer>(),
std::mem::size_of::<Option<ValuePointer>>()
);
let none_data = AlignedData([0; _]);
let none_val: &OptionalPointer = bytemuck::cast_ref(&none_data.0);
assert!(none_val.to_option().is_none());
let some_data = AlignedData([
1, 0, 0, 0, 0x44, 0x33, 0x22, 0x11, 0x88, 0x77, 0x66, 0x55, 0xDD, 0xCC, 0xBB, 0xAA, ]);
let some_val: &OptionalPointer = bytemuck::cast_ref(&some_data.0);
assert_eq!(
some_val.to_option(),
Some(ValuePointer {
page_id: 0x11223344,
block_offset: 0x55667788,
length: 0xAABBCCDD,
})
);
}
#[test]
#[ignore = "contains undefined behavior"]
fn test_layout_compatibility() {
assert_eq!(
std::mem::size_of::<OptionalPointer>(),
std::mem::size_of::<Option<ValuePointer>>()
);
let old_none = Option::<ValuePointer>::None;
let new_none = OptionalPointer::none();
unsafe { compare_layout(&old_none, &new_none) };
const PAGE_ID: PageId = 89;
const BLOCK_OFFSET: BlockOffset = 1000;
const LENGTH: u32 = 1234567;
let old_value = Some(ValuePointer {
page_id: PAGE_ID,
block_offset: BLOCK_OFFSET,
length: LENGTH,
});
let new_value = OptionalPointer::some(ValuePointer::new(PAGE_ID, BLOCK_OFFSET, LENGTH));
unsafe { compare_layout(&old_value, &new_value) };
unsafe fn compare_layout<A, B>(a: &A, b: &B) {
use std::slice::from_raw_parts;
let a_data =
unsafe { from_raw_parts((a as *const A).cast::<u8>(), std::mem::size_of::<A>()) };
let b_data =
unsafe { from_raw_parts((b as *const B).cast::<u8>(), std::mem::size_of::<B>()) };
assert_eq!(a_data, b_data);
}
}