mod support {
pub mod parser_hardening;
}
use proptest::prelude::*;
use reddb_server::storage::query::parser::{self, ParseError, ParserLimits};
use support::parser_hardening::{
self as harness, assert_no_panic_on, corpus::vector_search_adversarial_inputs,
vector_search_grammar, HardenedParser,
};
pub struct VectorParser;
impl HardenedParser for VectorParser {
type Error = ParseError;
fn parse(input: &str) -> Result<(), Self::Error> {
parser::parse(input).map(|_| ())
}
fn parse_with_limits(input: &str, limits: ParserLimits) -> Result<(), Self::Error> {
let mut p = parser::Parser::with_limits(input, limits)?;
p.parse().map(|_| ())
}
}
#[test]
fn vector_parser_does_not_panic_on_adversarial_corpus() {
let handle = std::thread::Builder::new()
.stack_size(8 * 1024 * 1024)
.spawn(|| {
for (name, input) in vector_search_adversarial_inputs() {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
assert_no_panic_on::<VectorParser>(&input);
}));
if result.is_err() {
panic!("vector-search adversarial corpus entry {} panicked", name);
}
}
})
.expect("spawn corpus thread");
handle.join().expect("corpus thread panic");
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 256,
max_shrink_iters: 64,
..ProptestConfig::default()
})]
#[test]
fn proptest_search_similar_vector_roundtrips(
s in vector_search_grammar::search_similar_vector_stmt(),
) {
harness::roundtrip_property::<VectorParser>(&s);
prop_assert!(
VectorParser::parse(&s).is_ok(),
"SEARCH SIMILAR vector did not parse: {}", s
);
}
#[test]
fn proptest_search_similar_text_roundtrips(
s in vector_search_grammar::search_similar_text_stmt(),
) {
harness::roundtrip_property::<VectorParser>(&s);
prop_assert!(
VectorParser::parse(&s).is_ok(),
"SEARCH SIMILAR TEXT did not parse: {}", s
);
}
#[test]
fn proptest_insert_auto_embed_roundtrips(
s in vector_search_grammar::insert_auto_embed_stmt(),
) {
harness::roundtrip_property::<VectorParser>(&s);
prop_assert!(
VectorParser::parse(&s).is_ok(),
"INSERT WITH AUTO EMBED did not parse: {}", s
);
}
#[test]
fn proptest_vector_search_roundtrips(
s in vector_search_grammar::vector_search_stmt(),
) {
harness::roundtrip_property::<VectorParser>(&s);
prop_assert!(
VectorParser::parse(&s).is_ok(),
"VECTOR SEARCH did not parse: {}", s
);
}
#[test]
fn proptest_search_hybrid_roundtrips(
s in vector_search_grammar::search_hybrid_stmt(),
) {
harness::roundtrip_property::<VectorParser>(&s);
prop_assert!(
VectorParser::parse(&s).is_ok(),
"SEARCH HYBRID did not parse: {}", s
);
}
#[test]
fn proptest_hybrid_from_roundtrips(
s in vector_search_grammar::hybrid_from_stmt(),
) {
harness::roundtrip_property::<VectorParser>(&s);
prop_assert!(
VectorParser::parse(&s).is_ok(),
"HYBRID FROM did not parse: {}", s
);
}
#[test]
fn proptest_vector_arbitrary_suffix_no_panic(
prefix in prop_oneof![
Just("SEARCH SIMILAR ".to_string()),
Just("SEARCH SIMILAR TEXT ".to_string()),
Just("SEARCH HYBRID ".to_string()),
Just("VECTOR SEARCH ".to_string()),
Just("HYBRID FROM ".to_string()),
Just("INSERT INTO t (a) VALUES ('x') WITH AUTO EMBED ".to_string()),
],
suffix in ".{0,512}",
) {
let s = format!("{}{}", prefix, suffix);
harness::roundtrip_property::<VectorParser>(&s);
}
#[test]
fn proptest_vector_input_size_limit_enforced(
dim in 200usize..2000,
) {
let limits = ParserLimits {
max_input_bytes: 64,
..ParserLimits::default()
};
let body: Vec<String> = (0..dim).map(|_| "0.1".to_string()).collect();
let input = format!("SEARCH SIMILAR [{}] COLLECTION c", body.join(","));
let r = VectorParser::parse_with_limits(&input, limits);
prop_assert!(r.is_err(), "oversized vector input must error");
}
}