Skip to content

Commit 93956b5

Browse files
committed
feat(test): Improved test coverage for edge cases and errors
1 parent fb0d576 commit 93956b5

4 files changed

Lines changed: 388 additions & 4 deletions

File tree

tests/python/test_errors.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import pytest
2+
import multiprocessing
3+
import sys
4+
from simstring_rust.extractors import CustomExtractor
5+
from simstring_rust.database import HashDb
6+
7+
def run_crashing_extractor():
8+
class CrashingExtractor:
9+
def apply(self, text):
10+
raise ValueError("Crash!")
11+
12+
extractor = CustomExtractor(CrashingExtractor())
13+
db = HashDb(extractor)
14+
# This should panic the Rust side because of the unhandled exception in the callback
15+
db.insert("foo")
16+
17+
def test_custom_extractor_panic():
18+
# Run the crashing code in a separate process
19+
p = multiprocessing.Process(target=run_crashing_extractor)
20+
p.start()
21+
p.join()
22+
23+
# Check if the process exited with an error (panic usually causes non-zero exit code)
24+
assert p.exitcode != 0
25+
26+
def test_custom_extractor_missing_apply():
27+
class BadExtractor:
28+
pass
29+
30+
with pytest.raises(TypeError, match="Custom extractor must provide an apply"):
31+
CustomExtractor(BadExtractor())

tests/test_features.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,30 @@ mod word_ngrams_tests {
229229
);
230230
}
231231
}
232+
233+
#[test]
234+
fn test_character_ngrams_input_shorter_than_n() {
235+
// The condition `total_len < self.n` is only reachable if `text_len + 2*(n-1) < n`.
236+
// This simplifies to `text_len + n < 2`.
237+
// This is only possible if n=1 and text_len=0.
238+
// For any n >= 2, the padding ensures total_len >= n.
239+
240+
let extractor = CharacterNgrams::new(1, "$");
241+
let mut interner = Rodeo::default();
242+
243+
// "" -> len 0. n=1. padding=0. total_len=0. 0 < 1.
244+
let features = extractor.features("", &mut interner);
245+
assert!(
246+
features.is_empty(),
247+
"Features should be empty when input length is shorter than n (and n=1)"
248+
);
249+
}
250+
251+
#[test]
252+
fn test_word_ngrams_n_zero() {
253+
let extractor = WordNgrams::new(0, " ", "#");
254+
let mut interner = Rodeo::default();
255+
256+
let features = extractor.features("hello world", &mut interner);
257+
assert!(features.is_empty(), "Features should be empty when n=0");
258+
}

tests/test_measures.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,77 @@ mod overlap_tests {
276276
assert_eq!(measure.minimum_common_feature_count(query_size, 5, 0.5), 3);
277277
}
278278
}
279+
280+
// --- Edge Case Tests ---
281+
282+
fn create_dummy_db() -> HashDb {
283+
let feature_extractor = Arc::new(CharacterNgrams::new(2, "$"));
284+
HashDb::new(feature_extractor)
285+
}
286+
287+
#[test]
288+
fn test_cosine_edge_cases() {
289+
let measure = Cosine;
290+
let mut interner = Rodeo::default();
291+
let x = vec![interner.get_or_intern("a")];
292+
let empty = vec![];
293+
294+
// Similarity: Empty inputs
295+
assert_eq!(measure.similarity(&empty, &empty), 0.0);
296+
assert_eq!(measure.similarity(&x, &empty), 0.0);
297+
assert_eq!(measure.similarity(&empty, &x), 0.0);
298+
299+
// max_feature_size: alpha = 0.0
300+
let db = create_dummy_db();
301+
// Should return db.max_feature_len() (which is 0 for empty db)
302+
assert_eq!(measure.max_feature_size(5, 0.0, &db), 0);
303+
}
304+
305+
#[test]
306+
fn test_dice_edge_cases() {
307+
let measure = Dice;
308+
let mut interner = Rodeo::default();
309+
let x = vec![interner.get_or_intern("a")];
310+
let empty = vec![];
311+
312+
// Similarity: Empty inputs
313+
assert_eq!(measure.similarity(&empty, &empty), 1.0);
314+
assert_eq!(measure.similarity(&x, &empty), 0.0);
315+
assert_eq!(measure.similarity(&empty, &x), 0.0);
316+
317+
// min_feature_size: alpha > 2.0
318+
assert_eq!(measure.min_feature_size(5, 2.1), 0);
319+
320+
// max_feature_size: alpha = 0.0
321+
let db = create_dummy_db();
322+
assert_eq!(measure.max_feature_size(5, 0.0, &db), 0);
323+
}
324+
325+
#[test]
326+
fn test_jaccard_edge_cases() {
327+
let measure = Jaccard;
328+
let mut interner = Rodeo::default();
329+
let x = vec![interner.get_or_intern("a")];
330+
let empty = vec![];
331+
332+
// Similarity: Empty inputs
333+
assert_eq!(measure.similarity(&empty, &empty), 1.0);
334+
assert_eq!(measure.similarity(&x, &empty), 0.0);
335+
assert_eq!(measure.similarity(&empty, &x), 0.0);
336+
337+
// minimum_common_feature_count: alpha = -1.0
338+
assert_eq!(measure.minimum_common_feature_count(5, 5, -1.0), 0);
339+
}
340+
341+
#[test]
342+
fn test_overlap_edge_cases() {
343+
let measure = Overlap;
344+
let mut interner = Rodeo::default();
345+
let x = vec![interner.get_or_intern("a")];
346+
let empty = vec![];
347+
348+
// Similarity: Empty inputs
349+
assert_eq!(measure.similarity(&empty, &empty), 1.0);
350+
assert_eq!(measure.similarity(&x, &empty), 0.0);
351+
assert_eq!(measure.similarity(&empty, &x), 0.0);
352+
}

0 commit comments

Comments
 (0)