diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/meson.build | 76 | ||||
-rwxr-xr-x | test/run_suite.py | 16 | ||||
-rw-r--r-- | test/sort/GOPS.nq | 13 | ||||
-rw-r--r-- | test/sort/GOSP.nq | 13 | ||||
-rw-r--r-- | test/sort/GPSO.nq | 13 | ||||
-rw-r--r-- | test/sort/GSOP.nq | 13 | ||||
-rw-r--r-- | test/sort/GSPO.nq | 13 | ||||
-rw-r--r-- | test/sort/OPS.nq | 13 | ||||
-rw-r--r-- | test/sort/OSP.nq | 13 | ||||
-rw-r--r-- | test/sort/POS.nq | 13 | ||||
-rw-r--r-- | test/sort/PSO.nq | 13 | ||||
-rw-r--r-- | test/sort/SOP.nq | 13 | ||||
-rw-r--r-- | test/sort/SPO.nq | 13 | ||||
-rw-r--r-- | test/sort/input.trig | 22 | ||||
-rw-r--r-- | test/sort/pretty.nq | 13 | ||||
-rw-r--r-- | test/sort/untyped.nq | 12 | ||||
-rw-r--r-- | test/test_cursor.c | 114 | ||||
-rw-r--r-- | test/test_free_null.c | 6 | ||||
-rw-r--r-- | test/test_model.c | 1494 | ||||
-rwxr-xr-x | test/test_sort.py | 99 | ||||
-rw-r--r-- | test/test_string.c | 2 |
21 files changed, 1990 insertions, 7 deletions
diff --git a/test/meson.build b/test/meson.build index 6ca0e38b..8e0688f6 100644 --- a/test/meson.build +++ b/test/meson.build @@ -23,6 +23,7 @@ simple_script_paths = [ 'test_multifile.py', 'test_patterns.py', 'test_quiet.py', + 'test_sort.py', 'test_stdin.py', 'test_write_error.py', ] @@ -129,10 +130,12 @@ subdir('headers') unit_tests = [ 'canon', 'caret', + 'cursor', 'env', 'filter', 'free_null', 'log', + 'model', 'node', 'node_syntax', 'nodes', @@ -227,6 +230,18 @@ simple_command_tests = { ['-k', '512', '-s', '<go:>a<go:> .'], ], }, + 'sort': { + 'bad': [ + ['-c', 'CHAOS'], + ['-c'], + ['-z'], + ['/no/such/file'], + ], + 'good': [ + ['-V'], + ['-h'], + ], + }, } foreach tool, tests : simple_command_tests @@ -441,7 +456,7 @@ if is_variable('serd_filter') # Command line options test( - 'garbage_pattern', + 'bad_pattern', tool, args: ['junk', serd_ttl], env: test_env, @@ -489,6 +504,34 @@ if is_variable('serd_filter') subdir('extra/filter') endif +# Tests specific to serd-sort +if is_variable('serd_sort') + sort_script_args = common_script_args + ['--tool', serd_sort] + + test( + 'stdin', + files('test_stdin.py'), + args: sort_script_args, + env: test_env, + suite: ['tools', 'sort', 'input'], + ) + test( + 'missing_output', + serd_sort, + args: ['-o', '/does/not/exist.ttl', serd_ttl], + env: test_env, + should_fail: true, + suite: ['tools', 'sort', 'output'], + ) + test( + 'sort', + files('test_sort.py'), + args: sort_script_args + files('sort/input.trig'), + env: test_env, + suite: ['tools', 'sort'], + ) +endif + ########################### # Data-Driven Test Suites # ########################### @@ -651,7 +694,36 @@ if is_variable('serd_pipe') run_suite, args: pipe_script_args + args, env: test_env, - suite: ['suite'], + suite: ['suite', 'pipe'], + timeout: 240, + ) + endforeach +endif + +# Run model test suites using serd-sort +if is_variable('serd_sort') + sort_suite_names = [ + 'nquads', + 'ntriples', + 'trig', + 'turtle', + + 'good', + 'pattern', + 'perfect_forward', + 'perfect_reverse', + 'qualify', + 'terse', + ] + + foreach name : sort_suite_names + args = test_suites[name] + test( + name, + run_suite, + args: sort_script_args + ['--unique'] + args, + env: test_env, + suite: ['suite', 'sort'], timeout: 240, ) endforeach diff --git a/test/run_suite.py b/test/run_suite.py index 463e40c4..aa95b90f 100755 --- a/test/run_suite.py +++ b/test/run_suite.py @@ -37,7 +37,7 @@ TEST_TYPES = [ ] -def run_eval_test(command, in_path, good_path, out_path): +def run_eval_test(command, in_path, good_path, out_path, getlines): """Run a positive eval test and return whether the output matches.""" command = command + ["-o", out_path, in_path] @@ -45,7 +45,9 @@ def run_eval_test(command, in_path, good_path, out_path): with open(good_path, "r", encoding="utf-8") as good: with open(out_path, "r", encoding="utf-8") as out: - return util.lines_equal(list(good), list(out), good_path, out_path) + return util.lines_equal( + getlines(good), getlines(out), good_path, out_path + ) def run_positive_test(command, in_path): @@ -93,8 +95,13 @@ def run_entry(args, entry, command, out_dir, suite_dir): if args.reverse: in_path, good_path = good_path, in_path - out_path = os.path.join(out_dir, os.path.basename(good_path)) - return run_eval_test(command, in_path, good_path, out_path) + return run_eval_test( + command, + in_path, + good_path, + os.path.join(out_dir, os.path.basename(good_path)), + lambda f: sorted(set(f)) if args.unique else list(f), + ) def run_suite(args, command, out_dir): @@ -154,6 +161,7 @@ def main(): parser.add_argument("--report", help="path to write result report to") parser.add_argument("--reverse", action="store_true", help="reverse test") parser.add_argument("--tool", default="tools/serd-pipe", help="executable") + parser.add_argument("--unique", action="store_true", help="sort lines") 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") diff --git a/test/sort/GOPS.nq b/test/sort/GOPS.nq new file mode 100644 index 00000000..3d033b6e --- /dev/null +++ b/test/sort/GOPS.nq @@ -0,0 +1,13 @@ +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . diff --git a/test/sort/GOSP.nq b/test/sort/GOSP.nq new file mode 100644 index 00000000..3d033b6e --- /dev/null +++ b/test/sort/GOSP.nq @@ -0,0 +1,13 @@ +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . diff --git a/test/sort/GPSO.nq b/test/sort/GPSO.nq new file mode 100644 index 00000000..67e13aa7 --- /dev/null +++ b/test/sort/GPSO.nq @@ -0,0 +1,13 @@ +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . diff --git a/test/sort/GSOP.nq b/test/sort/GSOP.nq new file mode 100644 index 00000000..dbfcb7c1 --- /dev/null +++ b/test/sort/GSOP.nq @@ -0,0 +1,13 @@ +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . diff --git a/test/sort/GSPO.nq b/test/sort/GSPO.nq new file mode 100644 index 00000000..5aab6e1e --- /dev/null +++ b/test/sort/GSPO.nq @@ -0,0 +1,13 @@ +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . diff --git a/test/sort/OPS.nq b/test/sort/OPS.nq new file mode 100644 index 00000000..593760be --- /dev/null +++ b/test/sort/OPS.nq @@ -0,0 +1,13 @@ +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . diff --git a/test/sort/OSP.nq b/test/sort/OSP.nq new file mode 100644 index 00000000..593760be --- /dev/null +++ b/test/sort/OSP.nq @@ -0,0 +1,13 @@ +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . diff --git a/test/sort/POS.nq b/test/sort/POS.nq new file mode 100644 index 00000000..20eabfe1 --- /dev/null +++ b/test/sort/POS.nq @@ -0,0 +1,13 @@ +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . diff --git a/test/sort/PSO.nq b/test/sort/PSO.nq new file mode 100644 index 00000000..113dd549 --- /dev/null +++ b/test/sort/PSO.nq @@ -0,0 +1,13 @@ +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . diff --git a/test/sort/SOP.nq b/test/sort/SOP.nq new file mode 100644 index 00000000..7867eb24 --- /dev/null +++ b/test/sort/SOP.nq @@ -0,0 +1,13 @@ +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . diff --git a/test/sort/SPO.nq b/test/sort/SPO.nq new file mode 100644 index 00000000..2b09a976 --- /dev/null +++ b/test/sort/SPO.nq @@ -0,0 +1,13 @@ +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . diff --git a/test/sort/input.trig b/test/sort/input.trig new file mode 100644 index 00000000..9561d9b8 --- /dev/null +++ b/test/sort/input.trig @@ -0,0 +1,22 @@ +@prefix eg: <http://example.org/> . + +eg:graph1 { + eg:s + a eg:Subject ; + eg:blank [ + a eg:Anonymous ; + eg:with eg:aProperty , + eg:orAnother + ] ; + eg:list ( + 1 + 2 + ) ; + eg:literal "s1" . +} + +eg:graph2 { + eg:a + a eg:OtherSubject ; + eg:b eg:c . +} diff --git a/test/sort/pretty.nq b/test/sort/pretty.nq new file mode 100644 index 00000000..97851b82 --- /dev/null +++ b/test/sort/pretty.nq @@ -0,0 +1,13 @@ +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Anonymous> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . diff --git a/test/sort/untyped.nq b/test/sort/untyped.nq new file mode 100644 index 00000000..74625d7d --- /dev/null +++ b/test/sort/untyped.nq @@ -0,0 +1,12 @@ +<http://example.org/s> <http://example.org/blank> _:b1 <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/aProperty> <http://example.org/graph1> . +_:b1 <http://example.org/with> <http://example.org/orAnother> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/list> _:b2 <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "1"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "2"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/graph1> . +_:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.org/graph1> . +<http://example.org/s> <http://example.org/literal> "s1" <http://example.org/graph1> . +<http://example.org/s> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Subject> <http://example.org/graph1> . +<http://example.org/a> <http://example.org/b> <http://example.org/c> <http://example.org/graph2> . +<http://example.org/a> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/OtherSubject> <http://example.org/graph2> . diff --git a/test/test_cursor.c b/test/test_cursor.c new file mode 100644 index 00000000..fbc78271 --- /dev/null +++ b/test/test_cursor.c @@ -0,0 +1,114 @@ +// Copyright 2011-2021 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#undef NDEBUG + +#include "serd/cursor.h" +#include "serd/model.h" +#include "serd/node.h" +#include "serd/nodes.h" +#include "serd/status.h" +#include "serd/world.h" + +#include <assert.h> +#include <stddef.h> + +static void +test_copy(void) +{ + assert(!serd_cursor_copy(NULL, NULL)); + + SerdWorld* const world = serd_world_new(NULL); + SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); + SerdCursor* const begin = serd_model_begin(NULL, model); + SerdCursor* const copy = serd_cursor_copy(NULL, begin); + + assert(serd_cursor_equals(copy, begin)); + + serd_cursor_free(NULL, copy); + serd_cursor_free(NULL, begin); + serd_model_free(model); + serd_world_free(world); +} + +static void +test_comparison(void) +{ + SerdWorld* const world = serd_world_new(NULL); + SerdNodes* const nodes = serd_world_nodes(world); + SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); + + serd_model_add_index(model, SERD_ORDER_OPS); + + const SerdNode* const a = + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/a")); + + const SerdNode* const b = + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/b")); + + const SerdNode* const c = + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/c")); + + // Add a single statement + assert(!serd_model_add(model, a, b, c, NULL)); + + // Make cursors that point to the statement but via different patterns + SerdCursor* const c1 = serd_model_find(NULL, model, a, NULL, NULL, NULL); + SerdCursor* const c2 = serd_model_find(NULL, model, a, b, NULL, NULL); + SerdCursor* const c3 = serd_model_find(NULL, model, NULL, b, c, NULL); + + // Ensure that they refer to the same statement but are not equal + assert(c1); + assert(c2); + assert(c3); + assert(serd_cursor_get(c1) == serd_cursor_get(c2)); + assert(serd_cursor_get(c2) == serd_cursor_get(c3)); + assert(!serd_cursor_equals(c1, c2)); + assert(!serd_cursor_equals(c2, c3)); + assert(!serd_cursor_equals(c1, c3)); + + // Check that none are equal to begin (which has a different mode) or end + SerdCursor* const begin = serd_model_begin(NULL, model); + assert(!serd_cursor_equals(c1, begin)); + assert(!serd_cursor_equals(c2, begin)); + assert(!serd_cursor_equals(c3, begin)); + assert(!serd_cursor_equals(c1, serd_model_end(model))); + assert(!serd_cursor_equals(c2, serd_model_end(model))); + assert(!serd_cursor_equals(c3, serd_model_end(model))); + serd_cursor_free(NULL, begin); + + // Check that a cursor that points to it via the same pattern is equal + SerdCursor* const c4 = serd_model_find(NULL, model, a, b, NULL, NULL); + assert(c4); + assert(serd_cursor_get(c4) == serd_cursor_get(c1)); + assert(serd_cursor_equals(c4, c2)); + assert(!serd_cursor_equals(c4, c3)); + serd_cursor_free(NULL, c4); + + // Advance everything to the end + assert(serd_cursor_advance(c1) == SERD_FAILURE); + assert(serd_cursor_advance(c2) == SERD_FAILURE); + assert(serd_cursor_advance(c3) == SERD_FAILURE); + + // Check that they are now equal, and equal to the model's end + assert(serd_cursor_equals(c1, c2)); + assert(serd_cursor_equals(c1, serd_model_end(model))); + assert(serd_cursor_equals(c2, serd_model_end(model))); + + serd_cursor_free(NULL, c3); + serd_cursor_free(NULL, c2); + serd_cursor_free(NULL, c1); + serd_model_free(model); + serd_world_free(world); +} + +int +main(void) +{ + assert(serd_cursor_advance(NULL) == SERD_FAILURE); + + test_copy(); + test_comparison(); + + return 0; +} diff --git a/test/test_free_null.c b/test/test_free_null.c index 7b186fba..89846c7f 100644 --- a/test/test_free_null.c +++ b/test/test_free_null.c @@ -4,12 +4,15 @@ #undef NDEBUG #include "serd/caret.h" +#include "serd/cursor.h" #include "serd/env.h" #include "serd/memory.h" +#include "serd/model.h" #include "serd/node.h" #include "serd/nodes.h" #include "serd/reader.h" #include "serd/sink.h" +#include "serd/statement.h" #include "serd/world.h" #include "serd/writer.h" @@ -27,6 +30,9 @@ main(void) serd_writer_free(NULL); serd_nodes_free(NULL); serd_caret_free(NULL, NULL); + serd_model_free(NULL); + serd_statement_free(NULL, NULL); + serd_cursor_free(NULL, NULL); return 0; } diff --git a/test/test_model.c b/test/test_model.c new file mode 100644 index 00000000..feb35440 --- /dev/null +++ b/test/test_model.c @@ -0,0 +1,1494 @@ +// Copyright 2011-2021 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#undef NDEBUG + +#include "failing_allocator.h" + +#include "serd/buffer.h" +#include "serd/caret.h" +#include "serd/cursor.h" +#include "serd/describe.h" +#include "serd/env.h" +#include "serd/inserter.h" +#include "serd/log.h" +#include "serd/memory.h" +#include "serd/model.h" +#include "serd/node.h" +#include "serd/nodes.h" +#include "serd/output_stream.h" +#include "serd/sink.h" +#include "serd/statement.h" +#include "serd/status.h" +#include "serd/string_view.h" +#include "serd/syntax.h" +#include "serd/world.h" +#include "serd/writer.h" +#include "zix/attributes.h" + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define WILDCARD_NODE NULL + +#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +#define RDF_FIRST NS_RDF "first" +#define RDF_REST NS_RDF "rest" +#define RDF_NIL NS_RDF "nil" + +#define N_OBJECTS_PER 2U + +typedef const SerdNode* Quad[4]; + +typedef struct { + Quad query; + int expected_num_results; +} QueryTest; + +static const SerdNode* +uri(SerdWorld* world, const unsigned num) +{ + char str[] = "http://example.org/0000000000"; + + snprintf(str + 19, 11, "%010u", num); + + return serd_nodes_get(serd_world_nodes(world), serd_a_uri_string(str)); +} + +static int +generate(SerdWorld* world, + SerdModel* model, + size_t n_quads, + const SerdNode* graph) +{ + SerdNodes* nodes = serd_world_nodes(world); + SerdStatus st = SERD_SUCCESS; + + for (unsigned i = 0; i < n_quads; ++i) { + unsigned num = (i * N_OBJECTS_PER) + 1U; + + const SerdNode* ids[2 + N_OBJECTS_PER]; + for (unsigned j = 0; j < 2 + N_OBJECTS_PER; ++j) { + ids[j] = uri(world, num++); + } + + for (unsigned j = 0; j < N_OBJECTS_PER; ++j) { + st = serd_model_add(model, ids[0], ids[1], ids[2 + j], graph); + assert(!st); + } + } + + // Add some literals + + // (98 4 "hello") and (98 4 "hello"^^<5>) + const SerdNode* hello = serd_nodes_get(nodes, serd_a_string("hello")); + + const SerdNode* hello_gb = serd_nodes_get( + nodes, serd_a_plain_literal(serd_string("hello"), serd_string("en-gb"))); + + const SerdNode* hello_us = serd_nodes_get( + nodes, serd_a_plain_literal(serd_string("hello"), serd_string("en-us"))); + + const SerdNode* hello_t4 = + serd_nodes_get(nodes, + serd_a_typed_literal(serd_string("hello"), + serd_node_string_view(uri(world, 4)))); + + const SerdNode* hello_t5 = + serd_nodes_get(nodes, + serd_a_typed_literal(serd_string("hello"), + serd_node_string_view(uri(world, 5)))); + + assert(!serd_model_add(model, uri(world, 98), uri(world, 4), hello, graph)); + assert( + !serd_model_add(model, uri(world, 98), uri(world, 4), hello_t5, graph)); + + // (96 4 "hello"^^<4>) and (96 4 "hello"^^<5>) + assert( + !serd_model_add(model, uri(world, 96), uri(world, 4), hello_t4, graph)); + assert( + !serd_model_add(model, uri(world, 96), uri(world, 4), hello_t5, graph)); + + // (94 5 "hello") and (94 5 "hello"@en-gb) + assert(!serd_model_add(model, uri(world, 94), uri(world, 5), hello, graph)); + assert( + !serd_model_add(model, uri(world, 94), uri(world, 5), hello_gb, graph)); + + // (92 6 "hello"@en-us) and (92 6 "hello"@en-gb) + assert( + !serd_model_add(model, uri(world, 92), uri(world, 6), hello_us, graph)); + assert( + !serd_model_add(model, uri(world, 92), uri(world, 6), hello_gb, graph)); + + // (14 6 "bonjour"@fr) and (14 6 "salut"@fr) + + const SerdNode* const bonjour = serd_nodes_get( + nodes, serd_a_plain_literal(serd_string("bonjour"), serd_string("fr"))); + + const SerdNode* const salut = serd_nodes_get( + nodes, serd_a_plain_literal(serd_string("salut"), serd_string("fr"))); + + assert(!serd_model_add(model, uri(world, 14), uri(world, 6), bonjour, graph)); + assert(!serd_model_add(model, uri(world, 14), uri(world, 6), salut, graph)); + + // Attempt to add duplicates + assert(serd_model_add(model, uri(world, 14), uri(world, 6), salut, graph)); + + // Add a blank node subject + const SerdNode* ablank = + serd_nodes_get(nodes, serd_a_blank(serd_string("ablank"))); + + assert(!serd_model_add(model, ablank, uri(world, 6), salut, graph)); + + // Add statement with URI object + assert(!serd_model_add(model, ablank, uri(world, 6), uri(world, 7), graph)); + + return EXIT_SUCCESS; +} + +static int +test_read(SerdWorld* world, + SerdModel* model, + const SerdNode* g, + const unsigned n_quads) +{ + SerdAllocator* const allocator = serd_default_allocator(); + SerdNodes* const nodes = serd_nodes_new(allocator); + + SerdCursor* cursor = serd_model_begin(NULL, model); + const SerdStatement* prev = NULL; + for (; !serd_cursor_equals(cursor, serd_model_end(model)); + serd_cursor_advance(cursor)) { + const SerdStatement* statement = serd_cursor_get(cursor); + assert(statement); + assert(serd_statement_subject(statement)); + assert(serd_statement_predicate(statement)); + assert(serd_statement_object(statement)); + assert(!serd_statement_equals(statement, prev)); + assert(!serd_statement_equals(prev, statement)); + prev = statement; + } + + // Attempt to increment past end + assert(serd_cursor_advance(cursor) == SERD_BAD_CURSOR); + serd_cursor_free(NULL, cursor); + + const SerdStringView s = serd_string("hello"); + + const SerdNode* plain_hello = serd_nodes_get(nodes, serd_a_string_view(s)); + + const SerdNode* type4_hello = serd_nodes_get( + nodes, serd_a_typed_literal(s, serd_node_string_view(uri(world, 4)))); + + const SerdNode* type5_hello = serd_nodes_get( + nodes, serd_a_typed_literal(s, serd_node_string_view(uri(world, 5)))); + + const SerdNode* gb_hello = + serd_nodes_get(nodes, serd_a_plain_literal(s, serd_string("en-gb"))); + + const SerdNode* us_hello = + serd_nodes_get(nodes, serd_a_plain_literal(s, serd_string("en-us"))); + +#define NUM_PATTERNS 18 + + QueryTest patterns[NUM_PATTERNS] = { + {{NULL, NULL, NULL}, (int)(n_quads * N_OBJECTS_PER) + 12}, + {{uri(world, 1), WILDCARD_NODE, WILDCARD_NODE}, 2}, + {{uri(world, 9), uri(world, 9), uri(world, 9)}, 0}, + {{uri(world, 1), uri(world, 2), uri(world, 4)}, 1}, + {{uri(world, 3), uri(world, 4), WILDCARD_NODE}, 2}, + {{WILDCARD_NODE, uri(world, 2), uri(world, 4)}, 1}, + {{WILDCARD_NODE, WILDCARD_NODE, uri(world, 4)}, 1}, + {{uri(world, 1), WILDCARD_NODE, WILDCARD_NODE}, 2}, + {{uri(world, 1), WILDCARD_NODE, uri(world, 4)}, 1}, + {{WILDCARD_NODE, uri(world, 2), WILDCARD_NODE}, 2}, + {{uri(world, 98), uri(world, 4), plain_hello}, 1}, + {{uri(world, 98), uri(world, 4), type5_hello}, 1}, + {{uri(world, 96), uri(world, 4), type4_hello}, 1}, + {{uri(world, 96), uri(world, 4), type5_hello}, 1}, + {{uri(world, 94), uri(world, 5), plain_hello}, 1}, + {{uri(world, 94), uri(world, 5), gb_hello}, 1}, + {{uri(world, 92), uri(world, 6), gb_hello}, 1}, + {{uri(world, 92), uri(world, 6), us_hello}, 1}}; + + Quad match = {uri(world, 1), uri(world, 2), uri(world, 4), g}; + assert(serd_model_ask(model, match[0], match[1], match[2], match[3])); + + Quad nomatch = {uri(world, 1), uri(world, 2), uri(world, 9), g}; + assert( + !serd_model_ask(model, nomatch[0], nomatch[1], nomatch[2], nomatch[3])); + + assert(!serd_model_get(model, NULL, NULL, uri(world, 3), g)); + assert(!serd_model_get(model, uri(world, 1), uri(world, 99), NULL, g)); + + assert(serd_node_equals( + serd_model_get(model, uri(world, 1), uri(world, 2), NULL, g), + uri(world, 3))); + assert(serd_node_equals( + serd_model_get(model, uri(world, 1), NULL, uri(world, 3), g), + uri(world, 2))); + assert(serd_node_equals( + serd_model_get(model, NULL, uri(world, 2), uri(world, 3), g), + uri(world, 1))); + if (g) { + assert(serd_node_equals( + serd_model_get(model, uri(world, 1), uri(world, 2), uri(world, 3), NULL), + g)); + } + + for (unsigned i = 0; i < NUM_PATTERNS; ++i) { + QueryTest test = patterns[i]; + Quad pat = {test.query[0], test.query[1], test.query[2], g}; + + SerdCursor* range = + serd_model_find(NULL, model, pat[0], pat[1], pat[2], pat[3]); + int num_results = 0; + for (; !serd_cursor_is_end(range); serd_cursor_advance(range)) { + ++num_results; + + const SerdStatement* first = serd_cursor_get(range); + assert(first); + assert(serd_statement_matches(first, pat[0], pat[1], pat[2], pat[3])); + } + + serd_cursor_free(NULL, range); + + assert(num_results == test.expected_num_results); + } + +#undef NUM_PATTERNS + + // Query blank node subject + + const SerdNode* ablank = + serd_nodes_get(nodes, serd_a_blank(serd_string("ablank"))); + + Quad pat = {ablank, 0, 0}; + int num_results = 0; + SerdCursor* range = + serd_model_find(NULL, model, pat[0], pat[1], pat[2], pat[3]); + + for (; !serd_cursor_is_end(range); serd_cursor_advance(range)) { + ++num_results; + const SerdStatement* statement = serd_cursor_get(range); + assert(serd_statement_matches(statement, pat[0], pat[1], pat[2], pat[3])); + } + serd_cursor_free(NULL, range); + + assert(num_results == 2); + + // Test nested queries + const SerdNode* last_subject = 0; + range = serd_model_find(NULL, model, NULL, NULL, NULL, NULL); + for (; !serd_cursor_is_end(range); serd_cursor_advance(range)) { + const SerdStatement* statement = serd_cursor_get(range); + const SerdNode* subject = serd_statement_subject(statement); + if (subject == last_subject) { + continue; + } + + Quad subpat = {subject, 0, 0}; + SerdCursor* const subrange = + serd_model_find(NULL, model, subpat[0], subpat[1], subpat[2], subpat[3]); + + assert(subrange); + + const SerdStatement* substatement = serd_cursor_get(subrange); + uint64_t num_sub_results = 0; + assert(serd_statement_subject(substatement) == subject); + for (; !serd_cursor_is_end(subrange); serd_cursor_advance(subrange)) { + const SerdStatement* const front = serd_cursor_get(subrange); + assert(front); + + assert(serd_statement_matches( + front, subpat[0], subpat[1], subpat[2], subpat[3])); + + ++num_sub_results; + } + serd_cursor_free(NULL, subrange); + assert(num_sub_results == N_OBJECTS_PER); + + uint64_t count = serd_model_count(model, subject, 0, 0, 0); + assert(count == num_sub_results); + + last_subject = subject; + } + serd_cursor_free(NULL, range); + + serd_nodes_free(nodes); + return 0; +} + +static SerdStatus +expected_error(void* const handle, + const SerdLogLevel level, + const size_t n_fields, + const SerdLogField* const fields, + const SerdStringView message) +{ + (void)level; + (void)n_fields; + (void)fields; + (void)handle; + + fprintf(stderr, "expected: %s\n", message.data); + return SERD_SUCCESS; +} + +ZIX_PURE_FUNC static SerdStatus +ignore_only_index_error(void* const handle, + const SerdLogLevel level, + const size_t n_fields, + const SerdLogField* const fields, + const SerdStringView message) +{ + (void)handle; + (void)level; + (void)n_fields; + (void)fields; + + const bool is_index_error = strstr(message.data, "index"); + + assert(is_index_error); + + return is_index_error ? SERD_SUCCESS : SERD_UNKNOWN_ERROR; +} + +static int +test_failed_new_alloc(SerdWorld* ignored_world, const unsigned n_quads) +{ + (void)ignored_world; + (void)n_quads; + + SerdFailingAllocator allocator = serd_failing_allocator(); + SerdWorld* const world = serd_world_new(&allocator.base); + const size_t n_world_allocs = allocator.n_allocations; + + // Successfully allocate a model to count the number of allocations + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + assert(model); + + // Test that each allocation failing is handled gracefully + const size_t n_new_allocs = allocator.n_allocations - n_world_allocs; + for (size_t i = 0; i < n_new_allocs; ++i) { + allocator.n_remaining = i; + assert(!serd_model_new(world, SERD_ORDER_SPO, 0U)); + } + + serd_model_free(model); + serd_world_free(world); + return 0; +} + +static int +test_free_null(SerdWorld* world, const unsigned n_quads) +{ + (void)world; + (void)n_quads; + + serd_model_free(NULL); // Shouldn't crash + return 0; +} + +static int +test_get_world(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + assert(serd_model_world(model) == world); + serd_model_free(model); + return 0; +} + +static int +test_get_default_order(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* const model1 = serd_model_new(world, SERD_ORDER_SPO, 0U); + SerdModel* const model2 = serd_model_new(world, SERD_ORDER_GPSO, 0U); + + assert(serd_model_default_order(model1) == SERD_ORDER_SPO); + assert(serd_model_default_order(model2) == SERD_ORDER_GPSO); + + serd_model_free(model2); + serd_model_free(model1); + return 0; +} + +static int +test_get_flags(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + const SerdModelFlags flags = SERD_STORE_GRAPHS | SERD_STORE_CARETS; + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, flags); + + assert(serd_model_flags(model) & SERD_STORE_GRAPHS); + assert(serd_model_flags(model) & SERD_STORE_CARETS); + serd_model_free(model); + return 0; +} + +static int +test_all_begin(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + SerdCursor* begin = serd_model_begin(NULL, model); + SerdCursor* first = serd_model_find(NULL, model, NULL, NULL, NULL, NULL); + + assert(serd_cursor_equals(begin, first)); + + serd_cursor_free(NULL, first); + serd_cursor_free(NULL, begin); + serd_model_free(model); + return 0; +} + +static int +test_begin_ordered(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + + assert( + !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0)); + + SerdCursor* i = serd_model_begin_ordered(NULL, model, SERD_ORDER_SPO); + assert(i); + assert(!serd_cursor_is_end(i)); + serd_cursor_free(NULL, i); + + i = serd_model_begin_ordered(NULL, model, SERD_ORDER_POS); + assert(serd_cursor_is_end(i)); + serd_cursor_free(NULL, i); + + serd_model_free(model); + return 0; +} + +static int +test_add_with_iterator(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + + serd_set_log_func(world, expected_error, NULL); + assert( + !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0)); + + // Add a statement with an active iterator + SerdCursor* iter = serd_model_begin(NULL, model); + assert( + !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 4), 0)); + + // Check that iterator has been invalidated + assert(!serd_cursor_get(iter)); + assert(serd_cursor_advance(iter) == SERD_BAD_CURSOR); + + serd_cursor_free(NULL, iter); + serd_model_free(model); + return 0; +} + +static int +test_add_remove_nodes(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + + assert(serd_model_nodes(model)); + assert(serd_nodes_size(serd_model_nodes(model)) == 0); + + const SerdNode* const a = uri(world, 1); + const SerdNode* const b = uri(world, 2); + const SerdNode* const c = uri(world, 3); + + // Add 2 statements with 3 nodes + assert(!serd_model_add(model, a, b, a, NULL)); + assert(!serd_model_add(model, c, b, c, NULL)); + assert(serd_model_size(model) == 2); + assert(serd_nodes_size(serd_model_nodes(model)) == 3); + + // Remove one statement to leave 2 nodes + SerdCursor* const begin = serd_model_begin(NULL, model); + assert(!serd_model_erase(model, begin)); + assert(serd_model_size(model) == 1); + assert(serd_nodes_size(serd_model_nodes(model)) == 2); + serd_cursor_free(NULL, begin); + + // Clear the last statement to leave 0 nodes + assert(!serd_model_clear(model)); + assert(serd_nodes_size(serd_model_nodes(model)) == 0); + + serd_model_free(model); + return 0; +} + +static int +test_add_index(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); + const SerdNode* const s = uri(world, 0); + const SerdNode* const p = uri(world, 1); + const SerdNode* const o1 = uri(world, 2); + const SerdNode* const o2 = uri(world, 3); + + // Try to add an existing index + assert(serd_model_add_index(model, SERD_ORDER_SPO) == SERD_FAILURE); + + // Add a couple of statements + serd_model_add(model, s, p, o1, NULL); + serd_model_add(model, s, p, o2, NULL); + assert(serd_model_size(model) == 2); + + // Add a new index + assert(!serd_model_add_index(model, SERD_ORDER_PSO)); + + // Count statements via the new index + size_t count = 0U; + SerdCursor* cur = serd_model_find(NULL, model, NULL, p, NULL, NULL); + while (!serd_cursor_is_end(cur)) { + ++count; + serd_cursor_advance(cur); + } + serd_cursor_free(NULL, cur); + + serd_model_free(model); + assert(count == 2); + return 0; +} + +static int +test_remove_index(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); + const SerdNode* const s = uri(world, 0); + const SerdNode* const p = uri(world, 1); + const SerdNode* const o1 = uri(world, 2); + const SerdNode* const o2 = uri(world, 3); + + // Try to remove default and non-existent indices + assert(serd_model_drop_index(model, SERD_ORDER_SPO) == SERD_BAD_CALL); + assert(serd_model_drop_index(model, SERD_ORDER_PSO) == SERD_FAILURE); + + // Add a couple of statements so that dropping an index isn't trivial + serd_model_add(model, s, p, o1, NULL); + serd_model_add(model, s, p, o2, NULL); + assert(serd_model_size(model) == 2); + + assert(serd_model_add_index(model, SERD_ORDER_PSO) == SERD_SUCCESS); + assert(serd_model_drop_index(model, SERD_ORDER_PSO) == SERD_SUCCESS); + assert(serd_model_drop_index(model, SERD_ORDER_PSO) == SERD_FAILURE); + assert(serd_model_size(model) == 2); + serd_model_free(model); + return 0; +} + +static int +test_inserter(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdAllocator* const allocator = serd_default_allocator(); + SerdNodes* const nodes = serd_nodes_new(allocator); + SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); + SerdSink* const inserter = serd_inserter_new(model, NULL); + + const SerdNode* const s = + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/s")); + + const SerdNode* const p = + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/p")); + + const SerdNode* const rel = serd_nodes_get(nodes, serd_a_uri_string("rel")); + + serd_set_log_func(world, expected_error, NULL); + + assert(serd_sink_write(inserter, 0, s, p, rel, NULL) == SERD_BAD_DATA); + + serd_sink_free(inserter); + serd_model_free(model); + serd_nodes_free(nodes); + return 0; +} + +static int +test_erase_with_iterator(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + + serd_set_log_func(world, expected_error, NULL); + assert( + !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0)); + assert( + !serd_model_add(model, uri(world, 4), uri(world, 5), uri(world, 6), 0)); + + // Erase a statement with an active iterator + SerdCursor* iter1 = serd_model_begin(NULL, model); + SerdCursor* iter2 = serd_model_begin(NULL, model); + assert(!serd_model_erase(model, iter1)); + + // Check that erased iterator points to the next statement + const SerdStatement* const s1 = serd_cursor_get(iter1); + assert(s1); + assert( + serd_statement_matches(s1, uri(world, 4), uri(world, 5), uri(world, 6), 0)); + + // Check that other iterator has been invalidated + assert(!serd_cursor_get(iter2)); + assert(serd_cursor_advance(iter2) == SERD_BAD_CURSOR); + + // Check that erasing the end iterator does nothing + SerdCursor* const end = + serd_cursor_copy(serd_world_allocator(world), serd_model_end(model)); + + assert(serd_model_erase(model, end) == SERD_FAILURE); + + serd_cursor_free(NULL, end); + serd_cursor_free(NULL, iter2); + serd_cursor_free(NULL, iter1); + serd_model_free(model); + return 0; +} + +static int +test_add_erase(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdAllocator* const allocator = serd_default_allocator(); + + SerdNodes* const nodes = serd_nodes_new(allocator); + SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); + + // Add (s p "hello") + const SerdNode* s = uri(world, 1); + const SerdNode* p = uri(world, 2); + const SerdNode* hello = serd_nodes_get(nodes, serd_a_string("hello")); + + assert(!serd_model_add(model, s, p, hello, NULL)); + assert(serd_model_ask(model, s, p, hello, NULL)); + + // Add (s p "hi") + const SerdNode* hi = serd_nodes_get(nodes, serd_a_string("hi")); + assert(!serd_model_add(model, s, p, hi, NULL)); + assert(serd_model_ask(model, s, p, hi, NULL)); + + // Erase (s p "hi") + SerdCursor* iter = serd_model_find(NULL, model, s, p, hi, NULL); + assert(iter); + assert(!serd_model_erase(model, iter)); + assert(serd_model_size(model) == 1); + serd_cursor_free(NULL, iter); + + // Check that erased statement can not be found + SerdCursor* empty = serd_model_find(NULL, model, s, p, hi, NULL); + assert(serd_cursor_is_end(empty)); + serd_cursor_free(NULL, empty); + + serd_model_free(model); + serd_nodes_free(nodes); + return 0; +} + +static int +test_add_bad_statement(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdAllocator* const allocator = serd_world_allocator(world); + SerdNodes* const nodes = serd_nodes_new(allocator); + + const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); + const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); + const SerdNode* o = serd_nodes_get(nodes, serd_a_uri_string("urn:o")); + + const SerdNode* f = + serd_nodes_get(nodes, serd_a_uri_string("file:///tmp/file.ttl")); + + SerdCaret* caret = serd_caret_new(allocator, f, 16, 18); + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + + assert(!serd_model_add_with_caret(model, s, p, o, NULL, caret)); + + SerdCursor* const begin = serd_model_begin(NULL, model); + const SerdStatement* statement = serd_cursor_get(begin); + assert(statement); + + assert(serd_node_equals(serd_statement_subject(statement), s)); + assert(serd_node_equals(serd_statement_predicate(statement), p)); + assert(serd_node_equals(serd_statement_object(statement), o)); + assert(!serd_statement_graph(statement)); + + const SerdCaret* statement_caret = serd_statement_caret(statement); + assert(statement_caret); + assert(serd_node_equals(serd_caret_document(statement_caret), f)); + assert(serd_caret_line(statement_caret) == 16); + assert(serd_caret_column(statement_caret) == 18); + + assert(!serd_model_erase(model, begin)); + + serd_cursor_free(NULL, begin); + serd_model_free(model); + serd_caret_free(allocator, caret); + serd_nodes_free(nodes); + return 0; +} + +static int +test_add_with_caret(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdNodes* const nodes = serd_nodes_new(serd_world_allocator(world)); + const SerdNode* lit = serd_nodes_get(nodes, serd_a_string("string")); + const SerdNode* uri = serd_nodes_get(nodes, serd_a_uri_string("urn:uri")); + + const SerdNode* blank = + serd_nodes_get(nodes, serd_a_blank(serd_string("b1"))); + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + + assert(serd_model_add(model, lit, uri, uri, NULL)); + assert(serd_model_add(model, uri, blank, uri, NULL)); + assert(serd_model_add(model, uri, uri, uri, lit)); + + serd_model_free(model); + serd_nodes_free(nodes); + return 0; +} + +static int +test_erase_all(SerdWorld* world, const unsigned n_quads) +{ + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + serd_model_add_index(model, SERD_ORDER_OSP); + generate(world, model, n_quads, NULL); + + SerdCursor* iter = serd_model_begin(NULL, model); + while (!serd_cursor_equals(iter, serd_model_end(model))) { + assert(!serd_model_erase(model, iter)); + } + + assert(serd_model_empty(model)); + + serd_cursor_free(NULL, iter); + serd_model_free(model); + return 0; +} + +static int +test_clear(SerdWorld* world, const unsigned n_quads) +{ + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + generate(world, model, n_quads, NULL); + + serd_model_clear(model); + assert(serd_model_empty(model)); + + serd_model_free(model); + return 0; +} + +static int +test_copy(SerdWorld* world, const unsigned n_quads) +{ + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + generate(world, model, n_quads, NULL); + + SerdModel* copy = serd_model_copy(serd_world_allocator(world), model); + assert(serd_model_equals(model, copy)); + + serd_model_free(model); + serd_model_free(copy); + return 0; +} + +static int +test_equals(SerdWorld* world, const unsigned n_quads) +{ + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + generate(world, model, n_quads, NULL); + serd_model_add( + model, uri(world, 0), uri(world, 1), uri(world, 2), uri(world, 3)); + + assert(serd_model_equals(NULL, NULL)); + assert(!serd_model_equals(NULL, model)); + assert(!serd_model_equals(model, NULL)); + + SerdModel* empty = serd_model_new(world, SERD_ORDER_SPO, 0U); + assert(!serd_model_equals(model, empty)); + + SerdModel* different = serd_model_new(world, SERD_ORDER_SPO, 0U); + generate(world, different, n_quads, NULL); + serd_model_add( + different, uri(world, 1), uri(world, 1), uri(world, 2), uri(world, 3)); + + assert(serd_model_size(model) == serd_model_size(different)); + assert(!serd_model_equals(model, different)); + + serd_model_free(model); + serd_model_free(empty); + serd_model_free(different); + return 0; +} + +static int +test_find_past_end(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + const SerdNode* s = uri(world, 1); + const SerdNode* p = uri(world, 2); + const SerdNode* o = uri(world, 3); + assert(!serd_model_add(model, s, p, o, 0)); + assert(serd_model_ask(model, s, p, o, 0)); + + const SerdNode* huge = uri(world, 999); + SerdCursor* range = serd_model_find(NULL, model, huge, huge, huge, 0); + assert(serd_cursor_is_end(range)); + + serd_cursor_free(NULL, range); + serd_model_free(model); + return 0; +} + +static int +test_find_unknown_node(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + const SerdNode* const s = uri(world, 1); + const SerdNode* const p = uri(world, 2); + const SerdNode* const o = uri(world, 3); + + SerdModel* const model = + serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + + // Add one statement + assert(!serd_model_add(model, s, p, o, NULL)); + assert(serd_model_ask(model, s, p, o, NULL)); + + /* Test searching for statements that contain a non-existent node. This is + semantically equivalent to any other non-matching pattern, but can be + implemented with a fast path that avoids searching a statement index + entirely. */ + + const SerdNode* const q = uri(world, 42); + assert(!serd_model_ask(model, s, p, o, q)); + assert(!serd_model_ask(model, s, p, q, NULL)); + assert(!serd_model_ask(model, s, q, o, NULL)); + assert(!serd_model_ask(model, q, p, o, NULL)); + + serd_model_free(model); + return 0; +} + +static int +test_find_graph(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + const SerdNode* const s = uri(world, 1); + const SerdNode* const p = uri(world, 2); + const SerdNode* const o1 = uri(world, 3); + const SerdNode* const o2 = uri(world, 4); + const SerdNode* const g = uri(world, 5); + + for (unsigned indexed = 0U; indexed < 2U; ++indexed) { + SerdModel* const model = + serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + + if (indexed) { + serd_model_add_index(model, SERD_ORDER_GSPO); + } + + // Add one statement in a named graph and one in the default graph + assert(!serd_model_add(model, s, p, o1, NULL)); + assert(!serd_model_add(model, s, p, o2, g)); + + // Both statements can be found in the default graph + assert(serd_model_ask(model, s, p, o1, NULL)); + assert(serd_model_ask(model, s, p, o2, NULL)); + + // Only the one statement can be found in the named graph + assert(!serd_model_ask(model, s, p, o1, g)); + assert(serd_model_ask(model, s, p, o2, g)); + + serd_model_free(model); + } + + return 0; +} + +static int +test_range(SerdWorld* world, const unsigned n_quads) +{ + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + generate(world, model, n_quads, NULL); + + SerdCursor* range1 = serd_model_begin(NULL, model); + SerdCursor* range2 = serd_model_begin(NULL, model); + + assert(!serd_cursor_is_end(range1)); + assert(serd_cursor_is_end(NULL)); + + assert(serd_cursor_equals(NULL, NULL)); + assert(!serd_cursor_equals(range1, NULL)); + assert(!serd_cursor_equals(NULL, range1)); + assert(serd_cursor_equals(range1, range2)); + + assert(!serd_cursor_advance(range2)); + assert(!serd_cursor_equals(range1, range2)); + + serd_cursor_free(NULL, range2); + serd_cursor_free(NULL, range1); + serd_model_free(model); + + return 0; +} + +static int +test_triple_index_read(SerdWorld* world, const unsigned n_quads) +{ + serd_set_log_func(world, ignore_only_index_error, NULL); + + for (unsigned i = 0; i < 6; ++i) { + SerdModel* model = serd_model_new(world, (SerdStatementOrder)i, 0U); + generate(world, model, n_quads, 0); + assert(!test_read(world, model, 0, n_quads)); + serd_model_free(model); + } + + return 0; +} + +static int +test_quad_index_read(SerdWorld* world, const unsigned n_quads) +{ + serd_set_log_func(world, ignore_only_index_error, NULL); + + for (unsigned i = 0; i < 6; ++i) { + SerdModel* model = + serd_model_new(world, (SerdStatementOrder)i, SERD_STORE_GRAPHS); + + const SerdNode* graph = uri(world, 42); + generate(world, model, n_quads, graph); + assert(!test_read(world, model, graph, n_quads)); + serd_model_free(model); + } + + return 0; +} + +static int +test_remove_graph(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_GSPO, SERD_STORE_GRAPHS); + + // Generate a couple of graphs + const SerdNode* graph42 = uri(world, 42); + const SerdNode* graph43 = uri(world, 43); + generate(world, model, 1, graph42); + generate(world, model, 1, graph43); + + // Find the start of graph43 + SerdCursor* range = serd_model_find(NULL, model, NULL, NULL, NULL, graph43); + assert(range); + + // Remove the entire range of statements in the graph + SerdStatus st = serd_model_erase_statements(model, range); + assert(!st); + serd_cursor_free(NULL, range); + + // Erase the first tuple (an element in the default graph) + SerdCursor* iter = serd_model_begin(NULL, model); + assert(!serd_model_erase(model, iter)); + serd_cursor_free(NULL, iter); + + // Ensure only the other graph is left + Quad pat = {0, 0, 0, graph42}; + for (iter = serd_model_begin(NULL, model); + !serd_cursor_equals(iter, serd_model_end(model)); + serd_cursor_advance(iter)) { + const SerdStatement* const s = serd_cursor_get(iter); + assert(s); + assert(serd_statement_matches(s, pat[0], pat[1], pat[2], pat[3])); + } + serd_cursor_free(NULL, iter); + + serd_model_free(model); + return 0; +} + +static int +test_default_graph(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + const SerdNode* s = uri(world, 1); + const SerdNode* p = uri(world, 2); + const SerdNode* o = uri(world, 3); + const SerdNode* g1 = uri(world, 101); + const SerdNode* g2 = uri(world, 102); + + { + // Make a model that does not store graphs + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + + // Insert a statement into a graph (which will be dropped) + assert(!serd_model_add(model, s, p, o, g1)); + + // Attempt to insert the same statement into another graph + assert(serd_model_add(model, s, p, o, g2) == SERD_FAILURE); + + // Ensure that we only see the statement once + assert(serd_model_count(model, s, p, o, NULL) == 1); + + serd_model_free(model); + } + + { + // Make a model that stores graphs + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + + // Insert the same statement into two graphs + assert(!serd_model_add(model, s, p, o, g1)); + assert(!serd_model_add(model, s, p, o, g2)); + + // Ensure we see the statement twice + assert(serd_model_count(model, s, p, o, NULL) == 2); + + serd_model_free(model); + } + + return 0; +} + +static int +test_write_flat_range(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdAllocator* const alloc = serd_world_allocator(world); + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + SerdNodes* nodes = serd_nodes_new(alloc); + + const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); + const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); + const SerdNode* b1 = serd_nodes_get(nodes, serd_a_blank(serd_string("b1"))); + const SerdNode* b2 = serd_nodes_get(nodes, serd_a_blank(serd_string("b2"))); + const SerdNode* o = serd_nodes_get(nodes, serd_a_uri_string("urn:o")); + + serd_model_add(model, s, p, b1, NULL); + serd_model_add(model, b1, p, o, NULL); + serd_model_add(model, s, p, b2, NULL); + serd_model_add(model, b2, p, o, NULL); + + SerdBuffer buffer = {NULL, NULL, 0}; + SerdEnv* env = serd_env_new(alloc, serd_empty_string()); + SerdOutputStream out = serd_open_output_buffer(&buffer); + + SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); + assert(writer); + + SerdCursor* all = serd_model_begin(NULL, model); + for (const SerdStatement* t = NULL; (t = serd_cursor_get(all)); + serd_cursor_advance(all)) { + serd_sink_write_statement(serd_writer_sink(writer), 0U, t); + } + serd_cursor_free(NULL, all); + + serd_writer_finish(writer); + serd_close_output(&out); + + const char* const str = (const char*)buffer.buf; + static const char* const expected = "<urn:s>\n" + "\t<urn:p> _:b1 ,\n" + "\t\t_:b2 .\n" + "\n" + "_:b1\n" + "\t<urn:p> <urn:o> .\n" + "\n" + "_:b2\n" + "\t<urn:p> <urn:o> .\n"; + + assert(str); + assert(!strcmp(str, expected)); + + serd_free(NULL, buffer.buf); + serd_writer_free(writer); + serd_model_free(model); + serd_env_free(env); + serd_nodes_free(nodes); + return 0; +} + +static int +test_write_bad_list(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdAllocator* const alloc = serd_world_allocator(world); + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + SerdNodes* nodes = serd_nodes_new(alloc); + + serd_model_add_index(model, SERD_ORDER_OPS); + + const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); + const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); + + const SerdNode* list1 = + serd_nodes_get(nodes, serd_a_blank(serd_string("l1"))); + + const SerdNode* list2 = + serd_nodes_get(nodes, serd_a_blank(serd_string("l2"))); + + const SerdNode* nofirst = + serd_nodes_get(nodes, serd_a_blank(serd_string("nof"))); + + const SerdNode* norest = + serd_nodes_get(nodes, serd_a_blank(serd_string("nor"))); + + const SerdNode* pfirst = serd_nodes_get(nodes, serd_a_uri_string(RDF_FIRST)); + const SerdNode* prest = serd_nodes_get(nodes, serd_a_uri_string(RDF_REST)); + + const SerdNode* val1 = serd_nodes_get(nodes, serd_a_string("a")); + const SerdNode* val2 = serd_nodes_get(nodes, serd_a_string("b")); + + // List where second node has no rdf:first + serd_model_add(model, s, p, list1, NULL); + serd_model_add(model, list1, pfirst, val1, NULL); + serd_model_add(model, list1, prest, nofirst, NULL); + + // List where second node has no rdf:rest + serd_model_add(model, s, p, list2, NULL); + serd_model_add(model, list2, pfirst, val1, NULL); + serd_model_add(model, list2, prest, norest, NULL); + serd_model_add(model, norest, pfirst, val2, NULL); + + SerdBuffer buffer = {NULL, NULL, 0}; + SerdEnv* env = serd_env_new(alloc, serd_empty_string()); + SerdOutputStream out = serd_open_output_buffer(&buffer); + + SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); + assert(writer); + + SerdCursor* all = serd_model_begin(NULL, model); + serd_describe_range(NULL, all, serd_writer_sink(writer), 0); + serd_cursor_free(NULL, all); + + serd_writer_finish(writer); + serd_close_output(&out); + + const char* str = (const char*)buffer.buf; + const char* expected = "<urn:s>\n" + " <urn:p> (\n" + " \"a\"\n" + " ) , (\n" + " \"a\"\n" + " \"b\"\n" + " ) .\n"; + + assert(str); + assert(!strcmp(str, expected)); + + serd_free(NULL, buffer.buf); + serd_writer_free(writer); + serd_close_output(&out); + serd_model_free(model); + serd_env_free(env); + serd_nodes_free(nodes); + return 0; +} + +static int +test_write_infinite_list(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdAllocator* const alloc = serd_world_allocator(world); + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + SerdNodes* nodes = serd_nodes_new(alloc); + + serd_model_add_index(model, SERD_ORDER_OPS); + + const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); + const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); + + const SerdNode* list1 = + serd_nodes_get(nodes, serd_a_blank(serd_string("l1"))); + + const SerdNode* list2 = + serd_nodes_get(nodes, serd_a_blank(serd_string("l2"))); + + const SerdNode* pfirst = serd_nodes_get(nodes, serd_a_uri_string(RDF_FIRST)); + const SerdNode* prest = serd_nodes_get(nodes, serd_a_uri_string(RDF_REST)); + const SerdNode* val1 = serd_nodes_get(nodes, serd_a_string("a")); + const SerdNode* val2 = serd_nodes_get(nodes, serd_a_string("b")); + + // List with a cycle: list1 -> list2 -> list1 -> list2 ... + serd_model_add(model, s, p, list1, NULL); + serd_model_add(model, list1, pfirst, val1, NULL); + serd_model_add(model, list1, prest, list2, NULL); + serd_model_add(model, list2, pfirst, val2, NULL); + serd_model_add(model, list2, prest, list1, NULL); + + SerdBuffer buffer = {NULL, NULL, 0}; + SerdEnv* env = serd_env_new(alloc, serd_empty_string()); + SerdOutputStream out = serd_open_output_buffer(&buffer); + + SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); + assert(writer); + + serd_env_set_prefix( + env, + serd_string("rdf"), + serd_string("http://www.w3.org/1999/02/22-rdf-syntax-ns#")); + + SerdCursor* all = serd_model_begin(NULL, model); + serd_describe_range(NULL, all, serd_writer_sink(writer), 0); + serd_cursor_free(NULL, all); + + serd_writer_finish(writer); + serd_close_output(&out); + const char* str = (const char*)buffer.buf; + const char* expected = "<urn:s>\n" + " <urn:p> _:l1 .\n" + "\n" + "_:l1\n" + " rdf:first \"a\" ;\n" + " rdf:rest [\n" + " rdf:first \"b\" ;\n" + " rdf:rest _:l1\n" + " ] .\n"; + + assert(str); + assert(!strcmp(str, expected)); + + serd_free(NULL, buffer.buf); + serd_writer_free(writer); + serd_close_output(&out); + serd_model_free(model); + serd_env_free(env); + serd_nodes_free(nodes); + + return 0; +} + +typedef struct { + size_t n_written; + size_t max_successes; +} FailingWriteFuncState; + +/// Write function that fails after a certain number of writes +static size_t +failing_write_func(const void* buf, size_t size, size_t nmemb, void* stream) +{ + (void)buf; + (void)size; + (void)nmemb; + + FailingWriteFuncState* state = (FailingWriteFuncState*)stream; + + return (++state->n_written > state->max_successes) ? 0 : nmemb; +} + +static int +test_write_error_in_list_subject(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdAllocator* const alloc = serd_world_allocator(world); + + serd_set_log_func(world, expected_error, NULL); + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + SerdNodes* nodes = serd_nodes_new(alloc); + + serd_model_add_index(model, SERD_ORDER_OPS); + + const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); + const SerdNode* o = serd_nodes_get(nodes, serd_a_uri_string("urn:o")); + const SerdNode* l1 = serd_nodes_get(nodes, serd_a_blank(serd_string("l1"))); + const SerdNode* one = serd_nodes_get(nodes, serd_a_integer(1)); + const SerdNode* l2 = serd_nodes_get(nodes, serd_a_blank(serd_string("l2"))); + const SerdNode* two = serd_nodes_get(nodes, serd_a_integer(2)); + + const SerdNode* rdf_first = + serd_nodes_get(nodes, serd_a_uri_string(RDF_FIRST)); + + const SerdNode* rdf_rest = serd_nodes_get(nodes, serd_a_uri_string(RDF_REST)); + + const SerdNode* rdf_nil = serd_nodes_get(nodes, serd_a_uri_string(RDF_NIL)); + + serd_model_add(model, l1, rdf_first, one, NULL); + serd_model_add(model, l1, rdf_rest, l2, NULL); + serd_model_add(model, l2, rdf_first, two, NULL); + serd_model_add(model, l2, rdf_rest, rdf_nil, NULL); + serd_model_add(model, l1, p, o, NULL); + + SerdEnv* env = serd_env_new(alloc, serd_empty_string()); + + for (size_t max_successes = 0; max_successes < 18; ++max_successes) { + FailingWriteFuncState state = {0, max_successes}; + SerdOutputStream out = + serd_open_output_stream(failing_write_func, NULL, NULL, &state); + + SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); + + const SerdSink* const sink = serd_writer_sink(writer); + SerdCursor* const all = serd_model_begin(NULL, model); + const SerdStatus st = serd_describe_range(NULL, all, sink, 0); + serd_cursor_free(NULL, all); + + assert(st == SERD_BAD_WRITE); + + serd_writer_free(writer); + serd_close_output(&out); + } + + serd_env_free(env); + serd_model_free(model); + serd_nodes_free(nodes); + return 0; +} + +static int +test_write_error_in_list_object(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdAllocator* const alloc = serd_world_allocator(world); + + serd_set_log_func(world, expected_error, NULL); + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + SerdNodes* nodes = serd_nodes_new(alloc); + + serd_model_add_index(model, SERD_ORDER_OPS); + + const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); + const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); + const SerdNode* l1 = serd_nodes_get(nodes, serd_a_blank(serd_string("l1"))); + const SerdNode* one = serd_nodes_get(nodes, serd_a_integer(1)); + const SerdNode* l2 = serd_nodes_get(nodes, serd_a_blank(serd_string("l2"))); + const SerdNode* two = serd_nodes_get(nodes, serd_a_integer(2)); + + const SerdNode* rdf_first = + serd_nodes_get(nodes, serd_a_uri_string(RDF_FIRST)); + + const SerdNode* rdf_rest = serd_nodes_get(nodes, serd_a_uri_string(RDF_REST)); + + const SerdNode* rdf_nil = serd_nodes_get(nodes, serd_a_uri_string(RDF_NIL)); + + serd_model_add(model, s, p, l1, NULL); + serd_model_add(model, l1, rdf_first, one, NULL); + serd_model_add(model, l1, rdf_rest, l2, NULL); + serd_model_add(model, l2, rdf_first, two, NULL); + serd_model_add(model, l2, rdf_rest, rdf_nil, NULL); + + SerdEnv* env = serd_env_new(alloc, serd_empty_string()); + + for (size_t max_successes = 0; max_successes < 21; ++max_successes) { + FailingWriteFuncState state = {0, max_successes}; + SerdOutputStream out = + serd_open_output_stream(failing_write_func, NULL, NULL, &state); + + SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); + + const SerdSink* const sink = serd_writer_sink(writer); + SerdCursor* const all = serd_model_begin(NULL, model); + const SerdStatus st = serd_describe_range(NULL, all, sink, 0); + serd_cursor_free(NULL, all); + + assert(st == SERD_BAD_WRITE); + + serd_writer_free(writer); + serd_close_output(&out); + } + + serd_env_free(env); + serd_model_free(model); + serd_nodes_free(nodes); + return 0; +} + +int +main(void) +{ + static const unsigned n_quads = 300; + + serd_model_free(NULL); // Shouldn't crash + + typedef int (*TestFunc)(SerdWorld*, unsigned); + + const TestFunc tests[] = {test_failed_new_alloc, + test_free_null, + test_get_world, + test_get_default_order, + test_get_flags, + test_all_begin, + test_begin_ordered, + test_add_with_iterator, + test_add_remove_nodes, + test_add_index, + test_remove_index, + test_inserter, + test_erase_with_iterator, + test_add_erase, + test_add_bad_statement, + test_add_with_caret, + test_erase_all, + test_clear, + test_copy, + test_equals, + test_find_past_end, + test_find_unknown_node, + test_find_graph, + test_range, + test_triple_index_read, + test_quad_index_read, + test_remove_graph, + test_default_graph, + test_write_flat_range, + test_write_bad_list, + test_write_infinite_list, + test_write_error_in_list_subject, + test_write_error_in_list_object, + NULL}; + + SerdWorld* world = serd_world_new(NULL); + int ret = 0; + + for (const TestFunc* t = tests; *t; ++t) { + serd_set_log_func(world, NULL, NULL); + ret += (*t)(world, n_quads); + } + + serd_world_free(world); + return ret; +} diff --git a/test/test_sort.py b/test/test_sort.py new file mode 100755 index 00000000..78147fbc --- /dev/null +++ b/test/test_sort.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +# Copyright 2022-2023 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: ISC + +"""Run the collation tests for serd-sort.""" + +import os +import shlex +import subprocess +import sys + +import serd_test_util as util + +collations = [ + "GOPS", + "GOSP", + "GPSO", + "GSOP", + "GSPO", + "OPS", + "OSP", + "POS", + "PSO", + "SOP", + "SPO", + "pretty", +] + + +def run_sort_test(command, in_path, good_path): + """Sort a single input in the named order and check the output. + + The expected output is assumed to exist at test_dir/NAME.untyped.nq. + """ + + result_name = os.path.basename(good_path) + options = [] + if result_name not in ["pretty.nq", "untyped.nq"]: + options += ["-c", os.path.splitext(result_name)[0]] + + command = command + options + [in_path] + + proc = subprocess.run( + command, check=True, encoding="utf-8", capture_output=True + ) + + lines = proc.stdout.splitlines(True) + with open(good_path, "r", encoding="utf-8") as good: + return util.lines_equal(list(good), lines, good_path, result_name) + + +def run_tests(test_dir, command): + """Run all the tests in the suite.""" + + n_failures = 0 + in_path = os.path.join(test_dir, "input.trig") + + # Test all the basic collations, and "pretty" with type first + for name in collations: + good_path = os.path.join(test_dir, name + ".nq") + prefixes = [command, command + ["-I", "trig"]] + for prefix in prefixes: + if not run_sort_test(prefix, in_path, good_path): + n_failures += 1 + + # Test "pretty" without type first + if not run_sort_test( + command + ["-O", "longhand"], + in_path, + os.path.join(test_dir, "untyped.nq"), + ): + n_failures += 1 + + return n_failures + + +def main(): + """Run the command line tool.""" + + args = util.wrapper_args(__doc__, True) + wrapper_prefix = shlex.split(args.wrapper) + command_prefix = wrapper_prefix + [args.tool] + + return run_tests(os.path.dirname(args.input), command_prefix) + + +if __name__ == "__main__": + try: + sys.exit(main()) + except subprocess.CalledProcessError as error: + if error.stderr is not None: + sys.stderr.write(error.stderr) + + sys.stderr.write(sys.argv[0]) + sys.stderr.write(": error: ") + sys.stderr.write(str(error)) + sys.stderr.write("\n") + sys.exit(error.returncode) diff --git a/test/test_string.c b/test/test_string.c index 2d16936a..286df8ca 100644 --- a/test/test_string.c +++ b/test/test_string.c @@ -14,7 +14,7 @@ test_strerror(void) { const char* msg = serd_strerror(SERD_SUCCESS); assert(!strcmp(msg, "Success")); - for (int i = SERD_FAILURE; i <= SERD_BAD_PATTERN; ++i) { + for (int i = SERD_FAILURE; i <= SERD_BAD_INDEX; ++i) { msg = serd_strerror((SerdStatus)i); assert(strcmp(msg, "Success")); } |