aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/meson.build18
-rwxr-xr-xtest/run_test_suite.py86
-rwxr-xr-xtest/run_validation_test_suite.py268
-rw-r--r--test/serd_test_util/__init__.py63
-rw-r--r--test/validate/bad-all-values-from.ttl18
-rw-r--r--test/validate/bad-cardinality-high.ttl18
-rw-r--r--test/validate/bad-cardinality-low.ttl16
-rw-r--r--test/validate/bad-cardinality.ttl18
-rw-r--r--test/validate/bad-datatype-property.ttl18
-rw-r--r--test/validate/bad-domain.ttl19
-rw-r--r--test/validate/bad-functional-property.ttl13
-rw-r--r--test/validate/bad-inverse-functional-property.ttl15
-rw-r--r--test/validate/bad-literal-pattern.ttl7
-rw-r--r--test/validate/bad-literal-value-high-exclusive.ttl20
-rw-r--r--test/validate/bad-literal-value-high-inclusive.ttl20
-rw-r--r--test/validate/bad-literal-value-low-exclusive.ttl20
-rw-r--r--test/validate/bad-literal-value-low-inclusive.ttl21
-rw-r--r--test/validate/bad-object-property.ttl12
-rw-r--r--test/validate/bad-pattern.ttl23
-rw-r--r--test/validate/bad-plain-literal.ttl12
-rw-r--r--test/validate/bad-range-instance-not-literal.ttl18
-rw-r--r--test/validate/bad-range-instance.ttl21
-rw-r--r--test/validate/bad-range-literal-not-instance.ttl15
-rw-r--r--test/validate/bad-range-literal.ttl27
-rw-r--r--test/validate/bad-some-values-from.ttl18
-rw-r--r--test/validate/bad-string-literal-value-high.ttl21
-rw-r--r--test/validate/bad-string-literal-value-low.ttl21
-rw-r--r--test/validate/bad-unknown-datatype.ttl7
-rw-r--r--test/validate/bad-unknown-property.ttl6
-rw-r--r--test/validate/good-cardinality.ttl17
-rw-r--r--test/validate/good-literal-value-high-inclusive.ttl21
-rw-r--r--test/validate/good-literal-value-low-inclusive.ttl21
-rw-r--r--test/validate/good-pattern.ttl22
-rw-r--r--test/validate/good-some-values-from.ttl17
-rw-r--r--test/validate/good-string-literal-value-low.ttl21
-rw-r--r--test/validate/manifest.ttl205
36 files changed, 1118 insertions, 65 deletions
diff --git a/test/meson.build b/test/meson.build
index 84045225..87b77334 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,5 +1,6 @@
autoship = find_program('autoship', required: false)
run_test_suite = find_program('run_test_suite.py')
+run_validation_test_suite = find_program('run_validation_test_suite.py')
wrapper = meson.get_cross_property('wrapper', '')
unit_tests = [
@@ -158,6 +159,8 @@ if get_option('utils')
timeout: 240)
endforeach
+ manifest = files('terse' / 'manifest.ttl')
+ base_uri = serd_base + 'terse' + '/'
test('terse', run_test_suite,
args: script_args + ['--osyntax', 'turtle', manifest, base_uri, '--', '-t'],
suite: ['rdf', 'serd'],
@@ -178,6 +181,21 @@ if get_option('utils')
suite: ['rdf', 'serd'],
timeout: 240)
+ # Validation test suite
+ manifest = files('validate' / 'manifest.ttl')
+ base_uri = serd_base + 'validate' + '/'
+ test('validate', run_validation_test_suite,
+ args: script_args + [
+ manifest,
+ base_uri,
+ meson.current_source_dir() / '../schemas/owl.ttl',
+ meson.current_source_dir() / '../schemas/rdf.ttl',
+ meson.current_source_dir() / '../schemas/rdfs.ttl',
+ meson.current_source_dir() / '../schemas/xsd.ttl',
+ ],
+ suite: ['rdf', 'serd'],
+ timeout: 240)
+
## Standard W3C test suites
w3c_suites = ['Turtle', 'NTriples', 'NQuads', 'TriG']
diff --git a/test/run_test_suite.py b/test/run_test_suite.py
index 05dc81ca..9af3df9e 100755
--- a/test/run_test_suite.py
+++ b/test/run_test_suite.py
@@ -2,6 +2,8 @@
"""Run an RDF test suite with serdi."""
+import serd_test_util
+
import argparse
import datetime
import difflib
@@ -15,31 +17,6 @@ import tempfile
import urllib.parse
-def earl_assertion(test, passed, asserter):
- """Return a Turtle description of an assertion for the test report."""
-
- asserter_str = ""
- if asserter is not None:
- asserter_str = "\n\tearl:assertedBy <%s> ;" % asserter
-
- return """
-[]
-\ta earl:Assertion ;%s
-\tearl:subject <http://drobilla.net/sw/serd> ;
-\tearl:test <%s> ;
-\tearl:result [
-\t\ta earl:TestResult ;
-\t\tearl:outcome %s ;
-\t\tdc:date "%s"^^xsd:dateTime
-\t] .
-""" % (
- asserter_str,
- test,
- "earl:passed" if passed else "earl:failed",
- datetime.datetime.now().replace(microsecond=0).isoformat(),
- )
-
-
def log_error(message):
"""Log an error message to stderr"""
@@ -141,37 +118,6 @@ def _test_output_syntax(test_class):
raise Exception("Unknown test class <{}>".format(test_class))
-def _load_rdf(filename, base_uri, command_prefix):
- """Load an RDF file as dictionaries via serdi (only supports URIs)."""
-
- rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
- model = {}
- instances = {}
-
- cmd = command_prefix + ["-I", base_uri, filename]
- proc = subprocess.run(cmd, capture_output=True, check=True)
- for line in proc.stdout.splitlines():
- matches = re.match(
- r"<([^ ]*)> <([^ ]*)> <([^ ]*)> \.", line.decode("utf-8")
- )
- if matches:
- s, p, o = (matches.group(1), matches.group(2), matches.group(3))
- if s not in model:
- model[s] = {p: [o]}
- elif p not in model[s]:
- model[s][p] = [o]
- else:
- model[s][p].append(o)
-
- if p == rdf_type:
- if o not in instances:
- instances[o] = set([s])
- else:
- instances[o].update([s])
-
- return model, instances
-
-
def _option_combinations(options):
"""Return an iterator that cycles through all combinations of options."""
@@ -213,7 +159,7 @@ def _file_lines_equal(patha, pathb, subst_from="", subst_to=""):
for path in (patha, pathb):
if not os.access(path, os.F_OK):
- sys.stderr.write("error: missing file %s" % path)
+ log_error("missing file %s\n" % path)
return False
la = sorted(set(io.open(patha, encoding="utf-8").readlines()))
@@ -237,7 +183,9 @@ def test_suite(
mf = "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"
test_dir = os.path.dirname(manifest_path)
- model, instances = _load_rdf(manifest_path, base_uri, command_prefix)
+ model, instances = serd_test_util.load_rdf(
+ manifest_path, base_uri, command_prefix
+ )
top_dir = os.path.commonpath([os.getcwd(), os.path.abspath(test_dir)])
out_test_dir = os.path.relpath(test_dir, top_dir)
@@ -306,7 +254,7 @@ def test_suite(
if not _file_equals(check_path, out_filename):
results.n_failures += 1
log_error(
- "Output {} does not match {}".format(
+ "Output {} does not match {}\n".format(
out_filename, check_path
)
)
@@ -324,14 +272,20 @@ def test_suite(
)
# Run model test for positive test (must succeed)
- out_filename = os.path.join(out_test_dir, test_name + ".model.out")
+ out_filename = os.path.join(
+ out_test_dir, test_name + ".model.out"
+ )
with open(out_filename, "w") as stdout:
- proc = subprocess.run([command[0]] + ['-m'] + command[1:],
- check=True,
- stdout=stdout)
+ proc = subprocess.run(
+ [command[0]] + ["-m"] + command[1:],
+ check=True,
+ stdout=stdout,
+ )
- if proc.returncode == 0 and ((mf + 'result') in model[test]):
+ if proc.returncode == 0 and (
+ (mf + "result") in model[test]
+ ):
if not _file_lines_equal(check_path, out_filename):
results.n_failures += 1
@@ -366,7 +320,9 @@ def test_suite(
# Write test report entry
if report_filename:
with open(report_filename, "a") as report:
- report.write(earl_assertion(test, passed, asserter))
+ report.write(
+ serd_test_util.earl_assertion(test, passed, asserter)
+ )
# Run all test types in the test suite
results = Results()
diff --git a/test/run_validation_test_suite.py b/test/run_validation_test_suite.py
new file mode 100755
index 00000000..a27e55de
--- /dev/null
+++ b/test/run_validation_test_suite.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python3
+
+"""Run the serd RDF validation test suite."""
+
+import serd_test_util
+
+import argparse
+import datetime
+import difflib
+import itertools
+import os
+import re
+import shlex
+import subprocess
+import sys
+import tempfile
+import urllib.parse
+
+
+def _uri_path(uri):
+ path = urllib.parse.urlparse(uri).path
+ drive = os.path.splitdrive(path[1:])[0]
+ return path if not drive else path[1:]
+
+
+def _load_rdf(filename, base_uri, command_prefix):
+ """Load an RDF file as dictionaries via serdi (only supports URIs)."""
+
+ rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
+ model = {}
+ instances = {}
+
+ cmd = command_prefix + ["-I", base_uri, filename]
+ proc = subprocess.run(cmd, capture_output=True, check=True)
+ for line in proc.stdout.splitlines():
+ matches = re.match(
+ r"<([^ ]*)> <([^ ]*)> <([^ ]*)> \.", line.decode("utf-8")
+ )
+ if matches:
+ s, p, o = (matches.group(1), matches.group(2), matches.group(3))
+ if s not in model:
+ model[s] = {p: [o]}
+ elif p not in model[s]:
+ model[s][p] = [o]
+ else:
+ model[s][p].append(o)
+
+ if p == rdf_type:
+ if o not in instances:
+ instances[o] = set([s])
+ else:
+ instances[o].update([s])
+
+ return model, instances
+
+
+def _option_combinations(options):
+ """Return an iterator that cycles through all combinations of options."""
+
+ combinations = []
+ for count in range(len(options) + 1):
+ combinations += list(itertools.combinations(options, count))
+
+ return itertools.cycle(combinations)
+
+
+def _show_diff(from_lines, to_lines, from_filename, to_filename):
+ same = True
+ for line in difflib.unified_diff(
+ from_lines,
+ to_lines,
+ fromfile=os.path.abspath(from_filename),
+ tofile=os.path.abspath(to_filename),
+ ):
+ sys.stderr.write(line)
+ same = False
+
+ return same
+
+
+def _file_equals(patha, pathb):
+
+ for path in (patha, pathb):
+ if not os.access(path, os.F_OK):
+ sys.stderr.write("error: missing file {}\n".format(path))
+ return False
+
+ with open(patha, "r", encoding="utf-8") as fa:
+ with open(pathb, "r", encoding="utf-8") as fb:
+ return _show_diff(fa.readlines(), fb.readlines(), patha, pathb)
+
+
+def _file_lines_equal(patha, pathb, subst_from="", subst_to=""):
+ import io
+
+ for path in (patha, pathb):
+ if not os.access(path, os.F_OK):
+ sys.stderr.write("error: missing file %s\n" % path)
+ return False
+
+ la = sorted(set(io.open(patha, encoding="utf-8").readlines()))
+ lb = sorted(set(io.open(pathb, encoding="utf-8").readlines()))
+ if la != lb:
+ _show_diff(la, lb, patha, pathb)
+ return False
+
+ return True
+
+
+def validation_test_suite(
+ manifest_path,
+ schemas,
+ base_uri,
+ report_filename,
+ isyntax,
+ command_prefix,
+):
+ """Run all tests in a test suite manifest."""
+
+ mf = "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"
+ test_dir = os.path.dirname(manifest_path)
+ model, instances = serd_test_util.load_rdf(
+ manifest_path, base_uri, command_prefix
+ )
+
+ top_dir = os.path.commonpath([os.getcwd(), os.path.abspath(test_dir)])
+ out_test_dir = os.path.relpath(test_dir, top_dir)
+
+ os.makedirs(out_test_dir, exist_ok=True)
+
+ asserter = ""
+ if os.getenv("USER") == "drobilla":
+ asserter = "http://drobilla.net/drobilla#me"
+
+ class Results:
+ def __init__(self):
+ self.n_tests = 0
+ self.n_failures = 0
+
+ def run_tests(test_class, tests, expected_return, results):
+ for test in sorted(tests):
+ test_uri = model[test][mf + "action"][0]
+ test_uri_path = _uri_path(test_uri)
+ test_name = os.path.basename(test_uri_path)
+ test_path = os.path.join(test_dir, test_name)
+
+ command = (
+ command_prefix
+ + [
+ "-V",
+ "-I",
+ test_uri,
+ test_path,
+ ]
+ + schemas
+ )
+ out_filename = os.path.join(out_test_dir, test_name + ".out")
+
+ results.n_tests += 1
+
+ if expected_return == 0: # Positive test
+
+ with open(out_filename, "w") as stdout:
+ proc = subprocess.run(command, check=False, stdout=stdout)
+ if proc.returncode == 0:
+ passed = True
+ else:
+ results.n_failures += 1
+ sys.stderr.write(
+ "error: Unexpected failure of command: {}\n".format(
+ " ".join(shlex.quote(c) for c in command)
+ )
+ )
+
+ else: # Negative test
+ with open(out_filename, "w") as stdout:
+ with tempfile.TemporaryFile() as stderr:
+ proc = subprocess.run(
+ command, check=False, stdout=stdout, stderr=stderr
+ )
+
+ if proc.returncode != 0:
+ passed = True
+ else:
+ results.n_failures += 1
+ sys.stderr.write(
+ "error: Unexpected success of command: {}\n".format(
+ " ".join(shlex.quote(c) for c in command)
+ )
+ )
+
+ # Check that an error message was printed
+ stderr.seek(0, 2) # Seek to end
+ if stderr.tell() == 0: # Empty
+ sys.stderr.write(
+ "error: No error message printed by command: {}\n".format(
+ " ".join(shlex.quote(c) for c in command)
+ )
+ )
+ result = 1
+
+ # Write test report entry
+ if report_filename:
+ with open(report_filename, "a") as report:
+ report.write(
+ serd_test_util.earl_assertion(test, passed, asserter)
+ )
+
+ # Run all test types in the test suite
+ results = Results()
+ ns_serd = "http://drobilla.net/ns/serd#"
+ for test_class, instances in instances.items():
+ if test_class.startswith(ns_serd):
+ expected = 1 if "Negative" in test_class else 0
+ run_tests(test_class, instances, expected, results)
+
+ # Print result summary
+ if results.n_failures > 0:
+ sys.stderr.write(
+ "error: {}/{} tests failed\n".format(
+ results.n_failures, results.n_tests
+ )
+ )
+ else:
+ sys.stdout.write("All {} tests passed\n".format(results.n_tests))
+
+ return results.n_failures
+
+
+def main():
+ """Run the command line tool."""
+
+ parser = argparse.ArgumentParser(
+ usage="%(prog)s [OPTION]... MANIFEST BASE_URI SCHEMA...",
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+
+ parser.add_argument("--report", help="path to write result report to")
+ parser.add_argument("--serdi", default="serdi", help="path to serdi")
+ parser.add_argument("--syntax", default="turtle", help="input syntax")
+ parser.add_argument("--wrapper", default="", help="executable wrapper")
+ parser.add_argument("manifest", help="test suite manifest.ttl file")
+ parser.add_argument("base_uri", help="base URI for tests")
+ parser.add_argument("schema", nargs="+", help="schema file")
+
+ args = parser.parse_args(sys.argv[1:])
+ command_prefix = shlex.split(args.wrapper) + [args.serdi]
+
+ return validation_test_suite(
+ args.manifest,
+ args.schema,
+ args.base_uri,
+ args.report,
+ args.syntax,
+ command_prefix,
+ )
+
+
+if __name__ == "__main__":
+ try:
+ sys.exit(main())
+ except subprocess.CalledProcessError as e:
+ if e.stderr is not None:
+ sys.stderr.write(e.stderr.decode("utf-8"))
+
+ sys.stderr.write("error: %s\n" % e)
+ sys.exit(e.returncode)
diff --git a/test/serd_test_util/__init__.py b/test/serd_test_util/__init__.py
new file mode 100644
index 00000000..f82ca760
--- /dev/null
+++ b/test/serd_test_util/__init__.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+
+"""Utilities for data-driven tests."""
+
+import datetime
+import re
+import subprocess
+
+
+def earl_assertion(test, passed, asserter):
+ """Return a Turtle description of an assertion for the test report."""
+
+ asserter_str = ""
+ if asserter is not None:
+ asserter_str = "\n\tearl:assertedBy <%s> ;" % asserter
+
+ return """
+[]
+\ta earl:Assertion ;%s
+\tearl:subject <http://drobilla.net/sw/serd> ;
+\tearl:test <%s> ;
+\tearl:result [
+\t\ta earl:TestResult ;
+\t\tearl:outcome %s ;
+\t\tdc:date "%s"^^xsd:dateTime
+\t] .
+""" % (
+ asserter_str,
+ test,
+ "earl:passed" if passed else "earl:failed",
+ datetime.datetime.now().replace(microsecond=0).isoformat(),
+ )
+
+
+def load_rdf(filename, base_uri, command_prefix):
+ """Load an RDF file as dictionaries via serdi (only supports URIs)."""
+
+ rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
+ model = {}
+ instances = {}
+
+ cmd = command_prefix + ["-I", base_uri, filename]
+ proc = subprocess.run(cmd, capture_output=True, check=True)
+ for line in proc.stdout.splitlines():
+ matches = re.match(
+ r"<([^ ]*)> <([^ ]*)> <([^ ]*)> \.", line.decode("utf-8")
+ )
+ if matches:
+ s, p, o = (matches.group(1), matches.group(2), matches.group(3))
+ if s not in model:
+ model[s] = {p: [o]}
+ elif p not in model[s]:
+ model[s][p] = [o]
+ else:
+ model[s][p].append(o)
+
+ if p == rdf_type:
+ if o not in instances:
+ instances[o] = set([s])
+ else:
+ instances[o].update([s])
+
+ return model, instances
diff --git a/test/validate/bad-all-values-from.ttl b/test/validate/bad-all-values-from.ttl
new file mode 100644
index 00000000..e8243423
--- /dev/null
+++ b/test/validate/bad-all-values-from.ttl
@@ -0,0 +1,18 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdfs:label ;
+ owl:allValuesFrom rdf:PlainLiteral
+ ] .
+
+eg:s
+ a eg:Thing ;
+ rdfs:label "plain" ,
+ "not plain"^^rdf:XMLLiteral .
+
diff --git a/test/validate/bad-cardinality-high.ttl b/test/validate/bad-cardinality-high.ttl
new file mode 100644
index 00000000..7e1605c3
--- /dev/null
+++ b/test/validate/bad-cardinality-high.ttl
@@ -0,0 +1,18 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdf:value ;
+ owl:maxCardinality 1
+ ] .
+
+eg:s
+ a eg:Thing ;
+ rdf:value 1 ,
+ 2 .
+
diff --git a/test/validate/bad-cardinality-low.ttl b/test/validate/bad-cardinality-low.ttl
new file mode 100644
index 00000000..93dd0051
--- /dev/null
+++ b/test/validate/bad-cardinality-low.ttl
@@ -0,0 +1,16 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdf:value ;
+ owl:minCardinality 1
+ ] .
+
+eg:s
+ a eg:Thing .
+
diff --git a/test/validate/bad-cardinality.ttl b/test/validate/bad-cardinality.ttl
new file mode 100644
index 00000000..481fe456
--- /dev/null
+++ b/test/validate/bad-cardinality.ttl
@@ -0,0 +1,18 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdf:value ;
+ owl:cardinality 3
+ ] .
+
+eg:s
+ a eg:Thing ;
+ rdf:value 1 ,
+ 2 .
+
diff --git a/test/validate/bad-datatype-property.ttl b/test/validate/bad-datatype-property.ttl
new file mode 100644
index 00000000..a3e993f3
--- /dev/null
+++ b/test/validate/bad-datatype-property.ttl
@@ -0,0 +1,18 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:value
+ rdfs:label "value" ;
+ a owl:DatatypeProperty .
+
+eg:Thing
+ a rdfs:Class .
+
+eg:s1
+ a eg:Thing .
+
+eg:s2
+ eg:value eg:s1 .
+
diff --git a/test/validate/bad-domain.ttl b/test/validate/bad-domain.ttl
new file mode 100644
index 00000000..d36b5652
--- /dev/null
+++ b/test/validate/bad-domain.ttl
@@ -0,0 +1,19 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class .
+
+eg:NonThing
+ a rdfs:Class .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:domain eg:Thing .
+
+eg:nonthing
+ a eg:NonThing ;
+ eg:value 42 .
+
diff --git a/test/validate/bad-functional-property.ttl b/test/validate/bad-functional-property.ttl
new file mode 100644
index 00000000..53a73ccd
--- /dev/null
+++ b/test/validate/bad-functional-property.ttl
@@ -0,0 +1,13 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:identity
+ rdfs:label "identity" ;
+ a owl:FunctionalProperty .
+
+eg:s
+ eg:identity "me" ,
+ "you" .
+
diff --git a/test/validate/bad-inverse-functional-property.ttl b/test/validate/bad-inverse-functional-property.ttl
new file mode 100644
index 00000000..95c0aaea
--- /dev/null
+++ b/test/validate/bad-inverse-functional-property.ttl
@@ -0,0 +1,15 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:identity
+ rdfs:label "identity" ;
+ a owl:InverseFunctionalProperty .
+
+eg:s1
+ eg:identity "me" .
+
+eg:s2
+ eg:identity "me" .
+
diff --git a/test/validate/bad-literal-pattern.ttl b/test/validate/bad-literal-pattern.ttl
new file mode 100644
index 00000000..40f9eec0
--- /dev/null
+++ b/test/validate/bad-literal-pattern.ttl
@@ -0,0 +1,7 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:s
+ rdf:value "no"^^xsd:boolean .
+
diff --git a/test/validate/bad-literal-value-high-exclusive.ttl b/test/validate/bad-literal-value-high-exclusive.ttl
new file mode 100644
index 00000000..f83d2216
--- /dev/null
+++ b/test/validate/bad-literal-value-high-exclusive.ttl
@@ -0,0 +1,20 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:Normal
+ a rdfs:Datatype ;
+ rdfs:label "normal" ;
+ owl:onDatatype xsd:double ;
+ owl:withRestrictions (
+ [
+ xsd:maxExclusive 1.0
+ ] [
+ xsd:minExclusive 0.0
+ ]
+ ) .
+
+eg:s
+ rdf:value "1.0"^^eg:Normal .
diff --git a/test/validate/bad-literal-value-high-inclusive.ttl b/test/validate/bad-literal-value-high-inclusive.ttl
new file mode 100644
index 00000000..c0753250
--- /dev/null
+++ b/test/validate/bad-literal-value-high-inclusive.ttl
@@ -0,0 +1,20 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:Normal
+ a rdfs:Datatype ;
+ rdfs:label "normal" ;
+ owl:onDatatype xsd:double ;
+ owl:withRestrictions (
+ [
+ xsd:maxInclusive 1.0
+ ] [
+ xsd:minInclusive 0.0
+ ]
+ ) .
+
+eg:s
+ rdf:value "1.1"^^eg:Normal .
diff --git a/test/validate/bad-literal-value-low-exclusive.ttl b/test/validate/bad-literal-value-low-exclusive.ttl
new file mode 100644
index 00000000..09ca9f93
--- /dev/null
+++ b/test/validate/bad-literal-value-low-exclusive.ttl
@@ -0,0 +1,20 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:Normal
+ a rdfs:Datatype ;
+ rdfs:label "normal" ;
+ owl:onDatatype xsd:double ;
+ owl:withRestrictions (
+ [
+ xsd:maxExclusive 1.0
+ ] [
+ xsd:minExclusive 0.0
+ ]
+ ) .
+
+eg:s
+ rdf:value "0.0"^^eg:Normal .
diff --git a/test/validate/bad-literal-value-low-inclusive.ttl b/test/validate/bad-literal-value-low-inclusive.ttl
new file mode 100644
index 00000000..6ae5758b
--- /dev/null
+++ b/test/validate/bad-literal-value-low-inclusive.ttl
@@ -0,0 +1,21 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:Normal
+ a rdfs:Datatype ;
+ rdfs:label "normal" ;
+ owl:onDatatype xsd:double ;
+ owl:withRestrictions (
+ [
+ xsd:maxInclusive 1.0
+ ] [
+ xsd:minInclusive 0.0
+ ]
+ ) .
+
+eg:s
+ rdf:value "-0.1"^^eg:Normal .
+
diff --git a/test/validate/bad-object-property.ttl b/test/validate/bad-object-property.ttl
new file mode 100644
index 00000000..b4a31f9d
--- /dev/null
+++ b/test/validate/bad-object-property.ttl
@@ -0,0 +1,12 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:value
+ rdfs:label "value" ;
+ a owl:ObjectProperty .
+
+eg:s
+ eg:value "literal" .
+
diff --git a/test/validate/bad-pattern.ttl b/test/validate/bad-pattern.ttl
new file mode 100644
index 00000000..1b764c78
--- /dev/null
+++ b/test/validate/bad-pattern.ttl
@@ -0,0 +1,23 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:BrokenLiteral
+ a rdfs:Datatype ;
+ rdfs:label "broken literal" ;
+ owl:withRestrictions (
+ [
+ xsd:pattern "["
+ ]
+ ) .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:range eg:BinaryLiteral .
+
+eg:s
+ eg:value "no match"^^eg:BrokenLiteral .
+
diff --git a/test/validate/bad-plain-literal.ttl b/test/validate/bad-plain-literal.ttl
new file mode 100644
index 00000000..116faac0
--- /dev/null
+++ b/test/validate/bad-plain-literal.ttl
@@ -0,0 +1,12 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:range rdf:PlainLiteral .
+
+eg:s
+ eg:value "literal"^^rdf:XMLLiteral .
+
diff --git a/test/validate/bad-range-instance-not-literal.ttl b/test/validate/bad-range-instance-not-literal.ttl
new file mode 100644
index 00000000..ea7803f6
--- /dev/null
+++ b/test/validate/bad-range-instance-not-literal.ttl
@@ -0,0 +1,18 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:range rdfs:Literal .
+
+eg:thing
+ a eg:Thing .
+
+eg:s
+ eg:value eg:thing .
+
diff --git a/test/validate/bad-range-instance.ttl b/test/validate/bad-range-instance.ttl
new file mode 100644
index 00000000..a04a5476
--- /dev/null
+++ b/test/validate/bad-range-instance.ttl
@@ -0,0 +1,21 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class .
+
+eg:NonThing
+ a rdfs:Class .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:range eg:Thing .
+
+eg:nonthing
+ a eg:NonThing .
+
+eg:s
+ eg:value eg:nonthing .
+
diff --git a/test/validate/bad-range-literal-not-instance.ttl b/test/validate/bad-range-literal-not-instance.ttl
new file mode 100644
index 00000000..f46de8ce
--- /dev/null
+++ b/test/validate/bad-range-literal-not-instance.ttl
@@ -0,0 +1,15 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:range eg:Thing .
+
+eg:s
+ eg:value "literal" .
+
diff --git a/test/validate/bad-range-literal.ttl b/test/validate/bad-range-literal.ttl
new file mode 100644
index 00000000..10750391
--- /dev/null
+++ b/test/validate/bad-range-literal.ttl
@@ -0,0 +1,27 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:Normal
+ a rdfs:Datatype ;
+ rdfs:label "normal" ;
+ owl:onDatatype xsd:double ;
+ owl:withRestrictions (
+ [
+ xsd:maxExclusive 1.0
+ ] [
+ xsd:minExclusive 0.0
+ ]
+ ) .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:range eg:Normal .
+
+eg:s
+ eg:value 2.0 .
+
+
diff --git a/test/validate/bad-some-values-from.ttl b/test/validate/bad-some-values-from.ttl
new file mode 100644
index 00000000..259bfb88
--- /dev/null
+++ b/test/validate/bad-some-values-from.ttl
@@ -0,0 +1,18 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdfs:label ;
+ owl:someValuesFrom rdf:PlainLiteral
+ ] .
+
+eg:s
+ a eg:Thing ;
+ rdfs:label "not plain"^^rdf:XMLLiteral .
+
+
diff --git a/test/validate/bad-string-literal-value-high.ttl b/test/validate/bad-string-literal-value-high.ttl
new file mode 100644
index 00000000..6622c35b
--- /dev/null
+++ b/test/validate/bad-string-literal-value-high.ttl
@@ -0,0 +1,21 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:startsWithC
+ a rdfs:Datatype ;
+ rdfs:label "starts with C" ;
+ owl:onDatatype xsd:string ;
+ owl:withRestrictions (
+ [
+ xsd:maxExclusive "D"
+ ] [
+ xsd:minInclusive "B"
+ ]
+ ) .
+
+eg:s
+ rdf:value "Door"^^eg:startsWithC .
+
diff --git a/test/validate/bad-string-literal-value-low.ttl b/test/validate/bad-string-literal-value-low.ttl
new file mode 100644
index 00000000..06833a46
--- /dev/null
+++ b/test/validate/bad-string-literal-value-low.ttl
@@ -0,0 +1,21 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:betweenBAndD
+ a rdfs:Datatype ;
+ rdfs:label "between B and D" ;
+ owl:onDatatype xsd:string ;
+ owl:withRestrictions (
+ [
+ xsd:maxInclusive "D"
+ ] [
+ xsd:minExclusive "B"
+ ]
+ ) .
+
+eg:s
+ rdf:value "Aardvark"^^eg:betweenBAndD .
+
diff --git a/test/validate/bad-unknown-datatype.ttl b/test/validate/bad-unknown-datatype.ttl
new file mode 100644
index 00000000..ed879414
--- /dev/null
+++ b/test/validate/bad-unknown-datatype.ttl
@@ -0,0 +1,7 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:s
+ rdfs:label "bad datatype"^^rdf:UndefinedLiteral .
+
diff --git a/test/validate/bad-unknown-property.ttl b/test/validate/bad-unknown-property.ttl
new file mode 100644
index 00000000..0db1e85c
--- /dev/null
+++ b/test/validate/bad-unknown-property.ttl
@@ -0,0 +1,6 @@
+@prefix eg: <http://example.org/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:s
+ eg:undefined 0 .
+
diff --git a/test/validate/good-cardinality.ttl b/test/validate/good-cardinality.ttl
new file mode 100644
index 00000000..6b0b87da
--- /dev/null
+++ b/test/validate/good-cardinality.ttl
@@ -0,0 +1,17 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdf:value ;
+ owl:cardinality 2
+ ] .
+
+eg:s
+ a eg:Thing ;
+ rdf:value 1 ,
+ 2 .
diff --git a/test/validate/good-literal-value-high-inclusive.ttl b/test/validate/good-literal-value-high-inclusive.ttl
new file mode 100644
index 00000000..bbaa84a2
--- /dev/null
+++ b/test/validate/good-literal-value-high-inclusive.ttl
@@ -0,0 +1,21 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:Normal
+ a rdfs:Datatype ;
+ rdfs:label "normal" ;
+ owl:onDatatype xsd:double ;
+ owl:withRestrictions (
+ [
+ xsd:maxInclusive 1.0
+ ]
+ [
+ xsd:minInclusive 0.0
+ ]
+ ) .
+
+eg:s
+ rdf:value "1.0"^^eg:Normal .
diff --git a/test/validate/good-literal-value-low-inclusive.ttl b/test/validate/good-literal-value-low-inclusive.ttl
new file mode 100644
index 00000000..61943a36
--- /dev/null
+++ b/test/validate/good-literal-value-low-inclusive.ttl
@@ -0,0 +1,21 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:Normal
+ a rdfs:Datatype ;
+ rdfs:label "normal" ;
+ owl:onDatatype xsd:double ;
+ owl:withRestrictions (
+ [
+ xsd:maxInclusive 1.0
+ ]
+ [
+ xsd:minInclusive 0.0
+ ]
+ ) .
+
+eg:s
+ rdf:value "0.0"^^eg:Normal .
diff --git a/test/validate/good-pattern.ttl b/test/validate/good-pattern.ttl
new file mode 100644
index 00000000..569cd424
--- /dev/null
+++ b/test/validate/good-pattern.ttl
@@ -0,0 +1,22 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:CapitalLiteral
+ a rdfs:Datatype ;
+ rdfs:label "capital literal" ;
+ owl:withRestrictions (
+ [
+ xsd:pattern "[A-Z][a-z]*"
+ ]
+ ) .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:range eg:CapitalLiteral .
+
+eg:s
+ eg:value "Uppercase"^^eg:CapitalLiteral .
diff --git a/test/validate/good-some-values-from.ttl b/test/validate/good-some-values-from.ttl
new file mode 100644
index 00000000..1da49270
--- /dev/null
+++ b/test/validate/good-some-values-from.ttl
@@ -0,0 +1,17 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Thing
+ a rdfs:Class ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdfs:label ;
+ owl:someValuesFrom rdf:PlainLiteral
+ ] .
+
+eg:s
+ a eg:Thing ;
+ rdfs:label "not plain"^^rdf:XMLLiteral ,
+ "plain" .
diff --git a/test/validate/good-string-literal-value-low.ttl b/test/validate/good-string-literal-value-low.ttl
new file mode 100644
index 00000000..5bfd6a9e
--- /dev/null
+++ b/test/validate/good-string-literal-value-low.ttl
@@ -0,0 +1,21 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:betweenBAndD
+ a rdfs:Datatype ;
+ rdfs:label "between B and D" ;
+ owl:onDatatype xsd:string ;
+ owl:withRestrictions (
+ [
+ xsd:maxInclusive "D"
+ ]
+ [
+ xsd:minExclusive "B"
+ ]
+ ) .
+
+eg:s
+ rdf:value "Cat"^^eg:betweenBAndD .
diff --git a/test/validate/manifest.ttl b/test/validate/manifest.ttl
new file mode 100644
index 00000000..68853073
--- /dev/null
+++ b/test/validate/manifest.ttl
@@ -0,0 +1,205 @@
+@prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdft: <http://www.w3.org/ns/rdftest#> .
+@prefix serd: <http://drobilla.net/ns/serd#> .
+
+serd:TestTurtleNegativeValidate
+ a rdfs:Class ;
+ rdfs:subClassOf rdft:Test .
+
+serd:TestTurtlePositiveValidate
+ a rdfs:Class ;
+ rdfs:subClassOf rdft:Test .
+
+<>
+ rdf:type mf:Manifest ;
+ rdfs:comment "Serd validation test cases" ;
+ mf:entries (
+ <#bad-all-values-from>
+ <#bad-cardinality-high>
+ <#bad-cardinality-low>
+ <#bad-cardinality>
+ <#bad-datatype-property>
+ <#bad-domain>
+ <#bad-functional-property>
+ <#bad-inverse-functional-property>
+ <#bad-literal-pattern>
+ <#bad-literal-value-high-inclusive>
+ <#bad-literal-value-low-inclusive>
+ <#bad-literal-value-high-exclusive>
+ <#bad-literal-value-low-exclusive>
+ <#bad-string-literal-value-high>
+ <#bad-string-literal-value-low>
+ <#bad-object-property>
+ <#bad-pattern>
+ <#bad-plain-literal>
+ <#bad-range-instance-not-literal>
+ <#bad-range-instance>
+ <#bad-range-literal-not-instance>
+ <#bad-range-literal>
+ <#bad-some-values-from>
+ <#bad-unknown-datatype>
+ <#bad-unknown-property>
+ <#good-cardinality>
+ <#good-literal-value-high-inclusive>
+ <#good-literal-value-low-inclusive>
+ <#good-pattern>
+ <#good-some-values-from>
+ <#good-string-literal-value-low>
+ ) .
+
+<#bad-all-values-from>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-all-values-from" ;
+ mf:action <bad-all-values-from.ttl> .
+
+<#bad-cardinality-low>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-cardinality-low" ;
+ mf:action <bad-cardinality-low.ttl> .
+
+<#bad-cardinality-high>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-cardinality-high" ;
+ mf:action <bad-cardinality-high.ttl> .
+
+<#bad-cardinality>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-cardinality" ;
+ mf:action <bad-cardinality.ttl> .
+
+<#bad-datatype-property>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-datatype-property" ;
+ mf:action <bad-datatype-property.ttl> .
+
+<#bad-domain>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-domain" ;
+ mf:action <bad-domain.ttl> .
+
+<#bad-functional-property>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-functional-property" ;
+ mf:action <bad-functional-property.ttl> .
+
+<#bad-inverse-functional-property>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-inverse-functional-property" ;
+ mf:action <bad-inverse-functional-property.ttl> .
+
+<#bad-literal-pattern>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-literal-pattern" ;
+ mf:action <bad-literal-pattern.ttl> .
+
+<#bad-literal-value-low-inclusive>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-literal-value-low-inclusive" ;
+ mf:action <bad-literal-value-low-inclusive.ttl> .
+
+<#bad-literal-value-high-inclusive>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-literal-value-high-inclusive" ;
+ mf:action <bad-literal-value-high-inclusive.ttl> .
+
+<#bad-literal-value-low-exclusive>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-literal-value-low-exclusive" ;
+ mf:action <bad-literal-value-low-exclusive.ttl> .
+
+<#bad-literal-value-high-exclusive>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-literal-value-high-exclusive" ;
+ mf:action <bad-literal-value-high-exclusive.ttl> .
+
+<#bad-string-literal-value-low>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-string-literal-value-low" ;
+ mf:action <bad-string-literal-value-low.ttl> .
+
+<#bad-string-literal-value-high>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-string-literal-value-high" ;
+ mf:action <bad-string-literal-value-high.ttl> .
+
+<#bad-object-property>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-object-property" ;
+ mf:action <bad-object-property.ttl> .
+
+<#bad-pattern>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-pattern" ;
+ mf:action <bad-pattern.ttl> .
+
+<#bad-plain-literal>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-plain-literal" ;
+ mf:action <bad-plain-literal.ttl> .
+
+<#bad-range-instance-not-literal>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-range-instance-not-literal" ;
+ mf:action <bad-range-instance-not-literal.ttl> .
+
+<#bad-range-instance>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-range-instance" ;
+ mf:action <bad-range-instance.ttl> .
+
+<#bad-range-literal-not-instance>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-range-literal-not-instance" ;
+ mf:action <bad-range-literal-not-instance.ttl> .
+
+<#bad-range-literal>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-range-literal" ;
+ mf:action <bad-range-literal.ttl> .
+
+<#bad-some-values-from>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-some-values-from" ;
+ mf:action <bad-some-values-from.ttl> .
+
+<#bad-unknown-datatype>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-unknown-datatype" ;
+ mf:action <bad-unknown-datatype.ttl> .
+
+<#bad-unknown-property>
+ rdf:type serd:TestTurtleNegativeValidate ;
+ mf:name "bad-unknown-property" ;
+ mf:action <bad-unknown-property.ttl> .
+
+<#good-cardinality>
+ rdf:type serd:TestTurtlePositiveValidate ;
+ mf:name "good-cardinality" ;
+ mf:action <good-cardinality.ttl> .
+
+<#good-literal-value-low-inclusive>
+ rdf:type serd:TestTurtlePositiveValidate ;
+ mf:name "good-literal-value-low-inclusive" ;
+ mf:action <good-literal-value-low-inclusive.ttl> .
+
+<#good-literal-value-high-inclusive>
+ rdf:type serd:TestTurtlePositiveValidate ;
+ mf:name "good-literal-value-high-inclusive" ;
+ mf:action <good-literal-value-high-inclusive.ttl> .
+
+<#good-some-values-from>
+ rdf:type serd:TestTurtlePositiveValidate ;
+ mf:name "good-some-values-from" ;
+ mf:action <good-some-values-from.ttl> .
+
+<#good-pattern>
+ rdf:type serd:TestTurtlePositiveValidate ;
+ mf:name "good-pattern" ;
+ mf:action <good-pattern.ttl> .
+
+<#good-string-literal-value-low>
+ rdf:type serd:TestTurtlePositiveValidate ;
+ mf:name "good-string-literal-value-low" ;
+ mf:action <good-string-literal-value-low.ttl> .