aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2018-05-12 13:28:47 +0200
committerDavid Robillard <d@drobilla.net>2018-05-27 21:10:20 +0200
commit582bfbe1cb0a6aa833f56c5bd8cf42abe5c5d13a (patch)
treeb866ca44592cc07a34fcad8ce18c29259fb39199 /tests
parentf48dac14b6533b4cdd4804513216f4f11de36d9a (diff)
downloadserd-582bfbe1cb0a6aa833f56c5bd8cf42abe5c5d13a.tar.gz
serd-582bfbe1cb0a6aa833f56c5bd8cf42abe5c5d13a.tar.bz2
serd-582bfbe1cb0a6aa833f56c5bd8cf42abe5c5d13a.zip
WIP: Add model
Diffstat (limited to 'tests')
-rw-r--r--tests/model_test.c673
1 files changed, 673 insertions, 0 deletions
diff --git a/tests/model_test.c b/tests/model_test.c
new file mode 100644
index 00000000..7ecd4eb2
--- /dev/null
+++ b/tests/model_test.c
@@ -0,0 +1,673 @@
+/*
+ Copyright 2011-2018 David Robillard <http://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.
+*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "serd/serd.h"
+
+#include "../src/log.h"
+#include "test_utils.h"
+
+#define WILDCARD_NODE NULL
+
+static const unsigned n_objects_per = 2;
+
+typedef const SerdNode* Quad[4];
+
+typedef struct
+{
+ Quad query;
+ int expected_num_results;
+} QueryTest;
+
+static SerdNode*
+uri(SerdWorld* world, int num)
+{
+ char str[] = "eg:000";
+ snprintf(str + 3, 4, "%03d", num);
+ return serd_node_new_uri(str);
+}
+
+static int
+generate(SerdWorld* world, SerdModel* model, size_t n_quads, SerdNode* graph)
+{
+ for (size_t i = 0; i < n_quads; ++i) {
+ int num = (i * n_objects_per) + 1;
+
+ 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) {
+ if (serd_model_add(model, ids[0], ids[1], ids[2 + j], graph)) {
+ FAIL("Failed to add quad\n");
+ }
+ }
+
+ for (unsigned j = 0; j < 2 + n_objects_per; ++j) {
+ serd_node_free(ids[j]);
+ }
+ }
+
+ // Add some literals
+
+ // (98 4 "hello") and (98 4 "hello"^^<5>)
+ SerdNode* hello = serd_node_new_literal("hello", NULL, NULL);
+ SerdNode* hello_gb = serd_node_new_literal("hello", NULL, "en-gb");
+ SerdNode* hello_us = serd_node_new_literal("hello", NULL, "en-us");
+ SerdNode* hello_t4 = serd_node_new_literal("hello", uri(world, 4), NULL);
+ SerdNode* hello_t5 = serd_node_new_literal("hello", uri(world, 5), NULL);
+ if (serd_model_add(model, uri(world, 98), uri(world, 4), hello, graph)) {
+ FAIL("Failed to add untyped literal\n");
+ }
+ if (serd_model_add(model, uri(world, 98), uri(world, 4), hello_t5, graph)) {
+ FAIL("Failed to add typed literal\n");
+ }
+
+ // (96 4 "hello"^^<4>) and (96 4 "hello"^^<5>)
+ if (serd_model_add(model, uri(world, 96), uri(world, 4), hello_t4, graph)) {
+ FAIL("Failed to add typed literal\n");
+ }
+ if (serd_model_add(model, uri(world, 96), uri(world, 4), hello_t5, graph)) {
+ FAIL("Failed to add typed literal\n");
+ }
+
+ // (94 5 "hello") and (94 5 "hello"@en-gb)
+ if (serd_model_add(model, uri(world, 94), uri(world, 5), hello, graph)) {
+ FAIL("Failed to add literal\n");
+ }
+ if (serd_model_add(model, uri(world, 94), uri(world, 5), hello_gb, graph)) {
+ FAIL("Failed to add literal with language\n");
+ }
+
+ // (92 6 "hello"@en-us) and (92 6 "hello"@en-gb)
+ if (serd_model_add(model, uri(world, 92), uri(world, 6), hello_us, graph)) {
+ FAIL("Failed to add literal with language\n");
+ }
+ if (serd_model_add(model, uri(world, 92), uri(world, 6), hello_gb, graph)) {
+ FAIL("Failed to add literal with language\n");
+ }
+
+ // (14 6 "bonjour"@fr) and (14 6 "salut"@fr)
+ SerdNode* bonjour = serd_node_new_literal("bonjour", NULL, "fr");
+ SerdNode* salut = serd_node_new_literal("salut", NULL, "fr");
+ if (serd_model_add(model, uri(world, 14), uri(world, 6), bonjour, graph)) {
+ FAIL("Failed to add literal with language\n");
+ }
+ if (serd_model_add(model, uri(world, 14), uri(world, 6), salut, graph)) {
+ FAIL("Failed to add literal with language\n");
+ }
+
+ // Attempt to add duplicates
+ if (!serd_model_add(model, uri(world, 14), uri(world, 6), salut, graph)) {
+ FAIL("Successfully added duplicate statement\n");
+ }
+
+ // Add a blank node subject
+ SerdNode* ablank = serd_node_new_blank("ablank");
+ if (serd_model_add(model, ablank, uri(world, 6), salut, graph)) {
+ FAIL("Failed to add blank subject statement\n");
+ }
+
+ // Add statement with URI object
+ if (serd_model_add(model, ablank, uri(world, 6), uri(world, 7), graph)) {
+ FAIL("Failed to add URI subject statement\n");
+ }
+
+ return EXIT_SUCCESS;
+}
+
+static int
+test_read(SerdWorld* world, SerdModel* model, SerdNode* g, const size_t n_quads)
+{
+ Quad id;
+
+ SerdIter* iter = serd_model_begin(model);
+ if (serd_iter_get_model(iter) != model) {
+ FAIL("Iterator has incorrect serd pointer\n");
+ }
+
+ for (; !serd_iter_end(iter); serd_iter_next(iter)) {
+ const SerdStatement* statement = serd_iter_get(iter);
+ if (!serd_statement_get_subject(statement) ||
+ !serd_statement_get_predicate(statement) ||
+ !serd_statement_get_object(statement)) {
+ FAIL("Iterator points to null node\n");
+ }
+ }
+
+ // Attempt to increment past end
+ if (!serd_iter_next(iter)) {
+ FAIL("Successfully incremented past end\n");
+ }
+
+ serd_iter_free(iter);
+
+ const char* s = "hello";
+ SerdNode* plain_hello = serd_node_new_literal(s, NULL, NULL);
+ SerdNode* type4_hello = serd_node_new_literal(s, uri(world, 4), NULL);
+ SerdNode* type5_hello = serd_node_new_literal(s, uri(world, 5), NULL);
+ SerdNode* gb_hello = serd_node_new_literal(s, NULL, "en-gb");
+ SerdNode* us_hello = serd_node_new_literal(s, NULL, "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 };
+ if (!serd_model_ask(model, match[0], match[1], match[2], match[3])) {
+ FAILF("No match for " TUP_FMT "\n", TUP_FMT_ARGS(match));
+ }
+
+ Quad nomatch = { uri(world, 1), uri(world, 2), uri(world, 9), g };
+ if (serd_model_ask(model, nomatch[0], nomatch[1], nomatch[2], nomatch[3])) {
+ FAILF("False match for " TUP_FMT "\n", TUP_FMT_ARGS(nomatch));
+ }
+
+ if (serd_model_get(model, NULL, NULL, uri(world, 3), g)) {
+ FAIL("Get *,*,3 succeeded\n");
+ } else if (serd_model_get(model, uri(world, 1), uri(world, 99), NULL, g)) {
+ FAIL("Get 1,2,9 succeeded\n");
+ } else if (!serd_node_equals(
+ serd_model_get(
+ model, uri(world, 1), uri(world, 2), NULL, g),
+ uri(world, 3))) {
+ FAIL("Get 1,2,* != 3\n");
+ } else if (!serd_node_equals(
+ serd_model_get(
+ model, uri(world, 1), NULL, uri(world, 3), g),
+ uri(world, 2))) {
+ FAIL("Get 1,*,3 != 2\n");
+ } else if (!serd_node_equals(
+ serd_model_get(
+ model, NULL, uri(world, 2), uri(world, 3), g),
+ uri(world, 1))) {
+ FAIL("Get *,2,3 != 1\n");
+ }
+
+ for (unsigned i = 0; i < NUM_PATTERNS; ++i) {
+ QueryTest test = patterns[i];
+ Quad pat = { test.query[0], test.query[1], test.query[2], g };
+
+ iter = serd_model_find(model, pat[0], pat[1], pat[2], pat[3]);
+ int num_results = 0;
+ for (; !serd_iter_end(iter); serd_iter_next(iter)) {
+ ++num_results;
+ if (!serd_statement_matches(
+ serd_iter_get(iter), pat[0], pat[1], pat[2], pat[3])) {
+ serd_iter_free(iter);
+ FAILF("Query result " TUP_FMT " does not match pattern " TUP_FMT
+ "\n",
+ TUP_FMT_ARGS(id),
+ TUP_FMT_ARGS(pat));
+ }
+ }
+ serd_iter_free(iter);
+ if (num_results != test.expected_num_results) {
+ FAILF("Expected %d results for " TUP_FMT ", got %d\n",
+ test.expected_num_results,
+ TUP_FMT_ARGS(pat),
+ num_results);
+ }
+ }
+
+ // Query blank node subject
+ Quad pat = { serd_node_new_blank("ablank"), 0, 0 };
+ int num_results = 0;
+ iter = serd_model_find(model, pat[0], pat[1], pat[2], pat[3]);
+ for (; !serd_iter_end(iter); serd_iter_next(iter)) {
+ ++num_results;
+ const SerdStatement* statement = serd_iter_get(iter);
+ if (!serd_statement_matches(
+ statement, pat[0], pat[1], pat[2], pat[3])) {
+ serd_iter_free(iter);
+ FAILF("Result for " TUP_FMT " does not match pattern\n",
+ TUP_FMT_ARGS(pat));
+ }
+ }
+ serd_iter_free(iter);
+
+ if (num_results != 2) {
+ FAIL("Blank node subject query failed\n");
+ }
+
+ // Test nested queries
+ const SerdNode* last_subject = 0;
+ iter = serd_model_find(model, NULL, NULL, NULL, NULL);
+ for (; !serd_iter_end(iter); serd_iter_next(iter)) {
+ const SerdStatement* statement = serd_iter_get(iter);
+ const SerdNode* subject = serd_statement_get_subject(statement);
+ if (subject == last_subject) {
+ continue;
+ }
+
+ Quad subpat = { subject, 0, 0 };
+ SerdIter* subiter = serd_model_find(
+ model, subpat[0], subpat[1], subpat[2], subpat[3]);
+ const SerdStatement* substatement = serd_iter_get(subiter);
+ uint64_t num_sub_results = 0;
+ if (serd_statement_get_subject(substatement) != subject) {
+ FAIL("Incorrect initial submatch\n");
+ }
+ for (; !serd_iter_end(subiter); serd_iter_next(subiter)) {
+ if (!serd_statement_matches(serd_iter_get(subiter),
+ subpat[0],
+ subpat[1],
+ subpat[2],
+ subpat[3])) {
+ serd_iter_free(iter);
+ serd_iter_free(subiter);
+ FAIL("Nested query result does not match pattern\n");
+ }
+ ++num_sub_results;
+ }
+ serd_iter_free(subiter);
+ if (num_sub_results != n_objects_per) {
+ FAILF("Nested query " TUP_FMT " failed"
+ " (%d results, expected %d)\n",
+ TUP_FMT_ARGS(subpat),
+ num_sub_results,
+ n_objects_per);
+ }
+
+ uint64_t count = serd_model_count(model, subject, 0, 0, 0);
+ if (count != num_sub_results) {
+ FAILF("Query " TUP_FMT " count %d does not match result count %d\n",
+ TUP_FMT_ARGS(subpat),
+ count,
+ num_sub_results);
+ }
+
+ last_subject = subject;
+ }
+ serd_iter_free(iter);
+
+ return 0;
+}
+
+static SerdStatus
+unexpected_error(void* handle, const SerdError* error)
+{
+ fprintf(stderr, "error: ");
+ vfprintf(stderr, error->fmt, *error->args);
+ return SERD_SUCCESS;
+}
+
+static SerdStatus
+expected_error(void* handle, const SerdError* error)
+{
+ fprintf(stderr, "expected: ");
+ vfprintf(stderr, error->fmt, *error->args);
+ return SERD_SUCCESS;
+}
+
+static int
+test_free_null(SerdWorld* world, const size_t n_quads)
+{
+ serd_model_free(NULL); // Shouldn't crash
+ return 0;
+}
+
+static int
+test_get_world(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, false);
+ if (serd_model_get_world(model) != world) {
+ FAIL("Model has incorrect world pointer\n");
+ }
+
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_add_null(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, false);
+
+ serd_world_set_error_sink(world, expected_error, NULL);
+ if (!serd_model_add(model, 0, 0, 0, 0)) {
+ FAIL("Added NULL tuple\n");
+ } else if (!serd_model_add(model, uri(world, 1), 0, 0, 0)) {
+ FAIL("Added tuple with NULL P and O\n");
+ } else if (!serd_model_add(model, uri(world, 1), uri(world, 2), 0, 0)) {
+ FAIL("Added tuple with NULL O\n");
+ } else if (serd_model_num_quads(model) != 0) {
+ FAIL("Model contains invalid statements\n");
+ }
+
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_add_with_iterator(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, false);
+
+ serd_world_set_error_sink(world, expected_error, NULL);
+ if (serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0)) {
+ FAIL("Failed to add statement\n");
+ }
+
+ // Add a statement with an active iterator
+ SerdIter* iter = serd_model_begin(model);
+ if (serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 4), 0)) {
+ FAIL("Failed to add statement with active iterator\n");
+ }
+
+ // Check that iterator has been invalidated
+ if (serd_iter_get(iter)) {
+ FAIL("Successfully dereferenced invalidated iterator\n");
+ } else if (!serd_iter_next(iter)) {
+ FAIL("Successfully incremented invalidated iterator\n");
+ }
+
+ serd_iter_free(iter);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_erase_with_iterator(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, false);
+
+ serd_world_set_error_sink(world, expected_error, NULL);
+ if (serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0) ||
+ serd_model_add(model, uri(world, 4), uri(world, 5), uri(world, 6), 0)) {
+ FAIL("Failed to add statements\n");
+ }
+
+ // Erase a statement with an active iterator
+ SerdIter* iter1 = serd_model_begin(model);
+ SerdIter* iter2 = serd_model_begin(model);
+ if (serd_model_erase(model, iter1)) {
+ FAIL("Failed to erase statement\n");
+ }
+
+ // Check that erased iterator points to the next statement
+ if (!serd_statement_matches(serd_iter_get(iter1),
+ uri(world, 4),
+ uri(world, 5),
+ uri(world, 6),
+ 0)) {
+ FAIL("Erased iterator was not incremented\n");
+ }
+
+ // Check that other iterator has been invalidated
+ if (serd_iter_get(iter2)) {
+ FAIL("Successfully dereferenced invalidated iterator\n");
+ } else if (!serd_iter_next(iter2)) {
+ FAIL("Successfully incremented invalidated iterator\n");
+ }
+
+ serd_iter_free(iter2);
+ serd_iter_free(iter1);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_add_erase(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, true);
+
+ // Add (s p "hello")
+ SerdNode* s = uri(world, 1);
+ SerdNode* p = uri(world, 2);
+ SerdNode* hello = serd_node_new_literal("hello", NULL, NULL);
+ if (serd_model_add(model, s, p, hello, 0)) {
+ FAIL("Failed to add statement (s p \"hello\")\n");
+ } else if (!serd_model_ask(model, s, p, hello, 0)) {
+ FAIL("Added statement (s p \"hello\") not found\n");
+ }
+
+ // Add (s p "hi")
+ SerdNode* hi = serd_node_new_literal("hi", NULL, NULL);
+ if (serd_model_add(model, s, p, hi, NULL)) {
+ FAIL("Failed to add statement (s p \"hi\")\n");
+ } else if (!serd_model_ask(model, s, p, hi, 0)) {
+ FAIL("Added statement (s p \"hi\") not found\n");
+ }
+
+ // Erase (s p "hi")
+ SerdIter* iter = serd_model_find(model, s, p, hi, NULL);
+ if (serd_model_erase(model, iter)) {
+ FAIL("Failed to erase statement (s p \"hi\")\n");
+ } else if (serd_model_num_quads(model) != 1) {
+ FAILF("Expected 1 node after erasure, not %zu\n",
+ serd_model_num_quads(model));
+ }
+ serd_iter_free(iter);
+
+ // Check that erased statement can not be found
+ iter = serd_model_find(model, s, p, hi, NULL);
+ if (!serd_iter_end(iter)) {
+ FAIL("Found removed statement (s p \"hi\")\n");
+ }
+ serd_iter_free(iter);
+
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_erase_all(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, false);
+ generate(world, model, n_quads, NULL);
+
+ SerdIter* iter = serd_model_begin(model);
+ while (!serd_iter_end(iter)) {
+ const SerdStatus st = serd_model_erase(model, iter);
+ if (st) {
+ FAILF("Failed to erase tuple via iterator (%s)\n",
+ serd_strerror(st));
+ }
+ }
+
+ serd_iter_free(iter);
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_find_past_end(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, false);
+ SerdNode* s = uri(world, 1);
+ SerdNode* p = uri(world, 2);
+ SerdNode* o = uri(world, 3);
+ if (serd_model_add(model, s, p, o, 0)) {
+ FAIL("Failed to add statement\n");
+ } else if (!serd_model_ask(model, s, p, o, 0)) {
+ FAIL("Added statement not found\n");
+ }
+
+ SerdNode* huge = uri(world, 999);
+ if (!serd_iter_end(serd_model_find(model, huge, huge, huge, 0))) {
+ FAIL("Found statement past end of model\n");
+ }
+
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_triple_index_read(SerdWorld* world, const size_t n_quads)
+{
+ static const char* const index_names[6] = { "spo", "sop", "ops",
+ "osp", "pso", "pos" };
+
+ for (unsigned i = 0; i < 6; ++i) {
+ SerdModel* model = serd_model_new(world, (1 << i), false);
+ generate(world, model, n_quads, 0);
+ if (test_read(world, model, 0, n_quads)) {
+ FAILF("Failure reading index `%s'\n", index_names[i]);
+ }
+ serd_model_free(model);
+ }
+
+ return 0;
+}
+
+static int
+test_quad_index_read(SerdWorld* world, const size_t n_quads)
+{
+ static const char* const index_names[6] = { "gspo", "gsop", "gops",
+ "gosp", "gpso", "gpos" };
+
+ for (unsigned i = 0; i < 6; ++i) {
+ SerdModel* model = serd_model_new(world, (1 << i), true);
+ SerdNode* graph = uri(world, 42);
+ generate(world, model, n_quads, graph);
+ if (test_read(world, model, graph, n_quads)) {
+ FAILF("Failure reading index `%s'\n", index_names[i]);
+ }
+ serd_model_free(model);
+ }
+
+ return 0;
+}
+
+static int
+test_remove_graph(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, true);
+
+ // Generate a couple of graphs
+ SerdNode* graph42 = uri(world, 42);
+ SerdNode* graph43 = uri(world, 43);
+ generate(world, model, 1, graph42);
+ generate(world, model, 1, graph43);
+
+ // Remove one graph via iterator
+ SerdStatus st;
+ SerdIter* iter = serd_model_find(model, NULL, NULL, NULL, graph43);
+ while (!serd_iter_end(iter)) {
+ if ((st = serd_model_erase(model, iter))) {
+ FAILF("Remove by iterator failed (%s)\n", serd_strerror(st));
+ }
+ }
+ serd_iter_free(iter);
+
+ // Erase the first tuple (an element in the default graph)
+ iter = serd_model_begin(model);
+ if (serd_model_erase(model, iter)) {
+ FAIL("Failed to erase begin iterator on non-empty model\n");
+ }
+ 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_end(iter);
+ serd_iter_next(iter)) {
+ if (!serd_statement_matches(
+ serd_iter_get(iter), pat[0], pat[1], pat[2], pat[3])) {
+ FAIL("Graph removal via iteration failed\n");
+ }
+ }
+ serd_iter_free(iter);
+
+ serd_model_free(model);
+ return 0;
+}
+
+static int
+test_default_graph(SerdWorld* world, const size_t n_quads)
+{
+ SerdModel* model = serd_model_new(world, SERD_SPO, true);
+ SerdNode* s = uri(world, 1);
+ SerdNode* p = uri(world, 2);
+ SerdNode* o = uri(world, 3);
+ SerdNode* g1 = uri(world, 101);
+ SerdNode* g2 = uri(world, 102);
+
+ // Insert the same statement into two graphs
+ if (serd_model_add(model, s, p, o, g1)) {
+ FAIL("Failed to insert statement into first graph\n");
+ }
+ if (serd_model_add(model, s, p, o, g2)) {
+ FAIL("Failed to insert statement into second graph\n");
+ }
+
+ // Ensure we only see statement once in the default graph
+ if (serd_model_count(model, s, p, o, NULL) != 1) {
+ FAIL("Found duplicate triple in default graph\n");
+ }
+
+ serd_model_free(model);
+ return 0;
+}
+
+int
+main(int argc, char** argv)
+{
+ static const size_t n_quads = 300;
+
+ serd_model_free(NULL); // Shouldn't crash
+
+ typedef int (*TestFunc)(SerdWorld*, size_t);
+
+ const TestFunc tests[] = { test_free_null,
+ test_get_world,
+ test_add_null,
+ test_add_with_iterator,
+ test_erase_with_iterator,
+ test_add_erase,
+ test_erase_all,
+ test_find_past_end,
+ test_triple_index_read,
+ test_quad_index_read,
+ test_remove_graph,
+ test_default_graph,
+ NULL };
+
+ SerdWorld* world = serd_world_new();
+ for (const TestFunc* t = tests; *t; ++t) {
+ serd_world_set_error_sink(world, unexpected_error, NULL);
+ if ((*t)(world, n_quads)) {
+ return EXIT_FAILURE;
+ }
+ }
+
+ serd_world_free(world);
+ return 0;
+}