use std::collections::BTreeSet;
use std::sync::Arc;
use proptest::prelude::*;
use rete_core::{
build_pyramid_meta, eval_sparql, write_file, Binding, DictionaryBuilder, GraphIndexBuilder,
RangeReader, Rete, DEFAULT_TILE_BUDGET,
};
struct VecReader(Vec<u8>);
impl RangeReader for VecReader {
fn len(&self) -> u64 {
self.0.len() as u64
}
fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
let s = offset as usize;
let e = s
.checked_add(len as usize)
.filter(|&e| e <= self.0.len())
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "oob"))?;
Ok(self.0[s..e].to_vec())
}
}
fn node(i: usize) -> String {
format!("<http://ex/n/{i}>")
}
fn pred(i: usize) -> String {
format!("<http://ex/p/{i}>")
}
fn lit(i: usize) -> String {
format!("\"value {i}\"")
}
#[derive(Debug, Clone)]
enum Obj {
Node(usize),
Lit(usize),
}
const N_NODES: usize = 8;
const N_PREDS: usize = 4;
const N_LITS: usize = 5;
fn graph() -> impl Strategy<Value = Vec<(usize, usize, Obj)>> {
let obj = prop_oneof![
(0..N_NODES).prop_map(Obj::Node),
(0..N_LITS).prop_map(Obj::Lit),
];
prop::collection::vec((0..N_NODES, 0..N_PREDS, obj), 0..40)
}
fn triples(specs: &[(usize, usize, Obj)]) -> BTreeSet<(String, String, String)> {
specs
.iter()
.map(|(s, p, o)| {
let obj = match o {
Obj::Node(i) => node(*i),
Obj::Lit(i) => lit(*i),
};
(node(*s), pred(*p), obj)
})
.collect()
}
fn build_image(want: &BTreeSet<(String, String, String)>) -> Vec<u8> {
let mut db = DictionaryBuilder::new();
for (s, p, o) in want {
db.observe(s, p, o);
}
let dict = db.build();
let ids: Vec<_> = want
.iter()
.map(|(s, p, o)| dict.encode(s, p, o).expect("just-observed term"))
.collect();
let mut ib = GraphIndexBuilder::new();
for &t in &ids {
ib.push(t);
}
let (meta, levels) = build_pyramid_meta(&dict, &ids, DEFAULT_TILE_BUDGET);
write_file(&dict, &ib.build(), false, &meta, levels)
}
fn rows(rete: &Rete, q: &str) -> Vec<Binding> {
let (_, mut sols) = eval_sparql(rete, q).expect("query evaluates");
sols.sort();
sols
}
fn sample_queries(want: &BTreeSet<(String, String, String)>) -> Vec<String> {
let mut qs = vec!["SELECT ?s ?p ?o WHERE { ?s ?p ?o }".to_string()];
if let Some((s, p, o)) = want.iter().next() {
qs.push(format!("SELECT ?p ?o WHERE {{ {s} ?p ?o }}"));
qs.push(format!("SELECT ?s ?o WHERE {{ ?s {p} ?o }}"));
if !o.starts_with('"') {
qs.push(format!("SELECT ?s ?p WHERE {{ ?s ?p {o} }}"));
}
qs.push(format!(
"SELECT ?s ?o WHERE {{ ?s {p} ?mid . ?mid ?p2 ?o }}"
));
}
qs
}
proptest! {
#[test]
fn prop_roundtrip(specs in graph()) {
let want = triples(&specs);
let rete = Rete::open(&build_image(&want)).unwrap();
let got: BTreeSet<_> = rete.dump(None).into_iter().collect();
prop_assert_eq!(got, want);
}
#[test]
fn prop_deterministic(specs in graph()) {
let want = triples(&specs);
prop_assert_eq!(build_image(&want), build_image(&want));
}
#[test]
fn prop_lazy_equals_eager(specs in graph()) {
let want = triples(&specs);
let image = build_image(&want);
let eager = Rete::open(&image).unwrap();
let lazy = Rete::open_ranged_lazy(Arc::new(VecReader(image.clone()))).unwrap();
for q in sample_queries(&want) {
prop_assert_eq!(rows(&eager, &q), rows(&lazy, &q), "lazy != eager for: {}", q);
}
prop_assert!(!lazy.index_incomplete(), "lazy open faulted incompletely");
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 512, ..ProptestConfig::default() })]
#[test]
fn fuzz_arbitrary_bytes_never_panic(bytes in prop::collection::vec(any::<u8>(), 0..4096)) {
if let Ok(rete) = Rete::open(&bytes) {
let _ = rete.dump(None);
let _ = eval_sparql(&rete, "SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 5");
}
}
#[test]
fn fuzz_mutated_image_never_panic(
specs in graph(),
muts in prop::collection::vec((any::<u8>(), 0.0f64..1.0), 1..24),
) {
let mut image = build_image(&triples(&specs));
for (byte, frac) in muts {
let i = ((frac * image.len() as f64) as usize).min(image.len() - 1);
image[i] = byte;
}
if let Ok(rete) = Rete::open(&image) {
let _ = rete.dump(None);
let _ = eval_sparql(&rete, "SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 5");
}
if let Ok(rete) = Rete::open_ranged_lazy(Arc::new(VecReader(image.clone()))) {
let _ = eval_sparql(&rete, "SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 5");
}
}
}