mod common;
use common::*;
use hyperfile::config::HyperFileRuntimeConfig;
use hyperfile::file::flags::FileFlags;
use hyperfile::file::hyper::Hyper;
use hyperfile::file::mode::FileMode;
use hyperfile::segment::Segment;
use hyperfile::SegmentId;
const MIB: usize = 1024 * 1024;
fn prefix_of(uri: &str) -> String {
uri.trim_start_matches("s3://").splitn(2, '/').nth(1).unwrap().to_string()
}
fn runtime(max_object: usize) -> HyperFileRuntimeConfig {
let mut r = HyperFileRuntimeConfig::default();
r.segment_max_object_bytes = max_object;
r.data_cache_dirty_max_bytes_threshold = 1 << 40;
r.data_cache_dirty_max_blocks_threshold = (1 << 40) / 4096;
r.data_cache_dirty_max_flush_interval = u64::MAX / 2;
r.segment_buffer_size = 1 << 40;
r
}
fn mib_of(i: usize) -> Vec<u8> {
let mut v = vec![(i % 251) as u8 + 1; MIB];
v[..8].copy_from_slice(&(i as u64).to_le_bytes());
v
}
async fn object_sizes(client: &aws_sdk_s3::Client, prefix: &str, cno: u64) -> Vec<(u16, i64)> {
let mut out = Vec::new();
for k in 0..64u16 {
let key = format!("{}/{}", prefix, Segment::segid_to_staging_file_id(SegmentId::new_from_cno(cno).at_part(k)));
match client.head_object().bucket(test_bucket()).key(&key).send().await {
Ok(h) => out.push((k, h.content_length().unwrap_or(0))),
Err(_) => break,
}
}
out
}
async fn open_uploads(client: &aws_sdk_s3::Client, prefix: &str) -> usize {
client.list_multipart_uploads().bucket(test_bucket()).prefix(format!("{}/", prefix))
.send().await.expect("list multipart uploads").uploads().len()
}
async fn write_and_read_back(mib: usize, max_object: usize) -> (std::io::Result<u64>, Vec<(u16, i64)>, Vec<usize>, usize) {
let client = make_client().await;
let tf = TestFile::new(&client).await;
let p = prefix_of(tf.uri());
{
let mut h = Hyper::fs_open_or_create_with_default_opt(&client, tf.uri(), FileFlags::rdwr(), FileMode::default_file())
.await.expect("create");
let _ = h.fs_release().await;
}
let res = {
let mut h = Hyper::fs_open_opt(&client, tf.uri(), FileFlags::rdwr(), &runtime(max_object)).await.expect("open");
for i in 0..mib {
h.fs_write(i * MIB, &mib_of(i)).await.expect("write");
}
let res = h.fs_flush().await;
let _ = h.fs_release().await;
res
};
let sizes = match &res { Ok(cno) => object_sizes(&client, &p, *cno).await, Err(_) => Vec::new() };
let mut wrong = Vec::new();
if res.is_ok() {
let mut c = Hyper::fs_open(&client, tf.uri(), FileFlags::rdonly()).await.expect("reopen");
let mut buf = vec![0u8; MIB];
for i in 0..mib {
let n = c.fs_read(i * MIB, &mut buf).await.expect("read");
if n != MIB || buf != mib_of(i) {
wrong.push(i);
}
}
let _ = c.fs_release().await;
}
let open = open_uploads(&client, &p).await;
tf.cleanup(&client).await;
(res, sizes, wrong, open)
}
#[tokio::test]
#[ignore]
async fn a_checkpoint_past_1_gib_is_written_as_two_objects_and_reads_back() {
let _ = env_logger::try_init();
let (res, sizes, wrong, open) = write_and_read_back(1100, 1 << 30).await;
res.expect("flush");
assert_eq!(open, 0, "an upload was left open");
assert_eq!(sizes.len(), 2, "1100 MiB was not two objects: {:?}", sizes);
assert!(sizes.iter().all(|(_, n)| *n as usize <= 1 << 30), "an object is past 1 GiB: {:?}", sizes);
assert!(wrong.is_empty(), "{} of 1100 MiB read back wrong, first {:?}", wrong.len(), wrong.first());
}
#[tokio::test]
#[ignore]
async fn under_a_smaller_limit_each_part_is_its_own_multipart_upload() {
let _ = env_logger::try_init();
let (res, sizes, wrong, open) = write_and_read_back(150, 40 * MIB).await;
res.expect("flush");
assert_eq!(open, 0, "an upload was left open");
assert_eq!(sizes.len(), 4, "150 MiB in 40 MiB objects: {:?}", sizes);
assert!(sizes.iter().all(|(_, n)| *n as usize <= 40 * MIB), "an object is past the limit: {:?}", sizes);
assert!(wrong.is_empty(), "{} of 150 MiB read back wrong, first {:?}", wrong.len(), wrong.first());
}