use oxrdf::{Graph, NamedNodeRef, Term};
use std::path::{Path, PathBuf};
const SH_CONFORMS: NamedNodeRef =
NamedNodeRef::new_unchecked("http://www.w3.org/ns/shacl#conforms");
const UNSUPPORTED_PREDICATES: &[&str] = &[
"http://www.w3.org/ns/shacl#sparql",
"http://www.w3.org/ns/shacl#rule",
"http://www.w3.org/ns/shacl#target",
"http://www.w3.org/ns/shacl#parameter",
"http://www.w3.org/ns/shacl#validator",
"http://www.w3.org/ns/shacl#nodeValidator",
"http://www.w3.org/ns/shacl#propertyValidator",
"http://www.w3.org/ns/shacl#js",
"http://www.w3.org/ns/shacl#jsLibrary",
"http://www.w3.org/ns/shacl#jsFunctionName",
"http://www.w3.org/ns/shacl#declare",
];
fn suite_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../testdata/test-suite/core")
.canonicalize()
.expect("core suite present")
}
fn collect_tests(dir: &Path, out: &mut Vec<PathBuf>) {
for entry in std::fs::read_dir(dir).unwrap().flatten() {
let path = entry.path();
if path.is_dir() {
collect_tests(&path, out);
} else if path.extension().is_some_and(|e| e == "ttl")
&& path.file_name().is_some_and(|n| n != "manifest.ttl")
{
out.push(path);
}
}
}
fn expected_conforms(g: &Graph) -> Option<bool> {
for t in g.triples_for_predicate(SH_CONFORMS) {
if let Term::Literal(l) = t.object.into_owned() {
match l.value() {
"true" => return Some(true),
"false" => return Some(false),
_ => {}
}
}
}
None
}
fn uses_unsupported(g: &Graph) -> bool {
g.iter()
.any(|t| UNSUPPORTED_PREDICATES.contains(&t.predicate.as_str()))
}
#[test]
fn w3c_core_conformance() {
let mut files = Vec::new();
collect_tests(&suite_dir(), &mut files);
files.sort();
assert!(!files.is_empty(), "no test files found");
let (mut passed, mut failed, mut skipped) = (0u32, 0u32, 0u32);
let mut failures: Vec<String> = Vec::new();
for file in &files {
let bytes = std::fs::read(file).unwrap();
let Ok(loaded) = shifty_parse::load_turtle(&bytes, None) else {
skipped += 1;
continue;
};
let Some(expected) = expected_conforms(&loaded.graph) else {
skipped += 1;
continue;
};
if uses_unsupported(&loaded.graph) {
skipped += 1;
continue;
}
let parsed = match shifty_parse::parse_turtle(&bytes, None) {
Ok(p) if p.diagnostics.is_empty() => p,
_ => {
skipped += 1;
continue;
}
};
let outcome = match shifty_engine::validate(&loaded.graph, &parsed.schema) {
Ok(o) => o,
Err(_) => {
skipped += 1;
continue;
}
};
let name = file.strip_prefix(suite_dir()).unwrap_or(file);
let normalized = shifty_opt::normalize(&parsed.schema);
let norm_conforms = shifty_engine::validate(&loaded.graph, &normalized)
.expect("normalized schema stays stratifiable")
.conforms;
assert_eq!(
norm_conforms,
outcome.conforms,
"normalize changed conformance for {}",
name.display()
);
let physical = shifty_opt::plan(&normalized);
let plan_conforms = shifty_engine::validate_plan(&loaded.graph, &physical)
.expect("planned schema stays stratifiable")
.conforms;
assert_eq!(
plan_conforms,
outcome.conforms,
"plan changed conformance for {}",
name.display()
);
if outcome.conforms == expected {
passed += 1;
} else {
failed += 1;
failures.push(format!(
"{}: expected conforms={expected}, got {}",
name.display(),
outcome.conforms
));
}
}
eprintln!(
"\nW3C core conformance: {passed} passed, {failed} failed, {skipped} skipped (of {} files)",
files.len()
);
for f in failures.iter().take(40) {
eprintln!(" FAIL {f}");
}
assert_eq!(failed, 0, "supported tests regressed: {failures:#?}");
assert!(passed >= 80, "coverage dropped: only {passed} passed");
}