use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use sz_orm_storage::{LocalStorage, Storage, StorageBuilder, StorageProvider};
static TEMP_DIR_COUNTER: AtomicU64 = AtomicU64::new(0);
fn temp_dir() -> std::path::PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let pid = std::process::id();
let counter = TEMP_DIR_COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"sz_orm_storage_stress_{}_{}_{}",
pid, nanos, counter
))
}
#[tokio::test]
async fn stress_storage_10k_files() {
let dir = temp_dir();
let storage = LocalStorage::new(dir.to_string_lossy().to_string());
let n: u64 = 10_000;
for i in 0..n {
let key = format!("file-{}.txt", i);
let data = format!("content-{}", i);
storage
.put(&key, data.as_bytes(), "text/plain")
.await
.unwrap();
}
for i in 0..n {
let key = format!("file-{}.txt", i);
let expected = format!("content-{}", i);
let data = storage.get(&key).await.unwrap();
assert_eq!(data, expected.as_bytes());
}
for i in 0..n {
let key = format!("file-{}.txt", i);
assert!(storage.exists(&key).await.unwrap());
}
for i in 0..n {
let key = format!("file-{}.txt", i);
storage.delete(&key).await.unwrap();
let mut retries = 0;
loop {
match storage.get(&key).await {
Err(sz_orm_storage::StorageError::NotFound(_)) => break,
Ok(_) => {
storage.delete(&key).await.ok();
retries += 1;
if retries > 30 {
panic!("file {} still readable after delete and 30 retries", key);
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
Err(e) => {
let _ = e;
break;
}
}
}
}
tokio::fs::remove_dir_all(&dir).await.ok();
}
#[tokio::test]
async fn stress_storage_large_files() {
let dir = temp_dir();
let storage = LocalStorage::new(dir.to_string_lossy().to_string());
let payload = vec![0xABu8; 10_000_000]; let n: usize = 100;
for i in 0..n {
let key = format!("large-{}.bin", i);
storage
.put(&key, &payload, "application/octet-stream")
.await
.unwrap();
}
for i in 0..n {
let key = format!("large-{}.bin", i);
let data = storage.get(&key).await.unwrap();
assert_eq!(data.len(), 10_000_000);
}
tokio::fs::remove_dir_all(&dir).await.ok();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn stress_storage_concurrent_put_get() {
let dir = temp_dir();
let storage = Arc::new(LocalStorage::new(dir.to_string_lossy().to_string()));
let per_task: u64 = 1000;
let task_count: u64 = 8;
let success = Arc::new(AtomicU64::new(0));
let mut handles = Vec::new();
for task_id in 0..task_count {
let s = storage.clone();
let succ = success.clone();
handles.push(tokio::spawn(async move {
for i in 0..per_task {
let key = format!("t{}-f{}.txt", task_id, i);
let data = format!("data-{}-{}", task_id, i);
s.put(&key, data.as_bytes(), "text/plain").await.unwrap();
let got = s.get(&key).await.unwrap();
assert_eq!(got, data.as_bytes());
succ.fetch_add(1, Ordering::Relaxed);
}
}));
}
for h in handles {
h.await.unwrap();
}
assert_eq!(success.load(Ordering::Relaxed), per_task * task_count);
tokio::fs::remove_dir_all(&dir).await.ok();
}
#[tokio::test]
async fn stress_storage_get_not_found() {
let dir = temp_dir();
let storage = LocalStorage::new(dir.to_string_lossy().to_string());
for i in 0..1000 {
let key = format!("nonexistent-{}", i);
let result = storage.get(&key).await;
assert!(
result.is_err(),
"get must fail for nonexistent at iter {}",
i
);
}
tokio::fs::remove_dir_all(&dir).await.ok();
}
#[tokio::test]
async fn stress_storage_delete_nonexistent_silent() {
let dir = temp_dir();
let storage = LocalStorage::new(dir.to_string_lossy().to_string());
for i in 0..1000 {
let key = format!("nonexistent-{}", i);
let result = storage.delete(&key).await;
assert!(
result.is_ok(),
"delete must not fail for nonexistent at iter {}",
i
);
}
tokio::fs::remove_dir_all(&dir).await.ok();
}
#[tokio::test]
async fn stress_storage_nested_directories() {
let dir = temp_dir();
let storage = LocalStorage::new(dir.to_string_lossy().to_string());
for i in 0..1000 {
let key = format!("level1/level2/level3/file-{}.txt", i);
let data = format!("nested-{}", i);
storage
.put(&key, data.as_bytes(), "text/plain")
.await
.unwrap();
let got = storage.get(&key).await.unwrap();
assert_eq!(got, data.as_bytes());
}
tokio::fs::remove_dir_all(&dir).await.ok();
}
#[tokio::test]
async fn stress_storage_builder_local() {
let dir = temp_dir();
let wrapper = StorageBuilder::new(StorageProvider::Local)
.with_base_path(dir.to_string_lossy().to_string())
.build()
.unwrap();
use sz_orm_storage::StorageWrapper;
match wrapper {
StorageWrapper::Local(ref s) => {
s.put("builder-test.txt", b"data", "text/plain")
.await
.unwrap();
let got = s.get("builder-test.txt").await.unwrap();
assert_eq!(got, b"data");
}
_ => panic!("expected Local variant"),
}
tokio::fs::remove_dir_all(&dir).await.ok();
}
#[tokio::test]
async fn stress_storage_put_overwrites() {
let dir = temp_dir();
let storage = LocalStorage::new(dir.to_string_lossy().to_string());
for cycle in 0..100 {
let key = "overwrite.txt";
let data = format!("version-{}", cycle);
storage
.put(key, data.as_bytes(), "text/plain")
.await
.unwrap();
let got = storage.get(key).await.unwrap();
assert_eq!(got, data.as_bytes());
}
tokio::fs::remove_dir_all(&dir).await.ok();
}