#[allow(dead_code)]
mod common;
use common::*;
use hyperfile::file::hyper::Hyper;
use hyperfile::file::flags::FileFlags;
use hyperfile::file::mode::FileMode;
use hyperfile::config::{HyperFileConfig, HyperFileConfigBuilder, HyperFileRuntimeConfig};
use hyperfile::data_cache::config::HyperFileDataCacheConfig;
use hyperfile::staging::config::StagingConfig;
const BLK: usize = 4096;
struct CacheDir(String);
impl CacheDir {
fn new(tag: &str) -> Self {
let path = format!("/tmp/hyperfile-ldc-test-{}-{}", std::process::id(), tag);
std::fs::create_dir_all(&path).expect("create cache dir");
Self(path)
}
}
impl Drop for CacheDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn local_disk_config(uri: &str, dir: &CacheDir, data_cache_blocks: usize) -> HyperFileConfig {
let mut runtime = HyperFileRuntimeConfig::default();
runtime.data_cache_blocks = data_cache_blocks;
let staging = StagingConfig::new_s3_uri(uri, None);
HyperFileConfigBuilder::new()
.with_staging_config(&staging)
.with_data_cache_config(&HyperFileDataCacheConfig::new_local_disk(Some(&dir.0), None))
.with_runtime_config(&runtime)
.build()
}
#[tokio::test]
#[ignore]
async fn create_empty_file_on_local_disk_cache() {
let _ = env_logger::try_init();
let client = make_client().await;
let dir = CacheDir::new("create");
let tf = TestFile::new(&client).await;
let mut h = Hyper::fs_open_or_create_with_config(
&client, local_disk_config(tf.uri(), &dir, 64), FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create an empty file with the local-disk data cache");
assert_eq!(h.fs_getattr().expect("getattr").st_size, 0);
let _ = h.fs_flush().await.expect("flush");
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn release_does_not_abort() {
let _ = env_logger::try_init();
let client = make_client().await;
let dir = CacheDir::new("release");
let tf = TestFile::new(&client).await;
for round in 0..3 {
let mut h = Hyper::fs_open_or_create_with_config(
&client, local_disk_config(tf.uri(), &dir, 32), FileFlags::rdwr(), FileMode::default_file(),
).await.unwrap_or_else(|e| panic!("round {round}: open: {e}"));
let _ = h.fs_write(0, &vec![round as u8; BLK]).await.expect("write");
let _ = h.fs_flush().await.expect("flush");
let _ = h.fs_release().await.expect("release");
}
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn extending_write_far_past_eof() {
let _ = env_logger::try_init();
let client = make_client().await;
let dir = CacheDir::new("extend");
let tf = TestFile::new(&client).await;
let mut h = Hyper::fs_open_or_create_with_config(
&client, local_disk_config(tf.uri(), &dir, 64), FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create");
let _ = h.fs_write(0, &vec![0x11u8; BLK]).await.expect("write block 0");
let _ = h.fs_flush().await.expect("flush");
let _ = h.fs_write(40 * BLK, &vec![0xEEu8; BLK]).await.expect("write block 40");
let _ = h.fs_write(500 * BLK, &vec![0xCCu8; BLK]).await.expect("write block 500");
let _ = h.fs_flush().await.expect("flush");
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(0, &mut buf).await.expect("read 0");
assert!(buf.iter().all(|b| *b == 0x11), "block 0 damaged");
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(40 * BLK, &mut buf).await.expect("read 40");
assert!(buf.iter().all(|b| *b == 0xEE), "block 40 wrong");
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(500 * BLK, &mut buf).await.expect("read 500");
assert!(buf.iter().all(|b| *b == 0xCC), "block 500 wrong");
let mut buf = vec![0xFFu8; BLK];
let _ = h.fs_read(100 * BLK, &mut buf).await.expect("read hole");
assert!(buf.iter().all(|b| *b == 0), "hole should read as zeros");
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn sparse_address_space() {
let _ = env_logger::try_init();
let client = make_client().await;
let dir = CacheDir::new("sparse");
let tf = TestFile::new(&client).await;
let mut h = Hyper::fs_open_or_create_with_config(
&client, local_disk_config(tf.uri(), &dir, 64), FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create");
let far = 1usize << 42;
let _ = h.fs_write(0, &vec![0xA0u8; BLK]).await.expect("write low");
let _ = h.fs_write(far, &vec![0xB0u8; BLK]).await.expect("write far");
let _ = h.fs_flush().await.expect("flush");
assert_eq!(h.fs_getattr().expect("getattr").st_size, (far + BLK) as i64);
assert_eq!(h.fs_getattr().expect("getattr").st_blocks, 2 * (BLK / 512) as i64);
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(0, &mut buf).await.expect("read low");
assert!(buf.iter().all(|b| *b == 0xA0));
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(far, &mut buf).await.expect("read far");
assert!(buf.iter().all(|b| *b == 0xB0));
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn slot_recycling_across_many_blocks() {
let _ = env_logger::try_init();
let client = make_client().await;
let dir = CacheDir::new("recycle");
let tf = TestFile::new(&client).await;
let mut h = Hyper::fs_open_or_create_with_config(
&client, local_disk_config(tf.uri(), &dir, 4), FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create");
const N: u64 = 64;
for i in 0..N {
let _ = h.fs_write(i as usize * BLK, &vec![(i + 1) as u8; BLK]).await
.unwrap_or_else(|e| panic!("write block {i}: {e}"));
if i % 8 == 7 {
let _ = h.fs_flush().await.expect("flush");
}
}
let _ = h.fs_flush().await.expect("flush");
for i in 0..N {
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(i as usize * BLK, &mut buf).await.expect("read");
let want = (i + 1) as u8;
assert!(buf.iter().all(|b| *b == want),
"block {i} should be all {want:#04x}, got first byte {:#04x}", buf[0]);
}
for i in (0..N).rev() {
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(i as usize * BLK, &mut buf).await.expect("reread");
let want = (i + 1) as u8;
assert!(buf.iter().all(|b| *b == want), "block {i} wrong on reread");
}
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn truncate_releases_slots_and_leaves_no_stale_bytes() {
let _ = env_logger::try_init();
let client = make_client().await;
let dir = CacheDir::new("truncate");
let tf = TestFile::new(&client).await;
let mut h = Hyper::fs_open_or_create_with_config(
&client, local_disk_config(tf.uri(), &dir, 64), FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create");
let _ = h.fs_write(0, &vec![0xABu8; 8 * BLK]).await.expect("write");
let _ = h.fs_flush().await.expect("flush");
let _ = h.fs_truncate(BLK).await.expect("shrink");
let _ = h.fs_flush().await.expect("flush");
let _ = h.fs_truncate(8 * BLK).await.expect("grow");
let _ = h.fs_flush().await.expect("flush");
let mut buf = vec![0xFFu8; BLK];
let _ = h.fs_read(0, &mut buf).await.expect("read surviving block");
assert!(buf.iter().all(|b| *b == 0xAB), "block 0 should survive the truncate");
for i in 1..8u64 {
let mut buf = vec![0xFFu8; BLK];
let _ = h.fs_read(i as usize * BLK, &mut buf).await.expect("read regrown");
assert!(buf.iter().all(|b| *b == 0),
"block {i} above the old EOF must read as zeros, found {:#04x}", buf[0]);
}
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}
#[tokio::test]
#[ignore]
async fn pool_exhaustion_falls_back_correctly() {
let _ = env_logger::try_init();
let client = make_client().await;
let dir = CacheDir::new("exhaust");
let tf = TestFile::new(&client).await;
let mut h = Hyper::fs_open_or_create_with_config(
&client, local_disk_config(tf.uri(), &dir, 8), FileFlags::rdwr(), FileMode::default_file(),
).await.expect("create");
const N: usize = 3000;
let mut payload = vec![0u8; N * BLK];
for i in 0..N {
payload[i * BLK..(i + 1) * BLK].fill((i % 251) as u8);
}
let _ = h.fs_write(0, &payload).await.expect("one big write");
let _ = h.fs_flush().await.expect("flush");
for i in [0usize, 1, 500, 1500, 2999] {
let mut buf = vec![0u8; BLK];
let _ = h.fs_read(i * BLK, &mut buf).await.expect("read");
let want = (i % 251) as u8;
assert!(buf.iter().all(|b| *b == want),
"block {i} should be all {want:#04x}, got {:#04x}", buf[0]);
}
let _ = h.fs_release().await.expect("release");
tf.cleanup(&client).await;
}