From 7efaf2431b9c1a96f6ed2e28626aff4886efc749 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Mon, 9 Nov 2020 14:07:46 +0100 Subject: Add validation to command line interface --- test/meson.build | 18 ++ test/run_test_suite.py | 86 ++----- test/run_validation_test_suite.py | 268 +++++++++++++++++++++ test/serd_test_util/__init__.py | 63 +++++ test/validate/bad-all-values-from.ttl | 18 ++ test/validate/bad-cardinality-high.ttl | 18 ++ test/validate/bad-cardinality-low.ttl | 16 ++ test/validate/bad-cardinality.ttl | 18 ++ test/validate/bad-datatype-property.ttl | 18 ++ test/validate/bad-domain.ttl | 19 ++ test/validate/bad-functional-property.ttl | 13 + test/validate/bad-inverse-functional-property.ttl | 15 ++ test/validate/bad-literal-pattern.ttl | 7 + test/validate/bad-literal-value-high-exclusive.ttl | 20 ++ test/validate/bad-literal-value-high-inclusive.ttl | 20 ++ test/validate/bad-literal-value-low-exclusive.ttl | 20 ++ test/validate/bad-literal-value-low-inclusive.ttl | 21 ++ test/validate/bad-object-property.ttl | 12 + test/validate/bad-pattern.ttl | 23 ++ test/validate/bad-plain-literal.ttl | 12 + test/validate/bad-range-instance-not-literal.ttl | 18 ++ test/validate/bad-range-instance.ttl | 21 ++ test/validate/bad-range-literal-not-instance.ttl | 15 ++ test/validate/bad-range-literal.ttl | 27 +++ test/validate/bad-some-values-from.ttl | 18 ++ test/validate/bad-string-literal-value-high.ttl | 21 ++ test/validate/bad-string-literal-value-low.ttl | 21 ++ test/validate/bad-unknown-datatype.ttl | 7 + test/validate/bad-unknown-property.ttl | 6 + test/validate/good-cardinality.ttl | 17 ++ .../validate/good-literal-value-high-inclusive.ttl | 21 ++ test/validate/good-literal-value-low-inclusive.ttl | 21 ++ test/validate/good-pattern.ttl | 22 ++ test/validate/good-some-values-from.ttl | 17 ++ test/validate/good-string-literal-value-low.ttl | 21 ++ test/validate/manifest.ttl | 205 ++++++++++++++++ 36 files changed, 1118 insertions(+), 65 deletions(-) create mode 100755 test/run_validation_test_suite.py create mode 100644 test/serd_test_util/__init__.py create mode 100644 test/validate/bad-all-values-from.ttl create mode 100644 test/validate/bad-cardinality-high.ttl create mode 100644 test/validate/bad-cardinality-low.ttl create mode 100644 test/validate/bad-cardinality.ttl create mode 100644 test/validate/bad-datatype-property.ttl create mode 100644 test/validate/bad-domain.ttl create mode 100644 test/validate/bad-functional-property.ttl create mode 100644 test/validate/bad-inverse-functional-property.ttl create mode 100644 test/validate/bad-literal-pattern.ttl create mode 100644 test/validate/bad-literal-value-high-exclusive.ttl create mode 100644 test/validate/bad-literal-value-high-inclusive.ttl create mode 100644 test/validate/bad-literal-value-low-exclusive.ttl create mode 100644 test/validate/bad-literal-value-low-inclusive.ttl create mode 100644 test/validate/bad-object-property.ttl create mode 100644 test/validate/bad-pattern.ttl create mode 100644 test/validate/bad-plain-literal.ttl create mode 100644 test/validate/bad-range-instance-not-literal.ttl create mode 100644 test/validate/bad-range-instance.ttl create mode 100644 test/validate/bad-range-literal-not-instance.ttl create mode 100644 test/validate/bad-range-literal.ttl create mode 100644 test/validate/bad-some-values-from.ttl create mode 100644 test/validate/bad-string-literal-value-high.ttl create mode 100644 test/validate/bad-string-literal-value-low.ttl create mode 100644 test/validate/bad-unknown-datatype.ttl create mode 100644 test/validate/bad-unknown-property.ttl create mode 100644 test/validate/good-cardinality.ttl create mode 100644 test/validate/good-literal-value-high-inclusive.ttl create mode 100644 test/validate/good-literal-value-low-inclusive.ttl create mode 100644 test/validate/good-pattern.ttl create mode 100644 test/validate/good-some-values-from.ttl create mode 100644 test/validate/good-string-literal-value-low.ttl create mode 100644 test/validate/manifest.ttl (limited to 'test') 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 ; -\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 ; +\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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix rdf: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . + +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: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +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: . +@prefix rdf: . +@prefix rdfs: . +@prefix rdft: . +@prefix 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-cardinality-low> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-cardinality-low" ; + mf:action . + +<#bad-cardinality-high> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-cardinality-high" ; + mf:action . + +<#bad-cardinality> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-cardinality" ; + mf:action . + +<#bad-datatype-property> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-datatype-property" ; + mf:action . + +<#bad-domain> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-domain" ; + mf:action . + +<#bad-functional-property> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-functional-property" ; + mf:action . + +<#bad-inverse-functional-property> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-inverse-functional-property" ; + mf:action . + +<#bad-literal-pattern> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-literal-pattern" ; + mf:action . + +<#bad-literal-value-low-inclusive> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-literal-value-low-inclusive" ; + mf:action . + +<#bad-literal-value-high-inclusive> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-literal-value-high-inclusive" ; + mf:action . + +<#bad-literal-value-low-exclusive> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-literal-value-low-exclusive" ; + mf:action . + +<#bad-literal-value-high-exclusive> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-literal-value-high-exclusive" ; + mf:action . + +<#bad-string-literal-value-low> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-string-literal-value-low" ; + mf:action . + +<#bad-string-literal-value-high> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-string-literal-value-high" ; + mf:action . + +<#bad-object-property> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-object-property" ; + mf:action . + +<#bad-pattern> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-pattern" ; + mf:action . + +<#bad-plain-literal> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-plain-literal" ; + mf:action . + +<#bad-range-instance-not-literal> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-range-instance-not-literal" ; + mf:action . + +<#bad-range-instance> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-range-instance" ; + mf:action . + +<#bad-range-literal-not-instance> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-range-literal-not-instance" ; + mf:action . + +<#bad-range-literal> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-range-literal" ; + mf:action . + +<#bad-some-values-from> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-some-values-from" ; + mf:action . + +<#bad-unknown-datatype> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-unknown-datatype" ; + mf:action . + +<#bad-unknown-property> + rdf:type serd:TestTurtleNegativeValidate ; + mf:name "bad-unknown-property" ; + mf:action . + +<#good-cardinality> + rdf:type serd:TestTurtlePositiveValidate ; + mf:name "good-cardinality" ; + mf:action . + +<#good-literal-value-low-inclusive> + rdf:type serd:TestTurtlePositiveValidate ; + mf:name "good-literal-value-low-inclusive" ; + mf:action . + +<#good-literal-value-high-inclusive> + rdf:type serd:TestTurtlePositiveValidate ; + mf:name "good-literal-value-high-inclusive" ; + mf:action . + +<#good-some-values-from> + rdf:type serd:TestTurtlePositiveValidate ; + mf:name "good-some-values-from" ; + mf:action . + +<#good-pattern> + rdf:type serd:TestTurtlePositiveValidate ; + mf:name "good-pattern" ; + mf:action . + +<#good-string-literal-value-low> + rdf:type serd:TestTurtlePositiveValidate ; + mf:name "good-string-literal-value-low" ; + mf:action . -- cgit v1.2.1