aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2023-03-27 15:42:27 -0400
committerDavid Robillard <d@drobilla.net>2023-04-05 09:41:43 -0400
commite970e63146fb5d8de511104eba7aef5319e8653b (patch)
tree9f8e24335b84012ec0473e62301f37491c31cdb9 /test
parent68c1a2e677775e489cff4beb38ef17c1efeae4e3 (diff)
downloadserd-e970e63146fb5d8de511104eba7aef5319e8653b.tar.gz
serd-e970e63146fb5d8de511104eba7aef5319e8653b.tar.bz2
serd-e970e63146fb5d8de511104eba7aef5319e8653b.zip
Add pretty-printing test suite
The earlier "test" was just hitting the code without actually checking the output. This new suite is a set of pretty-printed documents which serd must reproduce exactly to pass. This should make it easy to add cases in the future, since each case is just a document, as it should look.
Diffstat (limited to 'test')
-rw-r--r--test/good/manifest.ttl7
-rw-r--r--test/good/test-pretty.nt46
-rw-r--r--test/good/test-pretty.ttl44
-rw-r--r--test/meson.build47
-rw-r--r--test/pretty/README.md5
-rw-r--r--test/pretty/abbreviation.ttl57
-rw-r--r--test/pretty/anonymous-in-list-object.ttl10
-rw-r--r--test/pretty/anonymous-object.ttl10
-rw-r--r--test/pretty/anonymous-subject-and-object.ttl4
-rw-r--r--test/pretty/anonymous-subject.ttl4
-rw-r--r--test/pretty/datatypes.ttl15
-rw-r--r--test/pretty/empty-anonymous-object.ttl4
-rw-r--r--test/pretty/empty-list-object.ttl4
-rw-r--r--test/pretty/empty-list-subject-and-object.ttl4
-rw-r--r--test/pretty/empty-list-subject.ttl4
-rw-r--r--test/pretty/ext-named-blank.ttl11
-rw-r--r--test/pretty/graph-abbreviation.trig65
-rw-r--r--test/pretty/langtags.ttl5
-rw-r--r--test/pretty/list-in-object.ttl10
-rw-r--r--test/pretty/list-object.ttl8
-rw-r--r--test/pretty/local-name-escapes.ttl4
-rw-r--r--test/pretty/long-string-escapes.ttl4
-rw-r--r--test/pretty/long-string-quotes.ttl5
-rw-r--r--test/pretty/manifest.ttl171
-rw-r--r--test/pretty/many-objects.ttl9
-rw-r--r--test/pretty/named-graph.trig8
-rw-r--r--test/pretty/nested-list-object.ttl12
-rw-r--r--test/pretty/short-string-escapes.ttl4
-rw-r--r--test/pretty/uri-escapes.ttl2
-rwxr-xr-xtest/run_suite.py125
-rw-r--r--test/serd_test_util/__init__.py19
31 files changed, 618 insertions, 109 deletions
diff --git a/test/good/manifest.ttl b/test/good/manifest.ttl
index 25359fdc..aeb90340 100644
--- a/test/good/manifest.ttl
+++ b/test/good/manifest.ttl
@@ -44,7 +44,6 @@
<#test-num>
<#test-out-of-range-unicode>
<#test-prefix>
- <#test-pretty>
<#test-quote-escapes>
<#test-rel>
<#test-semi-dot>
@@ -276,12 +275,6 @@
mf:action <test-prefix.ttl> ;
mf:result <test-prefix.nt> .
-<#test-pretty>
- rdf:type rdft:TestTurtleEval ;
- mf:name "test-pretty" ;
- mf:action <test-pretty.ttl> ;
- mf:result <test-pretty.nt> .
-
<#test-quote-escapes>
rdf:type rdft:TestTurtleEval ;
mf:name "test-quote-escapes" ;
diff --git a/test/good/test-pretty.nt b/test/good/test-pretty.nt
deleted file mode 100644
index 9251563a..00000000
--- a/test/good/test-pretty.nt
+++ /dev/null
@@ -1,46 +0,0 @@
-<http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/isA> <http://example.org/List> .
-_:b1 <http://example.org/isA> <http://example.org/Blank> .
-<http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/sameAs> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
-_:b2 <http://example.org/sameAs> _:b3 .
-_:b4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "apple" .
-_:b4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b5 .
-_:b5 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "banana" .
-_:b5 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b6 .
-_:b6 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "pear" .
-_:b6 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
-_:b4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/List> .
-_:b7 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> _:b8 .
-_:b8 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://example.org/a> .
-_:b8 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b9 .
-_:b9 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://example.org/b> .
-_:b9 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
-_:b7 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b10 .
-_:b10 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> _:b11 .
-_:b11 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://example.org/c> .
-_:b11 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b12 .
-_:b12 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://example.org/d> .
-_:b12 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
-_:b10 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
-_:b7 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/List> .
-_:b13 <http://example.org/list> _:b14 .
-_:b14 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "apple" .
-_:b14 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b15 .
-_:b15 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "banana" .
-_:b15 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b16 .
-_:b16 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "pear" .
-_:b16 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
-_:b17 <http://example.org/a> <http://example.org/b> .
-_:b17 <http://example.org/a> <http://example.org/c> .
-_:b17 <http://example.org/a> <http://example.org/d> .
-_:b18 <http://example.org/a> _:b19 .
-_:b19 <http://example.org/b> <http://example.org/c> .
-_:b19 <http://example.org/d> <http://example.org/e> .
-_:b18 <http://example.org/a> _:b20 .
-_:b20 <http://example.org/f> <http://example.org/g> .
-_:b21 <http://example.org/list> _:b22 .
-_:b22 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> _:b23 .
-_:b23 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Apple> .
-_:b22 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b24 .
-_:b24 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> _:b25 .
-_:b25 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Banana> .
-_:b24 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
diff --git a/test/good/test-pretty.ttl b/test/good/test-pretty.ttl
deleted file mode 100644
index 4eb7204f..00000000
--- a/test/good/test-pretty.ttl
+++ /dev/null
@@ -1,44 +0,0 @@
-@prefix : <http://example.org/> .
-
-() :isA :List .
-
-[] :isA :Blank .
-
-() :sameAs () .
-
-[] :sameAs [] .
-
-(
- "apple"
- "banana"
- "pear"
-) a :List .
-
-(
- (:a :b)
- (:c :d)
-) a :List .
-
-[]
- :list (
- "apple"
- "banana"
- "pear"
- ) .
-
-[]
- :a :b , :c , :d .
-
-[]
- :a [
- :b :c ;
- :d :e ;
- ] , [
- :f :g
- ] .
-
-[]
- :list (
- [ a :Apple ]
- [ a :Banana ]
- ) . \ No newline at end of file
diff --git a/test/meson.build b/test/meson.build
index 847d5bef..6a4bbc55 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,6 +1,7 @@
# Copyright 2020-2023 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
+run_suite = find_program('run_suite.py')
run_test_suite = files('run_test_suite.py')
wrapper = meson.get_external_property('exe_wrapper', '')
@@ -64,13 +65,13 @@ endforeach
# System Tests #
################
-if not get_option('tools').disabled()
- if wrapper != ''
- script_args = ['--wrapper', wrapper, '--serdi', serdi.full_path()]
- else
- script_args = ['--serdi', serdi.full_path()]
- endif
+common_script_args = []
+if wrapper != ''
+ common_script_args += ['--wrapper', wrapper]
+endif
+if not get_option('tools').disabled()
+ script_args = common_script_args + ['--serdi', serdi]
serd_ttl = files('../serd.ttl')[0]
test('serd.ttl', serdi, args: [serd_ttl], suite: 'data')
@@ -158,18 +159,41 @@ if not get_option('tools').disabled()
test('write_error', files('test_write_error.py'),
args: script_args + [serd_ttl],
suite: 'io_errors')
+endif
- # RDF test suites
+###########################
+# Data-Driven Test Suites #
+###########################
+
+ns_serdtest = 'http://drobilla.net/sw/serd/test/'
+ns_w3 = 'http://www.w3.org/2013/'
+
+test_suites = {
+ 'pretty': [
+ files('pretty/manifest.ttl'), ns_serdtest + 'pretty/'
+ ],
+}
+
+if not get_option('tools').disabled()
+ script_args = common_script_args + ['--serdi', serdi]
+
+ foreach name, args : test_suites
+ test(
+ name, run_suite,
+ args: script_args + args,
+ suite: ['rdf'],
+ timeout: 240,
+ )
+ endforeach
## Serd-specific test suites
serd_suites = ['good', 'bad']
- serd_base = 'http://drobilla.net/sw/serd/test/'
### Run all suites with no special arguments
foreach name : serd_suites
manifest = files(name / 'manifest.ttl')
- base_uri = serd_base + name + '/'
+ base_uri = ns_serdtest + name + '/'
test(name, run_test_suite,
args: script_args + [manifest, base_uri],
suite: ['rdf', 'serd'],
@@ -178,7 +202,7 @@ if not get_option('tools').disabled()
### The lax suite is special because it is run twice...
lax_manifest = files('lax/manifest.ttl')
- lax_base_uri = serd_base + 'lax/'
+ lax_base_uri = ns_serdtest + 'lax/'
### ... once with strict parsing to test the hard errors
test('lax.strict', run_test_suite,
@@ -197,11 +221,10 @@ if not get_option('tools').disabled()
## Standard W3C test suites
w3c_suites = ['Turtle', 'NTriples', 'NQuads', 'TriG']
- w3c_base = 'http://www.w3.org/2013/'
foreach syntax : w3c_suites
manifest = files(syntax + 'Tests' / 'manifest.ttl')
- base_uri = w3c_base + syntax + 'Tests/'
+ base_uri = ns_w3 + syntax + 'Tests/'
args = ['--syntax', syntax, manifest, base_uri]
if syntax == 'TriG'
diff --git a/test/pretty/README.md b/test/pretty/README.md
new file mode 100644
index 00000000..361d9491
--- /dev/null
+++ b/test/pretty/README.md
@@ -0,0 +1,5 @@
+Pretty Test Suite
+=================
+
+This suite contains single-file tests that can be read and rewritten in the
+same syntax, exactly reproducing the input byte-for-byte.
diff --git a/test/pretty/abbreviation.ttl b/test/pretty/abbreviation.ttl
new file mode 100644
index 00000000..bf46f6d5
--- /dev/null
+++ b/test/pretty/abbreviation.ttl
@@ -0,0 +1,57 @@
+@prefix eg: <http://example.org/> .
+
+eg:s1
+ eg:b eg:c ,
+ eg:d ,
+ eg:e ;
+ eg:f eg:g ,
+ eg:h ;
+ eg:i eg:j ;
+ eg:k eg:l .
+
+eg:s2
+ a eg:Thing ;
+ eg:p1 eg:o1 ,
+ [
+ a eg:SubThing ;
+ eg:p2 eg:o2
+ ] , [
+ a eg:OtherSubThing ;
+ eg:p3 eg:o3
+ ] ;
+ eg:p4 eg:o4 .
+
+eg:s3
+ eg:resource eg:Blank .
+
+eg:s4
+ eg:anon [] .
+
+eg:s5
+ eg:blank [
+ eg:nestedEmptyBlank [] ;
+ eg:nestedNonEmptyBlanks [
+ eg:value 1
+ ] , [
+ eg:value 2
+ ]
+ ] ;
+ eg:listOfNumbers (
+ 3
+ 4
+ ) .
+
+eg:s6
+ eg:listOfNumbers (
+ 5
+ 6
+ ) .
+
+eg:s7
+ eg:listOfResources (
+ [
+ eg:value 7
+ ] [
+ eg:value 8
+ ]
+ ) .
diff --git a/test/pretty/anonymous-in-list-object.ttl b/test/pretty/anonymous-in-list-object.ttl
new file mode 100644
index 00000000..4f135e29
--- /dev/null
+++ b/test/pretty/anonymous-in-list-object.ttl
@@ -0,0 +1,10 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p (
+ [
+ a eg:Spy
+ ] [
+ a eg:Ninja
+ ]
+ ) .
diff --git a/test/pretty/anonymous-object.ttl b/test/pretty/anonymous-object.ttl
new file mode 100644
index 00000000..f592fed1
--- /dev/null
+++ b/test/pretty/anonymous-object.ttl
@@ -0,0 +1,10 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p1 [
+ eg:p2 eg:o2 ;
+ eg:p3 eg:o3
+ ] , [
+ eg:p4 eg:o4 ;
+ eg:p5 eg:o5
+ ] .
diff --git a/test/pretty/anonymous-subject-and-object.ttl b/test/pretty/anonymous-subject-and-object.ttl
new file mode 100644
index 00000000..8904cca6
--- /dev/null
+++ b/test/pretty/anonymous-subject-and-object.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+[]
+ eg:p [] .
diff --git a/test/pretty/anonymous-subject.ttl b/test/pretty/anonymous-subject.ttl
new file mode 100644
index 00000000..e0e467e2
--- /dev/null
+++ b/test/pretty/anonymous-subject.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+[]
+ eg:p eg:o .
diff --git a/test/pretty/datatypes.ttl b/test/pretty/datatypes.ttl
new file mode 100644
index 00000000..721dfe4d
--- /dev/null
+++ b/test/pretty/datatypes.ttl
@@ -0,0 +1,15 @@
+@prefix eg: <http://example.org/> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:s
+ eg:p -1.2 ,
+ "-3."^^xsd:decimal ,
+ -4 ,
+ 0 ,
+ 1 ,
+ 2.3 ,
+ "4."^^xsd:decimal ,
+ false ,
+ true ,
+ "x"^^eg:datatype ,
+ "y"^^<http://пример.испытание> .
diff --git a/test/pretty/empty-anonymous-object.ttl b/test/pretty/empty-anonymous-object.ttl
new file mode 100644
index 00000000..f3b8d7f7
--- /dev/null
+++ b/test/pretty/empty-anonymous-object.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p [] .
diff --git a/test/pretty/empty-list-object.ttl b/test/pretty/empty-list-object.ttl
new file mode 100644
index 00000000..b4045ec0
--- /dev/null
+++ b/test/pretty/empty-list-object.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:list () .
diff --git a/test/pretty/empty-list-subject-and-object.ttl b/test/pretty/empty-list-subject-and-object.ttl
new file mode 100644
index 00000000..3b84980e
--- /dev/null
+++ b/test/pretty/empty-list-subject-and-object.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+()
+ eg:list () .
diff --git a/test/pretty/empty-list-subject.ttl b/test/pretty/empty-list-subject.ttl
new file mode 100644
index 00000000..0610eb07
--- /dev/null
+++ b/test/pretty/empty-list-subject.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+()
+ a eg:ExampleList .
diff --git a/test/pretty/ext-named-blank.ttl b/test/pretty/ext-named-blank.ttl
new file mode 100644
index 00000000..807d6313
--- /dev/null
+++ b/test/pretty/ext-named-blank.ttl
@@ -0,0 +1,11 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p1 [
+ == <http://example.com/vocab#SomeClass> ;
+ eg:name "object"
+ ] ;
+ eg:p2 [
+ == eg:o ;
+ eg:name "o"
+ ] .
diff --git a/test/pretty/graph-abbreviation.trig b/test/pretty/graph-abbreviation.trig
new file mode 100644
index 00000000..8ec75f6e
--- /dev/null
+++ b/test/pretty/graph-abbreviation.trig
@@ -0,0 +1,65 @@
+@prefix : <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+
+:graph {
+ :a
+ :b :c ,
+ :d ,
+ :e ;
+ :f :g ,
+ :h ;
+ :i :j ;
+ :k :l .
+
+ :s
+ a :Thing ;
+ :p1 :o1 ,
+ [
+ a :SubThing ;
+ :p2 :o2
+ ] , [
+ a :OtherSubThing ;
+ :p3 :o3
+ ] ;
+ :p4 :o4 .
+
+ []
+ :isA :Blank .
+
+ []
+ :sameAs [] .
+
+ []
+ :blank [
+ :nestedEmptyBlank [] ;
+ :nestedNonEmptyBlanks [
+ rdf:value 1
+ ] , [
+ rdf:value 2
+ ]
+ ] ;
+ :lists (
+ 3
+ 4
+ ) .
+
+ []
+ :lists (
+ 5
+ 6
+ ) .
+
+ []
+ :lists (
+ [
+ rdf:value 7
+ ] [
+ rdf:value 8
+ ]
+ ) .
+}
+
+:separateGraph {
+ :m
+ a :OtherThing .
+}
diff --git a/test/pretty/langtags.ttl b/test/pretty/langtags.ttl
new file mode 100644
index 00000000..29d7e2d4
--- /dev/null
+++ b/test/pretty/langtags.ttl
@@ -0,0 +1,5 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p "eh?"@en-ca ,
+ "hä?"@de .
diff --git a/test/pretty/list-in-object.ttl b/test/pretty/list-in-object.ttl
new file mode 100644
index 00000000..ce887157
--- /dev/null
+++ b/test/pretty/list-in-object.ttl
@@ -0,0 +1,10 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p [
+ eg:list (
+ "apple"
+ "banana"
+ "cherry"
+ )
+ ] .
diff --git a/test/pretty/list-object.ttl b/test/pretty/list-object.ttl
new file mode 100644
index 00000000..735d9df6
--- /dev/null
+++ b/test/pretty/list-object.ttl
@@ -0,0 +1,8 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:list (
+ "apple"
+ "banana"
+ "cherry"
+ ) .
diff --git a/test/pretty/local-name-escapes.ttl b/test/pretty/local-name-escapes.ttl
new file mode 100644
index 00000000..91c3905d
--- /dev/null
+++ b/test/pretty/local-name-escapes.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p eg:local\'\!\#\$\%\&\(\)\*\+\,\/\;\=\?\@\~ .
diff --git a/test/pretty/long-string-escapes.ttl b/test/pretty/long-string-escapes.ttl
new file mode 100644
index 00000000..435e3d1a
--- /dev/null
+++ b/test/pretty/long-string-escapes.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p """\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b \u000B \u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F"'\\\u007F""" .
diff --git a/test/pretty/long-string-quotes.ttl b/test/pretty/long-string-quotes.ttl
new file mode 100644
index 00000000..aceaecda
--- /dev/null
+++ b/test/pretty/long-string-quotes.ttl
@@ -0,0 +1,5 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p1 """Unescaped "quotes" within""" ;
+ eg:p2 """Trickier ""\"case""\"""" .
diff --git a/test/pretty/manifest.ttl b/test/pretty/manifest.ttl
new file mode 100644
index 00000000..a78aa943
--- /dev/null
+++ b/test/pretty/manifest.ttl
@@ -0,0 +1,171 @@
+@prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
+@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#> .
+
+<>
+ a mf:Manifest ;
+ rdfs:comment "Serd pretty-printing test suite" ;
+ mf:entries (
+ <#abbreviation>
+ <#anonymous-in-list-object>
+ <#anonymous-object>
+ <#anonymous-subject>
+ <#anonymous-subject-and-object>
+ <#datatypes>
+ <#empty-anonymous-object>
+ <#empty-list-object>
+ <#empty-list-subject>
+ <#empty-list-subject-and-object>
+ <#ext-named-blank>
+ <#graph-abbreviation>
+ <#langtags>
+ <#list-in-object>
+ <#list-object>
+ <#local-name-escapes>
+ <#long-string-escapes>
+ <#long-string-quotes>
+ <#many-objects>
+ <#named-graph>
+ <#nested-list-object>
+ <#short-string-escapes>
+ <#uri-escapes>
+ ) .
+
+<#abbreviation>
+ a rdft:TestTurtleEval ;
+ mf:action <abbreviation.ttl> ;
+ mf:name "abbreviation" ;
+ mf:result <abbreviation.ttl> .
+
+<#anonymous-in-list-object>
+ a rdft:TestTurtleEval ;
+ mf:action <anonymous-in-list-object.ttl> ;
+ mf:name "anonymous-in-list-object" ;
+ mf:result <anonymous-in-list-object.ttl> .
+
+<#anonymous-object>
+ a rdft:TestTurtleEval ;
+ mf:action <anonymous-object.ttl> ;
+ mf:name "anonymous-object" ;
+ mf:result <anonymous-object.ttl> .
+
+<#anonymous-subject>
+ a rdft:TestTurtleEval ;
+ mf:action <anonymous-subject.ttl> ;
+ mf:name "anonymous-subject" ;
+ mf:result <anonymous-subject.ttl> .
+
+<#anonymous-subject-and-object>
+ a rdft:TestTurtleEval ;
+ mf:action <anonymous-subject-and-object.ttl> ;
+ mf:name "anonymous-subject-and-object" ;
+ mf:result <anonymous-subject-and-object.ttl> .
+
+<#datatypes>
+ a rdft:TestTurtleEval ;
+ mf:action <datatypes.ttl> ;
+ mf:name "datatypes" ;
+ mf:result <datatypes.ttl> .
+
+<#empty-anonymous-object>
+ a rdft:TestTurtleEval ;
+ mf:action <empty-anonymous-object.ttl> ;
+ mf:name "empty-anonymous-object" ;
+ mf:result <empty-anonymous-object.ttl> .
+
+<#empty-list-object>
+ a rdft:TestTurtleEval ;
+ mf:action <empty-list-object.ttl> ;
+ mf:name "empty-list-object" ;
+ mf:result <empty-list-object.ttl> .
+
+<#empty-list-subject>
+ a rdft:TestTurtleEval ;
+ mf:action <empty-list-subject.ttl> ;
+ mf:name "empty-list-subject" ;
+ mf:result <empty-list-subject.ttl> .
+
+<#empty-list-subject-and-object>
+ a rdft:TestTurtleEval ;
+ mf:action <empty-list-subject-and-object.ttl> ;
+ mf:name "empty-list-subject-and-object" ;
+ mf:result <empty-list-subject-and-object.ttl> .
+
+<#ext-named-blank>
+ a rdft:TestTurtleEval ;
+ mf:action <ext-named-blank.ttl> ;
+ mf:name "ext-named-blank" ;
+ mf:result <ext-named-blank.ttl> .
+
+<#graph-abbreviation>
+ a rdft:TestTrigEval ;
+ mf:action <graph-abbreviation.trig> ;
+ mf:name "graph-abbreviation" ;
+ mf:result <graph-abbreviation.trig> .
+
+<#langtags>
+ a rdft:TestTurtleEval ;
+ mf:action <langtags.ttl> ;
+ mf:name "langtags" ;
+ mf:result <langtags.ttl> .
+
+<#list-in-object>
+ a rdft:TestTurtleEval ;
+ mf:action <list-in-object.ttl> ;
+ mf:name "list-in-object" ;
+ mf:result <list-in-object.ttl> .
+
+<#list-object>
+ a rdft:TestTurtleEval ;
+ mf:action <list-object.ttl> ;
+ mf:name "list-object" ;
+ mf:result <list-object.ttl> .
+
+<#local-name-escapes>
+ a rdft:TestTurtleEval ;
+ mf:action <local-name-escapes.ttl> ;
+ mf:name "local-name-escapes" ;
+ mf:result <local-name-escapes.ttl> .
+
+<#long-string-escapes>
+ a rdft:TestTurtleEval ;
+ mf:action <long-string-escapes.ttl> ;
+ mf:name "long-string-escapes" ;
+ mf:result <long-string-escapes.ttl> .
+
+<#long-string-quotes>
+ a rdft:TestTurtleEval ;
+ mf:action <long-string-quotes.ttl> ;
+ mf:name "long-string-quotes" ;
+ mf:result <long-string-quotes.ttl> .
+
+<#many-objects>
+ a rdft:TestTurtleEval ;
+ mf:action <many-objects.ttl> ;
+ mf:name "many-objects" ;
+ mf:result <many-objects.ttl> .
+
+<#named-graph>
+ a rdft:TestTrigEval ;
+ mf:action <named-graph.trig> ;
+ mf:name "named-graph" ;
+ mf:result <named-graph.trig> .
+
+<#nested-list-object>
+ a rdft:TestTurtleEval ;
+ mf:action <nested-list-object.ttl> ;
+ mf:name "nested-list-object" ;
+ mf:result <nested-list-object.ttl> .
+
+<#short-string-escapes>
+ a rdft:TestTurtleEval ;
+ mf:action <short-string-escapes.ttl> ;
+ mf:name "short-string-escapes" ;
+ mf:result <short-string-escapes.ttl> .
+
+<#uri-escapes>
+ a rdft:TestTurtleEval ;
+ mf:action <uri-escapes.ttl> ;
+ mf:name "uri-escapes" ;
+ mf:result <uri-escapes.ttl> .
diff --git a/test/pretty/many-objects.ttl b/test/pretty/many-objects.ttl
new file mode 100644
index 00000000..534741dd
--- /dev/null
+++ b/test/pretty/many-objects.ttl
@@ -0,0 +1,9 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p1 "apple" ,
+ "banana" ,
+ "cherry" ;
+ eg:p2 "asparagus" ,
+ "beet" ,
+ "carrot" .
diff --git a/test/pretty/named-graph.trig b/test/pretty/named-graph.trig
new file mode 100644
index 00000000..5cd12f3b
--- /dev/null
+++ b/test/pretty/named-graph.trig
@@ -0,0 +1,8 @@
+@prefix eg: <http://example.org/> .
+
+eg:g {
+ eg:s
+ eg:p [
+ a eg:Object
+ ] .
+}
diff --git a/test/pretty/nested-list-object.ttl b/test/pretty/nested-list-object.ttl
new file mode 100644
index 00000000..d2177ba3
--- /dev/null
+++ b/test/pretty/nested-list-object.ttl
@@ -0,0 +1,12 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:list (
+ (
+ eg:l1e1
+ eg:l1e2
+ ) (
+ eg:l2e1
+ eg:l2e2
+ )
+ ) .
diff --git a/test/pretty/short-string-escapes.ttl b/test/pretty/short-string-escapes.ttl
new file mode 100644
index 00000000..0665e814
--- /dev/null
+++ b/test/pretty/short-string-escapes.ttl
@@ -0,0 +1,4 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:p "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\u000B\f\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\\\u007F" .
diff --git a/test/pretty/uri-escapes.ttl b/test/pretty/uri-escapes.ttl
new file mode 100644
index 00000000..09ced38a
--- /dev/null
+++ b/test/pretty/uri-escapes.ttl
@@ -0,0 +1,2 @@
+<http://example.org/s>
+ <http://example.org/p> <http://example.org/\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000D\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F!\u0022#$%&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\u005C]\u005E_\u0060abcdefghijklmnopqrstuvwxyz\u007B\u007C\u007D~\u007F> .
diff --git a/test/run_suite.py b/test/run_suite.py
new file mode 100755
index 00000000..423397b5
--- /dev/null
+++ b/test/run_suite.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+
+# Copyright 2022-2023 David Robillard <d@drobilla.net>
+# SPDX-License-Identifier: ISC
+
+"""Run a "simple" one-pass RDF-based test suite for serd."""
+
+import argparse
+import os
+import shlex
+import subprocess
+import sys
+import tempfile
+
+import serd_test_util as util
+
+NS_MF = "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"
+NS_RDFT = "http://www.w3.org/ns/rdftest#"
+
+DEVNULL = subprocess.DEVNULL
+PIPE = subprocess.PIPE
+
+TEST_TYPES = [
+ NS_RDFT + "TestNQuadsPositiveSyntax",
+ NS_RDFT + "TestNTriplesPositiveSyntax",
+ NS_RDFT + "TestTrigEval",
+ NS_RDFT + "TestTrigNegativeEval",
+ NS_RDFT + "TestTrigNegativeSyntax",
+ NS_RDFT + "TestTrigPositiveSyntax",
+ NS_RDFT + "TestTurtleEval",
+ NS_RDFT + "TestTurtleNegativeEval",
+ NS_RDFT + "TestTurtleNegativeSyntax",
+ NS_RDFT + "TestTurtlePositiveSyntax",
+]
+
+
+def run_eval_test(base_uri, command, in_path, good_path, out_path):
+ """Run a positive eval test and return whether the output matches."""
+
+ syntax = util.syntax_from_path(out_path)
+ command = command + ["-o", syntax, in_path, base_uri]
+
+ proc = subprocess.run(
+ command, check=True, encoding="utf-8", stderr=DEVNULL, stdout=PIPE
+ )
+
+ out = [l + "\n" for l in proc.stdout.split("\n")][:-1]
+ with open(good_path, "r", encoding="utf-8") as good:
+ return util.lines_equal(list(good), out, good_path, out_path)
+
+
+def run_entry(entry, base_uri, command_prefix, out_dir, suite_dir):
+ """Run a single test entry from the manifest."""
+
+ in_path = util.file_path(suite_dir, entry[NS_MF + "action"][0])
+ base = base_uri + os.path.basename(in_path)
+
+ good_path = in_path
+ if NS_MF + "result" in entry:
+ good_path = util.file_path(suite_dir, entry[NS_MF + "result"][0])
+
+ out_path = os.path.join(out_dir, os.path.basename(good_path))
+ return run_eval_test(base, command_prefix, in_path, good_path, out_path)
+
+
+def run_suite(args, command, out_dir):
+ """Run all tests in the manifest."""
+
+ # Load manifest model
+ top = os.path.dirname(args.manifest)
+ model, instances = util.load_rdf(args.manifest, args.base_uri, command)
+
+ # Run all the listed tests that have known types
+ command = command + args.arg
+ results = util.Results()
+ for klass, instances in instances.items():
+ check = klass in [NS_RDFT + "TestTrigEval", NS_RDFT + "TestTurtleEval"]
+ if klass == NS_MF + "Manifest":
+ continue
+
+ if klass not in TEST_TYPES:
+ raise RuntimeError("Unknown manifest entry type: " + klass)
+
+ for instance in instances:
+ try:
+ entry = model[instance]
+ if check and NS_MF + "result" not in entry:
+ raise RuntimeError("Eval test missing result: " + instance)
+
+ results.check(
+ run_entry(entry, args.base_uri, command, out_dir, top)
+ )
+
+ except subprocess.CalledProcessError as exception:
+ if exception.stderr is not None:
+ sys.stderr.write(exception.stderr)
+
+ results.check(False, str(exception) + "\n")
+
+ return util.print_result_summary(results)
+
+
+def main():
+ """Run the command line tool."""
+
+ parser = argparse.ArgumentParser(
+ usage="%(prog)s [OPTION]... MANIFEST BASE_URI -- [ARG]...",
+ description=__doc__,
+ )
+
+ parser.add_argument("--serdi", default="serdi", help="path to serdi")
+ 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("arg", nargs=argparse.REMAINDER, help="serdi argument")
+
+ args = parser.parse_args(sys.argv[1:])
+ command = shlex.split(args.wrapper) + [args.serdi]
+
+ with tempfile.TemporaryDirectory() as temp:
+ return run_suite(args, command, temp)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/test/serd_test_util/__init__.py b/test/serd_test_util/__init__.py
index 844c454c..7482762d 100644
--- a/test/serd_test_util/__init__.py
+++ b/test/serd_test_util/__init__.py
@@ -71,6 +71,25 @@ def uri_path(uri):
return path if not drive else path[1:]
+def file_path(suite_dir, uri):
+ """Return a relative path to a file in a test suite."""
+
+ return os.path.relpath(os.path.join(suite_dir, os.path.basename(uri)))
+
+
+def syntax_from_path(path):
+ """Return the serd syntax name corresponding to a file path."""
+
+ extensions = {
+ ".ttl": "turtle",
+ ".nt": "ntriples",
+ ".trig": "trig",
+ ".nq": "nquads",
+ }
+
+ return extensions[os.path.splitext(path)[1]]
+
+
def earl_assertion(test, passed, asserter):
"""Return a Turtle description of an assertion for the test report."""