#![cfg(feature = "var-collections")]
use std::path::Path;
use armdb::{Config, VarTree};
use tempfile::tempdir;
const HDR: usize = 16;
const KEY_LEN: usize = 8;
#[derive(Clone, Copy, Debug)]
enum Mode {
DirectIo,
#[cfg(feature = "encryption")]
Encrypted,
Plain,
}
fn config(mode: Mode) -> Config {
let mut c = Config::test();
c.shard_count = 1;
c.sync_batch_size = 1;
match mode {
Mode::DirectIo => c.direct_io = true,
#[cfg(feature = "encryption")]
Mode::Encrypted => c.encryption_key = Some([0x42; 32]),
Mode::Plain => {}
}
c
}
fn page_aligned_modes() -> Vec<Mode> {
vec![
Mode::DirectIo,
#[cfg(feature = "encryption")]
Mode::Encrypted,
]
}
fn key(i: u64) -> [u8; KEY_LEN] {
i.to_be_bytes()
}
fn value_for_entry(entry: usize, byte: u8) -> Vec<u8> {
vec![byte; entry - HDR - KEY_LEN]
}
fn write_and_reopen(mode: Mode, first_entry: usize) {
let dir = tempdir().unwrap();
let first = value_for_entry(first_entry, 0xAA);
{
let t = VarTree::<[u8; KEY_LEN]>::open(dir.path(), config(mode)).unwrap();
t.put(&key(1), &first).unwrap();
t.put(&key(2), &[0xBB; 100]).unwrap();
t.put(&key(3), &[0xCC; 200]).unwrap();
t.close().unwrap();
}
let t = VarTree::<[u8; KEY_LEN]>::open(dir.path(), config(mode))
.unwrap_or_else(|e| panic!("{mode:?}, entry {first_entry}: reopen failed: {e:?}"));
assert_eq!(t.len(), 3, "{mode:?}, entry {first_entry}");
assert_eq!(t.get(&key(1)).unwrap().as_ref(), &first[..]);
assert_eq!(t.get(&key(2)).unwrap().as_ref(), &[0xBB; 100][..]);
assert_eq!(t.get(&key(3)).unwrap().as_ref(), &[0xCC; 200][..]);
}
#[test]
fn reopen_across_8_byte_padding() {
for mode in page_aligned_modes() {
write_and_reopen(mode, 4088);
}
}
#[test]
fn reopen_across_16_and_24_byte_padding() {
for mode in page_aligned_modes() {
write_and_reopen(mode, 4080);
write_and_reopen(mode, 4072);
}
}
#[test]
fn plain_buffered_entry_4088_reopens() {
write_and_reopen(Mode::Plain, 4088);
}
fn rotate_and_compact(mode: Mode) {
let dir = tempdir().unwrap();
let mut cfg = config(mode);
cfg.max_file_size = 64 * 1024;
cfg.write_buffer_size = 16 * 1024;
cfg.compaction_threshold = 0.1;
let first = value_for_entry(4088, 0xAA);
let last = |i: u64| vec![(i % 251) as u8; 1000];
{
let t = VarTree::<[u8; KEY_LEN]>::open(dir.path(), cfg.clone()).unwrap();
t.put(&key(100), &first).unwrap();
for i in 0..200u64 {
t.put(&key(i % 5), &last(i)).unwrap();
}
t.put(&key(100), &[0xDD; 300]).unwrap();
t.compact()
.unwrap_or_else(|e| panic!("{mode:?}: compact failed: {e:?}"));
assert_eq!(t.get(&key(100)).unwrap().as_ref(), &[0xDD; 300][..]);
t.close().unwrap();
}
let t = VarTree::<[u8; KEY_LEN]>::open(dir.path(), cfg)
.unwrap_or_else(|e| panic!("{mode:?}: reopen failed: {e:?}"));
let shard = dir.path().join("shard_000");
let ids = data_file_ids(&shard);
assert!(
ids.len() > 1,
"{mode:?}: expected rotated files, got {ids:?}"
);
for id in &ids[..ids.len() - 1] {
assert!(has_hint(&shard, *id), "{mode:?}: no hint for {id:06}.data");
}
assert_eq!(t.len(), 6);
assert_eq!(t.get(&key(100)).unwrap().as_ref(), &[0xDD; 300][..]);
for k in 0..5u64 {
assert_eq!(t.get(&key(k)).unwrap().as_ref(), &last(195 + k)[..]);
}
}
fn data_file_ids(shard_dir: &Path) -> Vec<u32> {
let mut ids: Vec<u32> = std::fs::read_dir(shard_dir)
.unwrap()
.filter_map(|e| {
let name = e.ok()?.file_name().to_string_lossy().into_owned();
name.strip_suffix(".data")?.parse().ok()
})
.collect();
ids.sort_unstable();
ids
}
fn has_hint(shard_dir: &Path, file_id: u32) -> bool {
shard_dir.join(format!("{file_id:06}.hint")).exists()
}
#[test]
fn rotate_and_compact_across_8_byte_padding() {
for mode in page_aligned_modes() {
rotate_and_compact(mode);
}
}