Clean up tests
This commit is contained in:
parent
fa4dc716db
commit
5cdaeddc0e
3 changed files with 12 additions and 169 deletions
34
src/db.rs
34
src/db.rs
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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()]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue