Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI

on:
pull_request:
branches: [dev]

env:
CARGO_TERM_COLOR: always

jobs:
fmt:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- run: cargo +nightly fmt --all -- --check

clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings

test:
name: Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace
77 changes: 77 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,83 @@ fn parse_number_impl(n: &str) -> Option<u64> {
mod tests {
use super::*;

#[test]
fn test_write_ordering_from_str() {
assert!(matches!(
"Weak".parse::<WriteOrdering>().unwrap(),
WriteOrdering::Weak
));
assert!(matches!(
"Medium".parse::<WriteOrdering>().unwrap(),
WriteOrdering::Medium
));
assert!(matches!(
"Strong".parse::<WriteOrdering>().unwrap(),
WriteOrdering::Strong
));
assert!("weak".parse::<WriteOrdering>().is_err());
assert!("".parse::<WriteOrdering>().is_err());
}

#[test]
fn test_write_ordering_display_roundtrip() {
for ordering in [
WriteOrdering::Weak,
WriteOrdering::Medium,
WriteOrdering::Strong,
] {
let s = ordering.to_string();
let parsed: WriteOrdering = s.parse().unwrap();
assert_eq!(ordering.to_string(), parsed.to_string());
}
}

#[test]
fn test_read_consistency_type_from_str() {
assert!(matches!(
"All".parse::<ReadConsistencyType>().unwrap(),
ReadConsistencyType::All
));
assert!(matches!(
"Majority".parse::<ReadConsistencyType>().unwrap(),
ReadConsistencyType::Majority
));
assert!(matches!(
"Quorum".parse::<ReadConsistencyType>().unwrap(),
ReadConsistencyType::Quorum
));
assert!("all".parse::<ReadConsistencyType>().is_err());
assert!("".parse::<ReadConsistencyType>().is_err());
}

#[test]
fn test_read_consistency_type_display_roundtrip() {
for consistency in [
ReadConsistencyType::All,
ReadConsistencyType::Majority,
ReadConsistencyType::Quorum,
] {
let s = consistency.to_string();
let parsed: ReadConsistencyType = s.parse().unwrap();
assert_eq!(consistency.to_string(), parsed.to_string());
}
}

#[test]
fn test_read_consistency_from_str_with_factor() {
let parsed: ReadConsistency = "3".parse().unwrap();
assert!(matches!(parsed, ReadConsistency::Factor(3)));
}

#[test]
fn test_read_consistency_from_str_with_type() {
let parsed: ReadConsistency = "All".parse().unwrap();
assert!(matches!(
parsed,
ReadConsistency::Type(ReadConsistencyType::All)
));
}

#[test]
fn test_parse_number() {
// Basic numbers
Expand Down
119 changes: 119 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,122 @@ pub fn int_payload_name(id: usize) -> String {
pub fn float_payload_name(id: usize) -> String {
format!("{}{}", payload_prefixes(id), FLOAT_PAYLOAD_KEY)
}

#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;

fn seeded_rng() -> rand::rngs::StdRng {
rand::rngs::StdRng::seed_from_u64(42)
}

#[test]
fn test_payload_prefixes() {
assert_eq!(payload_prefixes(0), "");
assert_eq!(payload_prefixes(1), "payload_1_");
assert_eq!(payload_prefixes(5), "payload_5_");
}

#[test]
fn test_keyword_payload_name() {
assert_eq!(keyword_payload_name(0), KEYWORD_PAYLOAD_KEY);
assert_eq!(
keyword_payload_name(1),
format!("payload_1_{KEYWORD_PAYLOAD_KEY}")
);
}

#[test]
fn test_int_payload_name() {
assert_eq!(int_payload_name(0), INTEGERS_PAYLOAD_KEY);
assert_eq!(
int_payload_name(2),
format!("payload_2_{INTEGERS_PAYLOAD_KEY}")
);
}

#[test]
fn test_float_payload_name() {
assert_eq!(float_payload_name(0), FLOAT_PAYLOAD_KEY);
assert_eq!(
float_payload_name(3),
format!("payload_3_{FLOAT_PAYLOAD_KEY}")
);
}

#[test]
fn test_random_keyword_format() {
let mut rng = seeded_rng();
let kw = random_keyword(&mut rng, 10, 1);
assert!(kw.starts_with("keyword_"));
}

#[test]
fn test_random_keyword_length_multiplier() {
let mut rng = seeded_rng();
let single = random_keyword(&mut rng, 10, 1);
let mut rng = seeded_rng();
let triple = random_keyword(&mut rng, 10, 3);
assert_eq!(triple.len(), single.len() * 3);
}

#[test]
fn test_random_dense_vector_dimension() {
let mut rng = seeded_rng();
let vec = random_dense_vector(&mut rng, 128, false);
assert_eq!(vec.len(), 128);
}

#[test]
fn test_random_dense_vector_float_range() {
let mut rng = seeded_rng();
let vec = random_dense_vector(&mut rng, 1000, false);
for &v in &vec {
assert!((-1.0..1.0).contains(&v), "value {v} out of [-1, 1) range");
}
}

#[test]
fn test_random_dense_vector_uint_range() {
let mut rng = seeded_rng();
let vec = random_dense_vector(&mut rng, 1000, true);
for &v in &vec {
assert!(
(0.0..255.0).contains(&v),
"uint8 value {v} out of [0, 255) range"
);
assert_eq!(v, v.floor(), "uint8 value {v} should be integral");
}
}

#[test]
fn test_random_text_word_count() {
let mut rng = seeded_rng();
let zipf = create_zipf(DEFAULT_VOCAB_SIZE);
let text = random_text(&mut rng, 5, &zipf);
let words: Vec<&str> = text.split_whitespace().collect();
assert_eq!(words.len(), 5);
for word in &words {
assert!(word.starts_with("word_"));
}
}

#[test]
fn test_random_sparse_vector_bounds() {
let mut rng = seeded_rng();
let pairs = random_sparse_vector(&mut rng, 100, 0.5);
for &(idx, val) in &pairs {
assert!(idx >= 1, "sparse index should be >= 1, got {idx}");
assert!(idx <= 100, "sparse index should be <= max_size, got {idx}");
assert!(val >= 0.0, "sparse value should be non-negative, got {val}");
}
}

#[test]
fn test_random_sparse_vector_empty_with_zero_sparsity() {
let mut rng = seeded_rng();
let pairs = random_sparse_vector(&mut rng, 100, 0.0);
assert!(pairs.is_empty());
}
}
37 changes: 37 additions & 0 deletions src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,40 @@ async fn process_with_rps<P: Processor + Sync>(

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_searcher_results_serde_roundtrip() {
let results = SearcherResults {
server_timings: vec![1.0, 2.5, 3.7],
rps: vec![100.0, 200.0],
full_timings: vec![0.5, 1.5, 2.5, 3.5],
};

let json = serde_json::to_string(&results).unwrap();
let deserialized: SearcherResults = serde_json::from_str(&json).unwrap();

assert_eq!(results.server_timings, deserialized.server_timings);
assert_eq!(results.rps, deserialized.rps);
assert_eq!(results.full_timings, deserialized.full_timings);
}

#[test]
fn test_searcher_results_empty() {
let results = SearcherResults {
server_timings: vec![],
rps: vec![],
full_timings: vec![],
};

let json = serde_json::to_string(&results).unwrap();
let deserialized: SearcherResults = serde_json::from_str(&json).unwrap();

assert!(deserialized.server_timings.is_empty());
assert!(deserialized.rps.is_empty());
assert!(deserialized.full_timings.is_empty());
}
}
Loading