use armdb::{Config, ConstTree, DbError};
use tempfile::tempdir;
#[cfg(feature = "var-collections")]
use armdb::VarTree;
#[test]
fn test_shard_count_mismatch() {
let dir = tempdir().unwrap();
{
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();
tree.put(&1u64.to_be_bytes(), &100u64.to_be_bytes())
.unwrap();
tree.close().unwrap();
}
{
let mut config = Config::test();
config.shard_count = 5; let err = unwrap_err(ConstTree::<[u8; 8], 8>::open(dir.path(), config));
assert!(
matches!(err, DbError::FormatMismatch(_)),
"expected FormatMismatch, got: {err}"
);
let msg = err.to_string();
assert!(
msg.contains("shard_count"),
"error should mention shard_count: {msg}"
);
}
}
#[test]
fn test_shard_prefix_bits_mismatch() {
let dir = tempdir().unwrap();
{
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()).unwrap();
tree.put(&1u64.to_be_bytes(), &100u64.to_be_bytes())
.unwrap();
tree.close().unwrap();
}
{
let mut config = Config::test();
config.shard_prefix_bits = 32; let err = unwrap_err(ConstTree::<[u8; 8], 8>::open(dir.path(), config));
assert!(
matches!(err, DbError::FormatMismatch(_)),
"expected FormatMismatch, got: {err}"
);
let msg = err.to_string();
assert!(
msg.contains("shard_prefix_bits"),
"error should mention shard_prefix_bits: {msg}"
);
}
}
#[test]
fn test_db_meta_invalid_size() {
let dir = tempdir().unwrap();
std::fs::create_dir_all(dir.path()).unwrap();
std::fs::write(dir.path().join("db.meta"), [0u8; 2]).unwrap();
let err = unwrap_err(ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test()));
assert!(
matches!(err, DbError::FormatMismatch(_)),
"expected FormatMismatch, got: {err}"
);
}
#[test]
fn test_same_config_reopens_fine() {
let dir = tempdir().unwrap();
let config = Config::test();
{
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), config.clone()).unwrap();
tree.put(&1u64.to_be_bytes(), &42u64.to_be_bytes()).unwrap();
tree.close().unwrap();
}
{
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), config).unwrap();
assert_eq!(tree.get(&1u64.to_be_bytes()), Some(42u64.to_be_bytes()));
}
}
#[test]
fn test_const_recovery_no_hints() {
let dir = tempdir().unwrap();
let mut config = Config::test();
config.hints = false;
{
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), config.clone()).unwrap();
for i in 0u64..100 {
tree.put(&i.to_be_bytes(), &(i * 10).to_be_bytes()).unwrap();
}
tree.close().unwrap();
}
let has_hints = walkdir(dir.path(), ".hint");
assert!(!has_hints, "no .hint files should exist when hints=false");
{
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), config).unwrap();
assert_eq!(tree.len(), 100);
for i in 0u64..100 {
let actual = tree
.get(&i.to_be_bytes())
.unwrap_or_else(|| panic!("key {i} not found"));
assert_eq!(actual, (i * 10).to_be_bytes());
}
}
}
#[test]
fn test_const_recovery_no_hints_with_deletes() {
let dir = tempdir().unwrap();
let mut config = Config::test();
config.hints = false;
{
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), config.clone()).unwrap();
for i in 0u64..100 {
tree.put(&i.to_be_bytes(), &(i * 10).to_be_bytes()).unwrap();
}
for i in 0u64..50 {
tree.delete(&i.to_be_bytes()).unwrap();
}
tree.close().unwrap();
}
{
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), config).unwrap();
assert_eq!(tree.len(), 50);
for i in 0u64..50 {
assert!(tree.get(&i.to_be_bytes()).is_none());
}
for i in 50u64..100 {
let actual = tree
.get(&i.to_be_bytes())
.unwrap_or_else(|| panic!("key {i} not found"));
assert_eq!(actual, (i * 10).to_be_bytes());
}
}
}
#[cfg(feature = "var-collections")]
#[test]
fn test_var_recovery_no_hints() {
let dir = tempdir().unwrap();
let mut config = Config::test();
config.hints = false;
{
let tree = VarTree::<[u8; 8]>::open(dir.path(), config.clone()).unwrap();
for i in 0u64..100 {
let value = format!("val_{i}").into_bytes();
tree.put(&i.to_be_bytes(), &value).unwrap();
}
tree.close().unwrap();
}
let has_hints = walkdir(dir.path(), ".hint");
assert!(!has_hints, "no .hint files should exist when hints=false");
{
let tree = VarTree::<[u8; 8]>::open(dir.path(), config).unwrap();
assert_eq!(tree.len(), 100);
for i in 0u64..100 {
let expected = format!("val_{i}").into_bytes();
let actual = tree
.get(&i.to_be_bytes())
.unwrap_or_else(|| panic!("key {i} not found"));
assert_eq!(actual.as_bytes(), expected.as_slice());
}
}
}
#[test]
fn test_switch_hints_true_to_false() {
let dir = tempdir().unwrap();
{
let mut config = Config::test();
config.hints = true;
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), config).unwrap();
for i in 0u64..50 {
tree.put(&i.to_be_bytes(), &(i * 10).to_be_bytes()).unwrap();
}
tree.close().unwrap();
}
let had_hints = walkdir(dir.path(), ".hint");
assert!(had_hints, "hint files should exist after hints=true close");
{
let mut config = Config::test();
config.hints = false;
let tree = ConstTree::<[u8; 8], 8>::open(dir.path(), config).unwrap();
assert_eq!(tree.len(), 50);
for i in 0u64..50 {
let actual = tree
.get(&i.to_be_bytes())
.unwrap_or_else(|| panic!("key {i} not found"));
assert_eq!(actual, (i * 10).to_be_bytes());
}
}
}
fn unwrap_err<T>(result: Result<T, DbError>) -> DbError {
match result {
Err(e) => e,
Ok(_) => panic!("expected error, got Ok"),
}
}
fn walkdir(dir: &std::path::Path, ext: &str) -> bool {
fn walk(dir: &std::path::Path, ext: &str) -> bool {
let Ok(entries) = std::fs::read_dir(dir) else {
return false;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if walk(&path, ext) {
return true;
}
} else if path.to_string_lossy().ends_with(ext) {
return true;
}
}
false
}
walk(dir, ext)
}