diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/.clang-tidy | 1 | ||||
-rw-r--r-- | test/meson.build | 2 | ||||
-rwxr-xr-x | test/run_test_suite.py | 42 | ||||
-rw-r--r-- | test/test_cursor.c | 114 | ||||
-rw-r--r-- | test/test_free_null.c | 3 | ||||
-rw-r--r-- | test/test_model.c | 1396 | ||||
-rw-r--r-- | test/test_string.c | 2 |
7 files changed, 1559 insertions, 1 deletions
diff --git a/test/.clang-tidy b/test/.clang-tidy index dd609218..d8601571 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 77a9ba57..c4dc4b3b 100644 --- a/test/meson.build +++ b/test/meson.build @@ -8,9 +8,11 @@ unit_tests = [ 'byte_sink', 'byte_source', 'caret', + 'cursor', 'env', 'free_null', 'log', + 'model', 'node', 'node_syntax', 'nodes', diff --git a/test/run_test_suite.py b/test/run_test_suite.py index f23f29e6..f9524a9c 100755 --- a/test/run_test_suite.py +++ b/test/run_test_suite.py @@ -230,6 +230,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, @@ -333,6 +350,31 @@ 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" + ) + + model_command = command_prefix + [ + "-m", + "-a", + "-o", + osyntax, + "-w", + out_filename, + "-I", + test_uri, + test_path, + ] + + proc = subprocess.run(model_command, check=True) + + 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_cursor.c b/test/test_cursor.c new file mode 100644 index 00000000..cade0621 --- /dev/null +++ b/test/test_cursor.c @@ -0,0 +1,114 @@ +/* + Copyright 2011-2021 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> + +static void +test_copy(void) +{ + assert(!serd_cursor_copy(NULL)); + + SerdWorld* const world = serd_world_new(); + SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0u); + SerdCursor* const begin = serd_model_begin(model); + SerdCursor* const copy = serd_cursor_copy(begin); + + assert(serd_cursor_equals(copy, begin)); + + serd_cursor_free(copy); + serd_cursor_free(begin); + serd_model_free(model); + serd_world_free(world); +} + +static void +test_comparison(void) +{ + SerdWorld* const world = serd_world_new(); + 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_uri(nodes, SERD_STRING("http://example.org/a")); + const SerdNode* const b = + serd_nodes_uri(nodes, SERD_STRING("http://example.org/b")); + const SerdNode* const c = + serd_nodes_uri(nodes, SERD_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(model, a, NULL, NULL, NULL); + SerdCursor* const c2 = serd_model_find(model, a, b, NULL, NULL); + SerdCursor* const c3 = serd_model_find(model, NULL, b, c, NULL); + + // Ensure that they refer to the same statement but are not equal + 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(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(begin); + + // Check that a cursor that points to it via the same pattern is equal + SerdCursor* const c4 = serd_model_find(model, a, b, NULL, NULL); + assert(serd_cursor_get(c4) == serd_cursor_get(c1)); + assert(serd_cursor_equals(c4, c2)); + assert(!serd_cursor_equals(c4, c3)); + serd_cursor_free(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(c3); + serd_cursor_free(c2); + serd_cursor_free(c1); + serd_model_free(model); + serd_world_free(world); +} + +int +main(void) +{ + test_copy(); + test_comparison(); + + return 0; +} diff --git a/test/test_free_null.c b/test/test_free_null.c index 28f7563f..1fa87979 100644 --- a/test/test_free_null.c +++ b/test/test_free_null.c @@ -33,6 +33,9 @@ main(void) serd_reader_free(NULL); serd_writer_free(NULL); serd_nodes_free(NULL); + serd_model_free(NULL); + serd_statement_free(NULL); + serd_cursor_free(NULL); serd_caret_free(NULL); return 0; diff --git a/test/test_model.c b/test/test_model.c new file mode 100644 index 00000000..4cedfb2b --- /dev/null +++ b/test/test_model.c @@ -0,0 +1,1396 @@ +/* + Copyright 2011-2021 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* +uri(SerdWorld* world, const unsigned num) +{ + char str[] = "http://example.org/0000000000"; + + snprintf(str + 19, 11, "%010u", num); + + return serd_nodes_uri(serd_world_nodes(world), SERD_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_string(nodes, SERD_STRING("hello")); + + const SerdNode* hello_gb = serd_nodes_literal( + nodes, SERD_STRING("hello"), SERD_HAS_LANGUAGE, SERD_STRING("en-gb")); + + const SerdNode* hello_us = serd_nodes_literal( + nodes, SERD_STRING("hello"), SERD_HAS_LANGUAGE, SERD_STRING("en-us")); + + const SerdNode* hello_t4 = + serd_nodes_literal(nodes, + SERD_STRING("hello"), + SERD_HAS_DATATYPE, + serd_node_string_view(uri(world, 4))); + + const SerdNode* hello_t5 = + serd_nodes_literal(nodes, + SERD_STRING("hello"), + SERD_HAS_DATATYPE, + 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_literal( + nodes, SERD_STRING("bonjour"), SERD_HAS_LANGUAGE, SERD_STRING("fr")); + + const SerdNode* const salut = serd_nodes_literal( + nodes, SERD_STRING("salut"), SERD_HAS_LANGUAGE, 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_blank(nodes, 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) +{ + SerdNodes* const nodes = serd_nodes_new(); + + SerdCursor* cursor = serd_model_begin(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_ERR_BAD_CURSOR); + serd_cursor_free(cursor); + + const SerdStringView s = SERD_STRING("hello"); + + const SerdNode* plain_hello = serd_nodes_string(nodes, s); + + const SerdNode* type4_hello = serd_nodes_literal( + nodes, s, SERD_HAS_DATATYPE, serd_node_string_view(uri(world, 4))); + + const SerdNode* type5_hello = serd_nodes_literal( + nodes, s, SERD_HAS_DATATYPE, serd_node_string_view(uri(world, 5))); + + const SerdNode* gb_hello = + serd_nodes_literal(nodes, s, SERD_HAS_LANGUAGE, SERD_STRING("en-gb")); + + const SerdNode* us_hello = + serd_nodes_literal(nodes, s, SERD_HAS_LANGUAGE, 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(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(range); + + assert(num_results == test.expected_num_results); + } + + // Query blank node subject + + const SerdNode* ablank = serd_nodes_blank(nodes, SERD_STRING("ablank")); + + Quad pat = {ablank, 0, 0}; + int num_results = 0; + SerdCursor* range = serd_model_find(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(range); + + assert(num_results == 2); + + // Test nested queries + const SerdNode* last_subject = 0; + range = serd_model_find(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(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(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(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.buf); + return SERD_SUCCESS; +} + +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; + + if (!strstr(message.buf, "index")) { + fprintf(stderr, "error: %s\n", message.buf); + } + + assert(strstr(message.buf, "index")); + + 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_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(model); + SerdCursor* first = serd_model_find(model, NULL, NULL, NULL, NULL); + + assert(serd_cursor_equals(begin, first)); + + serd_cursor_free(first); + serd_cursor_free(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(model, SERD_ORDER_SPO); + assert(i); + assert(!serd_cursor_is_end(i)); + serd_cursor_free(i); + + i = serd_model_begin_ordered(model, SERD_ORDER_POS); + assert(serd_cursor_is_end(i)); + serd_cursor_free(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(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_ERR_BAD_CURSOR); + + serd_cursor_free(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(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(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(model, NULL, p, NULL, NULL); + while (!serd_cursor_is_end(cur)) { + ++count; + serd_cursor_advance(cur); + } + serd_cursor_free(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_ERR_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; + + SerdNodes* const nodes = serd_nodes_new(); + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0u); + SerdSink* inserter = serd_inserter_new(model, NULL); + + const SerdNode* const s = + serd_nodes_uri(nodes, SERD_STRING("http://example.org/s")); + + const SerdNode* const p = + serd_nodes_uri(nodes, SERD_STRING("http://example.org/p")); + + const SerdNode* const rel = serd_nodes_uri(nodes, SERD_STRING("rel")); + + serd_set_log_func(world, expected_error, NULL); + + assert(serd_sink_write(inserter, 0, s, p, rel, NULL) == SERD_ERR_BAD_ARG); + + 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(model); + SerdCursor* 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_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_ERR_BAD_CURSOR); + + // Check that erasing the end iterator does nothing + SerdCursor* const end = serd_cursor_copy(serd_model_end(model)); + assert(serd_model_erase(model, end) == SERD_FAILURE); + + serd_cursor_free(end); + serd_cursor_free(iter2); + serd_cursor_free(iter1); + serd_model_free(model); + return 0; +} + +static int +test_add_erase(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdNodes* const nodes = serd_nodes_new(); + 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_string(nodes, SERD_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_string(nodes, SERD_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(model, s, p, hi, NULL); + assert(!serd_model_erase(model, iter)); + assert(serd_model_size(model) == 1); + serd_cursor_free(iter); + + // Check that erased statement can not be found + SerdCursor* empty = serd_model_find(model, s, p, hi, NULL); + assert(serd_cursor_is_end(empty)); + serd_cursor_free(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; + + SerdNodes* const nodes = serd_nodes_new(); + const SerdNode* s = serd_nodes_uri(nodes, SERD_STRING("urn:s")); + const SerdNode* p = serd_nodes_uri(nodes, SERD_STRING("urn:p")); + const SerdNode* o = serd_nodes_uri(nodes, SERD_STRING("urn:o")); + + const SerdNode* f = + serd_nodes_uri(nodes, SERD_STRING("file:///tmp/file.ttl")); + + SerdCaret* caret = serd_caret_new(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(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_name(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(begin); + serd_model_free(model); + serd_caret_free(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(); + const SerdNode* lit = serd_nodes_string(nodes, SERD_STRING("string")); + const SerdNode* uri = serd_nodes_uri(nodes, SERD_STRING("urn:uri")); + const SerdNode* blank = serd_nodes_blank(nodes, 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(model); + while (!serd_cursor_equals(iter, serd_model_end(model))) { + assert(!serd_model_erase(model, iter)); + } + + assert(serd_model_empty(model)); + + serd_cursor_free(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(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(model, huge, huge, huge, 0); + assert(serd_cursor_is_end(range)); + + serd_cursor_free(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(model); + SerdCursor* range2 = serd_model_begin(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(range2); + serd_cursor_free(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); + + // Remove one graph via range + SerdCursor* range = serd_model_find(model, NULL, NULL, NULL, graph43); + SerdStatus st = serd_model_erase_statements(model, range); + assert(!st); + serd_cursor_free(range); + + // Erase the first tuple (an element in the default graph) + SerdCursor* iter = serd_model_begin(model); + assert(!serd_model_erase(model, iter)); + serd_cursor_free(iter); + + // Ensure only the other graph is left + Quad pat = {0, 0, 0, graph42}; + /* fprintf(stderr, "GRAPH: %s\n", serd_node_string(graph42)); */ + for (iter = serd_model_begin(model); + !serd_cursor_equals(iter, serd_model_end(model)); + serd_cursor_advance(iter)) { + const SerdStatement* const s = serd_cursor_get(iter); + assert(s); + /* fprintf(stderr, "STATEMENT GRAPH: %s\n", serd_node_string(pat[3])); */ + assert(serd_statement_matches(s, pat[0], pat[1], pat[2], pat[3])); + } + serd_cursor_free(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; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + SerdNodes* nodes = serd_nodes_new(); + + const SerdNode* s = serd_nodes_uri(nodes, SERD_STRING("urn:s")); + const SerdNode* p = serd_nodes_uri(nodes, SERD_STRING("urn:p")); + const SerdNode* b1 = serd_nodes_blank(nodes, SERD_STRING("b1")); + const SerdNode* b2 = serd_nodes_blank(nodes, SERD_STRING("b2")); + const SerdNode* o = serd_nodes_uri(nodes, SERD_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, 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); + + SerdCursor* all = serd_model_begin(model); + serd_describe_range(all, serd_writer_sink(writer), SERD_NO_INLINE_OBJECTS); + serd_cursor_free(all); + + serd_writer_finish(writer); + const char* str = serd_buffer_sink_finish(&buffer); + const char* expected = "<urn:s>\n" + " <urn:p> _:b1 ,\n" + " _:b2 .\n" + "\n" + "_:b1\n" + " <urn:p> <urn:o> .\n" + "\n" + "_:b2\n" + " <urn:p> <urn:o> .\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; +} + +static int +test_write_bad_list(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + SerdNodes* nodes = serd_nodes_new(); + + serd_model_add_index(model, SERD_ORDER_OPS); + + const SerdNode* s = serd_nodes_uri(nodes, SERD_STRING("urn:s")); + const SerdNode* p = serd_nodes_uri(nodes, SERD_STRING("urn:p")); + const SerdNode* list1 = serd_nodes_blank(nodes, SERD_STRING("l1")); + const SerdNode* list2 = serd_nodes_blank(nodes, SERD_STRING("l2")); + const SerdNode* nofirst = serd_nodes_blank(nodes, SERD_STRING("nof")); + const SerdNode* norest = serd_nodes_blank(nodes, SERD_STRING("nor")); + const SerdNode* pfirst = serd_nodes_uri(nodes, SERD_STRING(RDF_FIRST)); + const SerdNode* prest = serd_nodes_uri(nodes, SERD_STRING(RDF_REST)); + const SerdNode* val1 = serd_nodes_string(nodes, SERD_STRING("a")); + const SerdNode* val2 = serd_nodes_string(nodes, SERD_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); + + SerdCursor* all = serd_model_begin(model); + serd_describe_range(all, serd_writer_sink(writer), 0); + serd_cursor_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; +} + +static int +test_write_infinite_list(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); + SerdNodes* nodes = serd_nodes_new(); + + serd_model_add_index(model, SERD_ORDER_OPS); + + const SerdNode* s = serd_nodes_uri(nodes, SERD_STRING("urn:s")); + const SerdNode* p = serd_nodes_uri(nodes, SERD_STRING("urn:p")); + const SerdNode* list1 = serd_nodes_blank(nodes, SERD_STRING("l1")); + const SerdNode* list2 = serd_nodes_blank(nodes, SERD_STRING("l2")); + + const SerdNode* pfirst = serd_nodes_uri(nodes, SERD_STRING(RDF_FIRST)); + const SerdNode* prest = serd_nodes_uri(nodes, SERD_STRING(RDF_REST)); + const SerdNode* val1 = serd_nodes_string(nodes, SERD_STRING("a")); + const SerdNode* val2 = serd_nodes_string(nodes, SERD_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, 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); + + 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(model); + serd_describe_range(all, serd_writer_sink(writer), 0); + serd_cursor_free(all); + + serd_writer_finish(writer); + const char* str = serd_buffer_sink_finish(&buffer); + 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(!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_subject(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + serd_set_log_func(world, expected_error, NULL); + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0u); + SerdNodes* nodes = serd_nodes_new(); + + serd_model_add_index(model, SERD_ORDER_OPS); + + /* const SerdNode* s = serd_nodes_uri(nodes, SERD_STRING("urn:s")); */ + const SerdNode* p = serd_nodes_uri(nodes, SERD_STRING("urn:p")); + const SerdNode* o = serd_nodes_uri(nodes, SERD_STRING("urn:o")); + const SerdNode* l1 = serd_nodes_blank(nodes, SERD_STRING("l1")); + + const SerdNode* one = serd_nodes_integer(nodes, 1, SERD_EMPTY_STRING()); + const SerdNode* l2 = serd_nodes_blank(nodes, SERD_STRING("l2")); + const SerdNode* two = serd_nodes_integer(nodes, 2, SERD_EMPTY_STRING()); + + const SerdNode* rdf_first = serd_nodes_uri(nodes, SERD_STRING(RDF_FIRST)); + const SerdNode* rdf_rest = serd_nodes_uri(nodes, SERD_STRING(RDF_REST)); + const SerdNode* rdf_nil = serd_nodes_uri(nodes, SERD_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(SERD_EMPTY_STRING()); + + for (size_t max_successes = 0; max_successes < 18; ++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); + SerdCursor* const all = serd_model_begin(model); + const SerdStatus st = serd_describe_range(all, sink, 0); + serd_cursor_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; +} + +static int +test_write_error_in_list_object(SerdWorld* world, const unsigned n_quads) +{ + (void)n_quads; + + serd_set_log_func(world, expected_error, NULL); + + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0u); + SerdNodes* nodes = serd_nodes_new(); + + serd_model_add_index(model, SERD_ORDER_OPS); + + const SerdNode* s = serd_nodes_uri(nodes, SERD_STRING("urn:s")); + const SerdNode* p = serd_nodes_uri(nodes, SERD_STRING("urn:p")); + const SerdNode* l1 = serd_nodes_blank(nodes, SERD_STRING("l1")); + + const SerdNode* one = serd_nodes_integer(nodes, 1, SERD_EMPTY_STRING()); + const SerdNode* l2 = serd_nodes_blank(nodes, SERD_STRING("l2")); + const SerdNode* two = serd_nodes_integer(nodes, 2, SERD_EMPTY_STRING()); + + const SerdNode* rdf_first = serd_nodes_uri(nodes, SERD_STRING(RDF_FIRST)); + const SerdNode* rdf_rest = serd_nodes_uri(nodes, SERD_STRING(RDF_REST)); + const SerdNode* rdf_nil = serd_nodes_uri(nodes, SERD_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); + SerdCursor* const all = serd_model_begin(model); + const SerdStatus st = serd_describe_range(all, sink, 0); + serd_cursor_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_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(); + 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_string.c b/test/test_string.c index 1181de01..2bd47680 100644 --- a/test/test_string.c +++ b/test/test_string.c @@ -32,7 +32,7 @@ test_strerror(void) { const char* msg = serd_strerror(SERD_SUCCESS); assert(!strcmp(msg, "Success")); - for (int i = SERD_FAILURE; i <= SERD_ERR_BAD_URI; ++i) { + for (int i = SERD_FAILURE; i <= SERD_ERR_BAD_INDEX; ++i) { msg = serd_strerror((SerdStatus)i); assert(strcmp(msg, "Success")); } |