parent
70d2fab95d
commit
74a2d9859a
@ -0,0 +1,345 @@ |
|||||||
|
///! Integration tests based on [SPARQL 1.1 Test Cases](https://www.w3.org/2009/sparql/docs/tests/README.html)
|
||||||
|
|
||||||
|
#[macro_use] |
||||||
|
extern crate lazy_static; |
||||||
|
extern crate reqwest; |
||||||
|
extern crate rudf; |
||||||
|
extern crate url; |
||||||
|
|
||||||
|
use reqwest::Client; |
||||||
|
use reqwest::Response; |
||||||
|
use rudf::errors::*; |
||||||
|
use rudf::model::vocab::rdf; |
||||||
|
use rudf::model::vocab::rdfs; |
||||||
|
use rudf::model::*; |
||||||
|
use rudf::rio::ntriples::read_ntriples; |
||||||
|
use rudf::rio::turtle::read_turtle; |
||||||
|
use rudf::rio::xml::read_rdf_xml; |
||||||
|
use rudf::sparql::algebra::Query; |
||||||
|
use rudf::sparql::parser::read_sparql_query; |
||||||
|
use rudf::store::MemoryGraph; |
||||||
|
use std::error::Error; |
||||||
|
use std::fmt; |
||||||
|
use std::io::BufReader; |
||||||
|
use std::str::FromStr; |
||||||
|
use url::Url; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn sparql_w3c_syntax_testsuite() { |
||||||
|
let manifest_10_url = |
||||||
|
Url::parse("https://www.w3.org/2001/sw/DataAccess/tests/data-r2/manifest-syntax.ttl") |
||||||
|
.unwrap(); |
||||||
|
let manifest_11_url = Url::parse( |
||||||
|
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest.ttl", |
||||||
|
).unwrap(); |
||||||
|
let test_blacklist = vec![ |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/syntax-sparql2/manifest#syntax-form-construct02").unwrap(), |
||||||
|
//TODO: Deserialization of the serialization failing:
|
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/syntax-sparql2/manifest#syntax-form-construct04").unwrap(), |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/syntax-sparql2/manifest#syntax-function-04").unwrap(), |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/syntax-sparql1/manifest#syntax-lit-08").unwrap(), |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/syntax-sparql1/manifest#syntax-qname-04").unwrap(), |
||||||
|
]; |
||||||
|
let client = RDFClient::default(); |
||||||
|
|
||||||
|
for test_result in TestManifest::new(&client, manifest_10_url) |
||||||
|
.chain(TestManifest::new(&client, manifest_11_url)) |
||||||
|
{ |
||||||
|
let test = test_result.unwrap(); |
||||||
|
if test_blacklist.contains(&test.id) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if test.kind == "PositiveSyntaxTest" || test.kind == "PositiveSyntaxTest11" { |
||||||
|
match client.load_sparql_query(test.action.clone()) { |
||||||
|
Err(error) => assert!(false, "Failure on {} with error: {}", test, error), |
||||||
|
Ok(query) => { |
||||||
|
if let Err(error) = read_sparql_query(query.to_string().as_bytes(), None) { |
||||||
|
assert!( |
||||||
|
false, |
||||||
|
"Failure to deserialize \"{}\" of {} with error: {}", |
||||||
|
query.to_string(), |
||||||
|
test, |
||||||
|
error |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else if test.kind == "NegativeSyntaxTest" || test.kind == "NegativeSyntaxTest11" { |
||||||
|
//TODO
|
||||||
|
if let Ok(result) = client.load_sparql_query(test.action.clone()) { |
||||||
|
eprintln!("Failure on {}. The output tree is: {}", test, result); |
||||||
|
} |
||||||
|
} else { |
||||||
|
assert!(false, "Not supported test: {}", test); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct RDFClient { |
||||||
|
client: Client, |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for RDFClient { |
||||||
|
fn default() -> Self { |
||||||
|
Self { |
||||||
|
client: Client::new(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl RDFClient { |
||||||
|
pub fn load_turtle(&self, url: Url) -> Result<MemoryGraph> { |
||||||
|
Ok(read_turtle(self.get(&url)?, Some(url))?.collect()) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn load_ntriples(&self, url: Url) -> Result<MemoryGraph> { |
||||||
|
read_ntriples(self.get(&url)?).collect() |
||||||
|
} |
||||||
|
|
||||||
|
pub fn load_rdf_xml(&self, url: Url) -> Result<MemoryGraph> { |
||||||
|
read_rdf_xml(BufReader::new(self.get(&url)?), Some(url)).collect() |
||||||
|
} |
||||||
|
|
||||||
|
pub fn load_sparql_query(&self, url: Url) -> Result<Query> { |
||||||
|
read_sparql_query(self.get(&url)?, Some(url)) |
||||||
|
} |
||||||
|
|
||||||
|
fn get(&self, url: &Url) -> Result<Response> { |
||||||
|
match self.client.get(url.clone()).send() { |
||||||
|
Ok(response) => Ok(response), |
||||||
|
Err(error) => if error.description() == "parsed HTTP message from remote is incomplete" |
||||||
|
{ |
||||||
|
self.get(url) |
||||||
|
} else { |
||||||
|
Err(format!("HTTP request error: {}", error.description()).into()) |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct Test { |
||||||
|
pub id: NamedNode, |
||||||
|
pub kind: String, |
||||||
|
pub name: Option<String>, |
||||||
|
pub comment: Option<String>, |
||||||
|
pub action: Url, |
||||||
|
pub result: Option<Url>, |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Display for Test { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
write!(f, "{}", self.kind)?; |
||||||
|
for name in &self.name { |
||||||
|
write!(f, " named \"{}\"", name)?; |
||||||
|
} |
||||||
|
for comment in &self.comment { |
||||||
|
write!(f, " with comment \"{}\"", comment)?; |
||||||
|
} |
||||||
|
write!(f, " on file \"{}\"", self.action)?; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct TestManifest<'a> { |
||||||
|
client: &'a RDFClient, |
||||||
|
graph: MemoryGraph, |
||||||
|
tests_to_do: Vec<Term>, |
||||||
|
manifests_to_do: Vec<Url>, |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> TestManifest<'a> { |
||||||
|
pub fn new(client: &'a RDFClient, url: Url) -> TestManifest<'a> { |
||||||
|
Self { |
||||||
|
client, |
||||||
|
graph: MemoryGraph::default(), |
||||||
|
tests_to_do: Vec::default(), |
||||||
|
manifests_to_do: vec![url], |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub mod mf { |
||||||
|
use rudf::model::NamedNode; |
||||||
|
use std::str::FromStr; |
||||||
|
|
||||||
|
lazy_static! { |
||||||
|
pub static ref INCLUDE: NamedNode = |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#include") |
||||||
|
.unwrap(); |
||||||
|
pub static ref ENTRIES: NamedNode = |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#entries") |
||||||
|
.unwrap(); |
||||||
|
pub static ref NAME: NamedNode = |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#name") |
||||||
|
.unwrap(); |
||||||
|
pub static ref ACTION: NamedNode = |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#action") |
||||||
|
.unwrap(); |
||||||
|
pub static ref RESULT: NamedNode = |
||||||
|
NamedNode::from_str("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#result") |
||||||
|
.unwrap(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> Iterator for TestManifest<'a> { |
||||||
|
type Item = Result<Test>; |
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Result<Test>> { |
||||||
|
match self.tests_to_do.pop() { |
||||||
|
Some(Term::NamedNode(test_node)) => { |
||||||
|
let test_subject = NamedOrBlankNode::from(test_node.clone()); |
||||||
|
let kind = match self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(&test_subject, &rdf::TYPE) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
Some(Term::NamedNode(c)) => match c.value().split("#").last() { |
||||||
|
Some(k) => k.to_string(), |
||||||
|
None => return Some(Err("no type".into())), |
||||||
|
}, |
||||||
|
_ => return Some(Err("no type".into())), |
||||||
|
}; |
||||||
|
let name = match self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(&test_subject, &mf::NAME) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
Some(Term::Literal(c)) => Some(c.value().to_string()), |
||||||
|
_ => None, |
||||||
|
}; |
||||||
|
let comment = match self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(&test_subject, &rdfs::COMMENT) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
Some(Term::Literal(c)) => Some(c.value().to_string()), |
||||||
|
_ => None, |
||||||
|
}; |
||||||
|
let action = match self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(&test_subject, &*mf::ACTION) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
Some(Term::NamedNode(n)) => n.url().clone(), |
||||||
|
Some(_) => return Some(Err("invalid action".into())), |
||||||
|
None => return Some(Err("action not found".into())), |
||||||
|
}; |
||||||
|
let result = match self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(&test_subject, &*mf::RESULT) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
Some(Term::NamedNode(n)) => Some(n.url().clone()), |
||||||
|
Some(_) => return Some(Err("invalid result".into())), |
||||||
|
None => None, |
||||||
|
}; |
||||||
|
Some(Ok(Test { |
||||||
|
id: test_node, |
||||||
|
kind, |
||||||
|
name, |
||||||
|
comment, |
||||||
|
action, |
||||||
|
result, |
||||||
|
})) |
||||||
|
} |
||||||
|
Some(_) => Some(Err("invalid test list".into())), |
||||||
|
None => { |
||||||
|
match self.manifests_to_do.pop() { |
||||||
|
Some(url) => { |
||||||
|
let manifest = NamedOrBlankNode::from(NamedNode::new(url.clone())); |
||||||
|
match self.client.load_turtle(url) { |
||||||
|
Ok(g) => g |
||||||
|
.iter() |
||||||
|
.unwrap() |
||||||
|
.for_each(|g| self.graph.insert(&g.unwrap()).unwrap()), |
||||||
|
Err(e) => return Some(Err(e.into())), |
||||||
|
} |
||||||
|
|
||||||
|
// New manifests
|
||||||
|
match self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(&manifest, &*mf::INCLUDE) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
Some(Term::BlankNode(list)) => { |
||||||
|
self.manifests_to_do.extend( |
||||||
|
RdfListIterator::iter(&self.graph, list.clone().into()) |
||||||
|
.flat_map(|m| match m { |
||||||
|
Term::NamedNode(nm) => Some(nm.url().clone()), |
||||||
|
_ => None, |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
||||||
|
Some(_) => return Some(Err("invalid tests list".into())), |
||||||
|
None => (), |
||||||
|
} |
||||||
|
|
||||||
|
// New tests
|
||||||
|
match self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(&manifest, &*mf::ENTRIES) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
Some(Term::BlankNode(list)) => { |
||||||
|
self.tests_to_do.extend(RdfListIterator::iter( |
||||||
|
&self.graph, |
||||||
|
list.clone().into(), |
||||||
|
)); |
||||||
|
} |
||||||
|
Some(term) => { |
||||||
|
return Some(Err( |
||||||
|
format!("Invalid tests list. Got term {}", term).into() |
||||||
|
)) |
||||||
|
} |
||||||
|
None => (), |
||||||
|
} |
||||||
|
} |
||||||
|
None => return None, |
||||||
|
} |
||||||
|
self.next() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct RdfListIterator<'a, G: 'a + Graph> { |
||||||
|
graph: &'a G, |
||||||
|
current_node: Option<NamedOrBlankNode>, |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, G: 'a + Graph> RdfListIterator<'a, G> { |
||||||
|
fn iter(graph: &'a G, root: NamedOrBlankNode) -> RdfListIterator<'a, G> { |
||||||
|
RdfListIterator { |
||||||
|
graph, |
||||||
|
current_node: Some(root), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, G: 'a + Graph> Iterator for RdfListIterator<'a, G> { |
||||||
|
type Item = Term; |
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Term> { |
||||||
|
match self.current_node.clone() { |
||||||
|
Some(current) => { |
||||||
|
let result = self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(¤t, &rdf::FIRST) |
||||||
|
.unwrap()? |
||||||
|
.clone(); |
||||||
|
self.current_node = match self |
||||||
|
.graph |
||||||
|
.object_for_subject_predicate(¤t, &rdf::REST) |
||||||
|
.unwrap() |
||||||
|
{ |
||||||
|
Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None, |
||||||
|
Some(Term::NamedNode(n)) => Some(n.clone().into()), |
||||||
|
Some(Term::BlankNode(n)) => Some(n.clone().into()), |
||||||
|
_ => None, |
||||||
|
}; |
||||||
|
Some(result) |
||||||
|
} |
||||||
|
None => None, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue