use super::*;
use std::sync::Arc;
#[test]
#[cfg_attr(miri, ignore)]
fn test_stress_fuzz_strings() {
use rand::{RngExt, SeedableRng};
use std::collections::HashSet;
let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
let mut q = QuaminaBuilder::new()
.with_arena_byte_budget(100 * 1024 * 1024)
.build()
.unwrap();
let mut pattern_names: Vec<String> = Vec::new();
let mut used: HashSet<String> = HashSet::new();
let chars = b"abcdefghijklmnopqrstuvwxyz";
let str_len = 12;
for _ in 0..10_000 {
let s: String = (0..str_len)
.map(|_| chars[rng.random_range(0..chars.len())] as char)
.collect();
pattern_names.push(s.clone());
used.insert(s);
}
for pname in &pattern_names {
let pattern = format!(r#"{{"a": ["{}"]}}"#, pname);
q.add_pattern(pname.clone(), &pattern)
.expect("addPattern failed");
}
for pname in &pattern_names {
let event = format!(r#"{{"a": "{}"}}"#, pname);
assert_matches!(q, event, vec![pname.clone()]);
}
let mut should_not_count = 0;
while should_not_count < 10_000 {
let s: String = (0..str_len)
.map(|_| chars[rng.random_range(0..chars.len())] as char)
.collect();
if used.contains(&s) {
continue;
}
should_not_count += 1;
let event = format!(r#"{{"a": "{}"}}"#, s);
assert_no_match!(q, event);
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_stress_fuzz_numbers() {
use rand::{RngExt, SeedableRng};
use std::collections::HashSet;
let mut rng = rand::rngs::StdRng::seed_from_u64(98543);
let mut q = QuaminaBuilder::new()
.with_arena_byte_budget(100 * 1024 * 1024)
.build()
.unwrap();
let mut pattern_names: Vec<i64> = Vec::new();
let mut used: HashSet<i64> = HashSet::new();
for _ in 0..10_000 {
let n: i64 = rng.random();
pattern_names.push(n);
used.insert(n);
}
for pname in &pattern_names {
let pattern = format!(r#"{{"a": [{}]}}"#, pname);
q.add_pattern(pname.to_string(), &pattern)
.expect("addPattern failed");
}
for pname in &pattern_names {
let event = format!(r#"{{"a": {}}}"#, pname);
assert_matches!(q, event, vec![pname.to_string()]);
}
let mut should_not_count = 0;
while should_not_count < 10_000 {
let n: i64 = rng.random_range(0..1_000_000);
if used.contains(&n) {
continue;
}
should_not_count += 1;
let event = format!(r#"{{"a": {}}}"#, n);
assert_no_match!(q, event);
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_stress_citylots2_operators() {
use std::fs;
use std::path::Path;
let citylots_path = Path::new("testdata/citylots2.json");
if !citylots_path.exists() {
eprintln!("Skipping citylots test - testdata/citylots2.json not found");
return;
}
let data = fs::read_to_string(citylots_path).expect("Failed to read citylots2.json");
let q = q!(
"prefix_143" => r#"{"properties": {"BLKLOT": [{"prefix": "143"}]}}"#,
"suffix_218" => r#"{"properties": {"BLKLOT": [{"suffix": "218"}]}}"#,
"wildcard_0" => r#"{"properties": {"BLKLOT": [{"wildcard": "*0*"}]}}"#
);
let _matches = q.matches_for_event(data.as_bytes());
}
#[test]
fn test_arc_snapshot_isolation() {
let mut q = Quamina::<String>::new();
q.add_pattern("p1".to_string(), r#"{"status": ["active"]}"#)
.unwrap();
let q_arc = Arc::new(q);
let matches = q_arc
.matches_for_event(r#"{"status": "active"}"#.as_bytes())
.unwrap();
assert_eq!(matches, vec!["p1".to_string()]);
let mut q_clone = (*q_arc).clone();
q_clone
.add_pattern("p2".to_string(), r#"{"status": ["pending"]}"#)
.unwrap();
let matches = q_arc
.matches_for_event(r#"{"status": "pending"}"#.as_bytes())
.unwrap();
assert!(matches.is_empty());
let matches = q_clone
.matches_for_event(r#"{"status": "pending"}"#.as_bytes())
.unwrap();
assert_eq!(matches, vec!["p2".to_string()]);
}
#[test]
fn test_concurrent_miri_friendly() {
let mut q = Quamina::<String>::new();
q.add_pattern("p1".to_string(), r#"{"x": [1]}"#).unwrap();
q.add_pattern("p2".to_string(), r#"{"x": [2]}"#).unwrap();
let q_arc = Arc::new(q);
for i in 1..=5 {
let q_ref = Arc::clone(&q_arc);
let event = format!(r#"{{"x": {}}}"#, i % 2 + 1);
let matches = q_ref.matches_for_event(event.as_bytes()).unwrap();
assert!(!matches.is_empty());
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_arc_concurrent_read_write() {
use std::thread;
let mut q = Quamina::<String>::new();
q.add_pattern("p1".to_string(), r#"{"status": ["active"]}"#)
.unwrap();
let q_arc = Arc::new(q);
let mut handles = vec![];
for i in 0..4 {
let q_clone = Arc::clone(&q_arc);
let handle = thread::spawn(move || {
for _ in 0..100 {
let matches = q_clone
.matches_for_event(r#"{"status": "active"}"#.as_bytes())
.unwrap();
assert!(matches.contains(&"p1".to_string()), "Thread {} failed", i);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread panicked");
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_arc_pattern_lifecycle() {
use std::thread;
let mut q = Quamina::<String>::new();
for i in 0..10 {
q.add_pattern(format!("p{}", i), &format!(r#"{{"x": [{}]}}"#, i))
.unwrap();
}
let q_arc = Arc::new(q);
let mut handles = vec![];
for _ in 0..4 {
let q_clone = Arc::clone(&q_arc);
let handle = thread::spawn(move || {
for i in 0..10 {
let event = format!(r#"{{"x": {}}}"#, i);
let matches = q_clone.matches_for_event(event.as_bytes()).unwrap();
assert_eq!(matches.len(), 1);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread panicked");
}
}
#[test]
fn test_utf16_surrogate_pairs() {
let q = q!("p1" => r#"{"emoji": ["😀💋😺"]}"#);
let event = r#"{"emoji": "\ud83d\ude00\ud83d\udc8b\ud83d\ude3a"}"#;
assert_matches!(
q,
event,
vec!["p1"],
"Multiple surrogate pairs should decode correctly"
);
}
#[test]
fn test_json_escape_all_eight() {
let mut q = Quamina::new();
q.add_pattern("p1", r#"{"x": ["hello\"world"]}"#).unwrap();
assert_matches!(
q,
r#"{"x": "hello\"world"}"#,
vec!["p1"],
"Quote escape should match"
);
q.add_pattern("p2", r#"{"x": ["a/b"]}"#).unwrap();
assert_matches!(
q,
r#"{"x": "a\/b"}"#,
vec!["p2"],
"Forward slash escape should match"
);
q.add_pattern("p3", r#"{"x": ["a\nb"]}"#).unwrap();
assert_matches!(
q,
r#"{"x": "a\nb"}"#,
vec!["p3"],
"Newline escape should match"
);
q.add_pattern("p4", r#"{"x": ["a\tb"]}"#).unwrap();
assert_matches!(q, r#"{"x": "a\tb"}"#, vec!["p4"], "Tab escape should match");
q.add_pattern("p5", r#"{"x": ["a\rb"]}"#).unwrap();
assert_matches!(
q,
r#"{"x": "a\rb"}"#,
vec!["p5"],
"Carriage return escape should match"
);
}
#[test]
fn test_unicode_member_names() {
let q = q!("p1" => r#"{"日本語": ["はい"]}"#);
let event = r#"{"日本語": "はい"}"#;
assert_matches!(q, event, vec!["p1"], "Unicode field names should work");
}
#[test]
fn test_unicode_field_names() {
let q = q!("p1" => r#"{"field": ["value"]}"#);
let event = r#"{"\u0066ield": "value"}"#; assert_matches!(
q,
event,
vec!["p1"],
"Unicode escape in field name should work"
);
}
#[test]
fn test_numbits_through_numeric_matching() {
let q = q!("p1" => r#"{"x": [{"numeric": ["=", 42]}]}"#);
for event in [r#"{"x": 42}"#, r#"{"x": 42.0}"#, r#"{"x": 4.2e1}"#] {
assert_matches!(q, event, vec!["p1"]);
}
assert_no_match!(q, r#"{"x": 43}"#);
}
#[test]
fn test_numbits_ordering_through_range() {
let q = q!("p1" => r#"{"x": [{"numeric": [">=", -100, "<=", 100]}]}"#);
assert_matches!(q, r#"{"x": -100}"#, vec!["p1"], "-100 should match");
assert_matches!(q, r#"{"x": 0}"#, vec!["p1"], "0 should match");
assert_matches!(q, r#"{"x": 100}"#, vec!["p1"], "100 should match");
assert_no_match!(q, r#"{"x": -101}"#, "-101 should not match");
assert_no_match!(q, r#"{"x": 101}"#, "101 should not match");
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_memory_cleanup_miri_friendly() {
let mut q = Quamina::<String>::new();
for i in 0..5 {
q.add_pattern(format!("p{}", i), &format!(r#"{{"x": [{}]}}"#, i))
.unwrap();
}
for i in 0..3 {
q.delete_patterns(&format!("p{}", i)).unwrap();
}
let purged = q.rebuild();
assert_eq!(purged, 3, "Should have purged 3 patterns");
assert_matches!(q, r#"{"x": 3}"#, vec!["p3".to_string()]);
}
#[test]
#[cfg(miri)]
fn test_memory_cleanup_miri_minimal() {
let mut q = Quamina::<String>::new();
q.add_pattern("keep".to_string(), r#"{"x": ["a"]}"#)
.unwrap();
q.add_pattern("del".to_string(), r#"{"x": ["b"]}"#).unwrap();
q.delete_patterns(&"del".to_string()).unwrap();
let purged = q.rebuild();
assert_eq!(purged, 1, "Should have purged 1 pattern");
assert_matches!(q, r#"{"x": "a"}"#, vec!["keep".to_string()]);
assert_no_match!(q, r#"{"x": "b"}"#, "Deleted pattern should not match");
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_arc_memory_cleanup() {
let mut q = Quamina::<String>::new();
for i in 0..100 {
q.add_pattern(format!("p{}", i), &format!(r#"{{"x": [{}]}}"#, i))
.unwrap();
}
let q_arc = Arc::new(q);
for _ in 0..10 {
let q_clone = Arc::clone(&q_arc);
let matches = q_clone
.matches_for_event(r#"{"x": 50}"#.as_bytes())
.unwrap();
assert_eq!(matches, vec!["p50".to_string()]);
}
let matches = q_arc.matches_for_event(r#"{"x": 99}"#.as_bytes()).unwrap();
assert_eq!(matches, vec!["p99".to_string()]);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_concurrent_citylots_stress() {
use std::thread;
let mut q = Quamina::<String>::new();
q.add_pattern("exact".to_string(), r#"{"x": ["foo"]}"#)
.unwrap();
q.add_pattern("prefix".to_string(), r#"{"x": [{"prefix": "bar"}]}"#)
.unwrap();
q.add_pattern("suffix".to_string(), r#"{"x": [{"suffix": "baz"}]}"#)
.unwrap();
q.add_pattern("wildcard".to_string(), r#"{"x": [{"wildcard": "*qux*"}]}"#)
.unwrap();
let q_arc = Arc::new(q);
let mut handles = vec![];
for _ in 0..4 {
let q_clone = Arc::clone(&q_arc);
let handle = thread::spawn(move || {
for i in 0..100 {
let event = match i % 4 {
0 => r#"{"x": "foo"}"#,
1 => r#"{"x": "barxyz"}"#,
2 => r#"{"x": "xyzbaz"}"#,
_ => r#"{"x": "abcquxdef"}"#,
};
let matches = q_clone.matches_for_event(event.as_bytes()).unwrap();
assert!(!matches.is_empty());
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread panicked");
}
}
fn verify_bulk_add_correctness(count: usize) {
let mut q = Quamina::new();
for i in 0..count {
let pattern = format!(r#"{{"field{}": ["value{}"]}}"#, i, i);
q.add_pattern(format!("p{}", i), &pattern).unwrap();
}
assert_eq!(q.pattern_count(), count);
for i in 0..count {
let event = format!(r#"{{"field{}": "value{}"}}"#, i, i);
assert_matches!(
q,
event,
vec![format!("p{}", i)],
format!("Pattern {} should match", i)
);
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_bulk_add_correctness() {
verify_bulk_add_correctness(50);
}
#[test]
#[cfg(miri)]
fn test_bulk_add_correctness_miri_friendly() {
verify_bulk_add_correctness(5);
}
#[test]
fn test_multiple_patterns_same_id_comprehensive() {
let q = q!(
"x" => r#"{"x": ["a"]}"#,
"x" => r#"{"x": [1]}"#,
"x" => r#"{"x": [{"prefix": "b"}]}"#
);
assert_matches!(q, r#"{"x": "a"}"#, vec!["x"], "string 'a' should match");
assert_matches!(q, r#"{"x": 1}"#, vec!["x"], "number 1 should match");
assert_matches!(q, r#"{"x": "bcd"}"#, vec!["x"], "prefix 'b' should match");
assert_no_match!(q, r#"{"x": "z"}"#, "unrelated value should not match");
}
#[test]
fn test_invalid_utf8_dot_rejection() {
let q = q!("p1" => r#"{"a": [1]}"#);
let invalid_json = b"{\"a\xF0\x28\x8C\x28\": 1}";
let result = q.matches_for_event(invalid_json);
match result {
Ok(matches) => {
assert!(matches.is_empty() || matches == vec!["p1"]);
}
Err(_) => {
}
}
}
#[test]
fn test_unicode_escape_multiple_emojis() {
let q = q!("p1" => r#"{"emojis": ["😀💋😺"]}"#);
let event = r#"{"emojis": "\ud83d\ude00\ud83d\udc8b\ud83d\ude3a"}"#;
assert_matches!(
q,
event,
vec!["p1"],
"Multiple surrogate pairs should decode correctly"
);
}
#[test]
fn test_unicode_escape_mixed_codepoints() {
let q = q!("p1" => r#"{"mixed": ["Ж💋中"]}"#);
let event = r#"{"mixed": "\u0416\ud83d\udc8b\u4e2d"}"#;
assert_matches!(q, event, vec!["p1"], "Mixed codepoints should decode");
let q2 = q!("p2" => r#"{"mixed": ["x💋y"]}"#);
let event2 = r#"{"mixed": "\u0078\ud83d\udc8b\u0079"}"#;
assert_matches!(q2, event2, vec!["p2"], "ASCII + surrogate should decode");
}
#[test]
fn test_unicode_escape_standard_escapes() {
let mut q = Quamina::new();
q.add_pattern("newline", r#"{"text": ["hello\nworld"]}"#)
.unwrap();
assert_matches!(
q,
r#"{"text": "hello\nworld"}"#,
vec!["newline"],
"Newline escape should match"
);
q.add_pattern("tab", r#"{"text": ["hello\tworld"]}"#)
.unwrap();
assert_has_match!(
q,
r#"{"text": "hello\tworld"}"#,
"tab",
"Tab escape should match"
);
q.add_pattern("backslash", r#"{"text": ["hello\\world"]}"#)
.unwrap();
assert_has_match!(
q,
r#"{"text": "hello\\world"}"#,
"backslash",
"Backslash escape should match"
);
}
#[test]
fn test_multiple_shellstyle_citylots_patterns() {
let q = q!(
"pattern_143" => r#"{"x": [{"shellstyle": "143*"}]}"#,
"pattern_2017" => r#"{"x": [{"shellstyle": "2*0*1*7"}]}"#,
"pattern_218" => r#"{"x": [{"shellstyle": "*218"}]}"#,
"pattern_352" => r#"{"x": [{"shellstyle": "3*5*2"}]}"#,
"pattern_vail" => r#"{"x": [{"shellstyle": "VA*IL"}]}"#
);
let test_cases: Vec<(&str, Vec<&str>)> = vec![
("1430022", vec!["pattern_143"]), ("2607117", vec!["pattern_2017"]), ("2607218", vec!["pattern_218"]), ("3745012", vec!["pattern_352"]), ("VACSTWIL", vec!["pattern_vail"]), ("xyz", vec![]), ];
for (value, expected_patterns) in test_cases {
let event = format!(r#"{{"x": "{}"}}"#, value);
if expected_patterns.is_empty() {
assert_no_match!(q, event);
} else {
for expected in &expected_patterns {
assert_has_match!(q, event, *expected);
}
}
}
}
#[test]
fn test_unicode_field_names_surrogate_pairs() {
let q = q!("p1" => r#"{"xx💋y": ["value"]}"#);
let event = r#"{"x\u0078\ud83d\udc8by": "value"}"#;
assert_matches!(
q,
event,
vec!["p1"],
"Surrogate pair in field name should decode"
);
let q2 = q!("p2" => r#"{"😀💋😺": [1]}"#);
let event2 = r#"{"\ud83d\ude00\ud83d\udc8b\ud83d\ude3a": 1}"#;
assert_matches!(
q2,
event2,
vec!["p2"],
"Multiple surrogate pairs in field name should decode"
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_exercise_matching_comprehensive() {
let event = r#"{
"Image": {
"Width": 800,
"Height": 600,
"Title": "View from 15th Floor",
"Thumbnail": {
"Url": "https://www.example.com/image/481989943",
"Height": 125,
"Width": 100
},
"Animated" : false,
"IDs": [116, 943, 234, 38793]
}
}"#;
let should_match = [
(
r#"{"Image": {"Title": [{"exists": true}]}}"#,
"exists true on Title",
),
(
r#"{"Foo": [{"exists": false}]}"#,
"exists false on missing Foo",
),
(r#"{"Image": {"Width": [800]}}"#, "exact number match"),
(
r#"{"Image": {"Animated": [false], "Thumbnail": {"Height": [125]}}}"#,
"nested multi-field",
),
(
r#"{"Image": {"Width": [800], "Title": [{"exists": true}], "Animated": [false]}}"#,
"three fields",
),
(
r#"{"Image": {"Width": [800], "IDs": [{"exists": true}]}}"#,
"exists on array",
),
(
r#"{"Image": {"Thumbnail": {"Url": [{"shellstyle": "*9943"}]}}}"#,
"shellstyle suffix",
),
(
r#"{"Image": {"Thumbnail": {"Url": [{"shellstyle": "https://www.example.com/*"}]}}}"#,
"shellstyle prefix",
),
(
r#"{"Image": {"Thumbnail": {"Url": [{"shellstyle": "https://www.example.com/*9943"}]}}}"#,
"shellstyle infix",
),
(
r#"{"Image": {"Title": [{"anything-but": ["Pikachu", "Eevee"]}]}}"#,
"anything-but",
),
(
r#"{"Image": {"Thumbnail": {"Url": [{"prefix": "https:"}]}}}"#,
"prefix",
),
(
r#"{"Image": {"Thumbnail": {"Url": ["a", {"prefix": "https:"}]}}}"#,
"prefix or literal",
),
(
r#"{"Image": {"Title": [{"equals-ignore-case": "VIEW FROM 15th FLOOR"}]}}"#,
"equals-ignore-case",
),
(
r#"{"Image": {"Title": [{"regex": "View from .... Floor"}]}}"#,
"regex dots",
),
(
r#"{"Image": {"Title": [{"regex": "View from [0-9][0-9][rtn][dh] Floor"}]}}"#,
"regex char class",
),
(
r#"{"Image": {"Title": [{"regex": "View from 15th (Floor|Storey)"}]}}"#,
"regex alternation",
),
];
let should_not_match = [
(
r#"{"Image": {"Animated": [{"exists": false}]}}"#,
"exists false on present field",
),
(
r#"{"Image": {"NotThere": [{"exists": true}]}}"#,
"exists true on missing field",
),
(
r#"{"Image": {"IDs": [{"exists": false}], "Animated": [false]}}"#,
"exists false on array",
),
(
r#"{"Image": {"Thumbnail": {"Url": [{"prefix": "http:"}]}}}"#,
"wrong prefix",
),
];
for (pattern, desc) in &should_match {
let q = q!(*desc => pattern);
assert_has_match!(
q,
event,
*desc,
format!("Pattern '{}' should match: {}", desc, pattern)
);
}
for (pattern, desc) in &should_not_match {
let q = q!(*desc => pattern);
assert_no_match!(
q,
event,
format!("Pattern '{}' should NOT match: {}", desc, pattern)
);
}
let mut combined = Quamina::new();
for (pattern, desc) in &should_match {
combined.add_pattern(*desc, pattern).unwrap();
}
assert_match_count!(
combined,
event,
should_match.len(),
"All should_match patterns should match when combined"
);
}
#[test]
fn test_exercise_matching_miri_friendly() {
let event = r#"{
"Image": {
"Width": 800,
"Height": 600,
"Title": "View from 15th Floor",
"Thumbnail": {
"Url": "https://www.example.com/image/481989943",
"Height": 125,
"Width": 100
},
"Animated" : false,
"IDs": [116, 943, 234, 38793]
}
}"#;
let patterns: Vec<(&str, &str)> = vec![
(r#"{"Image": {"Width": [800]}}"#, "exact number match"),
(
r#"{"Image": {"Title": [{"exists": true}]}}"#,
"exists true on Title",
),
(
r#"{"Image": {"Thumbnail": {"Url": [{"prefix": "https:"}]}}}"#,
"prefix",
),
(
r#"{"Image": {"Thumbnail": {"Url": [{"shellstyle": "*9943"}]}}}"#,
"shellstyle suffix",
),
];
for (pattern, desc) in &patterns {
let q = q!(*desc => pattern);
assert_has_match!(
q,
event,
*desc,
format!("Pattern '{}' should match: {}", desc, pattern)
);
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_concurrent_update_during_matching() {
use flate2::read::GzDecoder;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::sync::mpsc;
use std::sync::Arc;
use std::thread;
const UPDATE_INTERVAL: usize = 250;
let path = "testdata/citylots2.json.gz";
let file = File::open(path).expect("Failed to open citylots2.json.gz");
let decoder = GzDecoder::new(file);
let reader = BufReader::new(decoder);
let lines: Vec<Vec<u8>> = reader
.lines()
.map(|l| l.expect("Failed to read line").into_bytes())
.collect();
let patterns = [
("CRANLEIGH", r#"{"properties": {"STREET": ["CRANLEIGH"]}}"#),
(
"shellstyle",
r#"{"properties": {"STREET": [{"shellstyle": "B*K"}]}}"#,
),
];
let q = Arc::new(std::sync::RwLock::new(Quamina::new()));
{
let mut q_write = q.write().unwrap();
for (name, pattern) in &patterns {
q_write.add_pattern(name.to_string(), pattern).unwrap();
}
}
let (tx, rx) = mpsc::channel::<String>();
fn add_pattern_concurrent(
q: Arc<std::sync::RwLock<Quamina<String>>>,
idx: usize,
tx: mpsc::Sender<String>,
) {
let val = format!("CONCURRENT_STREET_{}", idx);
let pattern = format!(r#"{{"properties": {{"STREET": ["{}"]}}}}"#, val);
{
let mut q_write = q.write().unwrap();
q_write
.add_pattern(val.clone(), &pattern)
.expect("add_pattern failed");
}
let _ = tx.send(val); }
let mut total_matches = 0usize;
let mut sent = 0;
let start = std::time::Instant::now();
for (i, line) in lines.iter().enumerate() {
let matches = {
let q_read = q.read().unwrap();
q_read
.matches_for_event(line)
.expect("matches_for_event failed")
};
total_matches += matches.len();
if (i + 1) % UPDATE_INTERVAL == 0 {
sent += 1;
let q_clone = Arc::clone(&q);
let tx_clone = tx.clone();
let idx = sent;
thread::spawn(move || {
add_pattern_concurrent(q_clone, idx, tx_clone);
});
}
}
let elapsed = start.elapsed();
drop(tx);
std::thread::sleep(std::time::Duration::from_millis(100));
let mut verified = 0;
for val in rx.iter() {
let event = format!(r#"{{"properties": {{"STREET": "{}"}}}}"#, val);
let q_read = q.read().unwrap();
let matches = q_read
.matches_for_event(event.as_bytes())
.expect("matches_for_event failed");
assert!(
matches.contains(&val),
"Concurrent pattern {} not found in matches: {:?}",
val,
matches
);
verified += 1;
}
let events_per_sec = lines.len() as f64 / elapsed.as_secs_f64();
println!(
"Concurrent update test: {:.0} events/sec, {} total matches, {} patterns added concurrently, {} verified",
events_per_sec, total_matches, sent, verified
);
assert_eq!(sent, verified, "Not all concurrent patterns were verified");
assert!(sent > 0, "Should have added some patterns concurrently");
assert!(total_matches > 0, "Should have gotten some matches");
}
#[test]
fn test_arc_field_matcher_sharing() {
let mut q = Quamina::new();
q.add_pattern("id1", r#"{"status": ["active"]}"#).unwrap();
q.add_pattern("id2", r#"{"status": ["active"]}"#).unwrap();
q.add_pattern("id3", r#"{"status": ["active"]}"#).unwrap();
let event = r#"{"status": "active"}"#;
let mut matches = q.matches_for_event(event.as_bytes()).unwrap();
matches.sort();
assert_eq!(matches, vec!["id1", "id2", "id3"]);
q.delete_patterns(&"id2").unwrap();
let mut matches2 = q.matches_for_event(event.as_bytes()).unwrap();
matches2.sort();
assert_eq!(matches2, vec!["id1", "id3"]);
let q2 = q.clone();
let mut matches3 = q2.matches_for_event(event.as_bytes()).unwrap();
matches3.sort();
assert_eq!(matches3, vec!["id1", "id3"]);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_pattern_insertion_scales_linearly() {
use std::time::Instant;
let layers: &[usize] = &[50, 500, 5000, 20_000];
{
let mut warmup = QuaminaBuilder::new()
.with_arena_byte_budget(100 * 1024 * 1024)
.build()
.unwrap();
for i in 0..100 {
let pattern = format!(r#"{{"key": ["warmup_{}"]}}"#, i);
warmup.add_pattern(format!("w{}", i), &pattern).unwrap();
}
let _ = warmup.matches_for_event(r#"{"key": "warmup_0"}"#.as_bytes());
}
let mut costs: Vec<(usize, f64)> = Vec::new();
for &n in layers {
let mut q = QuaminaBuilder::new()
.with_arena_byte_budget(100 * 1024 * 1024)
.build()
.unwrap();
let start = Instant::now();
for i in 0..n {
let pattern = format!(r#"{{"key": ["value_{}"]}}"#, i);
q.add_pattern(format!("p{}", i), &pattern).unwrap();
}
let matches = q
.matches_for_event(r#"{"key": "value_0"}"#.as_bytes())
.unwrap();
let elapsed = start.elapsed();
assert!(
matches.contains(&"p0".to_string()),
"Pattern p0 should match after adding {} patterns",
n,
);
let cost_per_pattern = elapsed.as_secs_f64() / n as f64;
costs.push((n, cost_per_pattern));
}
for i in 1..costs.len() {
let (small_n, small_cost) = costs[i - 1];
let (large_n, large_cost) = costs[i];
let ratio = large_cost / small_cost.max(1e-9);
assert!(
ratio < 6.0,
"Pattern insertion scales poorly between n={} and n={}: {:.1}x \
(n={}={:.4}ms/pattern, n={}={:.4}ms/pattern). \
This suggests O(n²) regression.",
small_n,
large_n,
ratio,
small_n,
small_cost * 1000.0,
large_n,
large_cost * 1000.0,
);
}
}