pull/41/head
parent
69f94777b6
commit
c3ae01e701
@ -1,336 +0,0 @@ |
||||
use criterion::{criterion_group, criterion_main, Criterion}; |
||||
use oxigraph::model::vocab::rdf; |
||||
use oxigraph::model::*; |
||||
use oxigraph::sparql::*; |
||||
use oxigraph::*; |
||||
use std::fs::File; |
||||
use std::io::{BufRead, BufReader, Read}; |
||||
use std::path::PathBuf; |
||||
|
||||
criterion_group!(sparql, sparql_w3c_syntax_bench); |
||||
|
||||
criterion_main!(sparql); |
||||
|
||||
fn sparql_w3c_syntax_bench(c: &mut Criterion) { |
||||
let manifest_urls = vec![ |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/manifest-syntax.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-fed/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/construct/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest.ttl", |
||||
]; |
||||
let queries: Vec<_> = manifest_urls |
||||
.into_iter() |
||||
.flat_map(TestManifest::new) |
||||
.flat_map(|test| { |
||||
let test = test.unwrap(); |
||||
if test.kind == "PositiveSyntaxTest" || test.kind == "PositiveSyntaxTest11" { |
||||
Some((read_file_to_string(&test.query).unwrap(), test.query)) |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect(); |
||||
|
||||
c.bench_function("query parser", |b| { |
||||
b.iter(|| { |
||||
for (query, base) in &queries { |
||||
Query::parse(query, Some(base)).unwrap(); |
||||
} |
||||
}) |
||||
}); |
||||
} |
||||
|
||||
fn load_graph_to_store( |
||||
url: &str, |
||||
store: &MemoryStore, |
||||
to_graph_name: Option<&NamedOrBlankNode>, |
||||
) -> Result<()> { |
||||
let syntax = if url.ends_with(".nt") { |
||||
GraphSyntax::NTriples |
||||
} else if url.ends_with(".ttl") { |
||||
GraphSyntax::Turtle |
||||
} else if url.ends_with(".rdf") { |
||||
GraphSyntax::RdfXml |
||||
} else { |
||||
return Err(Error::msg(format!( |
||||
"Serialization type not found for {}", |
||||
url |
||||
))); |
||||
}; |
||||
store.load_graph(read_file(url)?, syntax, to_graph_name, Some(url)) |
||||
} |
||||
|
||||
fn to_relative_path(url: &str) -> Result<String> { |
||||
if url.starts_with("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/") { |
||||
Ok(url.replace( |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/", |
||||
"rdf-tests/sparql11/", |
||||
)) |
||||
} else if url.starts_with("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/") { |
||||
Ok(url.replace( |
||||
"http://www.w3.org/2009/sparql/docs/tests/", |
||||
"rdf-tests/sparql11/", |
||||
)) |
||||
} else { |
||||
Err(Error::msg(format!("Not supported url for file: {}", url))) |
||||
} |
||||
} |
||||
|
||||
fn read_file(url: &str) -> Result<impl BufRead> { |
||||
let mut base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
||||
base_path.push("tests"); |
||||
base_path.push(to_relative_path(url)?); |
||||
|
||||
Ok(BufReader::new(File::open(&base_path).map_err(|e| { |
||||
Error::msg(format!( |
||||
"Opening file {} failed with {}", |
||||
base_path.display(), |
||||
e, |
||||
)) |
||||
})?)) |
||||
} |
||||
|
||||
fn read_file_to_string(url: &str) -> Result<String> { |
||||
let mut string = String::default(); |
||||
read_file(url)?.read_to_string(&mut string)?; |
||||
Ok(string) |
||||
} |
||||
|
||||
mod rs { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref RESULT_SET: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet") |
||||
.unwrap(); |
||||
pub static ref RESULT_VARIABLE: NamedNode = NamedNode::parse( |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/result-set#resultVariable" |
||||
) |
||||
.unwrap(); |
||||
pub static ref SOLUTION: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#solution") |
||||
.unwrap(); |
||||
pub static ref BINDING: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#binding") |
||||
.unwrap(); |
||||
pub static ref VALUE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#value") |
||||
.unwrap(); |
||||
pub static ref VARIABLE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#variable") |
||||
.unwrap(); |
||||
pub static ref INDEX: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#index") |
||||
.unwrap(); |
||||
pub static ref BOOLEAN: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#boolean") |
||||
.unwrap(); |
||||
} |
||||
} |
||||
|
||||
mod mf { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref INCLUDE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#include") |
||||
.unwrap(); |
||||
pub static ref ENTRIES: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#entries") |
||||
.unwrap(); |
||||
pub static ref NAME: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#name") |
||||
.unwrap(); |
||||
pub static ref ACTION: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#action") |
||||
.unwrap(); |
||||
pub static ref RESULT: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#result") |
||||
.unwrap(); |
||||
} |
||||
} |
||||
|
||||
mod qt { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref QUERY: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#query") |
||||
.unwrap(); |
||||
pub static ref DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#data").unwrap(); |
||||
pub static ref GRAPH_DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#graphData") |
||||
.unwrap(); |
||||
pub static ref SERVICE_DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#serviceData") |
||||
.unwrap(); |
||||
pub static ref ENDPOINT: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#endpoint") |
||||
.unwrap(); |
||||
} |
||||
} |
||||
|
||||
struct Test { |
||||
kind: String, |
||||
query: String, |
||||
} |
||||
|
||||
struct TestManifest { |
||||
graph: MemoryStore, |
||||
tests_to_do: Vec<Term>, |
||||
manifests_to_do: Vec<String>, |
||||
} |
||||
|
||||
impl TestManifest { |
||||
fn new(url: impl Into<String>) -> TestManifest { |
||||
Self { |
||||
graph: MemoryStore::new(), |
||||
tests_to_do: Vec::default(), |
||||
manifests_to_do: vec![url.into()], |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Iterator for TestManifest { |
||||
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 object_for_subject_predicate(&self.graph, &test_subject, &rdf::TYPE) { |
||||
Some(Term::NamedNode(c)) => match c.as_str().split('#').last() { |
||||
Some(k) => k.to_string(), |
||||
None => return self.next(), //We ignore the test
|
||||
}, |
||||
_ => return self.next(), //We ignore the test
|
||||
}; |
||||
let query = |
||||
match object_for_subject_predicate(&self.graph, &test_subject, &*mf::ACTION) { |
||||
Some(Term::NamedNode(n)) => n.as_str().to_owned(), |
||||
Some(Term::BlankNode(n)) => { |
||||
let n = n.clone().into(); |
||||
match object_for_subject_predicate(&self.graph, &n, &qt::QUERY) { |
||||
Some(Term::NamedNode(q)) => q.as_str().to_owned(), |
||||
Some(_) => return Some(Err(Error::msg("invalid query"))), |
||||
None => return Some(Err(Error::msg("query not found"))), |
||||
} |
||||
} |
||||
Some(_) => return Some(Err(Error::msg("invalid action"))), |
||||
None => { |
||||
return Some(Err(Error::msg(format!( |
||||
"action not found for test {}", |
||||
test_subject |
||||
)))); |
||||
} |
||||
}; |
||||
Some(Ok(Test { kind, query })) |
||||
} |
||||
Some(_) => Some(Err(Error::msg("invalid test list"))), |
||||
None => { |
||||
match self.manifests_to_do.pop() { |
||||
Some(url) => { |
||||
let manifest = |
||||
NamedOrBlankNode::from(NamedNode::parse(url.clone()).unwrap()); |
||||
if let Err(e) = load_graph_to_store(&url, &self.graph, None) { |
||||
return Some(Err(e)); |
||||
} |
||||
// New manifests
|
||||
match object_for_subject_predicate(&self.graph, &manifest, &*mf::INCLUDE) { |
||||
Some(Term::BlankNode(list)) => { |
||||
self.manifests_to_do.extend( |
||||
RdfListIterator::iter(&self.graph, list.clone().into()) |
||||
.filter_map(|m| match m { |
||||
Term::NamedNode(nm) => Some(nm.into_string()), |
||||
_ => None, |
||||
}), |
||||
); |
||||
} |
||||
Some(_) => return Some(Err(Error::msg("invalid tests list"))), |
||||
None => (), |
||||
} |
||||
|
||||
// New tests
|
||||
match object_for_subject_predicate(&self.graph, &manifest, &*mf::ENTRIES) { |
||||
Some(Term::BlankNode(list)) => { |
||||
self.tests_to_do.extend(RdfListIterator::iter( |
||||
&self.graph, |
||||
list.clone().into(), |
||||
)); |
||||
} |
||||
Some(term) => { |
||||
return Some(Err(Error::msg(format!( |
||||
"Invalid tests list. Got term {}", |
||||
term |
||||
)))); |
||||
} |
||||
None => (), |
||||
} |
||||
} |
||||
None => return None, |
||||
} |
||||
self.next() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct RdfListIterator<'a> { |
||||
graph: &'a MemoryStore, |
||||
current_node: Option<NamedOrBlankNode>, |
||||
} |
||||
|
||||
impl<'a> RdfListIterator<'a> { |
||||
fn iter(graph: &'a MemoryStore, root: NamedOrBlankNode) -> RdfListIterator<'a> { |
||||
RdfListIterator { |
||||
graph, |
||||
current_node: Some(root), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<'a> Iterator for RdfListIterator<'a> { |
||||
type Item = Term; |
||||
|
||||
fn next(&mut self) -> Option<Term> { |
||||
match self.current_node.clone() { |
||||
Some(current) => { |
||||
let result = object_for_subject_predicate(&self.graph, ¤t, &rdf::FIRST); |
||||
self.current_node = |
||||
match object_for_subject_predicate(&self.graph, ¤t, &rdf::REST) { |
||||
Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None, |
||||
Some(Term::NamedNode(n)) => Some(n.into()), |
||||
Some(Term::BlankNode(n)) => Some(n.into()), |
||||
_ => None, |
||||
}; |
||||
result |
||||
} |
||||
None => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn object_for_subject_predicate( |
||||
store: &MemoryStore, |
||||
subject: &NamedOrBlankNode, |
||||
predicate: &NamedNode, |
||||
) -> Option<Term> { |
||||
objects_for_subject_predicate(store, subject, predicate).next() |
||||
} |
||||
|
||||
fn objects_for_subject_predicate( |
||||
store: &MemoryStore, |
||||
subject: &NamedOrBlankNode, |
||||
predicate: &NamedNode, |
||||
) -> impl Iterator<Item = Term> { |
||||
store |
||||
.quads_for_pattern(Some(subject), Some(predicate), None, None) |
||||
.map(|t| t.object_owned()) |
||||
} |
@ -1 +0,0 @@ |
||||
Subproject commit dc237e319e6562f2913341f6ba964ecbcbbf4499 |
@ -1,755 +0,0 @@ |
||||
///! Integration tests based on [SPARQL 1.1 Test Cases](https://www.w3.org/2009/sparql/docs/tests/README.html)
|
||||
use oxigraph::model::vocab::rdf; |
||||
use oxigraph::model::vocab::rdfs; |
||||
use oxigraph::model::*; |
||||
use oxigraph::sparql::*; |
||||
use oxigraph::*; |
||||
use rayon::prelude::*; |
||||
use std::collections::HashMap; |
||||
use std::fmt; |
||||
use std::fs::File; |
||||
use std::io::Read; |
||||
use std::io::{BufRead, BufReader}; |
||||
use std::iter::once; |
||||
use std::path::PathBuf; |
||||
use std::sync::Arc; |
||||
|
||||
#[test] |
||||
fn sparql_w3c_syntax_testsuite() -> Result<()> { |
||||
let manifest_10_urls = |
||||
vec!["http://www.w3.org/2001/sw/DataAccess/tests/data-r2/manifest-syntax.ttl"]; |
||||
let manifest_11_urls = vec![ |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-fed/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/construct/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest.ttl", |
||||
]; |
||||
for test_result in manifest_10_urls |
||||
.into_iter() |
||||
.chain(manifest_11_urls.into_iter()) |
||||
.chain(once( |
||||
"https://github.com/oxigraph/oxigraph/tests/sparql/manifest.ttl", |
||||
)) |
||||
.flat_map(TestManifest::new) |
||||
{ |
||||
let test = test_result.unwrap(); |
||||
if test.kind == "PositiveSyntaxTest" || test.kind == "PositiveSyntaxTest11" { |
||||
match Query::parse(&read_file_to_string(&test.query)?, Some(&test.query)) { |
||||
Err(error) => panic!("Failure on {} with error: {}", test, error), |
||||
Ok(query) => { |
||||
if let Err(error) = Query::parse(&query.to_string(), 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) = Query::parse(&read_file_to_string(&test.query)?, Some(&test.query)) |
||||
{ |
||||
eprintln!("Failure on {}. The output tree is: {}", test, result); |
||||
} |
||||
} else if test.kind != "QueryEvaluationTest" { |
||||
panic!("Not supported test: {}", test); |
||||
} |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn sparql_w3c_query_evaluation_testsuite() -> Result<()> { |
||||
let manifest_10_urls = vec![ |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/algebra/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/ask/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/basic/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/bnode-coreference/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/boolean-effective-value/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/bound/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/cast/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest.ttl", |
||||
//TODO FROM and FROM NAMED "http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest.ttl",
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-equals/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-ops/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/graph/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/i18n/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/optional/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/optional-filter/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/reduced/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/regex/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/solution-seq/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/sort/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/triple-match/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/type-promotion/manifest.ttl", |
||||
]; |
||||
|
||||
let manifest_11_urls = vec![ |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/bind/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/bindings/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/construct/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/exists/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/functions/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/negation/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/project-expression/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/property-path/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/service/manifest.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/subquery/manifest.ttl", |
||||
]; |
||||
|
||||
let test_blacklist = vec![ |
||||
//Multiple writing of the same xsd:integer. Our system does strong normalization.
|
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#distinct-1").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#distinct-9").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#dawg-str-1").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#dawg-str-2").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-equals/manifest#eq-graph-1").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-equals/manifest#eq-graph-2").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-01").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-04").unwrap(), |
||||
//Multiple writing of the same xsd:double. Our system does strong normalization.
|
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#sameTerm").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#sameTerm-simple").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#sameTerm-eq").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#sameTerm-not-eq").unwrap(), |
||||
//Simple literal vs xsd:string. We apply RDF 1.1
|
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#distinct-2").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-08").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-10").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-11").unwrap(), |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-12").unwrap(), |
||||
//DATATYPE("foo"@en) returns rdf:langString in RDF 1.1
|
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#dawg-datatype-2").unwrap(), |
||||
// FROM support
|
||||
NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/construct/manifest#constructwhere04").unwrap(), |
||||
//BNODE() scope is currently wrong
|
||||
NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/functions/manifest#bnode01").unwrap(), |
||||
//Property path with unbound graph name are not supported yet
|
||||
NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/property-path/manifest#pp35").unwrap(), |
||||
//SERVICE name from a BGP
|
||||
NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/service/manifest#service5").unwrap(), |
||||
// We use XSD 1.1 equality on dates
|
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#date-2").unwrap(), |
||||
// We choose to simplify first the nested group patterns in OPTIONAL
|
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/optional-filter/manifest#dawg-optional-filter-005-not-simplified").unwrap(), |
||||
]; |
||||
|
||||
let tests: Result<Vec<_>> = manifest_10_urls |
||||
.into_iter() |
||||
.chain(manifest_11_urls.into_iter()) |
||||
.flat_map(TestManifest::new) |
||||
.collect(); |
||||
let failed: Vec<_> = tests?.into_par_iter().map(|test| { |
||||
if test_blacklist.contains(&test.id) { |
||||
Ok(()) |
||||
} else if test.kind == "QueryEvaluationTest" { |
||||
let store = MemoryStore::new(); |
||||
if let Some(data) = &test.data { |
||||
load_graph_to_store(&data, &store, None)?; |
||||
} |
||||
for graph_data in &test.graph_data { |
||||
load_graph_to_store( |
||||
&graph_data, |
||||
&store, |
||||
Some(&NamedNode::parse(graph_data)?.into()), |
||||
)?; |
||||
} |
||||
match store.prepare_query(&read_file_to_string(&test.query)?, QueryOptions::default().with_base_iri(&test.query).with_service_handler(StaticServiceHandler::new(&test.service_data)?)) |
||||
{ |
||||
Err(error) => Err(Error::msg(format!( |
||||
"Failure to parse query of {} with error: {}", |
||||
test, error |
||||
))), |
||||
Ok(query) => match query.exec() { |
||||
Err(error) => Err(Error::msg(format!( |
||||
"Failure to execute query of {} with error: {}", |
||||
test, error |
||||
))), |
||||
Ok(result) => { |
||||
let expected_graph = |
||||
load_sparql_query_result(test.result.as_ref().unwrap()).map_err(|e| Error::msg(format!("Error constructing expected graph for {}: {}", test, e)))?; |
||||
let with_order = expected_graph |
||||
.quads_for_pattern(None, Some(&rs::INDEX), None, None) |
||||
.next() |
||||
.is_some(); |
||||
let actual_graph = to_dataset(result, with_order).map_err(|e| Error::msg(format!("Error constructing result graph for {}: {}", test, e)))?; |
||||
if actual_graph.is_isomorphic(&expected_graph) { |
||||
Ok(()) |
||||
} else { |
||||
Err(Error::msg(format!("Failure on {}.\nExpected file:\n{}\nOutput file:\n{}\nParsed query:\n{}\nData:\n{}\n", |
||||
test, |
||||
expected_graph, |
||||
actual_graph, |
||||
Query::parse(&read_file_to_string(&test.query)?, Some(&test.query)).unwrap(), |
||||
store |
||||
))) |
||||
} |
||||
} |
||||
}, |
||||
} |
||||
} else if test.kind != "NegativeSyntaxTest11" { |
||||
panic!("Not supported test: {}", test) |
||||
} else { |
||||
Ok(()) |
||||
} |
||||
}).filter_map(|v| v.err()).map(|e| e.to_string()).collect(); |
||||
assert!( |
||||
failed.is_empty(), |
||||
"{} tests failed:\n{}", |
||||
failed.len(), |
||||
failed.join("\n") |
||||
); |
||||
Ok(()) |
||||
} |
||||
|
||||
fn load_graph(url: &str) -> Result<MemoryStore> { |
||||
let store = MemoryStore::new(); |
||||
load_graph_to_store(url, &store, None)?; |
||||
Ok(store) |
||||
} |
||||
|
||||
fn load_graph_to_store( |
||||
url: &str, |
||||
store: &MemoryStore, |
||||
to_graph_name: Option<&NamedOrBlankNode>, |
||||
) -> Result<()> { |
||||
let syntax = if url.ends_with(".nt") { |
||||
GraphSyntax::NTriples |
||||
} else if url.ends_with(".ttl") { |
||||
GraphSyntax::Turtle |
||||
} else if url.ends_with(".rdf") { |
||||
GraphSyntax::RdfXml |
||||
} else { |
||||
return Err(Error::msg(format!( |
||||
"Serialization type not found for {}", |
||||
url |
||||
))); |
||||
}; |
||||
store.load_graph(read_file(url)?, syntax, to_graph_name, Some(url)) |
||||
} |
||||
|
||||
fn load_sparql_query_result(url: &str) -> Result<MemoryStore> { |
||||
if url.ends_with(".srx") { |
||||
to_dataset( |
||||
QueryResult::read(read_file(url)?, QueryResultSyntax::Xml)?, |
||||
false, |
||||
) |
||||
} else { |
||||
load_graph(url) |
||||
} |
||||
} |
||||
|
||||
fn to_relative_path(url: &str) -> Result<String> { |
||||
if url.starts_with("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/") { |
||||
Ok(url.replace( |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/", |
||||
"rdf-tests/sparql11/", |
||||
)) |
||||
} else if url.starts_with("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/") { |
||||
Ok(url.replace( |
||||
"http://www.w3.org/2009/sparql/docs/tests/", |
||||
"rdf-tests/sparql11/", |
||||
)) |
||||
} else if url.starts_with("https://github.com/oxigraph/oxigraph/tests/") { |
||||
Ok(url.replace( |
||||
"https://github.com/oxigraph/oxigraph/tests/", |
||||
"oxigraph-tests/", |
||||
)) |
||||
} else { |
||||
Err(Error::msg(format!("Not supported url for file: {}", url))) |
||||
} |
||||
} |
||||
|
||||
fn read_file(url: &str) -> Result<impl BufRead> { |
||||
let mut base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
||||
base_path.push("tests"); |
||||
base_path.push(to_relative_path(url)?); |
||||
|
||||
Ok(BufReader::new(File::open(&base_path).map_err(|e| { |
||||
Error::msg(format!( |
||||
"Opening file {} failed with {}", |
||||
base_path.display(), |
||||
e, |
||||
)) |
||||
})?)) |
||||
} |
||||
|
||||
fn read_file_to_string(url: &str) -> Result<String> { |
||||
let mut string = String::default(); |
||||
read_file(url)?.read_to_string(&mut string)?; |
||||
Ok(string) |
||||
} |
||||
|
||||
fn to_dataset(result: QueryResult<'_>, with_order: bool) -> Result<MemoryStore> { |
||||
match result { |
||||
QueryResult::Graph(graph) => graph.map(|t| t.map(|t| t.in_graph(None))).collect(), |
||||
QueryResult::Boolean(value) => { |
||||
let store = MemoryStore::new(); |
||||
let result_set = BlankNode::default(); |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rdf::TYPE.clone(), |
||||
rs::RESULT_SET.clone(), |
||||
None, |
||||
)); |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rs::BOOLEAN.clone(), |
||||
Literal::from(value), |
||||
None, |
||||
)); |
||||
Ok(store) |
||||
} |
||||
QueryResult::Bindings(solutions) => { |
||||
let store = MemoryStore::new(); |
||||
let result_set = BlankNode::default(); |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rdf::TYPE.clone(), |
||||
rs::RESULT_SET.clone(), |
||||
None, |
||||
)); |
||||
for variable in solutions.variables() { |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rs::RESULT_VARIABLE.clone(), |
||||
Literal::new_simple_literal(variable.as_str()), |
||||
None, |
||||
)); |
||||
} |
||||
for (i, solution) in solutions.enumerate() { |
||||
let solution = solution?; |
||||
let solution_id = BlankNode::default(); |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rs::SOLUTION.clone(), |
||||
solution_id, |
||||
None, |
||||
)); |
||||
for (variable, value) in solution.iter() { |
||||
let binding = BlankNode::default(); |
||||
store.insert(Quad::new(solution_id, rs::BINDING.clone(), binding, None)); |
||||
store.insert(Quad::new(binding, rs::VALUE.clone(), value.clone(), None)); |
||||
store.insert(Quad::new( |
||||
binding, |
||||
rs::VARIABLE.clone(), |
||||
Literal::new_simple_literal(variable.as_str()), |
||||
None, |
||||
)); |
||||
} |
||||
if with_order { |
||||
store.insert(Quad::new( |
||||
solution_id, |
||||
rs::INDEX.clone(), |
||||
Literal::from((i + 1) as i128), |
||||
None, |
||||
)); |
||||
} |
||||
} |
||||
Ok(store) |
||||
} |
||||
} |
||||
} |
||||
|
||||
mod rs { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref RESULT_SET: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet") |
||||
.unwrap(); |
||||
pub static ref RESULT_VARIABLE: NamedNode = NamedNode::parse( |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/result-set#resultVariable" |
||||
) |
||||
.unwrap(); |
||||
pub static ref SOLUTION: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#solution") |
||||
.unwrap(); |
||||
pub static ref BINDING: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#binding") |
||||
.unwrap(); |
||||
pub static ref VALUE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#value") |
||||
.unwrap(); |
||||
pub static ref VARIABLE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#variable") |
||||
.unwrap(); |
||||
pub static ref INDEX: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#index") |
||||
.unwrap(); |
||||
pub static ref BOOLEAN: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#boolean") |
||||
.unwrap(); |
||||
} |
||||
} |
||||
|
||||
mod mf { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref INCLUDE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#include") |
||||
.unwrap(); |
||||
pub static ref ENTRIES: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#entries") |
||||
.unwrap(); |
||||
pub static ref NAME: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#name") |
||||
.unwrap(); |
||||
pub static ref ACTION: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#action") |
||||
.unwrap(); |
||||
pub static ref RESULT: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#result") |
||||
.unwrap(); |
||||
} |
||||
} |
||||
|
||||
mod qt { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref QUERY: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#query") |
||||
.unwrap(); |
||||
pub static ref DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#data").unwrap(); |
||||
pub static ref GRAPH_DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#graphData") |
||||
.unwrap(); |
||||
pub static ref SERVICE_DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#serviceData") |
||||
.unwrap(); |
||||
pub static ref ENDPOINT: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#endpoint") |
||||
.unwrap(); |
||||
} |
||||
} |
||||
|
||||
struct Test { |
||||
id: NamedNode, |
||||
kind: String, |
||||
name: Option<String>, |
||||
comment: Option<String>, |
||||
query: String, |
||||
data: Option<String>, |
||||
graph_data: Vec<String>, |
||||
service_data: Vec<(String, String)>, |
||||
result: Option<String>, |
||||
} |
||||
|
||||
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 query {}", self.query)?; |
||||
for data in &self.data { |
||||
write!(f, " with data {}", data)?; |
||||
} |
||||
for data in &self.graph_data { |
||||
write!(f, " and graph data {}", data)?; |
||||
} |
||||
for result in &self.result { |
||||
write!(f, " and expected result {}", result)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
struct TestManifest { |
||||
graph: MemoryStore, |
||||
tests_to_do: Vec<Term>, |
||||
manifests_to_do: Vec<String>, |
||||
} |
||||
|
||||
impl TestManifest { |
||||
fn new(url: impl Into<String>) -> TestManifest { |
||||
Self { |
||||
graph: MemoryStore::new(), |
||||
tests_to_do: Vec::default(), |
||||
manifests_to_do: vec![url.into()], |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Iterator for TestManifest { |
||||
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 object_for_subject_predicate(&self.graph, &test_subject, &rdf::TYPE) { |
||||
Some(Term::NamedNode(c)) => match c.as_str().split('#').last() { |
||||
Some(k) => k.to_string(), |
||||
None => return self.next(), //We ignore the test
|
||||
}, |
||||
_ => return self.next(), //We ignore the test
|
||||
}; |
||||
let name = match object_for_subject_predicate(&self.graph, &test_subject, &mf::NAME) |
||||
{ |
||||
Some(Term::Literal(c)) => Some(c.value().to_string()), |
||||
_ => None, |
||||
}; |
||||
let comment = match object_for_subject_predicate( |
||||
&self.graph, |
||||
&test_subject, |
||||
&rdfs::COMMENT, |
||||
) { |
||||
Some(Term::Literal(c)) => Some(c.value().to_string()), |
||||
_ => None, |
||||
}; |
||||
let (query, data, graph_data, service_data) = |
||||
match object_for_subject_predicate(&self.graph, &test_subject, &*mf::ACTION) { |
||||
Some(Term::NamedNode(n)) => (n.as_str().to_owned(), None, vec![], vec![]), |
||||
Some(Term::BlankNode(n)) => { |
||||
let n = n.clone().into(); |
||||
let query = |
||||
match object_for_subject_predicate(&self.graph, &n, &qt::QUERY) { |
||||
Some(Term::NamedNode(q)) => q.as_str().to_owned(), |
||||
Some(_) => return Some(Err(Error::msg("invalid query"))), |
||||
None => return Some(Err(Error::msg("query not found"))), |
||||
}; |
||||
let data = |
||||
match object_for_subject_predicate(&self.graph, &n, &qt::DATA) { |
||||
Some(Term::NamedNode(q)) => Some(q.as_str().to_owned()), |
||||
_ => None, |
||||
}; |
||||
let graph_data = |
||||
objects_for_subject_predicate(&self.graph, &n, &qt::GRAPH_DATA) |
||||
.filter_map(|g| match g { |
||||
Term::NamedNode(q) => Some(q.as_str().to_owned()), |
||||
_ => None, |
||||
}) |
||||
.collect(); |
||||
let service_data = |
||||
objects_for_subject_predicate(&self.graph, &n, &qt::SERVICE_DATA) |
||||
.filter_map(|g| match g { |
||||
Term::NamedNode(g) => Some(g.into()), |
||||
Term::BlankNode(g) => Some(g.into()), |
||||
_ => None, |
||||
}) |
||||
.filter_map(|g| { |
||||
if let ( |
||||
Some(Term::NamedNode(endpoint)), |
||||
Some(Term::NamedNode(data)), |
||||
) = ( |
||||
object_for_subject_predicate( |
||||
&self.graph, |
||||
&g, |
||||
&qt::ENDPOINT, |
||||
), |
||||
object_for_subject_predicate( |
||||
&self.graph, |
||||
&g, |
||||
&qt::DATA, |
||||
), |
||||
) { |
||||
Some(( |
||||
endpoint.as_str().to_owned(), |
||||
data.as_str().to_owned(), |
||||
)) |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect(); |
||||
(query, data, graph_data, service_data) |
||||
} |
||||
Some(_) => return Some(Err(Error::msg("invalid action"))), |
||||
None => { |
||||
return Some(Err(Error::msg(format!( |
||||
"action not found for test {}", |
||||
test_subject |
||||
)))); |
||||
} |
||||
}; |
||||
let result = |
||||
match object_for_subject_predicate(&self.graph, &test_subject, &*mf::RESULT) { |
||||
Some(Term::NamedNode(n)) => Some(n.as_str().to_owned()), |
||||
Some(_) => return Some(Err(Error::msg("invalid result"))), |
||||
None => None, |
||||
}; |
||||
Some(Ok(Test { |
||||
id: test_node, |
||||
kind, |
||||
name, |
||||
comment, |
||||
query, |
||||
data, |
||||
graph_data, |
||||
service_data, |
||||
result, |
||||
})) |
||||
} |
||||
Some(_) => Some(Err(Error::msg("invalid test list"))), |
||||
None => { |
||||
match self.manifests_to_do.pop() { |
||||
Some(url) => { |
||||
let manifest = |
||||
NamedOrBlankNode::from(NamedNode::parse(url.clone()).unwrap()); |
||||
if let Err(e) = load_graph_to_store(&url, &self.graph, None) { |
||||
return Some(Err(e)); |
||||
} |
||||
// New manifests
|
||||
match object_for_subject_predicate(&self.graph, &manifest, &*mf::INCLUDE) { |
||||
Some(Term::BlankNode(list)) => { |
||||
self.manifests_to_do.extend( |
||||
RdfListIterator::iter(&self.graph, list.clone().into()) |
||||
.filter_map(|m| match m { |
||||
Term::NamedNode(nm) => Some(nm.into_string()), |
||||
_ => None, |
||||
}), |
||||
); |
||||
} |
||||
Some(_) => return Some(Err(Error::msg("invalid tests list"))), |
||||
None => (), |
||||
} |
||||
|
||||
// New tests
|
||||
match object_for_subject_predicate(&self.graph, &manifest, &*mf::ENTRIES) { |
||||
Some(Term::BlankNode(list)) => { |
||||
self.tests_to_do.extend(RdfListIterator::iter( |
||||
&self.graph, |
||||
list.clone().into(), |
||||
)); |
||||
} |
||||
Some(term) => { |
||||
return Some(Err(Error::msg(format!( |
||||
"Invalid tests list. Got term {}", |
||||
term |
||||
)))); |
||||
} |
||||
None => (), |
||||
} |
||||
} |
||||
None => return None, |
||||
} |
||||
self.next() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct RdfListIterator<'a> { |
||||
graph: &'a MemoryStore, |
||||
current_node: Option<NamedOrBlankNode>, |
||||
} |
||||
|
||||
impl<'a> RdfListIterator<'a> { |
||||
fn iter(graph: &'a MemoryStore, root: NamedOrBlankNode) -> RdfListIterator<'a> { |
||||
RdfListIterator { |
||||
graph, |
||||
current_node: Some(root), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<'a> Iterator for RdfListIterator<'a> { |
||||
type Item = Term; |
||||
|
||||
fn next(&mut self) -> Option<Term> { |
||||
match self.current_node.clone() { |
||||
Some(current) => { |
||||
let result = object_for_subject_predicate(&self.graph, ¤t, &rdf::FIRST); |
||||
self.current_node = |
||||
match object_for_subject_predicate(&self.graph, ¤t, &rdf::REST) { |
||||
Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None, |
||||
Some(Term::NamedNode(n)) => Some(n.into()), |
||||
Some(Term::BlankNode(n)) => Some(n.into()), |
||||
_ => None, |
||||
}; |
||||
result |
||||
} |
||||
None => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone)] |
||||
struct StaticServiceHandler { |
||||
services: Arc<HashMap<NamedNode, MemoryStore>>, |
||||
} |
||||
|
||||
impl StaticServiceHandler { |
||||
fn new(services: &[(String, String)]) -> Result<Self> { |
||||
Ok(Self { |
||||
services: Arc::new( |
||||
services |
||||
.iter() |
||||
.map(|(name, data)| { |
||||
let name = NamedNode::parse(name)?; |
||||
let store = MemoryStore::new(); |
||||
load_graph_to_store(&data, &store, None)?; |
||||
Ok((name, store)) |
||||
}) |
||||
.collect::<Result<_>>()?, |
||||
), |
||||
}) |
||||
} |
||||
} |
||||
|
||||
impl ServiceHandler for StaticServiceHandler { |
||||
fn handle<'a>( |
||||
&'a self, |
||||
service_name: &NamedNode, |
||||
graph_pattern: &'a GraphPattern, |
||||
) -> Result<QuerySolutionsIterator<'a>> { |
||||
if let QueryResult::Bindings(iterator) = self |
||||
.services |
||||
.get(service_name) |
||||
.ok_or_else(|| Error::msg(format!("Service {} not found", service_name)))? |
||||
.prepare_query_from_pattern( |
||||
&graph_pattern, |
||||
QueryOptions::default().with_service_handler(self.clone()), |
||||
)? |
||||
.exec()? |
||||
{ |
||||
//TODO: very ugly
|
||||
let (variables, iter) = iterator.destruct(); |
||||
let collected = iter.collect::<Vec<_>>(); |
||||
Ok(QuerySolutionsIterator::new( |
||||
variables, |
||||
Box::new(collected.into_iter()), |
||||
)) |
||||
} else { |
||||
Err(Error::msg("Expected bindings but got another QueryResult")) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn object_for_subject_predicate( |
||||
store: &MemoryStore, |
||||
subject: &NamedOrBlankNode, |
||||
predicate: &NamedNode, |
||||
) -> Option<Term> { |
||||
objects_for_subject_predicate(store, subject, predicate).next() |
||||
} |
||||
|
||||
fn objects_for_subject_predicate( |
||||
store: &MemoryStore, |
||||
subject: &NamedOrBlankNode, |
||||
predicate: &NamedNode, |
||||
) -> impl Iterator<Item = Term> { |
||||
store |
||||
.quads_for_pattern(Some(subject), Some(predicate), None, None) |
||||
.map(|t| t.object_owned()) |
||||
} |
@ -0,0 +1,24 @@ |
||||
[package] |
||||
name = "oxigraph_testsuite" |
||||
version = "0.1.0" |
||||
authors = ["Tpt <thomas@pellissier-tanon.fr>"] |
||||
license = "MIT/Apache-2.0" |
||||
readme = "../README.md" |
||||
repository = "https://github.com/oxigraph/oxigraph" |
||||
description = """ |
||||
Implementation of W3C testsuites for Oxigraph |
||||
""" |
||||
edition = "2018" |
||||
publish = false |
||||
|
||||
[dependencies] |
||||
chrono = "0.4" |
||||
lazy_static = "1" |
||||
oxigraph = { path = "../lib" } |
||||
|
||||
[dev-dependencies] |
||||
criterion = "0.3" |
||||
|
||||
[[bench]] |
||||
name = "sparql_query" |
||||
harness = false |
@ -0,0 +1,39 @@ |
||||
use criterion::{criterion_group, criterion_main, Criterion}; |
||||
use oxigraph::sparql::*; |
||||
use oxigraph_testsuite::files::read_file_to_string; |
||||
use oxigraph_testsuite::manifest::TestManifest; |
||||
|
||||
criterion_group!(sparql, sparql_w3c_syntax_bench); |
||||
|
||||
criterion_main!(sparql); |
||||
|
||||
fn sparql_w3c_syntax_bench(c: &mut Criterion) { |
||||
let manifest_urls = vec![ |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/manifest-syntax.ttl", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/manifest-sparql11-query.ttl", |
||||
]; |
||||
let queries: Vec<_> = TestManifest::new(manifest_urls) |
||||
.flat_map(|test| { |
||||
let test = test.unwrap(); |
||||
if test.kind == "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#PositiveSyntaxTest" |
||||
|| test.kind |
||||
== "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#PositiveSyntaxTest11" { |
||||
if let Some(query) = test.action { |
||||
Some((read_file_to_string(&query).unwrap(), query)) |
||||
} else { |
||||
None |
||||
} |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect(); |
||||
|
||||
c.bench_function("query parser", |b| { |
||||
b.iter(|| { |
||||
for (query, base) in &queries { |
||||
Query::parse(query, Some(&base)).unwrap(); |
||||
} |
||||
}) |
||||
}); |
||||
} |
@ -1,3 +1,3 @@ |
||||
PREFIX : <http://www.example.org> |
||||
PREFIX : <http://www.example.org/> |
||||
|
||||
SELECT (GROUP_CONCAT(?opt) AS ?g) WHERE { ?baseS a :ex OPTIONAL { ?baseS :opt ?opt } } |
@ -1,4 +1,4 @@ |
||||
@prefix : <http://www.example.org/> . |
||||
|
||||
:a a :ex ; :s :opt "value" . |
||||
:a a :ex ; :opt "value" . |
||||
:b a :ex . |
@ -0,0 +1 @@ |
||||
Subproject commit 4dd2ac9136a10b8854396c646e91e9423d229d85 |
@ -0,0 +1,82 @@ |
||||
use oxigraph::model::NamedOrBlankNode; |
||||
use oxigraph::{DatasetSyntax, Error, GraphSyntax, MemoryStore, Result}; |
||||
use std::fs::File; |
||||
use std::io::{BufRead, BufReader, Read}; |
||||
use std::path::PathBuf; |
||||
|
||||
pub fn read_file(url: &str) -> Result<impl BufRead> { |
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
||||
path.push(if url.starts_with("http://w3c.github.io/rdf-tests/") { |
||||
Ok(url.replace("http://w3c.github.io/rdf-tests/", "rdf-tests/")) |
||||
} else if url.starts_with("http://www.w3.org/2013/RDFXMLTests/") { |
||||
Ok(url.replace("http://www.w3.org/2013/RDFXMLTests/", "rdf-tests/rdf-xml/")) |
||||
} else if url.starts_with("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/") { |
||||
Ok(url.replace( |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/", |
||||
"rdf-tests/sparql11/", |
||||
)) |
||||
} else if url.starts_with("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/") { |
||||
Ok(url.replace( |
||||
"http://www.w3.org/2009/sparql/docs/tests/", |
||||
"rdf-tests/sparql11/", |
||||
)) |
||||
} else if url.starts_with("https://github.com/oxigraph/oxigraph/tests/") { |
||||
Ok(url.replace( |
||||
"https://github.com/oxigraph/oxigraph/tests/", |
||||
"oxigraph-tests/", |
||||
)) |
||||
} else { |
||||
Err(Error::msg(format!("Not supported url for file: {}", url))) |
||||
}?); |
||||
Ok(BufReader::new(File::open(&path)?)) |
||||
} |
||||
|
||||
pub fn read_file_to_string(url: &str) -> Result<String> { |
||||
let mut buf = String::new(); |
||||
read_file(url)?.read_to_string(&mut buf)?; |
||||
Ok(buf) |
||||
} |
||||
|
||||
pub fn load_to_store( |
||||
url: &str, |
||||
store: &MemoryStore, |
||||
to_graph_name: Option<&NamedOrBlankNode>, |
||||
) -> Result<()> { |
||||
if url.ends_with(".nt") { |
||||
store.load_graph( |
||||
read_file(url)?, |
||||
GraphSyntax::NTriples, |
||||
to_graph_name, |
||||
Some(url), |
||||
) |
||||
} else if url.ends_with(".ttl") { |
||||
store.load_graph( |
||||
read_file(url)?, |
||||
GraphSyntax::Turtle, |
||||
to_graph_name, |
||||
Some(url), |
||||
) |
||||
} else if url.ends_with(".rdf") { |
||||
store.load_graph( |
||||
read_file(url)?, |
||||
GraphSyntax::RdfXml, |
||||
to_graph_name, |
||||
Some(url), |
||||
) |
||||
} else if url.ends_with(".nq") { |
||||
store.load_dataset(read_file(url)?, DatasetSyntax::NQuads, Some(url)) |
||||
} else if url.ends_with(".trig") { |
||||
store.load_dataset(read_file(url)?, DatasetSyntax::TriG, Some(url)) |
||||
} else { |
||||
Err(Error::msg(format!( |
||||
"Serialization type not found for {}", |
||||
url |
||||
))) |
||||
} |
||||
} |
||||
|
||||
pub fn load_store(url: &str) -> Result<MemoryStore> { |
||||
let store = MemoryStore::new(); |
||||
load_to_store(url, &store, None)?; |
||||
Ok(store) |
||||
} |
@ -0,0 +1,18 @@ |
||||
//! Implementation of [W3C RDF tests](http://w3c.github.io/rdf-tests/) to tests Oxigraph conformance.
|
||||
#![deny(
|
||||
future_incompatible, |
||||
nonstandard_style, |
||||
rust_2018_idioms, |
||||
missing_copy_implementations, |
||||
trivial_casts, |
||||
trivial_numeric_casts, |
||||
unsafe_code, |
||||
unused_qualifications |
||||
)] |
||||
|
||||
pub mod files; |
||||
pub mod manifest; |
||||
pub mod parser_evaluator; |
||||
pub mod report; |
||||
pub mod sparql_evaluator; |
||||
mod vocab; |
@ -0,0 +1,276 @@ |
||||
use crate::files::load_to_store; |
||||
use crate::vocab::*; |
||||
use oxigraph::model::vocab::*; |
||||
use oxigraph::model::*; |
||||
use oxigraph::{Error, MemoryStore, Result}; |
||||
use std::fmt; |
||||
|
||||
pub struct Test { |
||||
pub id: NamedNode, |
||||
pub kind: NamedNode, |
||||
pub name: Option<String>, |
||||
pub comment: Option<String>, |
||||
pub action: Option<String>, |
||||
pub query: Option<String>, |
||||
pub data: Option<String>, |
||||
pub graph_data: Vec<String>, |
||||
pub service_data: Vec<(String, String)>, |
||||
pub result: Option<String>, |
||||
} |
||||
|
||||
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)?; |
||||
} |
||||
if let Some(action) = &self.action { |
||||
write!(f, " on file \"{}\"", action)?; |
||||
} |
||||
if let Some(query) = &self.query { |
||||
write!(f, " on query {}", &query)?; |
||||
} |
||||
for data in &self.data { |
||||
write!(f, " with data {}", data)?; |
||||
} |
||||
for data in &self.graph_data { |
||||
write!(f, " and graph data {}", data)?; |
||||
} |
||||
for result in &self.result { |
||||
write!(f, " and expected result {}", result)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
pub struct TestManifest { |
||||
graph: MemoryStore, |
||||
tests_to_do: Vec<Term>, |
||||
manifests_to_do: Vec<String>, |
||||
} |
||||
|
||||
impl TestManifest { |
||||
pub fn new<S: ToString>(manifest_urls: impl IntoIterator<Item = S>) -> Self { |
||||
Self { |
||||
graph: MemoryStore::new(), |
||||
tests_to_do: Vec::new(), |
||||
manifests_to_do: manifest_urls |
||||
.into_iter() |
||||
.map(|url| url.to_string()) |
||||
.collect(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Iterator for TestManifest { |
||||
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 object_for_subject_predicate(&self.graph, &test_subject, &rdf::TYPE) { |
||||
Some(Term::NamedNode(c)) => c, |
||||
_ => return self.next(), //We ignore the test
|
||||
}; |
||||
let name = match object_for_subject_predicate(&self.graph, &test_subject, &mf::NAME) |
||||
{ |
||||
Some(Term::Literal(c)) => Some(c.value().to_string()), |
||||
_ => None, |
||||
}; |
||||
let comment = match object_for_subject_predicate( |
||||
&self.graph, |
||||
&test_subject, |
||||
&rdfs::COMMENT, |
||||
) { |
||||
Some(Term::Literal(c)) => Some(c.value().to_string()), |
||||
_ => None, |
||||
}; |
||||
let (action, query, data, graph_data, service_data) = |
||||
match object_for_subject_predicate(&self.graph, &test_subject, &*mf::ACTION) { |
||||
Some(Term::NamedNode(n)) => { |
||||
(Some(n.into_string()), None, None, vec![], vec![]) |
||||
} |
||||
Some(Term::BlankNode(n)) => { |
||||
let n = n.into(); |
||||
let query = |
||||
match object_for_subject_predicate(&self.graph, &n, &qt::QUERY) { |
||||
Some(Term::NamedNode(q)) => Some(q.into_string()), |
||||
_ => None, |
||||
}; |
||||
let data = |
||||
match object_for_subject_predicate(&self.graph, &n, &qt::DATA) { |
||||
Some(Term::NamedNode(q)) => Some(q.into_string()), |
||||
_ => None, |
||||
}; |
||||
let graph_data = |
||||
objects_for_subject_predicate(&self.graph, &n, &qt::GRAPH_DATA) |
||||
.filter_map(|g| match g { |
||||
Term::NamedNode(q) => Some(q.into_string()), |
||||
_ => None, |
||||
}) |
||||
.collect(); |
||||
let service_data = |
||||
objects_for_subject_predicate(&self.graph, &n, &qt::SERVICE_DATA) |
||||
.filter_map(|g| match g { |
||||
Term::NamedNode(g) => Some(g.into()), |
||||
Term::BlankNode(g) => Some(g.into()), |
||||
_ => None, |
||||
}) |
||||
.filter_map(|g| { |
||||
if let ( |
||||
Some(Term::NamedNode(endpoint)), |
||||
Some(Term::NamedNode(data)), |
||||
) = ( |
||||
object_for_subject_predicate( |
||||
&self.graph, |
||||
&g, |
||||
&qt::ENDPOINT, |
||||
), |
||||
object_for_subject_predicate( |
||||
&self.graph, |
||||
&g, |
||||
&qt::DATA, |
||||
), |
||||
) { |
||||
Some((endpoint.into_string(), data.into_string())) |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect(); |
||||
(None, query, data, graph_data, service_data) |
||||
} |
||||
Some(_) => return Some(Err(Error::msg("invalid action"))), |
||||
None => { |
||||
return Some(Err(Error::msg(format!( |
||||
"action not found for test {}", |
||||
test_subject |
||||
)))); |
||||
} |
||||
}; |
||||
let result = |
||||
match object_for_subject_predicate(&self.graph, &test_subject, &*mf::RESULT) { |
||||
Some(Term::NamedNode(n)) => Some(n.into_string()), |
||||
Some(_) => return Some(Err(Error::msg("invalid result"))), |
||||
None => None, |
||||
}; |
||||
Some(Ok(Test { |
||||
id: test_node, |
||||
kind, |
||||
name, |
||||
comment, |
||||
action, |
||||
query, |
||||
data, |
||||
graph_data, |
||||
service_data, |
||||
result, |
||||
})) |
||||
} |
||||
Some(_) => self.next(), |
||||
None => { |
||||
match self.manifests_to_do.pop() { |
||||
Some(url) => { |
||||
let manifest = |
||||
NamedOrBlankNode::from(NamedNode::parse(url.clone()).unwrap()); |
||||
if let Err(error) = load_to_store(&url, &self.graph, None) { |
||||
return Some(Err(error)); |
||||
} |
||||
|
||||
// New manifests
|
||||
match object_for_subject_predicate(&self.graph, &manifest, &*mf::INCLUDE) { |
||||
Some(Term::BlankNode(list)) => { |
||||
self.manifests_to_do.extend( |
||||
RdfListIterator::iter(&self.graph, list.clone().into()) |
||||
.filter_map(|m| match m { |
||||
Term::NamedNode(nm) => Some(nm.into_string()), |
||||
_ => None, |
||||
}), |
||||
); |
||||
} |
||||
Some(_) => return Some(Err(Error::msg("invalid tests list"))), |
||||
None => (), |
||||
} |
||||
|
||||
// New tests
|
||||
match object_for_subject_predicate(&self.graph, &manifest, &*mf::ENTRIES) { |
||||
Some(Term::BlankNode(list)) => { |
||||
self.tests_to_do.extend(RdfListIterator::iter( |
||||
&self.graph, |
||||
list.clone().into(), |
||||
)); |
||||
} |
||||
Some(term) => { |
||||
return Some(Err(Error::msg(format!( |
||||
"Invalid tests list. Got term {}", |
||||
term |
||||
)))); |
||||
} |
||||
None => (), |
||||
} |
||||
} |
||||
None => return None, |
||||
} |
||||
self.next() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct RdfListIterator<'a> { |
||||
graph: &'a MemoryStore, |
||||
current_node: Option<NamedOrBlankNode>, |
||||
} |
||||
|
||||
impl<'a> RdfListIterator<'a> { |
||||
fn iter(graph: &'a MemoryStore, root: NamedOrBlankNode) -> RdfListIterator<'a> { |
||||
RdfListIterator { |
||||
graph, |
||||
current_node: Some(root), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<'a> Iterator for RdfListIterator<'a> { |
||||
type Item = Term; |
||||
|
||||
fn next(&mut self) -> Option<Term> { |
||||
match self.current_node.clone() { |
||||
Some(current) => { |
||||
let result = object_for_subject_predicate(&self.graph, ¤t, &rdf::FIRST); |
||||
self.current_node = |
||||
match object_for_subject_predicate(&self.graph, ¤t, &rdf::REST) { |
||||
Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None, |
||||
Some(Term::NamedNode(n)) => Some(n.into()), |
||||
Some(Term::BlankNode(n)) => Some(n.into()), |
||||
_ => None, |
||||
}; |
||||
result |
||||
} |
||||
None => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn object_for_subject_predicate( |
||||
store: &MemoryStore, |
||||
subject: &NamedOrBlankNode, |
||||
predicate: &NamedNode, |
||||
) -> Option<Term> { |
||||
objects_for_subject_predicate(store, subject, predicate).next() |
||||
} |
||||
|
||||
fn objects_for_subject_predicate( |
||||
store: &MemoryStore, |
||||
subject: &NamedOrBlankNode, |
||||
predicate: &NamedNode, |
||||
) -> impl Iterator<Item = Term> { |
||||
store |
||||
.quads_for_pattern(Some(subject), Some(predicate), None, None) |
||||
.map(|t| t.object_owned()) |
||||
} |
@ -0,0 +1,80 @@ |
||||
use crate::files::load_store; |
||||
use crate::manifest::Test; |
||||
use crate::report::TestResult; |
||||
use chrono::Utc; |
||||
use oxigraph::{Error, Result}; |
||||
|
||||
pub fn evaluate_parser_tests( |
||||
manifest: impl Iterator<Item = Result<Test>>, |
||||
) -> Result<Vec<TestResult>> { |
||||
manifest |
||||
.map(|test| { |
||||
let test = test?; |
||||
let outcome = evaluate_parser_test(&test); |
||||
Ok(TestResult { |
||||
test: test.id, |
||||
outcome, |
||||
date: Utc::now(), |
||||
}) |
||||
}) |
||||
.collect() |
||||
} |
||||
|
||||
fn evaluate_parser_test(test: &Test) -> Result<()> { |
||||
let action = test |
||||
.action |
||||
.as_deref() |
||||
.ok_or_else(|| Error::msg(format!("No action found for test {}", test)))?; |
||||
if test.kind == "http://www.w3.org/ns/rdftest#TestNTriplesPositiveSyntax" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestNQuadsPositiveSyntax" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestTurtlePositiveSyntax" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestTrigPositiveSyntax" |
||||
{ |
||||
match load_store(action) { |
||||
Ok(_) => Ok(()), |
||||
Err(e) => Err(Error::msg(format!("Parse error: {}", e))), |
||||
} |
||||
} else if test.kind == "http://www.w3.org/ns/rdftest#TestNTriplesNegativeSyntax" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestNQuadsNegativeSyntax" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestTurtleNegativeSyntax" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestTurtleNegativeEval" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestTrigNegativeSyntax" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestTrigNegativeEval" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestXMLNegativeSyntax" |
||||
{ |
||||
match load_store(action) { |
||||
Ok(_) => Err(Error::msg( |
||||
"File parsed with an error even if it should not", |
||||
)), |
||||
Err(_) => Ok(()), |
||||
} |
||||
} else if test.kind == "http://www.w3.org/ns/rdftest#TestTurtleEval" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestTrigEval" |
||||
|| test.kind == "http://www.w3.org/ns/rdftest#TestXMLEval" |
||||
{ |
||||
match load_store(action) { |
||||
Ok(actual_graph) => { |
||||
if let Some(result) = &test.result { |
||||
match load_store(result) { |
||||
Ok(expected_graph) => { |
||||
if expected_graph.is_isomorphic(&actual_graph) { |
||||
Ok(()) |
||||
} else { |
||||
Err(Error::msg(format!( |
||||
"The two files are not isomorphic. Expected:\n{}\nActual:\n{}", |
||||
expected_graph, actual_graph |
||||
))) |
||||
} |
||||
} |
||||
Err(e) => Err(Error::msg(format!("Parse error on file {}: {}", action, e))), |
||||
} |
||||
} else { |
||||
Err(Error::msg("No tests result found".to_string())) |
||||
} |
||||
} |
||||
Err(e) => Err(Error::msg(format!("Parse error on file {}: {}", action, e))), |
||||
} |
||||
} else { |
||||
Err(Error::msg(format!("Unsupported test type: {}", test.kind))) |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
use chrono::{DateTime, Utc}; |
||||
use oxigraph::model::NamedNode; |
||||
use oxigraph::Result; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct TestResult { |
||||
pub test: NamedNode, |
||||
pub outcome: Result<()>, |
||||
pub date: DateTime<Utc>, |
||||
} |
@ -0,0 +1,484 @@ |
||||
use crate::files::*; |
||||
use crate::manifest::*; |
||||
use crate::report::*; |
||||
use crate::vocab::*; |
||||
use chrono::Utc; |
||||
use oxigraph::model::vocab::*; |
||||
use oxigraph::model::*; |
||||
use oxigraph::sparql::*; |
||||
use oxigraph::{Error, MemoryStore, Result}; |
||||
use std::collections::HashMap; |
||||
use std::fmt; |
||||
use std::str::FromStr; |
||||
use std::sync::Arc; |
||||
|
||||
pub fn evaluate_sparql_tests( |
||||
manifest: impl Iterator<Item = Result<Test>>, |
||||
) -> Result<Vec<TestResult>> { |
||||
manifest |
||||
.map(|test| { |
||||
let test = test?; |
||||
let outcome = evaluate_sparql_test(&test); |
||||
Ok(TestResult { |
||||
test: test.id, |
||||
outcome, |
||||
date: Utc::now(), |
||||
}) |
||||
}) |
||||
.collect() |
||||
} |
||||
|
||||
fn evaluate_sparql_test(test: &Test) -> Result<()> { |
||||
if test.kind == "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#PositiveSyntaxTest" |
||||
|| test.kind |
||||
== "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#PositiveSyntaxTest11" |
||||
{ |
||||
let query_file = test |
||||
.action |
||||
.as_deref() |
||||
.ok_or_else(|| Error::msg(format!("No action found for test {}", test)))?; |
||||
match Query::parse(&read_file_to_string(&query_file)?, Some(&query_file)) { |
||||
Err(error) => Err(Error::msg(format!( |
||||
"Not able to parse {} with error: {}", |
||||
test, error |
||||
))), |
||||
Ok(query) => match Query::parse(&query.to_string(), None) { |
||||
Ok(_) => Ok(()), |
||||
Err(error) => Err(Error::msg(format!( |
||||
"Failure to deserialize \"{}\" of {} with error: {}", |
||||
query.to_string(), |
||||
test, |
||||
error |
||||
))), |
||||
}, |
||||
} |
||||
} else if test.kind |
||||
== "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#NegativeSyntaxTest" |
||||
|| test.kind |
||||
== "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#NegativeSyntaxTest11" |
||||
{ |
||||
let query_file = test |
||||
.action |
||||
.as_deref() |
||||
.ok_or_else(|| Error::msg(format!("No action found for test {}", test)))?; |
||||
match Query::parse(&read_file_to_string(query_file)?, Some(query_file)) { |
||||
Ok(result) => Err(Error::msg(format!( |
||||
"Oxigraph parses even if it should not {}. The output tree is: {}", |
||||
test, result |
||||
))), |
||||
Err(_) => Ok(()), |
||||
} |
||||
} else if test.kind |
||||
== "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#QueryEvaluationTest" |
||||
{ |
||||
let store = MemoryStore::new(); |
||||
if let Some(data) = &test.data { |
||||
load_to_store(data, &store, None)?; |
||||
} |
||||
for graph_data in &test.graph_data { |
||||
load_to_store( |
||||
&graph_data, |
||||
&store, |
||||
Some(&NamedNode::parse(graph_data)?.into()), |
||||
)?; |
||||
} |
||||
let query_file = test |
||||
.query |
||||
.as_deref() |
||||
.ok_or_else(|| Error::msg(format!("No action found for test {}", test)))?; |
||||
let options = QueryOptions::default() |
||||
.with_base_iri(query_file) |
||||
.with_service_handler(StaticServiceHandler::new(&test.service_data)?); |
||||
match store.prepare_query(&read_file_to_string(query_file)?, options) { |
||||
Err(error) => Err(Error::msg(format!( |
||||
"Failure to parse query of {} with error: {}", |
||||
test, error |
||||
))), |
||||
Ok(query) => match query.exec() { |
||||
Err(error) => Err(Error::msg(format!( |
||||
"Failure to execute query of {} with error: {}", |
||||
test, error |
||||
))), |
||||
Ok(actual_results) => { |
||||
let expected_results = load_sparql_query_result(test.result.as_ref().unwrap()) |
||||
.map_err(|e| { |
||||
Error::msg(format!( |
||||
"Error constructing expected graph for {}: {}", |
||||
test, e |
||||
)) |
||||
})?; |
||||
let with_order = |
||||
if let StaticQueryResults::Solutions { ordered, .. } = &expected_results { |
||||
*ordered |
||||
} else { |
||||
false |
||||
}; |
||||
let actual_results = |
||||
StaticQueryResults::from_query_results(actual_results, with_order)?; |
||||
|
||||
if are_query_results_isomorphic(&expected_results, &actual_results) { |
||||
Ok(()) |
||||
} else { |
||||
Err(Error::msg(format!("Failure on {}.\nExpected file:\n{}\nOutput file:\n{}\nParsed query:\n{}\nData:\n{}\n", |
||||
test, |
||||
actual_results, |
||||
expected_results, |
||||
Query::parse(&read_file_to_string(query_file)?, Some(query_file)).unwrap(), |
||||
store |
||||
))) |
||||
} |
||||
} |
||||
}, |
||||
} |
||||
} else { |
||||
Err(Error::msg(format!("Unsupported test type: {}", test.kind))) |
||||
} |
||||
} |
||||
|
||||
fn load_sparql_query_result(url: &str) -> Result<StaticQueryResults> { |
||||
if url.ends_with(".srx") { |
||||
StaticQueryResults::from_query_results( |
||||
QueryResult::read(read_file(url)?, QueryResultSyntax::Xml)?, |
||||
false, |
||||
) |
||||
} else if url.ends_with(".srj") { |
||||
StaticQueryResults::from_query_results( |
||||
QueryResult::read(read_file(url)?, QueryResultSyntax::Json)?, |
||||
false, |
||||
) |
||||
} else { |
||||
Ok(StaticQueryResults::from_dataset(load_store(url)?)) |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone)] |
||||
struct StaticServiceHandler { |
||||
services: Arc<HashMap<NamedNode, MemoryStore>>, |
||||
} |
||||
|
||||
impl StaticServiceHandler { |
||||
fn new(services: &[(String, String)]) -> Result<Self> { |
||||
Ok(Self { |
||||
services: Arc::new( |
||||
services |
||||
.iter() |
||||
.map(|(name, data)| { |
||||
let name = NamedNode::parse(name)?; |
||||
let store = MemoryStore::new(); |
||||
load_to_store(&data, &store, None)?; |
||||
Ok((name, store)) |
||||
}) |
||||
.collect::<Result<_>>()?, |
||||
), |
||||
}) |
||||
} |
||||
} |
||||
|
||||
impl ServiceHandler for StaticServiceHandler { |
||||
fn handle<'a>( |
||||
&'a self, |
||||
service_name: &NamedNode, |
||||
graph_pattern: &'a GraphPattern, |
||||
) -> Result<QuerySolutionsIterator<'a>> { |
||||
if let QueryResult::Bindings(iterator) = self |
||||
.services |
||||
.get(service_name) |
||||
.ok_or_else(|| Error::msg(format!("Service {} not found", service_name)))? |
||||
.prepare_query_from_pattern( |
||||
&graph_pattern, |
||||
QueryOptions::default().with_service_handler(self.clone()), |
||||
)? |
||||
.exec()? |
||||
{ |
||||
//TODO: very ugly
|
||||
let (variables, iter) = iterator.destruct(); |
||||
let collected = iter.collect::<Vec<_>>(); |
||||
Ok(QuerySolutionsIterator::new( |
||||
variables, |
||||
Box::new(collected.into_iter()), |
||||
)) |
||||
} else { |
||||
Err(Error::msg("Expected solutions but got another QueryResult")) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn to_dataset(result: QueryResult<'_>, with_order: bool) -> Result<MemoryStore> { |
||||
match result { |
||||
QueryResult::Graph(graph) => graph.map(|t| t.map(|t| t.in_graph(None))).collect(), |
||||
QueryResult::Boolean(value) => { |
||||
let store = MemoryStore::new(); |
||||
let result_set = BlankNode::default(); |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rdf::TYPE.clone(), |
||||
rs::RESULT_SET.clone(), |
||||
None, |
||||
)); |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rs::BOOLEAN.clone(), |
||||
Literal::from(value), |
||||
None, |
||||
)); |
||||
Ok(store) |
||||
} |
||||
QueryResult::Bindings(solutions) => { |
||||
let store = MemoryStore::new(); |
||||
let result_set = BlankNode::default(); |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rdf::TYPE.clone(), |
||||
rs::RESULT_SET.clone(), |
||||
None, |
||||
)); |
||||
for variable in solutions.variables() { |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rs::RESULT_VARIABLE.clone(), |
||||
Literal::new_simple_literal(variable.as_str()), |
||||
None, |
||||
)); |
||||
} |
||||
for (i, solution) in solutions.enumerate() { |
||||
let solution = solution?; |
||||
let solution_id = BlankNode::default(); |
||||
store.insert(Quad::new( |
||||
result_set, |
||||
rs::SOLUTION.clone(), |
||||
solution_id, |
||||
None, |
||||
)); |
||||
for (variable, value) in solution.iter() { |
||||
let binding = BlankNode::default(); |
||||
store.insert(Quad::new(solution_id, rs::BINDING.clone(), binding, None)); |
||||
store.insert(Quad::new(binding, rs::VALUE.clone(), value.clone(), None)); |
||||
store.insert(Quad::new( |
||||
binding, |
||||
rs::VARIABLE.clone(), |
||||
Literal::new_simple_literal(variable.as_str()), |
||||
None, |
||||
)); |
||||
} |
||||
if with_order { |
||||
store.insert(Quad::new( |
||||
solution_id, |
||||
rs::INDEX.clone(), |
||||
Literal::from((i + 1) as i128), |
||||
None, |
||||
)); |
||||
} |
||||
} |
||||
Ok(store) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn are_query_results_isomorphic( |
||||
expected: &StaticQueryResults, |
||||
actual: &StaticQueryResults, |
||||
) -> bool { |
||||
match (expected, actual) { |
||||
( |
||||
StaticQueryResults::Solutions { |
||||
variables: expected_variables, |
||||
solutions: expected_solutions, |
||||
ordered, |
||||
}, |
||||
StaticQueryResults::Solutions { |
||||
variables: actual_variables, |
||||
solutions: actual_solutions, |
||||
.. |
||||
}, |
||||
) => { |
||||
expected_variables == actual_variables |
||||
&& if *ordered { |
||||
expected_solutions.iter().zip(actual_solutions).all( |
||||
|(expected_solution, actual_solution)| { |
||||
compare_solutions(expected_solution, actual_solution) |
||||
}, |
||||
) |
||||
} else { |
||||
expected_solutions.iter().all(|expected_solution| { |
||||
actual_solutions.iter().any(|actual_solution| { |
||||
compare_solutions(expected_solution, actual_solution) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
(StaticQueryResults::Boolean(expected), StaticQueryResults::Boolean(actual)) => { |
||||
expected == actual |
||||
} |
||||
(StaticQueryResults::Graph(expected), StaticQueryResults::Graph(actual)) => { |
||||
expected.is_isomorphic(&actual) |
||||
} |
||||
_ => false, |
||||
} |
||||
} |
||||
|
||||
fn compare_solutions(expected: &[(Variable, Term)], actual: &[(Variable, Term)]) -> bool { |
||||
let mut bnode_map = HashMap::new(); |
||||
expected.iter().zip(actual).all( |
||||
move |((expected_variable, expected_value), (actual_variable, actual_value))| { |
||||
expected_variable == actual_variable |
||||
&& expected_value |
||||
== if let Term::BlankNode(actual_value) = actual_value { |
||||
bnode_map.entry(actual_value).or_insert(expected_value) |
||||
} else { |
||||
actual_value |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
|
||||
enum StaticQueryResults { |
||||
Graph(MemoryStore), |
||||
Solutions { |
||||
variables: Vec<Variable>, |
||||
solutions: Vec<Vec<(Variable, Term)>>, |
||||
ordered: bool, |
||||
}, |
||||
Boolean(bool), |
||||
} |
||||
|
||||
impl fmt::Display for StaticQueryResults { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
match self { |
||||
StaticQueryResults::Graph(g) => g.fmt(f), |
||||
StaticQueryResults::Solutions { |
||||
variables, |
||||
solutions, |
||||
.. |
||||
} => { |
||||
write!(f, "Variables:")?; |
||||
for v in variables { |
||||
write!(f, " {}", v)?; |
||||
} |
||||
for solution in solutions { |
||||
write!(f, "\n{{")?; |
||||
for (k, v) in solution { |
||||
write!(f, "{} = {} ", k, v)?; |
||||
} |
||||
write!(f, "}}")?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
StaticQueryResults::Boolean(b) => b.fmt(f), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl StaticQueryResults { |
||||
fn from_query_results( |
||||
results: QueryResult<'_>, |
||||
with_order: bool, |
||||
) -> Result<StaticQueryResults> { |
||||
Ok(Self::from_dataset(to_dataset(results, with_order)?)) |
||||
} |
||||
|
||||
fn from_dataset(dataset: MemoryStore) -> StaticQueryResults { |
||||
if let Some(result_set) = dataset |
||||
.quads_for_pattern( |
||||
None, |
||||
Some(&rdf::TYPE), |
||||
Some(&rs::RESULT_SET.clone().into()), |
||||
None, |
||||
) |
||||
.map(|q| q.subject_owned()) |
||||
.next() |
||||
{ |
||||
if let Some(bool) = dataset |
||||
.quads_for_pattern(Some(&result_set), Some(&rs::BOOLEAN), None, None) |
||||
.map(|q| q.object_owned()) |
||||
.next() |
||||
{ |
||||
// Boolean query
|
||||
StaticQueryResults::Boolean(bool == Literal::from(true).into()) |
||||
} else { |
||||
// Regular query
|
||||
let mut variables: Vec<Variable> = dataset |
||||
.quads_for_pattern(Some(&result_set), Some(&rs::RESULT_VARIABLE), None, None) |
||||
.filter_map(|q| { |
||||
if let Term::Literal(l) = q.object_owned() { |
||||
Some(Variable::new(l.value())) |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect(); |
||||
variables.sort(); |
||||
|
||||
let mut solutions: Vec<_> = dataset |
||||
.quads_for_pattern(Some(&result_set), Some(&rs::SOLUTION), None, None) |
||||
.filter_map(|q| { |
||||
if let Term::BlankNode(solution) = q.object_owned() { |
||||
let solution = solution.into(); |
||||
let mut bindings = dataset |
||||
.quads_for_pattern(Some(&solution), Some(&rs::BINDING), None, None) |
||||
.filter_map(|q| { |
||||
if let Term::BlankNode(binding) = q.object_owned() { |
||||
let binding = binding.into(); |
||||
if let (Some(Term::Literal(variable)), Some(value)) = ( |
||||
dataset |
||||
.quads_for_pattern( |
||||
Some(&binding), |
||||
Some(&rs::VARIABLE), |
||||
None, |
||||
None, |
||||
) |
||||
.map(|q| q.object_owned()) |
||||
.next(), |
||||
dataset |
||||
.quads_for_pattern( |
||||
Some(&binding), |
||||
Some(&rs::VALUE), |
||||
None, |
||||
None, |
||||
) |
||||
.map(|q| q.object_owned()) |
||||
.next(), |
||||
) { |
||||
Some((Variable::new(variable.value()), value)) |
||||
} else { |
||||
None |
||||
} |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect::<Vec<_>>(); |
||||
bindings.sort_by(|(a, _), (b, _)| a.cmp(&b)); |
||||
let index = dataset |
||||
.quads_for_pattern(Some(&solution), Some(&rs::INDEX), None, None) |
||||
.filter_map(|q| { |
||||
if let Term::Literal(l) = q.object_owned() { |
||||
u64::from_str(l.value()).ok() |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.next(); |
||||
Some((bindings, index)) |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect(); |
||||
solutions.sort_by(|(_, index_a), (_, index_b)| index_a.cmp(index_b)); |
||||
|
||||
let ordered = solutions.iter().all(|(_, index)| index.is_some()); |
||||
|
||||
StaticQueryResults::Solutions { |
||||
variables, |
||||
solutions: solutions |
||||
.into_iter() |
||||
.map(|(solution, _)| solution) |
||||
.collect(), |
||||
ordered, |
||||
} |
||||
} |
||||
} else { |
||||
StaticQueryResults::Graph(dataset) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
pub mod rs { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref RESULT_SET: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet") |
||||
.unwrap(); |
||||
pub static ref RESULT_VARIABLE: NamedNode = NamedNode::parse( |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/result-set#resultVariable" |
||||
) |
||||
.unwrap(); |
||||
pub static ref SOLUTION: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#solution") |
||||
.unwrap(); |
||||
pub static ref BINDING: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#binding") |
||||
.unwrap(); |
||||
pub static ref VALUE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#value") |
||||
.unwrap(); |
||||
pub static ref VARIABLE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#variable") |
||||
.unwrap(); |
||||
pub static ref INDEX: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#index") |
||||
.unwrap(); |
||||
pub static ref BOOLEAN: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#boolean") |
||||
.unwrap(); |
||||
} |
||||
} |
||||
|
||||
pub mod mf { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref INCLUDE: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#include") |
||||
.unwrap(); |
||||
pub static ref ENTRIES: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#entries") |
||||
.unwrap(); |
||||
pub static ref NAME: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#name") |
||||
.unwrap(); |
||||
pub static ref ACTION: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#action") |
||||
.unwrap(); |
||||
pub static ref RESULT: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#result") |
||||
.unwrap(); |
||||
} |
||||
} |
||||
|
||||
pub mod qt { |
||||
use lazy_static::lazy_static; |
||||
use oxigraph::model::NamedNode; |
||||
|
||||
lazy_static! { |
||||
pub static ref QUERY: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#query") |
||||
.unwrap(); |
||||
pub static ref DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#data").unwrap(); |
||||
pub static ref GRAPH_DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#graphData") |
||||
.unwrap(); |
||||
pub static ref SERVICE_DATA: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#serviceData") |
||||
.unwrap(); |
||||
pub static ref ENDPOINT: NamedNode = |
||||
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#endpoint") |
||||
.unwrap(); |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
use oxigraph::Result; |
||||
use oxigraph_testsuite::manifest::TestManifest; |
||||
use oxigraph_testsuite::sparql_evaluator::evaluate_sparql_tests; |
||||
|
||||
fn run_testsuite(manifest_urls: Vec<&str>) -> Result<()> { |
||||
let manifest = TestManifest::new(manifest_urls); |
||||
let results = evaluate_sparql_tests(manifest)?; |
||||
|
||||
let mut errors = Vec::default(); |
||||
for result in results { |
||||
if let Err(error) = &result.outcome { |
||||
errors.push(format!("{}: failed with error {}", result.test, error)) |
||||
} |
||||
} |
||||
|
||||
assert!(errors.is_empty(), "\n{}\n", errors.join("\n")); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn oxigraph_sparql_testsuite() -> Result<()> { |
||||
run_testsuite(vec![ |
||||
"https://github.com/oxigraph/oxigraph/tests/sparql/manifest.ttl", |
||||
]) |
||||
} |
@ -0,0 +1,43 @@ |
||||
use oxigraph::Result; |
||||
use oxigraph_testsuite::manifest::TestManifest; |
||||
use oxigraph_testsuite::parser_evaluator::evaluate_parser_tests; |
||||
|
||||
fn run_testsuite(manifest_url: &str) -> Result<()> { |
||||
let manifest = TestManifest::new(vec![manifest_url]); |
||||
let results = evaluate_parser_tests(manifest)?; |
||||
|
||||
let mut errors = Vec::default(); |
||||
for result in results { |
||||
if let Err(error) = &result.outcome { |
||||
errors.push(format!("{}: failed with error {}", result.test, error)) |
||||
} |
||||
} |
||||
|
||||
assert!(errors.is_empty(), "\n{}\n", errors.join("\n")); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn ntriples_w3c_testsuite() -> Result<()> { |
||||
run_testsuite("http://w3c.github.io/rdf-tests/ntriples/manifest.ttl") |
||||
} |
||||
|
||||
#[test] |
||||
fn nquads_w3c_testsuite() -> Result<()> { |
||||
run_testsuite("http://w3c.github.io/rdf-tests/nquads/manifest.ttl") |
||||
} |
||||
|
||||
#[test] |
||||
fn turtle_w3c_testsuite() -> Result<()> { |
||||
run_testsuite("http://w3c.github.io/rdf-tests/turtle/manifest.ttl") |
||||
} |
||||
|
||||
#[test] |
||||
fn trig_w3c_testsuite() -> Result<()> { |
||||
run_testsuite("http://w3c.github.io/rdf-tests/trig/manifest.ttl") |
||||
} |
||||
|
||||
#[test] |
||||
fn rdf_xml_w3c_testsuite() -> Result<()> { |
||||
run_testsuite("http://www.w3.org/2013/RDFXMLTests/manifest.ttl") |
||||
} |
@ -0,0 +1,125 @@ |
||||
use oxigraph::Result; |
||||
use oxigraph_testsuite::manifest::TestManifest; |
||||
use oxigraph_testsuite::sparql_evaluator::evaluate_sparql_tests; |
||||
|
||||
fn run_testsuite(manifest_urls: Vec<&str>, ignored_tests: Vec<&str>) -> Result<()> { |
||||
let manifest = TestManifest::new(manifest_urls); |
||||
let results = evaluate_sparql_tests(manifest)?; |
||||
|
||||
let mut errors = Vec::default(); |
||||
for result in results { |
||||
if let Err(error) = &result.outcome { |
||||
if !ignored_tests.contains(&result.test.as_str()) { |
||||
errors.push(format!("{}: failed with error {}", result.test, error)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
assert!(errors.is_empty(), "\n{}\n", errors.join("\n")); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn sparql10_w3c_query_evaluation_testsuite() -> Result<()> { |
||||
let manifest_urls = vec![ |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/manifest-syntax.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/algebra/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/ask/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/basic/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/bnode-coreference/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/boolean-effective-value/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/bound/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/cast/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest.ttl", |
||||
//TODO FROM and FROM NAMED "http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest.ttl",
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-equals/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-ops/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/graph/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/i18n/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/optional/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/optional-filter/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/reduced/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/regex/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/solution-seq/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/sort/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/triple-match/manifest.ttl", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/type-promotion/manifest.ttl", |
||||
]; |
||||
|
||||
let test_blacklist = vec![ |
||||
//Bad SPARQL query that should be rejected by the parser
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/syntax-sparql4/manifest#syn-bad-38", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/syntax-sparql4/manifest#syn-bad-34", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/syntax-sparql3/manifest#syn-bad-26", |
||||
|
||||
//Multiple writing of the same xsd:integer. Our system does strong normalization.
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#distinct-1", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#distinct-9", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#dawg-str-1", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#dawg-str-2", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-equals/manifest#eq-graph-1", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-equals/manifest#eq-graph-2", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-01", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-04", |
||||
//Multiple writing of the same xsd:double. Our system does strong normalization.
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#sameTerm", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#sameTerm-simple", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#sameTerm-eq", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#sameTerm-not-eq", |
||||
//Simple literal vs xsd:string. We apply RDF 1.1
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#distinct-2", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-08", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-10", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-11", |
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#open-eq-12", |
||||
//DATATYPE("foo"@en) returns rdf:langString in RDF 1.1
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/expr-builtin/manifest#dawg-datatype-2", |
||||
// We use XSD 1.1 equality on dates
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/open-world/manifest#date-2", |
||||
// We choose to simplify first the nested group patterns in OPTIONAL
|
||||
"http://www.w3.org/2001/sw/DataAccess/tests/data-r2/optional-filter/manifest#dawg-optional-filter-005-not-simplified" |
||||
]; |
||||
|
||||
run_testsuite(manifest_urls, test_blacklist) |
||||
} |
||||
|
||||
#[test] |
||||
fn sparql11_query_w3c_evaluation_testsuite() -> Result<()> { |
||||
let manifest_urls = |
||||
vec!["http://www.w3.org/2009/sparql/docs/tests/data-sparql11/manifest-sparql11-query.ttl"]; |
||||
|
||||
let test_blacklist = vec![ |
||||
//Bad SPARQL query that should be rejected by the parser
|
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg08", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg09", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg10", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg11", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg12", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest#group07", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest#group06", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest#group07", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_43", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_44", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_45", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_60", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_61a", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_62a", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_65", |
||||
// SPARQL 1.1 JSON query results deserialization is not implemented yet
|
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg-empty-group-count-1", |
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg-empty-group-count-2", |
||||
// FROM support
|
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/construct/manifest#constructwhere04", |
||||
//BNODE() scope is currently wrong
|
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/functions/manifest#bnode01", |
||||
//Property path with unbound graph name are not supported yet
|
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/property-path/manifest#pp35", |
||||
//SERVICE name from a BGP
|
||||
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/service/manifest#service5" |
||||
]; |
||||
|
||||
run_testsuite(manifest_urls, test_blacklist) |
||||
} |
Loading…
Reference in new issue