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 } } |
SELECT (GROUP_CONCAT(?opt) AS ?g) WHERE { ?baseS a :ex OPTIONAL { ?baseS :opt ?opt } } |
@ -1,4 +1,4 @@ |
|||||||
@prefix : <http://www.example.org/> . |
@prefix : <http://www.example.org/> . |
||||||
|
|
||||||
:a a :ex ; :s :opt "value" . |
:a a :ex ; :opt "value" . |
||||||
:b a :ex . |
: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