mod common;
use chisel::{Chisel, ChiselError, Options, Result, DEFAULT_SUPERBLOCK_COUNT, MAX_SUPERBLOCKS};
use tempfile::{NamedTempFile, TempDir};
fn expect_err(r: Result<Chisel>) -> ChiselError {
match r {
Ok(_) => panic!("expected an error, got a live Chisel handle"),
Err(e) => e,
}
}
fn opts(superblock_count: u32) -> Options {
Options::default()
.cache_max_bytes(16 * chisel::PAGE_SIZE as u64)
.spillway_max_bytes(0)
.drain_insertion(chisel::DrainInsertion::LruTail)
.superblock_count(superblock_count)
}
#[test]
fn test_superblock_count_zero_rejected() {
let bad = opts(0);
let f = NamedTempFile::new().unwrap();
assert!(matches!(
expect_err(Chisel::open(f.path(), bad.clone())),
ChiselError::InvalidSuperblockCount { value: 0 }
));
assert!(matches!(
expect_err(Chisel::open_in_memory_with_options(bad)),
ChiselError::InvalidSuperblockCount { value: 0 }
));
}
#[test]
fn test_superblock_count_one_rejected() {
let bad = opts(1);
let f = NamedTempFile::new().unwrap();
assert!(matches!(
expect_err(Chisel::open(f.path(), bad.clone())),
ChiselError::InvalidSuperblockCount { value: 1 }
));
assert!(matches!(
expect_err(Chisel::open_in_memory_with_options(bad)),
ChiselError::InvalidSuperblockCount { value: 1 }
));
}
#[test]
fn test_superblock_count_seventeen_rejected() {
let bad = opts(17);
let f = NamedTempFile::new().unwrap();
assert!(matches!(
expect_err(Chisel::open(f.path(), bad.clone())),
ChiselError::InvalidSuperblockCount { value: 17 }
));
assert!(matches!(
expect_err(Chisel::open_in_memory_with_options(bad)),
ChiselError::InvalidSuperblockCount { value: 17 }
));
}
#[test]
fn test_superblock_count_min_valid() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db_min.chsl");
let mut db = Chisel::open(&path, opts(2)).unwrap();
db.begin().unwrap();
let h = db.allocate(b"hello").unwrap();
db.commit().unwrap();
db.close().unwrap();
let db = Chisel::open(&path, opts(DEFAULT_SUPERBLOCK_COUNT)).unwrap();
assert_eq!(db.read(h).unwrap(), b"hello");
}
#[test]
fn test_superblock_count_max_valid_roundtrip() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db_max.chsl");
let mut db = Chisel::open(&path, opts(MAX_SUPERBLOCKS)).unwrap();
db.begin().unwrap();
let h = db.allocate(b"max slots").unwrap();
db.commit().unwrap();
db.close().unwrap();
let db = Chisel::open(&path, opts(MAX_SUPERBLOCKS)).unwrap();
assert_eq!(db.read(h).unwrap(), b"max slots");
let stats = db.stats().unwrap();
assert!(stats.total_pages >= MAX_SUPERBLOCKS as u64);
}
#[test]
fn test_superblock_count_mid_valid_roundtrip() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db_8.chsl");
let mut db = Chisel::open(&path, opts(8)).unwrap();
db.begin().unwrap();
let h = db.allocate(b"eight").unwrap();
db.commit().unwrap();
db.close().unwrap();
let db = Chisel::open(&path, opts(8)).unwrap();
assert_eq!(db.read(h).unwrap(), b"eight");
}
#[test]
fn test_reopen_discovers_superblock_count_from_disk() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("db_reopen.chsl");
let mut db = Chisel::open(&path, opts(4)).unwrap();
db.begin().unwrap();
let h = db.allocate(b"v").unwrap();
db.commit().unwrap();
let pages_after_create = db.stats().unwrap().total_pages;
db.close().unwrap();
assert!(pages_after_create >= 4);
let db = Chisel::open(&path, opts(3)).unwrap();
assert_eq!(db.read(h).unwrap(), b"v");
assert!(db.stats().unwrap().total_pages >= 4);
}
#[test]
fn test_create_if_missing_false_on_missing_file_errors() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("does_not_exist.chsl");
let err = expect_err(Chisel::open(
&path,
Options::default().create_if_missing(false),
));
assert!(matches!(err, ChiselError::FileNotFound));
}
#[test]
fn test_create_if_missing_false_on_zero_length_file_errors() {
let f = NamedTempFile::new().unwrap(); assert_eq!(std::fs::metadata(f.path()).unwrap().len(), 0);
let err = expect_err(Chisel::open(
f.path(),
Options::default().create_if_missing(false),
));
assert!(matches!(err, ChiselError::FileNotFound));
}
#[test]
fn test_zero_length_file_is_initialized_as_fresh_db() {
let f = NamedTempFile::new().unwrap();
let mut db = Chisel::open(f.path(), Options::default()).unwrap();
db.begin().unwrap();
let h = db.allocate(b"fresh").unwrap();
db.commit().unwrap();
assert_eq!(db.read(h).unwrap(), b"fresh");
}
#[test]
fn test_read_only_open_rejects_begin() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("ro.chsl");
{
let mut db = Chisel::open(&path, Options::default()).unwrap();
db.begin().unwrap();
let _ = db.allocate(b"seed").unwrap();
db.commit().unwrap();
}
let mut db = Chisel::open(
&path,
Options::default()
.cache_max_bytes(16 * chisel::PAGE_SIZE as u64)
.spillway_max_bytes(0)
.drain_insertion(chisel::DrainInsertion::LruTail)
.create_if_missing(false)
.read_only(true)
.superblock_count(DEFAULT_SUPERBLOCK_COUNT),
)
.unwrap();
let err = match db.begin() {
Ok(()) => panic!("begin() unexpectedly succeeded on a read-only handle"),
Err(e) => e,
};
assert!(
matches!(err, ChiselError::ReadOnlyMode),
"expected ReadOnlyMode, got {err:?}"
);
}
#[test]
fn test_second_open_on_same_file_fails_with_lock_error() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("locked.chsl");
let _first = Chisel::open(&path, Options::default()).unwrap();
let err = expect_err(Chisel::open(&path, Options::default()));
assert!(matches!(err, ChiselError::LockFailed));
}