aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-08-12 22:28:59 -0400
committerDavid Robillard <d@drobilla.net>2022-01-28 21:57:07 -0500
commit1409f161793a289819df1f31eccb579b71f45475 (patch)
tree4076c291a397f0a0a2ea560305ee87c47810df33
parent6ffe331cfc8af0735b263ab7af6f84b1b4418102 (diff)
downloadserd-1409f161793a289819df1f31eccb579b71f45475.tar.gz
serd-1409f161793a289819df1f31eccb579b71f45475.tar.bz2
serd-1409f161793a289819df1f31eccb579b71f45475.zip
Fix pretty-printing nested empty lists and add 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 from a model 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.
-rw-r--r--src/describe.c11
-rw-r--r--src/n3.c6
-rw-r--r--src/writer.c5
-rw-r--r--test/good/manifest.ttl7
-rw-r--r--test/good/pretty.trig98
-rw-r--r--test/good/test-pretty.nt46
-rw-r--r--test/good/test-pretty.ttl44
-rw-r--r--test/meson.build2
-rw-r--r--test/pretty/anonymous-in-list-object.ttl11
-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/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/list-in-object.ttl10
-rw-r--r--test/pretty/list-object.ttl8
-rw-r--r--test/pretty/list-subject-with-extras.ttl9
-rw-r--r--test/pretty/list-subject-with-list-extras.ttl12
-rw-r--r--test/pretty/list-subject.ttl8
-rw-r--r--test/pretty/manifest.ttl121
-rw-r--r--test/pretty/many-objects.ttl9
-rw-r--r--test/pretty/meson.build17
-rw-r--r--test/pretty/nested-list-object-with-empty-lists.ttl11
-rw-r--r--test/pretty/nested-list-object.ttl13
-rw-r--r--test/pretty/nested-list-subject.ttl13
-rwxr-xr-xtest/run_pretty_suite.py153
28 files changed, 446 insertions, 202 deletions
diff --git a/src/describe.c b/src/describe.c
index 9bd9d3ae..718eaf1c 100644
--- a/src/describe.c
+++ b/src/describe.c
@@ -204,7 +204,7 @@ write_range_statement(const SerdSink* const sink,
// First write inline list subject, which this statement will follow
if (zix_hash_insert(list_subjects, subject) != ZIX_STATUS_EXISTS) {
st = write_list(
- sink, model, list_subjects, 2, SERD_LIST_S, subject, graph);
+ sink, model, list_subjects, 2, flags | SERD_LIST_S, subject, graph);
}
}
}
@@ -226,9 +226,12 @@ write_range_statement(const SerdSink* const sink,
serd_cursor_free(iter);
} else if (object_style == LIST_O) { // Write list object like "( ... )"
- flags |= SERD_LIST_O;
- if (!(st = serd_sink_write_statement(sink, flags, statement))) {
- st = write_list(sink, model, list_subjects, depth + 1, 0, object, graph);
+ if (!(st =
+ serd_sink_write_statement(sink, flags | SERD_LIST_O, statement))) {
+ flags = flags & ~((unsigned)SERD_LIST_S);
+
+ st =
+ write_list(sink, model, list_subjects, depth + 1, flags, object, graph);
}
} else {
diff --git a/src/n3.c b/src/n3.c
index ff142976..3b12dfec 100644
--- a/src/n3.c
+++ b/src/n3.c
@@ -866,11 +866,11 @@ read_collection(SerdReader* const reader,
return SERD_ERR_OVERFLOW;
}
- if (ctx.subject) {
- // subject predicate _:head
+ if (ctx.subject) { // Reading a collection object
*ctx.flags |= (end ? 0 : SERD_LIST_O);
TRY(st, emit_statement(reader, ctx, *dest));
- } else {
+ *ctx.flags &= ~((unsigned)SERD_LIST_O);
+ } else { // Reading a collection subject
*ctx.flags |= (end ? 0 : SERD_LIST_S);
}
diff --git a/src/writer.c b/src/writer.c
index 5f9fdaab..452d3679 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -1064,6 +1064,11 @@ write_list_statement(SerdWriter* const writer,
SerdStatus st = SERD_SUCCESS;
bool is_end = false;
+ if (serd_node_equals(predicate, writer->world->rdf_first) &&
+ serd_node_equals(object, writer->world->rdf_nil)) {
+ return esink("()", 2, writer);
+ }
+
TRY(st, write_list_obj(writer, flags, predicate, object, &is_end));
if (is_end) {
pop_context(writer);
diff --git a/test/good/manifest.ttl b/test/good/manifest.ttl
index b546b9f7..0198a8c8 100644
--- a/test/good/manifest.ttl
+++ b/test/good/manifest.ttl
@@ -46,7 +46,6 @@
<#test-non-curie-uri>
<#test-num>
<#test-prefix>
- <#test-pretty>
<#test-rel>
<#test-semi-dot>
<#test-uri-escape>
@@ -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-rel>
rdf:type rdft:TestTurtleEval ;
mf:name "test-rel" ;
diff --git a/test/good/pretty.trig b/test/good/pretty.trig
deleted file mode 100644
index 140769ce..00000000
--- a/test/good/pretty.trig
+++ /dev/null
@@ -1,98 +0,0 @@
-@prefix : <http://example.org/> .
-@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
-
-:a
- :b :c ,
- :d ,
- :e ;
- :f :g ,
- :h .
-
-(
- 1
-)
- :isA :List .
-
-[]
- :isA :Blank .
-
-(
- 2
-)
- :sameAs (
- 2
- ) .
-
-[]
- :sameAs [] .
-
-(
- 1
- 2
-)
- a :List ;
- rdf:value 3 .
-
-(
- (
- 3
- )
- (
- 4
- )
-)
- a :NestedList ;
- :sum 7 .
-
-[
- a :BlankSubject
-]
- a rdf:Resource .
-
-[
- a :BlankSubject
-] .
-
-[]
- :blank [
- :nestedEmptyBlank [] ;
- :nestedNonEmptyBlanks [
- rdf:value 1
- ] , [
- rdf:value 2
- ]
- ] ;
- :lists (
- 3
- 4
- ) , (
- 5
- 6
- ) , (
- [
- rdf:value 7
- ]
- [
- rdf:value 8
- ]
- ) .
-
-:s
- a :Thing ;
- :predicate1 :object1 ,
- [
- a :SubThing ;
- :predicate2 :object2
- ] , [
- a :OtherSubThing ;
- :p3 :o3
- ] ;
- :p4 :o4 .
-
-eg:graph {
- :a
- :b :c ;
- :d [
- :e :f
- ] .
-}
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 c0ed56e4..c314e1d4 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -2,6 +2,7 @@ autoship = find_program('autoship', required: false)
run_filter_suite = find_program('run_filter_suite.py')
run_pipe_suite = find_program('run_pipe_suite.py')
+run_pretty_suite = find_program('run_pretty_suite.py')
run_sort_suite = find_program('run_sort_suite.py')
wrapper = meson.get_cross_property('exe_wrapper', '')
@@ -404,6 +405,7 @@ if is_variable('serd_pipe') and is_variable('serd_sort')
subdir('good')
subdir('lax')
subdir('pattern')
+ subdir('pretty')
subdir('terse')
endif
diff --git a/test/pretty/anonymous-in-list-object.ttl b/test/pretty/anonymous-in-list-object.ttl
new file mode 100644
index 00000000..a6b202f4
--- /dev/null
+++ b/test/pretty/anonymous-in-list-object.ttl
@@ -0,0 +1,11 @@
+@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/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/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/list-subject-with-extras.ttl b/test/pretty/list-subject-with-extras.ttl
new file mode 100644
index 00000000..dcfb8753
--- /dev/null
+++ b/test/pretty/list-subject-with-extras.ttl
@@ -0,0 +1,9 @@
+@prefix eg: <http://example.org/> .
+
+(
+ "apple"
+ "banana"
+ "cherry"
+)
+ eg:with eg:someProperties ;
+ a eg:ExampleList .
diff --git a/test/pretty/list-subject-with-list-extras.ttl b/test/pretty/list-subject-with-list-extras.ttl
new file mode 100644
index 00000000..0a0cd0e0
--- /dev/null
+++ b/test/pretty/list-subject-with-list-extras.ttl
@@ -0,0 +1,12 @@
+@prefix eg: <http://example.org/> .
+
+(
+ "apple"
+ "banana"
+ "cherry"
+)
+ eg:list (
+ "asparagus"
+ "beet"
+ "carrot"
+ ) .
diff --git a/test/pretty/list-subject.ttl b/test/pretty/list-subject.ttl
new file mode 100644
index 00000000..eb1c7a2e
--- /dev/null
+++ b/test/pretty/list-subject.ttl
@@ -0,0 +1,8 @@
+@prefix eg: <http://example.org/> .
+
+(
+ "apple"
+ "banana"
+ "cherry"
+)
+ a eg:ExampleList .
diff --git a/test/pretty/manifest.ttl b/test/pretty/manifest.ttl
new file mode 100644
index 00000000..faae5857
--- /dev/null
+++ b/test/pretty/manifest.ttl
@@ -0,0 +1,121 @@
+@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 cases" ;
+ mf:entries (
+ <#anonymous-in-list-object>
+ <#anonymous-object>
+ <#anonymous-subject-and-object>
+ <#anonymous-subject>
+ <#empty-anonymous-object>
+ <#empty-list-object>
+ <#empty-list-subject-and-object>
+ <#empty-list-subject>
+ <#list-in-object>
+ <#list-object>
+ <#list-subject-with-extras>
+ <#list-subject-with-list-extras>
+ <#list-subject>
+ <#many-objects>
+ <#nested-list-object-with-empty-lists>
+ <#nested-list-object>
+ <#nested-list-subject>
+ ) .
+
+<#anonymous-in-list-object>
+ a serd:TestTurtlePrint ;
+ mf:action <anonymous-in-list-object.ttl> ;
+ mf:name "anonymous-in-list-object" .
+
+<#anonymous-object>
+ a serd:TestTurtlePrint ;
+ mf:action <anonymous-object.ttl> ;
+ mf:name "anonymous-object" .
+
+<#anonymous-subject>
+ a serd:TestTurtlePrint ;
+ mf:action <anonymous-subject.ttl> ;
+ mf:name "anonymous-subject" .
+
+<#anonymous-subject-and-object>
+ a serd:TestTurtlePrint ;
+ mf:action <anonymous-subject-and-object.ttl> ;
+ mf:name "anonymous-subject-and-object" .
+
+<#empty-anonymous-object>
+ a serd:TestTurtlePrint ;
+ mf:action <empty-anonymous-object.ttl> ;
+ mf:name "empty-anonymous-object" .
+
+<#empty-list-object>
+ a serd:TestTurtlePrint ;
+ mf:action <empty-list-object.ttl> ;
+ mf:name "empty-list-object" .
+
+<#empty-list-subject>
+ a serd:TestTurtlePrint ;
+ mf:action <empty-list-subject.ttl> ;
+ mf:name "empty-list-subject" .
+
+<#empty-list-subject-and-object>
+ a serd:TestTurtlePrint ;
+ mf:action <empty-list-subject-and-object.ttl> ;
+ mf:name "empty-list-subject-and-object" .
+
+<#list-in-object>
+ a serd:TestTurtlePrint ;
+ mf:action <list-in-object.ttl> ;
+ mf:name "list-in-object" .
+
+<#list-object>
+ a serd:TestTurtlePrint ;
+ mf:action <list-object.ttl> ;
+ mf:name "list-object" .
+
+<#list-subject>
+ a serd:TestTurtlePrint ;
+ mf:action <list-subject.ttl> ;
+ mf:name "list-subject" .
+
+<#list-subject-with-extras>
+ a serd:TestTurtlePrint ;
+ mf:action <list-subject-with-extras.ttl> ;
+ mf:name "list-subject-with-extras" .
+
+<#list-subject-with-list-extras>
+ a serd:TestTurtlePrint ;
+ mf:action <list-subject-with-list-extras.ttl> ;
+ mf:name "list-subject-with-list-extras" .
+
+<#many-objects>
+ a serd:TestTurtlePrint ;
+ mf:action <many-objects.ttl> ;
+ mf:name "many-objects" .
+
+<#nested-list-object>
+ a serd:TestTurtlePrint ;
+ mf:action <nested-list-object.ttl> ;
+ mf:name "nested-list-object" .
+
+<#nested-list-object-with-empty-lists>
+ a serd:TestTurtlePrint ;
+ mf:action <nested-list-object-with-empty-lists.ttl> ;
+ mf:name "nested-list-object-with-empty-lists" .
+
+<#nested-list-subject>
+ a serd:TestTurtlePrint ;
+ mf:action <nested-list-subject.ttl> ;
+ mf:name "nested-list-subject" .
+
+serd:TestTurtlePrint
+ a rdfs:Class ;
+ rdfs:comment "Tests that a Turtle document pretty-prints exactly as written." ;
+ rdfs:label "Turtle Pretty-Printing" ;
+ rdfs:subClassOf rdft:Test .
+
+rdft:Test
+ rdfs:subClassOf mf:ManifestEntry .
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/meson.build b/test/pretty/meson.build
new file mode 100644
index 00000000..634f1e0e
--- /dev/null
+++ b/test/pretty/meson.build
@@ -0,0 +1,17 @@
+base_uri = 'http://drobilla.net/sw/serd/test/good/'
+
+args = [files('manifest.ttl')]
+
+test('pretty',
+ run_pretty_suite,
+ args: pipe_test_script_args + ['-o', meson.current_build_dir() / 'pipe'] + args,
+ env: test_env,
+ suite: ['suite', 'extra', 'pipe'],
+ timeout: 240)
+
+test('pretty',
+ run_pretty_suite,
+ args: sort_test_script_args + ['-o', meson.current_build_dir() / 'sort'] + args,
+ env: test_env,
+ suite: ['suite', 'extra', 'sort'],
+ timeout: 240)
diff --git a/test/pretty/nested-list-object-with-empty-lists.ttl b/test/pretty/nested-list-object-with-empty-lists.ttl
new file mode 100644
index 00000000..4760fd2b
--- /dev/null
+++ b/test/pretty/nested-list-object-with-empty-lists.ttl
@@ -0,0 +1,11 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:list (
+ (
+ ()
+ )
+ (
+ ()
+ )
+ ) .
diff --git a/test/pretty/nested-list-object.ttl b/test/pretty/nested-list-object.ttl
new file mode 100644
index 00000000..c4b1898f
--- /dev/null
+++ b/test/pretty/nested-list-object.ttl
@@ -0,0 +1,13 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ eg:list (
+ (
+ eg:l1e1
+ eg:l1e2
+ )
+ (
+ eg:l2e1
+ eg:l2e2
+ )
+ ) .
diff --git a/test/pretty/nested-list-subject.ttl b/test/pretty/nested-list-subject.ttl
new file mode 100644
index 00000000..b32aa133
--- /dev/null
+++ b/test/pretty/nested-list-subject.ttl
@@ -0,0 +1,13 @@
+@prefix eg: <http://example.org/> .
+
+(
+ (
+ eg:l1e1
+ eg:l1e2
+ )
+ (
+ eg:l2e1
+ eg:l2e2
+ )
+)
+ a eg:ExampleList .
diff --git a/test/run_pretty_suite.py b/test/run_pretty_suite.py
new file mode 100755
index 00000000..a17dd0f8
--- /dev/null
+++ b/test/run_pretty_suite.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+
+"""Run the RDF-based test suite for serd-filter."""
+
+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 log_error(message):
+ """Log an error message to stderr"""
+
+ sys.stderr.write("error: ")
+ sys.stderr.write(message)
+
+
+def _uri_path(test_dir, uri):
+ path = urllib.parse.urlparse(uri).path
+ drive = os.path.splitdrive(path[1:])[0]
+ path = path if not drive else path[1:]
+ return os.path.join(test_dir, os.path.basename(path))
+
+
+def test_suite(
+ manifest_path,
+ base_uri,
+ command_prefix,
+ out_dir,
+):
+ """Run all tests in the manifest."""
+
+ mf = "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"
+ suite_dir = os.path.dirname(manifest_path)
+
+ model, instances = serd_test_util.load_rdf(
+ command_prefix + ["-B", base_uri], manifest_path
+ )
+
+ os.makedirs(out_dir, exist_ok=True)
+
+ class Results:
+ def __init__(self):
+ self.n_tests = 0
+ self.n_failures = 0
+
+ def run_test(entry, results):
+ """Run a single test entry from the manifest."""
+
+ input_uri = model[entry][mf + "action"][0]
+ input_path = _uri_path(suite_dir, input_uri)
+
+ output_path = os.path.join(out_dir, os.path.basename(input_path))
+
+ command = command_prefix + [
+ "-B",
+ base_uri,
+ "-O",
+ "turtle",
+ "-o",
+ output_path,
+ input_path,
+ ]
+
+ # Read the flat input and pretty-print output
+ results.n_tests += 1
+ try:
+ subprocess.run(command, check=True)
+
+ # Check that the output is exactly the same as the expected result
+ if not serd_test_util.file_equals(input_path, output_path):
+ results.n_failures += 1
+ log_error(
+ "Output {} differs from {}\n".format(
+ output_path, input_path
+ )
+ )
+
+ except Exception as e:
+ log_error(str(e))
+ results.n_failures += 1
+
+ # Run all test types in the test suite
+ results = Results()
+ for klass, instances in instances.items():
+ if klass == "http://drobilla.net/ns/serd#TestTurtlePrint":
+ for entry in instances:
+ run_test(entry, results)
+
+ # Print result summary
+ if results.n_failures > 0:
+ log_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 -- [TOOL_OPTION]...",
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+
+ parser.add_argument("--tool", default="tools/serd-sort", help="executable")
+ parser.add_argument("--wrapper", default="", help="executable wrapper")
+
+ parser.add_argument(
+ "-o", "--out-dir", default="test/pretty", help="output directory"
+ )
+
+ parser.add_argument(
+ "--base-uri",
+ default="http://drobilla.net/sw/serd/test/",
+ help="base URI",
+ )
+ parser.add_argument("manifest", help="test suite manifest.ttl file")
+
+ args = parser.parse_args(sys.argv[1:])
+ wrapper_prefix = shlex.split(args.wrapper)
+ command_prefix = wrapper_prefix + [args.tool]
+
+ return test_suite(
+ args.manifest,
+ args.base_uri,
+ command_prefix,
+ args.out_dir,
+ )
+
+
+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)