Makes testsuite parsing more robust

- avoids stack overflow if there are a lot of empty tests
- allows blank node test ids
- runs tests in the same order they are defined
pull/171/head
Tpt 3 years ago
parent 7764f41d37
commit 0ba0e4e399
  1. 4
      testsuite/oxigraph-tests/sparql/manifest.ttl
  2. 447
      testsuite/src/manifest.rs

@ -22,10 +22,10 @@
qt:data <describe_input.ttl> ] ; qt:data <describe_input.ttl> ] ;
mf:result <describe_output.ttl> . mf:result <describe_output.ttl> .
:describe rdf:type mf:QueryEvaluationTest ; :describe_where rdf:type mf:QueryEvaluationTest ;
mf:name "Simple DESCRIBE request" ; mf:name "Simple DESCRIBE request" ;
mf:action mf:action
[ qt:query <describe.rq> ; [ qt:query <describe_where.rq> ;
qt:data <describe_input.ttl> ] ; qt:data <describe_input.ttl> ] ;
mf:result <describe_output.ttl> . mf:result <describe_output.ttl> .

@ -3,6 +3,7 @@ use crate::vocab::*;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use oxigraph::model::vocab::*; use oxigraph::model::vocab::*;
use oxigraph::model::*; use oxigraph::model::*;
use std::collections::VecDeque;
use std::fmt; use std::fmt;
pub struct Test { pub struct Test {
@ -50,15 +51,15 @@ impl fmt::Display for Test {
pub struct TestManifest { pub struct TestManifest {
graph: Graph, graph: Graph,
tests_to_do: Vec<Term>, tests_to_do: VecDeque<Term>,
manifests_to_do: Vec<String>, manifests_to_do: VecDeque<String>,
} }
impl TestManifest { impl TestManifest {
pub fn new<S: ToString>(manifest_urls: impl IntoIterator<Item = S>) -> Self { pub fn new<S: ToString>(manifest_urls: impl IntoIterator<Item = S>) -> Self {
Self { Self {
graph: Graph::new(), graph: Graph::new(),
tests_to_do: Vec::new(), tests_to_do: VecDeque::new(),
manifests_to_do: manifest_urls manifests_to_do: manifest_urls
.into_iter() .into_iter()
.map(|url| url.to_string()) .map(|url| url.to_string())
@ -71,231 +72,261 @@ impl Iterator for TestManifest {
type Item = Result<Test>; type Item = Result<Test>;
fn next(&mut self) -> Option<Result<Test>> { fn next(&mut self) -> Option<Result<Test>> {
match self.tests_to_do.pop() { loop {
Some(Term::NamedNode(test_node)) => { return match self.tests_to_do.pop_front().map(|term| match term {
let kind = match self Term::NamedNode(n) => Ok(n),
.graph Term::BlankNode(n) => Ok(NamedNode::new(format!(
.object_for_subject_predicate(&test_node, rdf::TYPE) "http://oxigraph.org/.well-known/genid/{}",
{ n.as_str()
Some(TermRef::NamedNode(c)) => c.into_owned(), ))?),
_ => return self.next(), //We ignore the test _ => Err(anyhow!("Invalid test identifier. Got {}", term)),
}; }) {
let name = match self Some(Ok(test_node)) => {
.graph if self.graph.triples_for_subject(&test_node).next().is_none() {
.object_for_subject_predicate(&test_node, mf::NAME) continue; // This test does not exists
{ }
Some(TermRef::Literal(c)) => Some(c.value().to_string()), let name = match self
_ => None, .graph
}; .object_for_subject_predicate(&test_node, mf::NAME)
let comment = match self {
.graph Some(TermRef::Literal(c)) => Some(c.value().to_string()),
.object_for_subject_predicate(&test_node, rdfs::COMMENT) _ => None,
{ };
Some(TermRef::Literal(c)) => Some(c.value().to_string()), let kind = match self
_ => None, .graph
}; .object_for_subject_predicate(&test_node, rdf::TYPE)
let (action, query, update, data, graph_data, service_data) = match self {
.graph Some(TermRef::NamedNode(c)) => c.into_owned(),
.object_for_subject_predicate(&test_node, mf::ACTION) _ => {
{ return Some(Err(anyhow!(
Some(TermRef::NamedNode(n)) => ( "The test {} named {} has no rdf:type",
Some(n.as_str().to_owned()), test_node,
None, name.as_deref().unwrap_or("")
None, )))
None, }
vec![], };
vec![], let comment = match self
), .graph
Some(TermRef::BlankNode(n)) => { .object_for_subject_predicate(&test_node, rdfs::COMMENT)
let query = match self.graph.object_for_subject_predicate(n, qt::QUERY) { {
Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()), Some(TermRef::Literal(c)) => Some(c.value().to_string()),
_ => None, _ => None,
}; };
let update = match self.graph.object_for_subject_predicate(n, ut::REQUEST) { let (action, query, update, data, graph_data, service_data) = match self
Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()), .graph
_ => None, .object_for_subject_predicate(&test_node, mf::ACTION)
}; {
let data = match self Some(TermRef::NamedNode(n)) => (
.graph Some(n.as_str().to_owned()),
.object_for_subject_predicate(n, qt::DATA) None,
.or_else(|| self.graph.object_for_subject_predicate(n, ut::DATA)) None,
{ None,
Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()), vec![],
_ => None, vec![],
}; ),
let graph_data = self Some(TermRef::BlankNode(n)) => {
.graph let query = match self.graph.object_for_subject_predicate(n, qt::QUERY)
.objects_for_subject_predicate(n, qt::GRAPH_DATA) {
.chain(self.graph.objects_for_subject_predicate(n, ut::GRAPH_DATA)) Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()),
.filter_map(|g| match g {
TermRef::NamedNode(q) => {
Some((q.into_owned(), q.as_str().to_owned()))
}
TermRef::BlankNode(node) => {
if let Some(TermRef::NamedNode(graph)) =
self.graph.object_for_subject_predicate(node, ut::GRAPH)
{
if let Some(TermRef::Literal(name)) = self
.graph
.object_for_subject_predicate(node, rdfs::LABEL)
{
Some((
NamedNode::new(name.value()).unwrap(),
graph.as_str().to_owned(),
))
} else {
Some((graph.into_owned(), graph.as_str().to_owned()))
}
} else {
None
}
}
_ => None, _ => None,
}) };
.collect(); let update =
let service_data = self match self.graph.object_for_subject_predicate(n, ut::REQUEST) {
.graph Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()),
.objects_for_subject_predicate(n, qt::SERVICE_DATA) _ => None,
.filter_map(|g| match g { };
TermRef::NamedNode(g) => Some(g.into()), let data = match self
TermRef::BlankNode(g) => Some(g.into()), .graph
.object_for_subject_predicate(n, qt::DATA)
.or_else(|| self.graph.object_for_subject_predicate(n, ut::DATA))
{
Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()),
_ => None, _ => None,
}) };
.filter_map(|g: SubjectRef<'_>| { let graph_data = self
if let ( .graph
Some(TermRef::NamedNode(endpoint)), .objects_for_subject_predicate(n, qt::GRAPH_DATA)
Some(TermRef::NamedNode(data)), .chain(self.graph.objects_for_subject_predicate(n, ut::GRAPH_DATA))
) = ( .filter_map(|g| match g {
self.graph.object_for_subject_predicate(g, qt::ENDPOINT), TermRef::NamedNode(q) => {
self.graph.object_for_subject_predicate(g, qt::DATA), Some((q.into_owned(), q.as_str().to_owned()))
) { }
Some((endpoint.as_str().to_owned(), data.as_str().to_owned())) TermRef::BlankNode(node) => {
} else { if let Some(TermRef::NamedNode(graph)) =
None self.graph.object_for_subject_predicate(node, ut::GRAPH)
}
})
.collect();
(None, query, update, data, graph_data, service_data)
}
Some(_) => return Some(Err(anyhow!("invalid action"))),
None => {
return Some(Err(anyhow!("action not found for test {}", test_node)));
}
};
let (result, result_graph_data) = match self
.graph
.object_for_subject_predicate(&test_node, mf::RESULT)
{
Some(TermRef::NamedNode(n)) => (Some(n.as_str().to_owned()), Vec::new()),
Some(TermRef::BlankNode(n)) => (
if let Some(TermRef::NamedNode(result)) =
self.graph.object_for_subject_predicate(n, ut::DATA)
{
Some(result.as_str().to_owned())
} else {
None
},
self.graph
.objects_for_subject_predicate(n, ut::GRAPH_DATA)
.filter_map(|g| match g {
TermRef::NamedNode(q) => {
Some((q.into_owned(), q.as_str().to_owned()))
}
TermRef::BlankNode(node) => {
if let Some(TermRef::NamedNode(graph)) =
self.graph.object_for_subject_predicate(node, ut::GRAPH)
{
if let Some(TermRef::Literal(name)) = self
.graph
.object_for_subject_predicate(node, rdfs::LABEL)
{ {
Some(( if let Some(TermRef::Literal(name)) = self
NamedNode::new(name.value()).unwrap(), .graph
graph.as_str().to_owned(), .object_for_subject_predicate(node, rdfs::LABEL)
)) {
Some((
NamedNode::new(name.value()).unwrap(),
graph.as_str().to_owned(),
))
} else {
Some((
graph.into_owned(),
graph.as_str().to_owned(),
))
}
} else { } else {
Some((graph.into_owned(), graph.as_str().to_owned())) None
} }
}
_ => None,
})
.collect();
let service_data = self
.graph
.objects_for_subject_predicate(n, qt::SERVICE_DATA)
.filter_map(|g| match g {
TermRef::NamedNode(g) => Some(g.into()),
TermRef::BlankNode(g) => Some(g.into()),
_ => None,
})
.filter_map(|g: SubjectRef<'_>| {
if let (
Some(TermRef::NamedNode(endpoint)),
Some(TermRef::NamedNode(data)),
) = (
self.graph.object_for_subject_predicate(g, qt::ENDPOINT),
self.graph.object_for_subject_predicate(g, qt::DATA),
) {
Some((
endpoint.as_str().to_owned(),
data.as_str().to_owned(),
))
} else { } else {
None None
} }
} })
_ => None, .collect();
}) (None, query, update, data, graph_data, service_data)
.collect(),
),
Some(_) => return Some(Err(anyhow!("invalid result"))),
None => (None, Vec::new()),
};
Some(Ok(Test {
id: test_node,
kind,
name,
comment,
action,
query,
update,
data,
graph_data,
service_data,
result,
result_graph_data,
}))
}
Some(_) => self.next(),
None => {
match self.manifests_to_do.pop() {
Some(url) => {
self.graph.clear();
if let Err(error) = load_to_graph(&url, &mut self.graph) {
return Some(Err(error));
} }
Some(_) => return Some(Err(anyhow!("invalid action"))),
for manifest in self None => {
.graph return Some(Err(anyhow!("action not found for test {}", test_node)));
.subjects_for_predicate_object(rdf::TYPE, mf::MANIFEST) }
{ };
match self let (result, result_graph_data) = match self
.graph .graph
.object_for_subject_predicate(manifest, mf::INCLUDE) .object_for_subject_predicate(&test_node, mf::RESULT)
{
Some(TermRef::NamedNode(n)) => (Some(n.as_str().to_owned()), Vec::new()),
Some(TermRef::BlankNode(n)) => (
if let Some(TermRef::NamedNode(result)) =
self.graph.object_for_subject_predicate(n, ut::DATA)
{ {
Some(TermRef::BlankNode(list)) => { Some(result.as_str().to_owned())
self.manifests_to_do.extend( } else {
RdfListIterator::iter(&self.graph, list.into()).filter_map( None
|m| match m { },
Term::NamedNode(nm) => Some(nm.into_string()), self.graph
_ => None, .objects_for_subject_predicate(n, ut::GRAPH_DATA)
}, .filter_map(|g| match g {
), TermRef::NamedNode(q) => {
); Some((q.into_owned(), q.as_str().to_owned()))
} }
Some(_) => return Some(Err(anyhow!("invalid tests list"))), TermRef::BlankNode(node) => {
None => (), if let Some(TermRef::NamedNode(graph)) =
self.graph.object_for_subject_predicate(node, ut::GRAPH)
{
if let Some(TermRef::Literal(name)) = self
.graph
.object_for_subject_predicate(node, rdfs::LABEL)
{
Some((
NamedNode::new(name.value()).unwrap(),
graph.as_str().to_owned(),
))
} else {
Some((
graph.into_owned(),
graph.as_str().to_owned(),
))
}
} else {
None
}
}
_ => None,
})
.collect(),
),
Some(_) => return Some(Err(anyhow!("invalid result"))),
None => (None, Vec::new()),
};
Some(Ok(Test {
id: test_node,
kind,
name,
comment,
action,
query,
update,
data,
graph_data,
service_data,
result,
result_graph_data,
}))
}
Some(Err(error)) => Some(Err(error)),
None => {
match self.manifests_to_do.pop_front() {
Some(url) => {
self.graph.clear();
if let Err(error) = load_to_graph(&url, &mut self.graph) {
return Some(Err(error));
} }
// New tests for manifest in self
match self
.graph .graph
.object_for_subject_predicate(manifest, mf::ENTRIES) .subjects_for_predicate_object(rdf::TYPE, mf::MANIFEST)
{ {
Some(TermRef::BlankNode(list)) => { match self
self.tests_to_do .graph
.extend(RdfListIterator::iter(&self.graph, list.into())); .object_for_subject_predicate(manifest, mf::INCLUDE)
{
Some(TermRef::BlankNode(list)) => {
self.manifests_to_do.extend(
RdfListIterator::iter(&self.graph, list.into())
.filter_map(|m| match m {
Term::NamedNode(nm) => Some(nm.into_string()),
_ => None,
}),
);
}
Some(_) => return Some(Err(anyhow!("invalid tests list"))),
None => (),
} }
Some(term) => {
return Some(Err(anyhow!( // New tests
"Invalid tests list. Got term {}", match self
term .graph
))); .object_for_subject_predicate(manifest, mf::ENTRIES)
{
Some(TermRef::BlankNode(list)) => {
self.tests_to_do.extend(RdfListIterator::iter(
&self.graph,
list.into(),
));
}
Some(term) => {
return Some(Err(anyhow!(
"Invalid tests list. Got term {}",
term
)));
}
None => (),
} }
None => (),
} }
continue;
} }
None => None,
} }
None => return None,
} }
self.next() };
}
} }
} }
} }

Loading…
Cancel
Save