aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/.clang-tidy1
-rw-r--r--test/meson.build3
-rwxr-xr-xtest/run_test_suite.py29
-rw-r--r--test/test_free_null.c4
-rw-r--r--test/test_model.c900
-rw-r--r--test/test_sink.c151
-rw-r--r--test/test_statement.c91
7 files changed, 1179 insertions, 0 deletions
diff --git a/test/.clang-tidy b/test/.clang-tidy
index b7dd752b..8e55b93e 100644
--- a/test/.clang-tidy
+++ b/test/.clang-tidy
@@ -10,6 +10,7 @@ Checks: >
-clang-analyzer-nullability.NullabilityBase,
-clang-analyzer-nullability.NullableDereferenced,
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
+ -clang-analyzer-valist.Uninitialized,
-cppcoreguidelines-avoid-non-const-global-variables,
-hicpp-signed-bitwise,
-llvmlibc-*,
diff --git a/test/meson.build b/test/meson.build
index 79ffc85d..84045225 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -6,12 +6,15 @@ unit_tests = [
'cursor',
'env',
'free_null',
+ 'model',
'node',
'node_syntax',
'nodes',
'overflow',
'read_chunk',
'reader_writer',
+ 'sink',
+ 'statement',
'string',
'terse_write',
'uri',
diff --git a/test/run_test_suite.py b/test/run_test_suite.py
index d25fb3a1..05dc81ca 100755
--- a/test/run_test_suite.py
+++ b/test/run_test_suite.py
@@ -208,6 +208,23 @@ def _file_equals(patha, pathb):
return _show_diff(fa.readlines(), fb.readlines(), patha, pathb)
+def _file_lines_equal(patha, pathb, subst_from="", subst_to=""):
+ import io
+
+ for path in (patha, pathb):
+ if not os.access(path, os.F_OK):
+ sys.stderr.write("error: missing file %s" % path)
+ return False
+
+ la = sorted(set(io.open(patha, encoding="utf-8").readlines()))
+ lb = sorted(set(io.open(pathb, encoding="utf-8").readlines()))
+ if la != lb:
+ _show_diff(la, lb, patha, pathb)
+ return False
+
+ return True
+
+
def test_suite(
manifest_path,
base_uri,
@@ -306,6 +323,18 @@ def test_suite(
command_prefix,
)
+ # Run model test for positive test (must succeed)
+ out_filename = os.path.join(out_test_dir, test_name + ".model.out")
+
+ with open(out_filename, "w") as stdout:
+ proc = subprocess.run([command[0]] + ['-m'] + command[1:],
+ check=True,
+ stdout=stdout)
+
+ if proc.returncode == 0 and ((mf + 'result') in model[test]):
+ if not _file_lines_equal(check_path, out_filename):
+ results.n_failures += 1
+
else: # Negative test
with open(out_filename, "w") as stdout:
with tempfile.TemporaryFile() as stderr:
diff --git a/test/test_free_null.c b/test/test_free_null.c
index a101379a..aee79f0e 100644
--- a/test/test_free_null.c
+++ b/test/test_free_null.c
@@ -33,6 +33,10 @@ main(void)
serd_reader_free(NULL);
serd_writer_free(NULL);
serd_nodes_free(NULL);
+ serd_model_free(NULL);
+ serd_statement_free(NULL);
+ serd_iter_free(NULL);
+ serd_range_free(NULL);
serd_cursor_free(NULL);
return 0;
diff --git a/test/test_model.c b/test/test_model.c
new file mode 100644
index 00000000..cd1728e8
--- /dev/null
+++ b/test/test_model.c
@@ -0,0 +1,900 @@
+/*
+ Copyright 2011-2018 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "serd/serd.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*
+manage(SerdWorld* world, SerdNode* node)
+{
+ return serd_nodes_manage(serd_world_nodes(world), node);
+}
+
+static const SerdNode*
+uri(SerdWorld* world, const unsigned num)
+{
+ char str[] = "eg:0000000000";
+ snprintf(str + 3, 11, "%03u", num);
+ return manage(world, serd_new_uri(SERD_STATIC_STRING(str)));
+}
+
+static int
+generate(SerdWorld* world,
+ SerdModel* model,
+ size_t n_quads,
+ const SerdNode* graph)
+{
+ SerdNodes* nodes = serd_world_nodes(world);
+
+ 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) {
+ assert(!serd_model_add(model, ids[0], ids[1], ids[2 + j], graph));
+ }
+ }
+
+ // Add some literals
+
+ // (98 4 "hello") and (98 4 "hello"^^<5>)
+ const SerdNode* hello =
+ manage(world, serd_new_string(SERD_STATIC_STRING("hello")));
+
+ const SerdNode* hello_gb =
+ manage(world,
+ serd_new_plain_literal(SERD_STATIC_STRING("hello"),
+ SERD_STATIC_STRING("en-gb")));
+
+ const SerdNode* hello_us =
+ manage(world,
+ serd_new_plain_literal(SERD_STATIC_STRING("hello"),
+ SERD_STATIC_STRING("en-us")));
+
+ const SerdNode* hello_t4 = serd_nodes_manage(
+ nodes,
+ serd_new_typed_literal(SERD_STATIC_STRING("hello"),
+ serd_node_string_view(uri(world, 4))));
+
+ const SerdNode* hello_t5 = serd_nodes_manage(
+ nodes,
+ serd_new_typed_literal(SERD_STATIC_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 =
+ manage(world,
+ serd_new_plain_literal(SERD_STATIC_STRING("bonjour"),
+ SERD_STATIC_STRING("fr")));
+
+ const SerdNode* const salut =
+ manage(world,
+ serd_new_plain_literal(SERD_STATIC_STRING("salut"),
+ SERD_STATIC_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 =
+ manage(world, serd_new_blank(SERD_STATIC_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)
+{
+ SerdIter* iter = serd_model_begin(model);
+ const SerdStatement* prev = NULL;
+ for (; !serd_iter_equals(iter, serd_model_end(model)); serd_iter_next(iter)) {
+ const SerdStatement* statement = serd_iter_get(iter);
+ 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_iter_next(iter));
+ serd_iter_free(iter);
+
+ const SerdStringView s = SERD_STATIC_STRING("hello");
+
+ const SerdNode* plain_hello = manage(world, serd_new_string(s));
+
+ const SerdNode* type4_hello = manage(
+ world, serd_new_typed_literal(s, serd_node_string_view(uri(world, 4))));
+
+ const SerdNode* type5_hello = manage(
+ world, serd_new_typed_literal(s, serd_node_string_view(uri(world, 5))));
+
+ const SerdNode* gb_hello =
+ manage(world, serd_new_plain_literal(s, SERD_STATIC_STRING("en-gb")));
+
+ const SerdNode* us_hello =
+ manage(world, serd_new_plain_literal(s, SERD_STATIC_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};
+
+ SerdRange* range = serd_model_range(model, pat[0], pat[1], pat[2], pat[3]);
+ int num_results = 0;
+ for (; !serd_range_empty(range); serd_range_next(range)) {
+ ++num_results;
+
+ const SerdStatement* first = serd_range_front(range);
+ assert(first);
+ assert(serd_statement_matches(first, pat[0], pat[1], pat[2], pat[3]));
+ }
+
+ serd_range_free(range);
+
+ assert(num_results == test.expected_num_results);
+ }
+
+ // Query blank node subject
+
+ const SerdNode* ablank =
+ manage(world, serd_new_blank(SERD_STATIC_STRING("ablank")));
+
+ Quad pat = {ablank, 0, 0};
+ int num_results = 0;
+ SerdRange* range = serd_model_range(model, pat[0], pat[1], pat[2], pat[3]);
+ for (; !serd_range_empty(range); serd_range_next(range)) {
+ ++num_results;
+ const SerdStatement* statement = serd_range_front(range);
+ assert(serd_statement_matches(statement, pat[0], pat[1], pat[2], pat[3]));
+ }
+ serd_range_free(range);
+
+ assert(num_results == 2);
+
+ // Test nested queries
+ const SerdNode* last_subject = 0;
+ range = serd_model_range(model, NULL, NULL, NULL, NULL);
+ for (; !serd_range_empty(range); serd_range_next(range)) {
+ const SerdStatement* statement = serd_range_front(range);
+ const SerdNode* subject = serd_statement_subject(statement);
+ if (subject == last_subject) {
+ continue;
+ }
+
+ Quad subpat = {subject, 0, 0};
+ SerdRange* subrange =
+ serd_model_range(model, subpat[0], subpat[1], subpat[2], subpat[3]);
+ const SerdStatement* substatement = serd_range_front(subrange);
+ uint64_t num_sub_results = 0;
+ assert(serd_statement_subject(substatement) == subject);
+ for (; !serd_range_empty(subrange); serd_range_next(subrange)) {
+ assert(serd_statement_matches(serd_range_front(subrange),
+ subpat[0],
+ subpat[1],
+ subpat[2],
+ subpat[3]));
+ ++num_sub_results;
+ }
+ serd_range_free(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_range_free(range);
+
+ return 0;
+}
+
+static SerdStatus
+expected_error(void* handle, const SerdLogEntry* entry)
+{
+ (void)handle;
+
+ fprintf(stderr, "expected: ");
+ vfprintf(stderr, entry->fmt, *entry->args);
+ return SERD_SUCCESS;
+}
+
+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_INDEX_SPO);
+ assert(serd_model_world(model) == world);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_get_flags(SerdWorld* world, const unsigned n_quads)
+{
+ (void)n_quads;
+
+ const SerdModelFlags flags = SERD_INDEX_OPS | SERD_INDEX_GRAPHS;
+ SerdModel* model = serd_model_new(world, flags);
+ assert(serd_model_flags(model) == (SERD_INDEX_SPO | flags));
+ 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_INDEX_SPO);
+ SerdRange* all = serd_model_all(model);
+ SerdIter* begin = serd_model_find(model, NULL, NULL, NULL, NULL);
+ assert(serd_iter_equals(serd_range_begin(all), begin));
+ assert(serd_iter_equals(serd_range_cbegin(all), begin));
+
+ serd_range_free(all);
+ serd_iter_free(begin);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_add_null(SerdWorld* world, const unsigned n_quads)
+{
+ (void)n_quads;
+
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
+
+ serd_world_set_log_func(world, expected_error, NULL);
+
+ assert(serd_model_add(model, 0, 0, 0, 0));
+ assert(serd_model_add(model, uri(world, 1), 0, 0, 0));
+ assert(serd_model_add(model, uri(world, 1), uri(world, 2), 0, 0));
+ assert(serd_model_empty(model));
+
+ 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_INDEX_SPO);
+
+ serd_world_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
+ SerdIter* iter = serd_model_begin(model);
+ assert(
+ !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 4), 0));
+
+ // Check that iterator has been invalidated
+ assert(!serd_iter_get(iter));
+ assert(serd_iter_next(iter));
+
+ serd_iter_free(iter);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_erase_with_iterator(SerdWorld* world, const unsigned n_quads)
+{
+ (void)n_quads;
+
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
+
+ serd_world_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
+ SerdIter* iter1 = serd_model_begin(model);
+ SerdIter* iter2 = serd_model_begin(model);
+ assert(!serd_model_erase(model, iter1));
+
+ // Check that erased iterator points to the next statement
+ const SerdStatement* const s1 = serd_iter_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_iter_get(iter2));
+ assert(serd_iter_next(iter2));
+
+ serd_iter_free(iter2);
+ serd_iter_free(iter1);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_add_erase(SerdWorld* world, const unsigned n_quads)
+{
+ (void)n_quads;
+
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO | SERD_INDEX_GRAPHS);
+
+ // Add (s p "hello")
+ const SerdNode* s = uri(world, 1);
+ const SerdNode* p = uri(world, 2);
+ const SerdNode* hello =
+ manage(world, serd_new_string(SERD_STATIC_STRING("hello")));
+
+ assert(!serd_model_add(model, s, p, hello, 0));
+ assert(serd_model_ask(model, s, p, hello, 0));
+
+ // Add (s p "hi")
+ const SerdNode* hi = manage(world, serd_new_string(SERD_STATIC_STRING("hi")));
+ assert(!serd_model_add(model, s, p, hi, NULL));
+ assert(serd_model_ask(model, s, p, hi, 0));
+
+ // Erase (s p "hi")
+ SerdIter* iter = serd_model_find(model, s, p, hi, NULL);
+ assert(!serd_model_erase(model, iter));
+ assert(serd_model_size(model) == 1);
+ serd_iter_free(iter);
+
+ // Check that erased statement can not be found
+ SerdRange* empty = serd_model_range(model, s, p, hi, NULL);
+ assert(serd_range_empty(empty));
+ serd_range_free(empty);
+
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_erase_all(SerdWorld* world, const unsigned n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
+ generate(world, model, n_quads, NULL);
+
+ SerdIter* iter = serd_model_begin(model);
+ while (!serd_iter_equals(iter, serd_model_end(model))) {
+ assert(!serd_model_erase(model, iter));
+ }
+
+ serd_iter_free(iter);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_copy(SerdWorld* world, const unsigned n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
+ generate(world, model, n_quads, NULL);
+
+ SerdModel* copy = serd_model_copy(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_INDEX_SPO);
+ 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_INDEX_SPO);
+ assert(!serd_model_equals(model, empty));
+
+ SerdModel* different = serd_model_new(world, SERD_INDEX_SPO);
+ 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_INDEX_SPO);
+ 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);
+ SerdRange* range = serd_model_range(model, huge, huge, huge, 0);
+ assert(serd_range_empty(range));
+
+ serd_range_free(range);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_range(SerdWorld* world, const unsigned n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
+ generate(world, model, n_quads, NULL);
+
+ SerdRange* range1 = serd_model_all(model);
+ SerdRange* range2 = serd_model_all(model);
+
+ assert(!serd_range_empty(range1));
+ assert(serd_range_empty(NULL));
+
+ assert(!serd_range_equals(range1, NULL));
+ assert(!serd_range_equals(NULL, range1));
+ assert(serd_range_equals(range1, range2));
+
+ assert(serd_iter_equals(serd_range_begin(range1), serd_range_begin(range2)));
+ assert(
+ serd_iter_equals(serd_range_cbegin(range1), serd_range_cbegin(range2)));
+ assert(serd_iter_equals(serd_range_end(range1), serd_range_end(range2)));
+ assert(serd_iter_equals(serd_range_cend(range1), serd_range_cend(range2)));
+
+ assert(!serd_range_next(range2));
+ assert(!serd_range_equals(range1, range2));
+
+ serd_range_free(range2);
+ serd_range_free(range1);
+ serd_model_free(model);
+
+ return 0;
+}
+
+static int
+test_iter_comparison(SerdWorld* world, const unsigned n_quads)
+{
+ (void)n_quads;
+
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
+
+ assert(serd_iter_equals(serd_iter_copy(NULL), NULL));
+
+ serd_world_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
+ SerdIter* iter1 = serd_model_begin(model);
+ SerdIter* iter2 = serd_model_begin(model);
+ assert(serd_iter_equals(iter1, iter2));
+
+ serd_iter_next(iter1);
+ assert(!serd_iter_equals(iter1, iter2));
+
+ const SerdIter* end = serd_model_end(model);
+ assert(serd_iter_equals(iter1, end));
+
+ serd_iter_free(iter2);
+ serd_iter_free(iter1);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_triple_index_read(SerdWorld* world, const unsigned n_quads)
+{
+ for (unsigned i = 0; i < 6; ++i) {
+ SerdModel* model = serd_model_new(world, (1u << i));
+ 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)
+{
+ for (unsigned i = 0; i < 6; ++i) {
+ SerdModel* model = serd_model_new(world, (1u << i) | SERD_INDEX_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_INDEX_SPO | SERD_INDEX_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);
+
+ // Remove one graph via range
+ SerdRange* range = serd_model_range(model, NULL, NULL, NULL, graph43);
+ SerdStatus st = serd_model_erase_range(model, range);
+ assert(!st);
+ serd_range_free(range);
+
+ // Erase the first tuple (an element in the default graph)
+ SerdIter* iter = serd_model_begin(model);
+ assert(!serd_model_erase(model, iter));
+ serd_iter_free(iter);
+
+ // Ensure only the other graph is left
+ Quad pat = {0, 0, 0, graph42};
+ for (iter = serd_model_begin(model);
+ !serd_iter_equals(iter, serd_model_end(model));
+ serd_iter_next(iter)) {
+ const SerdStatement* const s = serd_iter_get(iter);
+ assert(s);
+ assert(serd_statement_matches(s, pat[0], pat[1], pat[2], pat[3]));
+ }
+ serd_iter_free(iter);
+
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_default_graph(SerdWorld* world, const unsigned n_quads)
+{
+ (void)n_quads;
+
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO | SERD_INDEX_GRAPHS);
+ 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);
+
+ // 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 only see statement once in the default graph
+ assert(serd_model_count(model, s, p, o, NULL) == 1);
+
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_write_bad_list(SerdWorld* world, const unsigned n_quads)
+{
+ (void)n_quads;
+
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO | SERD_INDEX_GRAPHS);
+ SerdNodes* nodes = serd_nodes_new();
+
+ const SerdNode* s = manage(world, serd_new_uri(SERD_STATIC_STRING("urn:s")));
+ const SerdNode* p = manage(world, serd_new_uri(SERD_STATIC_STRING("urn:p")));
+ const SerdNode* list1 =
+ manage(world, serd_new_blank(SERD_STATIC_STRING("l1")));
+ const SerdNode* list2 =
+ manage(world, serd_new_blank(SERD_STATIC_STRING("l2")));
+ const SerdNode* nofirst =
+ manage(world, serd_new_blank(SERD_STATIC_STRING("nof")));
+ const SerdNode* norest =
+ manage(world, serd_new_blank(SERD_STATIC_STRING("nor")));
+ const SerdNode* pfirst =
+ manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_FIRST)));
+ const SerdNode* prest =
+ manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_REST)));
+ const SerdNode* val1 =
+ manage(world, serd_new_string(SERD_STATIC_STRING("a")));
+ const SerdNode* val2 =
+ manage(world, serd_new_string(SERD_STATIC_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, 0};
+ SerdEnv* env = serd_env_new(SERD_EMPTY_STRING());
+ SerdByteSink* out = serd_byte_sink_new_buffer(&buffer);
+ SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, out);
+
+ SerdRange* all = serd_model_all(model);
+ serd_range_serialise(all, serd_writer_sink(writer), 0);
+ serd_range_free(all);
+
+ serd_writer_finish(writer);
+ const char* str = serd_buffer_sink_finish(&buffer);
+ const char* expected = "<urn:s>\n"
+ " <urn:p> (\n"
+ " \"a\"\n"
+ " ) , (\n"
+ " \"a\"\n"
+ " \"b\"\n"
+ " ) .\n";
+
+ assert(!strcmp(str, expected));
+
+ free(buffer.buf);
+ serd_writer_free(writer);
+ serd_byte_sink_free(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(SerdWorld* world, const unsigned n_quads)
+{
+ (void)n_quads;
+
+ serd_world_set_log_func(world, expected_error, NULL);
+
+ SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
+ SerdNodes* nodes = serd_nodes_new();
+
+ const SerdNode* s = manage(world, serd_new_uri(SERD_STATIC_STRING("urn:s")));
+ const SerdNode* p = manage(world, serd_new_uri(SERD_STATIC_STRING("urn:p")));
+ const SerdNode* l1 = manage(world, serd_new_blank(SERD_STATIC_STRING("l1")));
+
+ const SerdNode* one = manage(world, serd_new_integer(1, NULL));
+ const SerdNode* l2 = manage(world, serd_new_blank(SERD_STATIC_STRING("l2")));
+ const SerdNode* two = manage(world, serd_new_integer(2, NULL));
+
+ const SerdNode* rdf_first =
+ manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_FIRST)));
+ const SerdNode* rdf_rest =
+ manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_REST)));
+ const SerdNode* rdf_nil =
+ manage(world, serd_new_uri(SERD_STATIC_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(SERD_EMPTY_STRING());
+
+ for (size_t max_successes = 0; max_successes < 21; ++max_successes) {
+ FailingWriteFuncState state = {0, max_successes};
+ SerdByteSink* out =
+ serd_byte_sink_new_function(failing_write_func, &state, 1);
+ SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, out);
+
+ const SerdSink* const sink = serd_writer_sink(writer);
+ SerdRange* const all = serd_model_all(model);
+ const SerdStatus st = serd_range_serialise(all, sink, 0);
+ serd_range_free(all);
+
+ assert(st == SERD_ERR_BAD_WRITE);
+
+ serd_writer_free(writer);
+ serd_byte_sink_free(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_free_null,
+ test_get_world,
+ test_get_flags,
+ test_all_begin,
+ test_add_null,
+ test_add_with_iterator,
+ test_erase_with_iterator,
+ test_add_erase,
+ test_erase_all,
+ test_copy,
+ test_equals,
+ test_find_past_end,
+ test_range,
+ test_iter_comparison,
+ test_triple_index_read,
+ test_quad_index_read,
+ test_remove_graph,
+ test_default_graph,
+ test_write_bad_list,
+ test_write_error_in_list,
+ NULL};
+
+ SerdWorld* world = serd_world_new();
+ int ret = 0;
+
+ for (const TestFunc* t = tests; *t; ++t) {
+ serd_world_set_log_func(world, NULL, NULL);
+ ret += (*t)(world, n_quads);
+ }
+
+ serd_world_free(world);
+ return ret;
+}
diff --git a/test/test_sink.c b/test/test_sink.c
new file mode 100644
index 00000000..b1eb8df8
--- /dev/null
+++ b/test/test_sink.c
@@ -0,0 +1,151 @@
+/*
+ Copyright 2019-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#undef NDEBUG
+
+#include "serd/serd.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+#define NS_EG "http://example.org/"
+
+typedef struct {
+ const SerdNode* last_base;
+ const SerdNode* last_name;
+ const SerdNode* last_namespace;
+ const SerdNode* last_end;
+ const SerdStatement* last_statement;
+ SerdStatus return_status;
+} State;
+
+static SerdStatus
+on_base(void* handle, const SerdNode* uri)
+{
+ State* state = (State*)handle;
+
+ state->last_base = uri;
+ return state->return_status;
+}
+
+static SerdStatus
+on_prefix(void* handle, const SerdNode* name, const SerdNode* uri)
+{
+ State* state = (State*)handle;
+
+ state->last_name = name;
+ state->last_namespace = uri;
+ return state->return_status;
+}
+
+static SerdStatus
+on_statement(void* handle,
+ SerdStatementFlags flags,
+ const SerdStatement* statement)
+{
+ (void)flags;
+
+ State* state = (State*)handle;
+
+ state->last_statement = statement;
+ return state->return_status;
+}
+
+static SerdStatus
+on_end(void* handle, const SerdNode* node)
+{
+ State* state = (State*)handle;
+
+ state->last_end = node;
+ return state->return_status;
+}
+
+static SerdStatus
+on_event(void* handle, const SerdEvent* event)
+{
+ switch (event->type) {
+ case SERD_BASE:
+ return on_base(handle, event->base.uri);
+ case SERD_PREFIX:
+ return on_prefix(handle, event->prefix.name, event->prefix.uri);
+ case SERD_STATEMENT:
+ return on_statement(
+ handle, event->statement.flags, event->statement.statement);
+ case SERD_END:
+ return on_end(handle, event->end.node);
+ }
+
+ return SERD_SUCCESS;
+}
+
+int
+main(void)
+{
+ SerdNodes* const nodes = serd_nodes_new();
+
+ const SerdNode* base =
+ serd_nodes_manage(nodes, serd_new_uri(SERD_STATIC_STRING(NS_EG)));
+
+ const SerdNode* name =
+ serd_nodes_manage(nodes, serd_new_string(SERD_STATIC_STRING("eg")));
+
+ const SerdNode* uri =
+ serd_nodes_manage(nodes, serd_new_uri(SERD_STATIC_STRING(NS_EG "uri")));
+
+ const SerdNode* blank =
+ serd_nodes_manage(nodes, serd_new_blank(SERD_STATIC_STRING("b1")));
+
+ SerdEnv* env = serd_env_new(serd_node_string_view(base));
+
+ SerdStatement* const statement =
+ serd_statement_new(base, uri, blank, NULL, NULL);
+
+ State state = {0, 0, 0, 0, 0, SERD_SUCCESS};
+
+ // Call functions on a sink with no functions set
+
+ SerdSink* null_sink = serd_sink_new(&state, NULL, NULL);
+ assert(!serd_sink_write_base(null_sink, base));
+ assert(!serd_sink_write_prefix(null_sink, name, uri));
+ assert(!serd_sink_write_statement(null_sink, 0, statement));
+ assert(!serd_sink_write(null_sink, 0, base, uri, blank, NULL));
+ assert(!serd_sink_write_end(null_sink, blank));
+ serd_sink_free(null_sink);
+
+ // Try again with a sink that has the event handler set
+
+ SerdSink* sink = serd_sink_new(&state, on_event, NULL);
+
+ assert(!serd_sink_write_base(sink, base));
+ assert(serd_node_equals(state.last_base, base));
+
+ assert(!serd_sink_write_prefix(sink, name, uri));
+ assert(serd_node_equals(state.last_name, name));
+ assert(serd_node_equals(state.last_namespace, uri));
+
+ assert(!serd_sink_write_statement(sink, 0, statement));
+ assert(serd_statement_equals(state.last_statement, statement));
+
+ assert(!serd_sink_write_end(sink, blank));
+ assert(serd_node_equals(state.last_end, blank));
+
+ serd_sink_free(sink);
+ serd_statement_free(statement);
+ serd_env_free(env);
+ serd_nodes_free(nodes);
+
+ return 0;
+}
diff --git a/test/test_statement.c b/test/test_statement.c
new file mode 100644
index 00000000..8e1de1a2
--- /dev/null
+++ b/test/test_statement.c
@@ -0,0 +1,91 @@
+/*
+ Copyright 2011-2020 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#undef NDEBUG
+
+#include "serd/serd.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+#define NS_EG "http://example.org/"
+
+int
+main(void)
+{
+ SerdNodes* const nodes = serd_nodes_new();
+
+ const SerdNode* const f =
+ serd_nodes_manage(nodes, serd_new_string(SERD_STATIC_STRING("file")));
+
+ const SerdNode* const s =
+ serd_nodes_manage(nodes, serd_new_uri(SERD_STATIC_STRING(NS_EG "s")));
+
+ const SerdNode* const p =
+
+ serd_nodes_manage(nodes, serd_new_uri(SERD_STATIC_STRING(NS_EG "p")));
+
+ const SerdNode* const o =
+ serd_nodes_manage(nodes, serd_new_uri(SERD_STATIC_STRING(NS_EG "o")));
+
+ const SerdNode* const g =
+ serd_nodes_manage(nodes, serd_new_uri(SERD_STATIC_STRING(NS_EG "g")));
+
+ assert(!serd_statement_copy(NULL));
+
+ SerdCursor* const cursor = serd_cursor_new(f, 1, 1);
+ SerdStatement* const statement = serd_statement_new(s, p, o, g, cursor);
+ assert(serd_statement_equals(statement, statement));
+ assert(!serd_statement_equals(statement, NULL));
+ assert(!serd_statement_equals(NULL, statement));
+ assert(serd_statement_subject(statement) == s);
+ assert(serd_statement_predicate(statement) == p);
+ assert(serd_statement_object(statement) == o);
+ assert(serd_statement_graph(statement) == g);
+ assert(serd_statement_cursor(statement) != cursor);
+ assert(serd_cursor_equals(serd_statement_cursor(statement), cursor));
+ assert(serd_statement_matches(statement, s, p, o, g));
+ assert(serd_statement_matches(statement, NULL, p, o, g));
+ assert(serd_statement_matches(statement, s, NULL, o, g));
+ assert(serd_statement_matches(statement, s, p, NULL, g));
+ assert(serd_statement_matches(statement, s, p, o, NULL));
+ assert(!serd_statement_matches(statement, o, NULL, NULL, NULL));
+ assert(!serd_statement_matches(statement, NULL, o, NULL, NULL));
+ assert(!serd_statement_matches(statement, NULL, NULL, s, NULL));
+ assert(!serd_statement_matches(statement, NULL, NULL, NULL, s));
+
+ SerdStatement* const diff_s = serd_statement_new(o, p, o, g, cursor);
+ assert(!serd_statement_equals(statement, diff_s));
+ serd_statement_free(diff_s);
+
+ SerdStatement* const diff_p = serd_statement_new(s, o, o, g, cursor);
+ assert(!serd_statement_equals(statement, diff_p));
+ serd_statement_free(diff_p);
+
+ SerdStatement* const diff_o = serd_statement_new(s, p, s, g, cursor);
+ assert(!serd_statement_equals(statement, diff_o));
+ serd_statement_free(diff_o);
+
+ SerdStatement* const diff_g = serd_statement_new(s, p, o, s, cursor);
+ assert(!serd_statement_equals(statement, diff_g));
+ serd_statement_free(diff_g);
+
+ serd_statement_free(statement);
+ serd_cursor_free(cursor);
+ serd_nodes_free(nodes);
+
+ return 0;
+}