use std::time::{Duration, Instant};
fn assert_parses_within(input: &str, budget: Duration) {
let owned = input.to_string();
let handle = std::thread::spawn(move || {
let start = Instant::now();
let _ = uni_cypher::parse_locy(&owned);
start.elapsed()
});
let deadline = Instant::now() + budget;
while Instant::now() < deadline {
if handle.is_finished() {
let elapsed = handle.join().expect("parse thread panicked");
assert!(
elapsed < budget,
"parse took {elapsed:?}, over budget {budget:?}"
);
return;
}
std::thread::sleep(Duration::from_millis(20));
}
panic!(
"parse did not finish within {budget:?} for input of {} bytes — \
exponential backtracking regressed",
input.len()
);
}
#[test]
fn fuzz_timeout_artifact_parses_fast() {
let bytes: &[u8] = &[
117, 110, 119, 105, 110, 100, 13, 13, 91, 91, 91, 91, 91, 91, 91, 97, 91, 91, 91, 91, 91,
91, 91, 91, 91, 91, 91, 97, 91, 91, 91, 91, 110, 119, 105, 110, 100, 13, 13, 91, 91, 91,
91, 91, 91, 91, 97, 91, 91, 91, 75, 91, 91, 97, 91, 91, 110, 119, 105, 110, 100, 13, 13,
91, 91, 91, 91, 91, 91, 91, 97, 91, 91, 91, 91, 91, 97, 91, 91, 91, 91, 110, 119, 105, 110,
100, 13, 13, 91, 91, 91, 91, 91, 91, 91, 97, 91, 91, 91, 75, 91, 91, 97, 91, 91, 110, 119,
105, 110, 100, 13, 13, 91, 91, 91, 91, 91, 91, 75, 91, 91, 97, 91, 91, 91, 75, 62, 61, 120,
79,
];
let input = std::str::from_utf8(bytes).unwrap();
assert_parses_within(input, Duration::from_secs(5));
}
#[test]
fn stacked_unmatched_brackets_parse_fast() {
let input = format!("unwind\r\r{}", "[a".repeat(120));
assert_parses_within(&input, Duration::from_secs(5));
}
#[test]
fn index_and_slice_surface_still_parses() {
let queries = [
"RETURN xs[0]",
"RETURN xs[1 + 2]",
"RETURN xs[1..3]",
"RETURN xs[..3]",
"RETURN xs[1..]",
"RETURN xs[..]",
"RETURN xs[0][1]",
"RETURN [1, 2, 3]",
"RETURN []",
"RETURN m['key']",
"RETURN [x IN range(0, 10) WHERE x > 2 | x * 2]",
];
for q in queries {
assert!(
uni_cypher::parse_locy(q).is_ok(),
"expected {q:?} to parse after the index_or_slice factoring"
);
}
}