Clean up tests

This commit is contained in:
Silas Brack 2026-03-08 13:31:35 +01:00
parent fa4dc716db
commit 5cdaeddc0e
3 changed files with 12 additions and 169 deletions

View file

@ -205,43 +205,15 @@ impl Db {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn test_prefix_upper_bound_simple() {
assert_eq!(prefix_upper_bound("abc"), Some("abd".to_string()));
assert_eq!(prefix_upper_bound("a"), Some("b".to_string()));
assert_eq!(prefix_upper_bound("foo/bar"), Some("foo/bas".to_string()));
}
#[test]
fn test_prefix_upper_bound_paths() {
// '/' is 0x2F, +1 = '0' (0x30)
assert_eq!(prefix_upper_bound("users/"), Some("users0".to_string()));
// '_' is 0x5F, +1 = '`' (0x60)
assert_eq!(prefix_upper_bound("img_"), Some("img`".to_string()));
}
#[test]
fn test_prefix_upper_bound_empty() {
assert_eq!(prefix_upper_bound(""), None);
}
#[test]
fn test_prefix_upper_bound_single_char() {
assert_eq!(prefix_upper_bound("z"), Some("{".to_string())); // 'z' + 1 = '{'
assert_eq!(prefix_upper_bound("9"), Some(":".to_string())); // '9' + 1 = ':'
}
#[test] #[test]
fn test_prefix_upper_bound_range_correctness() { fn test_prefix_upper_bound_range_correctness() {
// Verify the bound works for range queries: // The only test that matters: does the bound actually work for range queries?
// All strings starting with "foo" should be >= "foo" and < "fop" // All strings starting with "foo" should be >= "foo" and < upper_bound("foo")
let prefix = "foo"; let prefix = "foo";
let upper = prefix_upper_bound(prefix).unwrap(); let upper = prefix_upper_bound(prefix).unwrap();
assert_eq!(upper, "fop");
let upper = upper.as_str(); let upper = upper.as_str();
// These should be in range [foo, fop) // These should be in range [prefix, upper)
assert!("foo" >= prefix && "foo" < upper); assert!("foo" >= prefix && "foo" < upper);
assert!("foo/bar" >= prefix && "foo/bar" < upper); assert!("foo/bar" >= prefix && "foo/bar" < upper);
assert!("foobar" >= prefix && "foobar" < upper); assert!("foobar" >= prefix && "foobar" < upper);

View file

@ -106,67 +106,19 @@ pub async fn run(args: &Args) {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn test_merge_empty_scans() {
let scans: Vec<(String, Vec<(String, i64)>)> = vec![];
let index = merge_volume_scans(&scans);
assert!(index.is_empty());
}
#[test]
fn test_merge_single_volume() {
let scans = vec![(
"http://vol1".to_string(),
vec![
("key1".to_string(), 100),
("key2".to_string(), 200),
],
)];
let index = merge_volume_scans(&scans);
assert_eq!(index.len(), 2);
assert_eq!(index.get("key1"), Some(&(vec!["http://vol1".to_string()], 100)));
assert_eq!(index.get("key2"), Some(&(vec!["http://vol1".to_string()], 200)));
}
#[test]
fn test_merge_key_on_multiple_volumes() {
let scans = vec![
("http://vol1".to_string(), vec![("shared".to_string(), 100)]),
("http://vol2".to_string(), vec![("shared".to_string(), 100)]),
("http://vol3".to_string(), vec![("shared".to_string(), 100)]),
];
let index = merge_volume_scans(&scans);
assert_eq!(index.len(), 1);
let (volumes, size) = index.get("shared").unwrap();
assert_eq!(volumes.len(), 3);
assert!(volumes.contains(&"http://vol1".to_string()));
assert!(volumes.contains(&"http://vol2".to_string()));
assert!(volumes.contains(&"http://vol3".to_string()));
assert_eq!(*size, 100);
}
#[test] #[test]
fn test_merge_takes_max_size() { fn test_merge_takes_max_size() {
// Same key with different sizes on different volumes (corruption or update race) // Edge case: same key with different sizes across volumes
// (can happen due to incomplete writes or corruption)
// We take the max size as the authoritative value
let scans = vec![ let scans = vec![
("http://vol1".to_string(), vec![("key".to_string(), 50)]), ("http://vol1".to_string(), vec![("key".to_string(), 50)]),
("http://vol2".to_string(), vec![("key".to_string(), 200)]), ("http://vol2".to_string(), vec![("key".to_string(), 200)]),
("http://vol3".to_string(), vec![("key".to_string(), 100)]), ("http://vol3".to_string(), vec![("key".to_string(), 100)]),
]; ];
let index = merge_volume_scans(&scans); let index = merge_volume_scans(&scans);
let (_, size) = index.get("key").unwrap(); let (volumes, size) = index.get("key").unwrap();
assert_eq!(volumes.len(), 3);
assert_eq!(*size, 200, "should take maximum size across volumes"); assert_eq!(*size, 200, "should take maximum size across volumes");
} }
#[test]
fn test_merge_disjoint_keys() {
let scans = vec![
("http://vol1".to_string(), vec![("a".to_string(), 10)]),
("http://vol2".to_string(), vec![("b".to_string(), 20)]),
];
let index = merge_volume_scans(&scans);
assert_eq!(index.len(), 2);
assert_eq!(index.get("a").unwrap().0, vec!["http://vol1".to_string()]);
assert_eq!(index.get("b").unwrap().0, vec!["http://vol2".to_string()]);
}
} }

View file

@ -243,88 +243,7 @@ pub async fn list_keys(
Ok((StatusCode::OK, keys.join("\n")).into_response()) Ok((StatusCode::OK, keys.join("\n")).into_response())
} }
#[cfg(test)] // Note: first_healthy_volume and shuffle_volumes are trivial functions
mod tests { // (essentially .find() and .shuffle()). Testing them would just test
use super::*; // that standard library functions work. The real test is integration:
// does failover work with actual down volumes?
#[test]
fn test_volumes_for_key_sufficient() {
let volumes: Vec<String> = (1..=3).map(|i| format!("http://vol{i}")).collect();
let selected = crate::hasher::volumes_for_key("test-key", &volumes, 2);
assert_eq!(selected.len(), 2);
}
#[test]
fn test_volumes_for_key_insufficient() {
let volumes: Vec<String> = vec!["http://vol1".into()];
let selected = crate::hasher::volumes_for_key("test-key", &volumes, 2);
assert_eq!(selected.len(), 1);
}
#[test]
fn test_first_healthy_volume_finds_first() {
let volumes = vec!["http://vol1".into(), "http://vol2".into(), "http://vol3".into()];
let results = vec![true, true, true];
assert_eq!(
first_healthy_volume("key", &volumes, &results),
ProbeResult::Found("http://vol1/key".into())
);
}
#[test]
fn test_first_healthy_volume_skips_unhealthy() {
let volumes = vec!["http://vol1".into(), "http://vol2".into(), "http://vol3".into()];
let results = vec![false, false, true];
assert_eq!(
first_healthy_volume("key", &volumes, &results),
ProbeResult::Found("http://vol3/key".into())
);
}
#[test]
fn test_first_healthy_volume_all_failed() {
let volumes = vec!["http://vol1".into(), "http://vol2".into()];
let results = vec![false, false];
assert_eq!(
first_healthy_volume("key", &volumes, &results),
ProbeResult::AllFailed
);
}
#[test]
fn test_first_healthy_volume_early_exit() {
// Simulates early exit: only first two volumes were probed
let volumes = vec!["http://vol1".into(), "http://vol2".into(), "http://vol3".into()];
let results = vec![false, true]; // Only 2 results because we stopped early
assert_eq!(
first_healthy_volume("key", &volumes, &results),
ProbeResult::Found("http://vol2/key".into())
);
}
#[test]
fn test_shuffle_volumes_deterministic_with_seed() {
let volumes: Vec<String> = (1..=5).map(|i| format!("http://vol{i}")).collect();
let a = shuffle_volumes(volumes.clone(), 42);
let b = shuffle_volumes(volumes.clone(), 42);
assert_eq!(a, b, "same seed should produce same order");
}
#[test]
fn test_shuffle_volumes_different_seeds() {
let volumes: Vec<String> = (1..=10).map(|i| format!("http://vol{i}")).collect();
let a = shuffle_volumes(volumes.clone(), 1);
let b = shuffle_volumes(volumes.clone(), 2);
assert_ne!(a, b, "different seeds should produce different orders");
}
#[test]
fn test_shuffle_volumes_preserves_elements() {
let volumes: Vec<String> = (1..=5).map(|i| format!("http://vol{i}")).collect();
let mut shuffled = shuffle_volumes(volumes.clone(), 123);
shuffled.sort();
let mut original = volumes;
original.sort();
assert_eq!(shuffled, original);
}
}