Improves error handling code in testsuite and server

pull/334/head
Tpt 2 years ago committed by Thomas Tanon
parent 1ded5ac4b4
commit 7fdd045516
  1. 39
      server/src/main.rs
  2. 33
      testsuite/src/files.rs
  3. 12
      testsuite/src/manifest.rs
  4. 54
      testsuite/src/parser_evaluator.rs
  5. 272
      testsuite/src/sparql_evaluator.rs

@ -1,4 +1,4 @@
use anyhow::{anyhow, bail};
use anyhow::bail;
use clap::{Parser, Subcommand};
use flate2::read::MultiGzDecoder;
use oxhttp::model::{Body, HeaderName, HeaderValue, Request, Response, Status};
@ -189,30 +189,31 @@ impl GraphOrDatasetFormat {
}
fn from_extension(name: &str) -> anyhow::Result<Self> {
match (GraphFormat::from_extension(name), DatasetFormat::from_extension(name)) {
(Some(g), Some(d)) => Err(anyhow!("The file extension '{}' can be resolved to both '{}' and '{}', not sure what to pick", name, g.file_extension(), d.file_extension())),
(Some(g), None) => Ok(GraphOrDatasetFormat::Graph(g)),
(None, Some(d)) => Ok(GraphOrDatasetFormat::Dataset(d)),
Ok( match (GraphFormat::from_extension(name), DatasetFormat::from_extension(name)) {
(Some(g), Some(d)) => bail!("The file extension '{name}' can be resolved to both '{}' and '{}', not sure what to pick", g.file_extension(), d.file_extension()),
(Some(g), None) => GraphOrDatasetFormat::Graph(g),
(None, Some(d)) => GraphOrDatasetFormat::Dataset(d),
(None, None) =>
Err(anyhow!("The file extension '{}' is unknown", name))
}
bail!("The file extension '{name}' is unknown")
})
}
fn from_media_type(name: &str) -> anyhow::Result<Self> {
match (
GraphFormat::from_media_type(name),
DatasetFormat::from_media_type(name),
) {
(Some(g), Some(d)) => Err(anyhow!(
"The media type '{}' can be resolved to both '{}' and '{}', not sure what to pick",
name,
Ok(
match (
GraphFormat::from_media_type(name),
DatasetFormat::from_media_type(name),
) {
(Some(g), Some(d)) => bail!(
"The media type '{name}' can be resolved to both '{}' and '{}', not sure what to pick",
g.file_extension(),
d.file_extension()
)),
(Some(g), None) => Ok(GraphOrDatasetFormat::Graph(g)),
(None, Some(d)) => Ok(GraphOrDatasetFormat::Dataset(d)),
(None, None) => Err(anyhow!("The media type '{}' is unknown", name)),
}
),
(Some(g), None) => GraphOrDatasetFormat::Graph(g),
(None, Some(d)) => GraphOrDatasetFormat::Dataset(d),
(None, None) => bail!("The media type '{name}' is unknown"),
},
)
}
}

@ -1,4 +1,4 @@
use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Result};
use oxigraph::io::{DatasetFormat, DatasetParser, GraphFormat, GraphParser};
use oxigraph::model::{Dataset, Graph, GraphNameRef};
use oxigraph::store::Store;
@ -9,29 +9,29 @@ 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/"))
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/"))
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(
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(
url.replace(
"http://www.w3.org/2009/sparql/docs/tests/",
"rdf-tests/sparql11/",
))
)
} else if url.starts_with("https://w3c.github.io/rdf-star/") {
Ok(url.replace("https://w3c.github.io/", ""))
url.replace("https://w3c.github.io/", "")
} else if url.starts_with("https://github.com/oxigraph/oxigraph/tests/") {
Ok(url.replace(
url.replace(
"https://github.com/oxigraph/oxigraph/tests/",
"oxigraph-tests/",
))
)
} else {
Err(anyhow!("Not supported url for file: {}", url))
}?);
bail!("Not supported url for file: {url}")
});
Ok(BufReader::new(File::open(&path)?))
}
@ -72,7 +72,7 @@ pub fn load_to_store<'a>(
} else if url.ends_with(".trig") {
store.load_dataset(read_file(url)?, DatasetFormat::TriG, Some(url))?
} else {
return Err(anyhow!("Serialization type not found for {}", url));
bail!("Serialization type not found for {url}");
}
Ok(())
}
@ -81,7 +81,7 @@ pub fn load_to_graph(url: &str, graph: &mut Graph) -> Result<()> {
let format = url
.rsplit_once('.')
.and_then(|(_, extension)| GraphFormat::from_extension(extension))
.ok_or_else(|| anyhow!("Serialization type not found for {}", url))?;
.ok_or_else(|| anyhow!("Serialization type not found for {url}"))?;
let parser = GraphParser::from_format(format).with_base_iri(url)?;
for t in parser.read_triples(read_file(url)?)? {
graph.insert(&t?);
@ -107,16 +107,15 @@ pub fn load_to_dataset<'a>(
for t in parser.read_triples(read_file(url)?)? {
dataset.insert(&t?.in_graph(to_graph_name));
}
Ok(())
} else if let Some(format) = extension.and_then(DatasetFormat::from_extension) {
let parser = DatasetParser::from_format(format).with_base_iri(url)?;
for q in parser.read_quads(read_file(url)?)? {
dataset.insert(&q?);
}
Ok(())
} else {
Err(anyhow!("Serialization type not found for {}", url))
bail!("Serialization type not found for {url}")
}
Ok(())
}
pub fn load_dataset(url: &str) -> Result<Dataset> {

@ -89,7 +89,7 @@ impl TestManifest {
let test_node = match test_node {
Term::NamedNode(n) => n,
_ => {
return Some(Err(anyhow!("Invalid test identifier. Got {}", test_node)));
return Some(Err(anyhow!("Invalid test identifier. Got {test_node}")));
}
};
@ -107,8 +107,7 @@ impl TestManifest {
Some(TermRef::NamedNode(c)) => c.into_owned(),
_ => {
return Some(Err(anyhow!(
"The test {} named {} has no rdf:type",
test_node,
"The test {test_node} named {} has no rdf:type",
name.as_deref().unwrap_or("")
)));
}
@ -202,7 +201,7 @@ impl TestManifest {
}
Some(_) => return Some(Err(anyhow!("invalid action"))),
None => {
return Some(Err(anyhow!("action not found for test {}", test_node)));
return Some(Err(anyhow!("action not found for test {test_node}")));
}
};
let (result, result_graph_data) = match self
@ -276,8 +275,7 @@ impl TestManifest {
.collect::<Vec<_>>();
if manifests.len() != 1 {
return Some(Err(anyhow!(
"The file {} should contain a single manifest",
url
"The file {url} should contain a single manifest"
)));
}
for manifest in manifests {
@ -307,7 +305,7 @@ impl TestManifest {
.extend(RdfListIterator::iter(&self.graph, list.into()));
}
Some(term) => {
return Some(Err(anyhow!("Invalid tests list. Got term {}", term)));
return Some(Err(anyhow!("Invalid tests list. Got term {term}")));
}
None => (),
}

@ -2,7 +2,7 @@ use crate::evaluator::TestEvaluator;
use crate::files::load_dataset;
use crate::manifest::Test;
use crate::report::dataset_diff;
use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Result};
pub fn register_parser_tests(evaluator: &mut TestEvaluator) {
evaluator.register(
@ -67,20 +67,18 @@ fn evaluate_positive_syntax_test(test: &Test) -> Result<()> {
let action = test
.action
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
match load_dataset(action) {
Ok(_) => Ok(()),
Err(e) => Err(anyhow!(format!("Parse error: {e}"))),
}
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
load_dataset(action).map_err(|e| anyhow!("Parse error: {e}"))?;
Ok(())
}
fn evaluate_negative_syntax_test(test: &Test) -> Result<()> {
let action = test
.action
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
match load_dataset(action) {
Ok(_) => Err(anyhow!("File parsed with an error even if it should not",)),
Ok(_) => bail!("File parsed with an error even if it should not"),
Err(_) => Ok(()),
}
}
@ -89,29 +87,23 @@ fn evaluate_eval_test(test: &Test) -> Result<()> {
let action = test
.action
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
match load_dataset(action) {
Ok(mut actual_graph) => {
actual_graph.canonicalize();
if let Some(result) = &test.result {
match load_dataset(result) {
Ok(mut expected_graph) => {
expected_graph.canonicalize();
if expected_graph == actual_graph {
Ok(())
} else {
Err(anyhow!(
"The two files are not isomorphic. Diff:\n{}",
dataset_diff(&expected_graph, &actual_graph)
))
}
}
Err(e) => Err(anyhow!("Parse error on file {}: {}", action, e)),
}
} else {
Err(anyhow!("No tests result found"))
}
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
let mut actual_graph =
load_dataset(action).map_err(|e| anyhow!("Parse error on file {action}: {e}"))?;
actual_graph.canonicalize();
if let Some(result) = &test.result {
let mut expected_graph =
load_dataset(result).map_err(|e| anyhow!("Parse error on file {action}: {e}"))?;
expected_graph.canonicalize();
if expected_graph == actual_graph {
Ok(())
} else {
bail!(
"The two files are not isomorphic. Diff:\n{}",
dataset_diff(&expected_graph, &actual_graph)
)
}
Err(e) => Err(anyhow!("Parse error on file {}: {}", action, e)),
} else {
bail!("No tests result found")
}
}

@ -3,7 +3,7 @@ use crate::files::*;
use crate::manifest::*;
use crate::report::dataset_diff;
use crate::vocab::*;
use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Result};
use oxigraph::model::vocab::*;
use oxigraph::model::*;
use oxigraph::sparql::*;
@ -73,32 +73,23 @@ fn evaluate_positive_syntax_test(test: &Test) -> Result<()> {
let query_file = test
.action
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
match Query::parse(&read_file_to_string(query_file)?, Some(query_file)) {
Err(error) => Err(anyhow!("Not able to parse {} with error: {}", test, error)),
Ok(query) => match Query::parse(&query.to_string(), None) {
Ok(_) => Ok(()),
Err(error) => Err(anyhow!(
"Failure to deserialize \"{}\" of {} with error: {}",
query.to_string(),
test,
error
)),
},
}
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
let query = Query::parse(&read_file_to_string(query_file)?, Some(query_file))
.map_err(|e| anyhow!("Not able to parse {test} with error: {e}"))?;
Query::parse(&query.to_string(), None)
.map_err(|e| anyhow!("Failure to deserialize \"{query}\" of {test} with error: {e}"))?;
Ok(())
}
fn evaluate_negative_syntax_test(test: &Test) -> Result<()> {
let query_file = test
.action
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
match Query::parse(&read_file_to_string(query_file)?, Some(query_file)) {
Ok(result) => Err(anyhow!(
"Oxigraph parses even if it should not {}. The output tree is: {}",
test,
result
)),
Ok(result) => {
bail!("Oxigraph parses even if it should not {test}. The output tree is: {result}")
}
Err(_) => Ok(()),
}
}
@ -109,7 +100,7 @@ fn evaluate_positive_json_result_syntax_test(test: &Test) -> Result<()> {
fn evaluate_negative_json_result_syntax_test(test: &Test) -> Result<()> {
if result_syntax_check(test, QueryResultsFormat::Json).is_ok() {
Err(anyhow!("Oxigraph parses even if it should not {}.", test))
bail!("Oxigraph parses even if it should not {test}.")
} else {
Ok(())
}
@ -121,7 +112,7 @@ fn evaluate_positive_xml_result_syntax_test(test: &Test) -> Result<()> {
fn evaluate_negative_xml_result_syntax_test(test: &Test) -> Result<()> {
if result_syntax_check(test, QueryResultsFormat::Xml).is_ok() {
Err(anyhow!("Oxigraph parses even if it should not {}.", test))
bail!("Oxigraph parses even if it should not {test}.")
} else {
Ok(())
}
@ -129,7 +120,7 @@ fn evaluate_negative_xml_result_syntax_test(test: &Test) -> Result<()> {
fn evaluate_negative_tsv_result_syntax_test(test: &Test) -> Result<()> {
if result_syntax_check(test, QueryResultsFormat::Tsv).is_ok() {
Err(anyhow!("Oxigraph parses even if it should not {}.", test))
bail!("Oxigraph parses even if it should not {test}.")
} else {
Ok(())
}
@ -139,7 +130,7 @@ fn result_syntax_check(test: &Test, format: QueryResultsFormat) -> Result<()> {
let results_file = test
.action
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
match QueryResults::read(Cursor::new(read_file_to_string(results_file)?), format)? {
QueryResults::Solutions(solutions) => {
for s in solutions {
@ -167,125 +158,83 @@ fn evaluate_evaluation_test(test: &Test) -> Result<()> {
let query_file = test
.query
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
let options = QueryOptions::default()
.with_service_handler(StaticServiceHandler::new(&test.service_data)?);
match Query::parse(&read_file_to_string(query_file)?, Some(query_file)) {
Err(error) => Err(anyhow!(
"Failure to parse query of {} with error: {}",
test,
error
)),
Ok(query) => {
// We check parsing roundtrip
if let Err(error) = Query::parse(&query.to_string(), None) {
return Err(anyhow!(
"Failure to deserialize \"{}\" of {} with error: {}",
query.to_string(),
test,
error
));
let query = Query::parse(&read_file_to_string(query_file)?, Some(query_file))
.map_err(|e| anyhow!("Failure to parse query of {test} with error: {e}"))?;
// We check parsing roundtrip
Query::parse(&query.to_string(), None)
.map_err(|e| anyhow!("Failure to deserialize \"{query}\" of {test} with error: {e}"))?;
// FROM and FROM NAMED support. We make sure the data is in the store
if !query.dataset().is_default_dataset() {
for graph_name in query.dataset().default_graph_graphs().unwrap_or(&[]) {
if let GraphName::NamedNode(graph_name) = graph_name {
load_to_store(graph_name.as_str(), &store, graph_name.as_ref())?;
} else {
bail!("Invalid FROM in query {query} for test {test}");
}
// FROM and FROM NAMED support. We make sure the data is in the store
if !query.dataset().is_default_dataset() {
for graph_name in query.dataset().default_graph_graphs().unwrap_or(&[]) {
if let GraphName::NamedNode(graph_name) = graph_name {
load_to_store(graph_name.as_str(), &store, graph_name.as_ref())?;
} else {
return Err(anyhow!("Invalid FROM in query {} for test {}", query, test));
}
}
for graph_name in query.dataset().available_named_graphs().unwrap_or(&[]) {
if let NamedOrBlankNode::NamedNode(graph_name) = graph_name {
load_to_store(graph_name.as_str(), &store, graph_name.as_ref())?;
} else {
return Err(anyhow!(
"Invalid FROM NAMED in query {} for test {}",
query,
test
));
}
}
}
for graph_name in query.dataset().available_named_graphs().unwrap_or(&[]) {
if let NamedOrBlankNode::NamedNode(graph_name) = graph_name {
load_to_store(graph_name.as_str(), &store, graph_name.as_ref())?;
} else {
bail!("Invalid FROM NAMED in query {query} for test {test}");
}
}
}
for with_query_optimizer in [true, false] {
let mut options = options.clone();
if !with_query_optimizer {
options = options.without_optimizations();
}
match store.query_opt(query.clone(), options) {
Err(error) => {
return Err(anyhow!(
"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| {
anyhow!("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) {
return Err(anyhow!("Failure on {}.\nExpected file:\n{}\nOutput file:\n{}\nParsed query:\n{}\nData:\n{}\n",
test,
expected_results,
actual_results,
Query::parse(&read_file_to_string(query_file)?, Some(query_file)).unwrap(),
store
));
}
}
}
}
Ok(())
let expected_results = load_sparql_query_result(test.result.as_ref().unwrap())
.map_err(|e| anyhow!("Error constructing expected graph for {test}: {e}"))?;
let with_order = if let StaticQueryResults::Solutions { ordered, .. } = &expected_results {
*ordered
} else {
false
};
for with_query_optimizer in [true, false] {
let mut options = options.clone();
if !with_query_optimizer {
options = options.without_optimizations();
}
let actual_results = store
.query_opt(query.clone(), options)
.map_err(|e| anyhow!("Failure to execute query of {test} with error: {e}"))?;
let actual_results = StaticQueryResults::from_query_results(actual_results, with_order)?;
if !are_query_results_isomorphic(&expected_results, &actual_results) {
bail!(
"Failure on {test}.\nExpected file:\n{expected_results}\nOutput file:\n{actual_results}\nParsed query:\n{}\nData:\n{store}\n",
Query::parse(&read_file_to_string(query_file)?, Some(query_file)).unwrap()
);
}
}
Ok(())
}
fn evaluate_positive_update_syntax_test(test: &Test) -> Result<()> {
let update_file = test
.action
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
match Update::parse(&read_file_to_string(update_file)?, Some(update_file)) {
Err(error) => Err(anyhow!("Not able to parse {} with error: {}", test, error)),
Ok(update) => match Update::parse(&update.to_string(), None) {
Ok(_) => Ok(()),
Err(error) => Err(anyhow!(
"Failure to deserialize \"{}\" of {} with error: {}",
update.to_string(),
test,
error
)),
},
}
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
let update = Update::parse(&read_file_to_string(update_file)?, Some(update_file))
.map_err(|e| anyhow!("Not able to parse {test} with error: {e}"))?;
Update::parse(&update.to_string(), None)
.map_err(|e| anyhow!("Failure to deserialize \"{update}\" of {test} with error: {e}"))?;
Ok(())
}
fn evaluate_negative_update_syntax_test(test: &Test) -> Result<()> {
let update_file = test
.action
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
match Update::parse(&read_file_to_string(update_file)?, Some(update_file)) {
Ok(result) => Err(anyhow!(
"Oxigraph parses even if it should not {}. The output tree is: {}",
test,
result
)),
Ok(result) => {
bail!("Oxigraph parses even if it should not {test}. The output tree is: {result}")
}
Err(_) => Ok(()),
}
}
@ -310,50 +259,29 @@ fn evaluate_update_evaluation_test(test: &Test) -> Result<()> {
let update_file = test
.update
.as_deref()
.ok_or_else(|| anyhow!("No action found for test {}", test))?;
match Update::parse(&read_file_to_string(update_file)?, Some(update_file)) {
Err(error) => Err(anyhow!(
"Failure to parse update of {} with error: {}",
test,
error
)),
Ok(update) => {
// We check parsing roundtrip
if let Err(error) = Update::parse(&update.to_string(), None) {
return Err(anyhow!(
"Failure to deserialize \"{}\" of {} with error: {}",
update.to_string(),
test,
error
));
}
match store.update(update) {
Err(error) => Err(anyhow!(
"Failure to execute update of {} with error: {}",
test,
error
)),
Ok(()) => {
let mut store_dataset: Dataset = store.iter().collect::<Result<_, _>>()?;
store_dataset.canonicalize();
let mut result_store_dataset: Dataset =
result_store.iter().collect::<Result<_, _>>()?;
result_store_dataset.canonicalize();
if store_dataset == result_store_dataset {
Ok(())
} else {
Err(anyhow!(
"Failure on {}.\nDiff:\n{}\nParsed update:\n{}\n",
test,
dataset_diff(&result_store_dataset, &store_dataset),
Update::parse(&read_file_to_string(update_file)?, Some(update_file))
.unwrap(),
))
}
}
}
}
.ok_or_else(|| anyhow!("No action found for test {test}"))?;
let update = Update::parse(&read_file_to_string(update_file)?, Some(update_file))
.map_err(|e| anyhow!("Failure to parse update of {test} with error: {e}"))?;
// We check parsing roundtrip
Update::parse(&update.to_string(), None)
.map_err(|e| anyhow!("Failure to deserialize \"{update}\" of {test} with error: {e}"))?;
store
.update(update)
.map_err(|e| anyhow!("Failure to execute update of {test} with error: {e}"))?;
let mut store_dataset: Dataset = store.iter().collect::<Result<_, _>>()?;
store_dataset.canonicalize();
let mut result_store_dataset: Dataset = result_store.iter().collect::<Result<_, _>>()?;
result_store_dataset.canonicalize();
if store_dataset == result_store_dataset {
Ok(())
} else {
bail!(
"Failure on {test}.\nDiff:\n{}\nParsed update:\n{}\n",
dataset_diff(&result_store_dataset, &store_dataset),
Update::parse(&read_file_to_string(update_file)?, Some(update_file)).unwrap(),
)
}
}
@ -624,7 +552,7 @@ impl StaticQueryResults {
if let TermRef::Literal(l) = object {
Ok(Variable::new_unchecked(l.value()))
} else {
Err(anyhow!("Invalid rs:resultVariable: {}", object))
bail!("Invalid rs:resultVariable: {object}")
}
})
.collect::<Result<Vec<_>>>()?;
@ -650,10 +578,10 @@ impl StaticQueryResults {
value.into_owned(),
))
} else {
Err(anyhow!("Invalid rs:binding: {}", binding))
bail!("Invalid rs:binding: {binding}")
}
} else {
Err(anyhow!("Invalid rs:binding: {}", object))
bail!("Invalid rs:binding: {object}")
}
})
.collect::<Result<Vec<_>>>()?;
@ -664,13 +592,13 @@ impl StaticQueryResults {
if let TermRef::Literal(l) = object {
Ok(u64::from_str(l.value())?)
} else {
Err(anyhow!("Invalid rs:index: {}", object))
bail!("Invalid rs:index: {object}")
}
})
.transpose()?;
Ok((bindings, index))
} else {
Err(anyhow!("Invalid rs:solution: {}", object))
bail!("Invalid rs:solution: {object}")
}
})
.collect::<Result<Vec<_>>>()?;

Loading…
Cancel
Save