// Copyright 2011-2021 David Robillard // SPDX-License-Identifier: ISC #undef NDEBUG #include "failing_allocator.h" #include "serd/buffer.h" #include "serd/caret.h" #include "serd/cursor.h" #include "serd/describe.h" #include "serd/env.h" #include "serd/inserter.h" #include "serd/log.h" #include "serd/model.h" #include "serd/node.h" #include "serd/nodes.h" #include "serd/output_stream.h" #include "serd/sink.h" #include "serd/statement.h" #include "serd/status.h" #include "serd/syntax.h" #include "serd/world.h" #include "serd/writer.h" #include "zix/allocator.h" #include "zix/attributes.h" #include "zix/string_view.h" #include #include #include #include #include #include #define WILDCARD_NODE NULL #define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" #define RDF_FIRST NS_RDF "first" #define RDF_REST NS_RDF "rest" #define RDF_NIL NS_RDF "nil" #define N_OBJECTS_PER 2U typedef const SerdNode* Quad[4]; typedef struct { Quad query; int expected_num_results; } QueryTest; static const SerdNode* uri(SerdWorld* world, const unsigned num) { char str[] = "http://example.org/0000000000"; snprintf(str + 19, 11, "%010u", num); return serd_nodes_get(serd_world_nodes(world), serd_a_uri_string(str)); } static int generate(SerdWorld* world, SerdModel* model, size_t n_quads, const SerdNode* graph) { SerdNodes* nodes = serd_world_nodes(world); SerdStatus st = SERD_SUCCESS; for (unsigned i = 0; i < n_quads; ++i) { unsigned num = (i * N_OBJECTS_PER) + 1U; const SerdNode* ids[2 + N_OBJECTS_PER]; for (unsigned j = 0; j < 2 + N_OBJECTS_PER; ++j) { ids[j] = uri(world, num++); } for (unsigned j = 0; j < N_OBJECTS_PER; ++j) { st = serd_model_add(model, ids[0], ids[1], ids[2 + j], graph); assert(!st); } } // Add some literals // (98 4 "hello") and (98 4 "hello"^^<5>) const SerdNode* hello = serd_nodes_get(nodes, serd_a_string("hello")); const SerdNode* hello_gb = serd_nodes_get( nodes, serd_a_plain_literal(zix_string("hello"), zix_string("en-gb"))); const SerdNode* hello_us = serd_nodes_get( nodes, serd_a_plain_literal(zix_string("hello"), zix_string("en-us"))); const SerdNode* hello_t4 = serd_nodes_get(nodes, serd_a_typed_literal(zix_string("hello"), serd_node_string_view(uri(world, 4)))); const SerdNode* hello_t5 = serd_nodes_get(nodes, serd_a_typed_literal(zix_string("hello"), serd_node_string_view(uri(world, 5)))); assert(!serd_model_add(model, uri(world, 98), uri(world, 4), hello, graph)); assert( !serd_model_add(model, uri(world, 98), uri(world, 4), hello_t5, graph)); // (96 4 "hello"^^<4>) and (96 4 "hello"^^<5>) assert( !serd_model_add(model, uri(world, 96), uri(world, 4), hello_t4, graph)); assert( !serd_model_add(model, uri(world, 96), uri(world, 4), hello_t5, graph)); // (94 5 "hello") and (94 5 "hello"@en-gb) assert(!serd_model_add(model, uri(world, 94), uri(world, 5), hello, graph)); assert( !serd_model_add(model, uri(world, 94), uri(world, 5), hello_gb, graph)); // (92 6 "hello"@en-us) and (92 6 "hello"@en-gb) assert( !serd_model_add(model, uri(world, 92), uri(world, 6), hello_us, graph)); assert( !serd_model_add(model, uri(world, 92), uri(world, 6), hello_gb, graph)); // (14 6 "bonjour"@fr) and (14 6 "salut"@fr) const SerdNode* const bonjour = serd_nodes_get( nodes, serd_a_plain_literal(zix_string("bonjour"), zix_string("fr"))); const SerdNode* const salut = serd_nodes_get( nodes, serd_a_plain_literal(zix_string("salut"), zix_string("fr"))); assert(!serd_model_add(model, uri(world, 14), uri(world, 6), bonjour, graph)); assert(!serd_model_add(model, uri(world, 14), uri(world, 6), salut, graph)); // Attempt to add duplicates assert(serd_model_add(model, uri(world, 14), uri(world, 6), salut, graph)); // Add a blank node subject const SerdNode* ablank = serd_nodes_get(nodes, serd_a_blank(zix_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) { ZixAllocator* const allocator = zix_default_allocator(); SerdNodes* const nodes = serd_nodes_new(allocator); SerdCursor* cursor = serd_model_begin(NULL, model); const SerdStatement* prev = NULL; for (; !serd_cursor_equals(cursor, serd_model_end(model)); serd_cursor_advance(cursor)) { const SerdStatement* statement = serd_cursor_get(cursor); assert(statement); assert(serd_statement_subject(statement)); assert(serd_statement_predicate(statement)); assert(serd_statement_object(statement)); assert(!serd_statement_equals(statement, prev)); assert(!serd_statement_equals(prev, statement)); prev = statement; } // Attempt to increment past end assert(serd_cursor_advance(cursor) == SERD_BAD_CURSOR); serd_cursor_free(NULL, cursor); static const ZixStringView s = ZIX_STATIC_STRING("hello"); const SerdNode* plain_hello = serd_nodes_get(nodes, serd_a_string_view(s)); const SerdNode* type4_hello = serd_nodes_get( nodes, serd_a_typed_literal(s, serd_node_string_view(uri(world, 4)))); const SerdNode* type5_hello = serd_nodes_get( nodes, serd_a_typed_literal(s, serd_node_string_view(uri(world, 5)))); const SerdNode* gb_hello = serd_nodes_get(nodes, serd_a_plain_literal(s, zix_string("en-gb"))); const SerdNode* us_hello = serd_nodes_get(nodes, serd_a_plain_literal(s, zix_string("en-us"))); #define NUM_PATTERNS 18 QueryTest patterns[NUM_PATTERNS] = { {{NULL, NULL, NULL}, (int)(n_quads * N_OBJECTS_PER) + 12}, {{uri(world, 1), WILDCARD_NODE, WILDCARD_NODE}, 2}, {{uri(world, 9), uri(world, 9), uri(world, 9)}, 0}, {{uri(world, 1), uri(world, 2), uri(world, 4)}, 1}, {{uri(world, 3), uri(world, 4), WILDCARD_NODE}, 2}, {{WILDCARD_NODE, uri(world, 2), uri(world, 4)}, 1}, {{WILDCARD_NODE, WILDCARD_NODE, uri(world, 4)}, 1}, {{uri(world, 1), WILDCARD_NODE, WILDCARD_NODE}, 2}, {{uri(world, 1), WILDCARD_NODE, uri(world, 4)}, 1}, {{WILDCARD_NODE, uri(world, 2), WILDCARD_NODE}, 2}, {{uri(world, 98), uri(world, 4), plain_hello}, 1}, {{uri(world, 98), uri(world, 4), type5_hello}, 1}, {{uri(world, 96), uri(world, 4), type4_hello}, 1}, {{uri(world, 96), uri(world, 4), type5_hello}, 1}, {{uri(world, 94), uri(world, 5), plain_hello}, 1}, {{uri(world, 94), uri(world, 5), gb_hello}, 1}, {{uri(world, 92), uri(world, 6), gb_hello}, 1}, {{uri(world, 92), uri(world, 6), us_hello}, 1}}; Quad match = {uri(world, 1), uri(world, 2), uri(world, 4), g}; assert(serd_model_ask(model, match[0], match[1], match[2], match[3])); Quad nomatch = {uri(world, 1), uri(world, 2), uri(world, 9), g}; assert( !serd_model_ask(model, nomatch[0], nomatch[1], nomatch[2], nomatch[3])); assert(!serd_model_get(model, NULL, NULL, uri(world, 3), g)); assert(!serd_model_get(model, uri(world, 1), uri(world, 99), NULL, g)); assert(serd_node_equals( serd_model_get(model, uri(world, 1), uri(world, 2), NULL, g), uri(world, 3))); assert(serd_node_equals( serd_model_get(model, uri(world, 1), NULL, uri(world, 3), g), uri(world, 2))); assert(serd_node_equals( serd_model_get(model, NULL, uri(world, 2), uri(world, 3), g), uri(world, 1))); if (g) { assert(serd_node_equals( serd_model_get(model, uri(world, 1), uri(world, 2), uri(world, 3), NULL), g)); } for (unsigned i = 0; i < NUM_PATTERNS; ++i) { QueryTest test = patterns[i]; Quad pat = {test.query[0], test.query[1], test.query[2], g}; SerdCursor* range = serd_model_find(NULL, model, pat[0], pat[1], pat[2], pat[3]); int num_results = 0; for (; !serd_cursor_is_end(range); serd_cursor_advance(range)) { ++num_results; const SerdStatement* first = serd_cursor_get(range); assert(first); assert(serd_statement_matches(first, pat[0], pat[1], pat[2], pat[3])); } serd_cursor_free(NULL, range); assert(num_results == test.expected_num_results); } #undef NUM_PATTERNS // Query blank node subject const SerdNode* ablank = serd_nodes_get(nodes, serd_a_blank(zix_string("ablank"))); Quad pat = {ablank, 0, 0}; int num_results = 0; SerdCursor* range = serd_model_find(NULL, model, pat[0], pat[1], pat[2], pat[3]); for (; !serd_cursor_is_end(range); serd_cursor_advance(range)) { ++num_results; const SerdStatement* statement = serd_cursor_get(range); assert(serd_statement_matches(statement, pat[0], pat[1], pat[2], pat[3])); } serd_cursor_free(NULL, range); assert(num_results == 2); // Test nested queries const SerdNode* last_subject = 0; range = serd_model_find(NULL, model, NULL, NULL, NULL, NULL); for (; !serd_cursor_is_end(range); serd_cursor_advance(range)) { const SerdStatement* statement = serd_cursor_get(range); const SerdNode* subject = serd_statement_subject(statement); if (subject == last_subject) { continue; } Quad subpat = {subject, 0, 0}; SerdCursor* const subrange = serd_model_find(NULL, model, subpat[0], subpat[1], subpat[2], subpat[3]); assert(subrange); const SerdStatement* substatement = serd_cursor_get(subrange); uint64_t num_sub_results = 0; assert(serd_statement_subject(substatement) == subject); for (; !serd_cursor_is_end(subrange); serd_cursor_advance(subrange)) { const SerdStatement* const front = serd_cursor_get(subrange); assert(front); assert(serd_statement_matches( front, subpat[0], subpat[1], subpat[2], subpat[3])); ++num_sub_results; } serd_cursor_free(NULL, subrange); assert(num_sub_results == N_OBJECTS_PER); uint64_t count = serd_model_count(model, subject, 0, 0, 0); assert(count == num_sub_results); last_subject = subject; } serd_cursor_free(NULL, range); serd_nodes_free(nodes); return 0; } static SerdStatus expected_error(void* const handle, const SerdLogLevel level, const size_t n_fields, const SerdLogField* const fields, const ZixStringView message) { (void)level; (void)n_fields; (void)fields; (void)handle; fprintf(stderr, "expected: %s\n", message.data); return SERD_SUCCESS; } ZIX_PURE_FUNC static SerdStatus ignore_only_index_error(void* const handle, const SerdLogLevel level, const size_t n_fields, const SerdLogField* const fields, const ZixStringView message) { (void)handle; (void)level; (void)n_fields; (void)fields; const bool is_index_error = strstr(message.data, "index"); assert(is_index_error); return is_index_error ? SERD_SUCCESS : SERD_UNKNOWN_ERROR; } static int test_failed_new_alloc(SerdWorld* ignored_world, const unsigned n_quads) { (void)ignored_world; (void)n_quads; SerdFailingAllocator allocator = serd_failing_allocator(); SerdWorld* const world = serd_world_new(&allocator.base); const size_t n_world_allocs = allocator.n_allocations; // Successfully allocate a model to count the number of allocations SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); assert(model); // Test that each allocation failing is handled gracefully const size_t n_new_allocs = allocator.n_allocations - n_world_allocs; for (size_t i = 0; i < n_new_allocs; ++i) { allocator.n_remaining = i; assert(!serd_model_new(world, SERD_ORDER_SPO, 0U)); } serd_model_free(model); serd_world_free(world); return 0; } static int test_free_null(SerdWorld* world, const unsigned n_quads) { (void)world; (void)n_quads; serd_model_free(NULL); // Shouldn't crash return 0; } static int test_get_world(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); assert(serd_model_world(model) == world); serd_model_free(model); return 0; } static int test_get_default_order(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* const model1 = serd_model_new(world, SERD_ORDER_SPO, 0U); SerdModel* const model2 = serd_model_new(world, SERD_ORDER_GPSO, 0U); assert(serd_model_default_order(model1) == SERD_ORDER_SPO); assert(serd_model_default_order(model2) == SERD_ORDER_GPSO); serd_model_free(model2); serd_model_free(model1); return 0; } static int test_get_flags(SerdWorld* world, const unsigned n_quads) { (void)n_quads; const SerdModelFlags flags = SERD_STORE_GRAPHS | SERD_STORE_CARETS; SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, flags); assert(serd_model_flags(model) & SERD_STORE_GRAPHS); assert(serd_model_flags(model) & SERD_STORE_CARETS); serd_model_free(model); return 0; } static int test_all_begin(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); SerdCursor* begin = serd_model_begin(NULL, model); SerdCursor* first = serd_model_find(NULL, model, NULL, NULL, NULL, NULL); assert(serd_cursor_equals(begin, first)); serd_cursor_free(NULL, first); serd_cursor_free(NULL, begin); serd_model_free(model); return 0; } static int test_begin_ordered(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); assert( !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0)); SerdCursor* i = serd_model_begin_ordered(NULL, model, SERD_ORDER_SPO); assert(i); assert(!serd_cursor_is_end(i)); serd_cursor_free(NULL, i); i = serd_model_begin_ordered(NULL, model, SERD_ORDER_POS); assert(serd_cursor_is_end(i)); serd_cursor_free(NULL, i); serd_model_free(model); return 0; } static int test_add_with_iterator(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); serd_set_log_func(world, expected_error, NULL); assert( !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0)); // Add a statement with an active iterator SerdCursor* iter = serd_model_begin(NULL, model); assert( !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 4), 0)); // Check that iterator has been invalidated assert(!serd_cursor_get(iter)); assert(serd_cursor_advance(iter) == SERD_BAD_CURSOR); serd_cursor_free(NULL, iter); serd_model_free(model); return 0; } static int test_add_remove_nodes(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); assert(serd_model_nodes(model)); assert(serd_nodes_size(serd_model_nodes(model)) == 0); const SerdNode* const a = uri(world, 1); const SerdNode* const b = uri(world, 2); const SerdNode* const c = uri(world, 3); // Add 2 statements with 3 nodes assert(!serd_model_add(model, a, b, a, NULL)); assert(!serd_model_add(model, c, b, c, NULL)); assert(serd_model_size(model) == 2); assert(serd_nodes_size(serd_model_nodes(model)) == 3); // Remove one statement to leave 2 nodes SerdCursor* const begin = serd_model_begin(NULL, model); assert(!serd_model_erase(model, begin)); assert(serd_model_size(model) == 1); assert(serd_nodes_size(serd_model_nodes(model)) == 2); serd_cursor_free(NULL, begin); // Clear the last statement to leave 0 nodes assert(!serd_model_clear(model)); assert(serd_nodes_size(serd_model_nodes(model)) == 0); serd_model_free(model); return 0; } static int test_add_index(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); const SerdNode* const s = uri(world, 0); const SerdNode* const p = uri(world, 1); const SerdNode* const o1 = uri(world, 2); const SerdNode* const o2 = uri(world, 3); // Try to add an existing index assert(serd_model_add_index(model, SERD_ORDER_SPO) == SERD_FAILURE); // Add a couple of statements serd_model_add(model, s, p, o1, NULL); serd_model_add(model, s, p, o2, NULL); assert(serd_model_size(model) == 2); // Add a new index assert(!serd_model_add_index(model, SERD_ORDER_PSO)); // Count statements via the new index size_t count = 0U; SerdCursor* cur = serd_model_find(NULL, model, NULL, p, NULL, NULL); while (!serd_cursor_is_end(cur)) { ++count; serd_cursor_advance(cur); } serd_cursor_free(NULL, cur); serd_model_free(model); assert(count == 2); return 0; } static int test_remove_index(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); const SerdNode* const s = uri(world, 0); const SerdNode* const p = uri(world, 1); const SerdNode* const o1 = uri(world, 2); const SerdNode* const o2 = uri(world, 3); // Try to remove default and non-existent indices assert(serd_model_drop_index(model, SERD_ORDER_SPO) == SERD_BAD_CALL); assert(serd_model_drop_index(model, SERD_ORDER_PSO) == SERD_FAILURE); // Add a couple of statements so that dropping an index isn't trivial serd_model_add(model, s, p, o1, NULL); serd_model_add(model, s, p, o2, NULL); assert(serd_model_size(model) == 2); assert(serd_model_add_index(model, SERD_ORDER_PSO) == SERD_SUCCESS); assert(serd_model_drop_index(model, SERD_ORDER_PSO) == SERD_SUCCESS); assert(serd_model_drop_index(model, SERD_ORDER_PSO) == SERD_FAILURE); assert(serd_model_size(model) == 2); serd_model_free(model); return 0; } static int test_inserter(SerdWorld* world, const unsigned n_quads) { (void)n_quads; ZixAllocator* const allocator = zix_default_allocator(); SerdNodes* const nodes = serd_nodes_new(allocator); SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); SerdSink* const inserter = serd_inserter_new(model, NULL); const SerdNode* const s = serd_nodes_get(nodes, serd_a_uri_string("http://example.org/s")); const SerdNode* const p = serd_nodes_get(nodes, serd_a_uri_string("http://example.org/p")); const SerdNode* const rel = serd_nodes_get(nodes, serd_a_uri_string("rel")); serd_set_log_func(world, expected_error, NULL); assert(serd_sink_write(inserter, 0, s, p, rel, NULL) == SERD_BAD_DATA); serd_sink_free(inserter); serd_model_free(model); serd_nodes_free(nodes); return 0; } static int test_erase_with_iterator(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); serd_set_log_func(world, expected_error, NULL); assert( !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0)); assert( !serd_model_add(model, uri(world, 4), uri(world, 5), uri(world, 6), 0)); // Erase a statement with an active iterator SerdCursor* iter1 = serd_model_begin(NULL, model); SerdCursor* iter2 = serd_model_begin(NULL, model); assert(!serd_model_erase(model, iter1)); // Check that erased iterator points to the next statement const SerdStatement* const s1 = serd_cursor_get(iter1); assert(s1); assert( serd_statement_matches(s1, uri(world, 4), uri(world, 5), uri(world, 6), 0)); // Check that other iterator has been invalidated assert(!serd_cursor_get(iter2)); assert(serd_cursor_advance(iter2) == SERD_BAD_CURSOR); // Check that erasing the end iterator does nothing SerdCursor* const end = serd_cursor_copy(serd_world_allocator(world), serd_model_end(model)); assert(serd_model_erase(model, end) == SERD_FAILURE); serd_cursor_free(NULL, end); serd_cursor_free(NULL, iter2); serd_cursor_free(NULL, iter1); serd_model_free(model); return 0; } static int test_add_erase(SerdWorld* world, const unsigned n_quads) { (void)n_quads; ZixAllocator* const allocator = zix_default_allocator(); SerdNodes* const nodes = serd_nodes_new(allocator); SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, 0U); // Add (s p "hello") const SerdNode* s = uri(world, 1); const SerdNode* p = uri(world, 2); const SerdNode* hello = serd_nodes_get(nodes, serd_a_string("hello")); assert(!serd_model_add(model, s, p, hello, NULL)); assert(serd_model_ask(model, s, p, hello, NULL)); // Add (s p "hi") const SerdNode* hi = serd_nodes_get(nodes, serd_a_string("hi")); assert(!serd_model_add(model, s, p, hi, NULL)); assert(serd_model_ask(model, s, p, hi, NULL)); // Erase (s p "hi") SerdCursor* iter = serd_model_find(NULL, model, s, p, hi, NULL); assert(iter); assert(!serd_model_erase(model, iter)); assert(serd_model_size(model) == 1); serd_cursor_free(NULL, iter); // Check that erased statement can not be found SerdCursor* empty = serd_model_find(NULL, model, s, p, hi, NULL); assert(serd_cursor_is_end(empty)); serd_cursor_free(NULL, empty); serd_model_free(model); serd_nodes_free(nodes); return 0; } static int test_add_bad_statement(SerdWorld* world, const unsigned n_quads) { (void)n_quads; ZixAllocator* const allocator = serd_world_allocator(world); SerdNodes* const nodes = serd_nodes_new(allocator); const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); const SerdNode* o = serd_nodes_get(nodes, serd_a_uri_string("urn:o")); const SerdNode* f = serd_nodes_get(nodes, serd_a_uri_string("file:///tmp/file.ttl")); SerdCaret* caret = serd_caret_new(allocator, f, 16, 18); SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); assert(!serd_model_add_with_caret(model, s, p, o, NULL, caret)); SerdCursor* const begin = serd_model_begin(NULL, model); const SerdStatement* statement = serd_cursor_get(begin); assert(statement); assert(serd_node_equals(serd_statement_subject(statement), s)); assert(serd_node_equals(serd_statement_predicate(statement), p)); assert(serd_node_equals(serd_statement_object(statement), o)); assert(!serd_statement_graph(statement)); const SerdCaret* statement_caret = serd_statement_caret(statement); assert(statement_caret); assert(serd_node_equals(serd_caret_document(statement_caret), f)); assert(serd_caret_line(statement_caret) == 16); assert(serd_caret_column(statement_caret) == 18); assert(!serd_model_erase(model, begin)); serd_cursor_free(NULL, begin); serd_model_free(model); serd_caret_free(allocator, caret); serd_nodes_free(nodes); return 0; } static int test_add_with_caret(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdNodes* const nodes = serd_nodes_new(serd_world_allocator(world)); const SerdNode* lit = serd_nodes_get(nodes, serd_a_string("string")); const SerdNode* uri = serd_nodes_get(nodes, serd_a_uri_string("urn:uri")); const SerdNode* blank = serd_nodes_get(nodes, serd_a_blank(zix_string("b1"))); SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); assert(serd_model_add(model, lit, uri, uri, NULL)); assert(serd_model_add(model, uri, blank, uri, NULL)); assert(serd_model_add(model, uri, uri, uri, lit)); serd_model_free(model); serd_nodes_free(nodes); return 0; } static int test_erase_all(SerdWorld* world, const unsigned n_quads) { SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); serd_model_add_index(model, SERD_ORDER_OSP); generate(world, model, n_quads, NULL); SerdCursor* iter = serd_model_begin(NULL, model); while (!serd_cursor_equals(iter, serd_model_end(model))) { assert(!serd_model_erase(model, iter)); } assert(serd_model_empty(model)); serd_cursor_free(NULL, iter); serd_model_free(model); return 0; } static int test_clear(SerdWorld* world, const unsigned n_quads) { SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); generate(world, model, n_quads, NULL); serd_model_clear(model); assert(serd_model_empty(model)); serd_model_free(model); return 0; } static int test_copy(SerdWorld* world, const unsigned n_quads) { SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); generate(world, model, n_quads, NULL); SerdModel* copy = serd_model_copy(serd_world_allocator(world), model); assert(serd_model_equals(model, copy)); serd_model_free(model); serd_model_free(copy); return 0; } static int test_equals(SerdWorld* world, const unsigned n_quads) { SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); generate(world, model, n_quads, NULL); serd_model_add( model, uri(world, 0), uri(world, 1), uri(world, 2), uri(world, 3)); assert(serd_model_equals(NULL, NULL)); assert(!serd_model_equals(NULL, model)); assert(!serd_model_equals(model, NULL)); SerdModel* empty = serd_model_new(world, SERD_ORDER_SPO, 0U); assert(!serd_model_equals(model, empty)); SerdModel* different = serd_model_new(world, SERD_ORDER_SPO, 0U); generate(world, different, n_quads, NULL); serd_model_add( different, uri(world, 1), uri(world, 1), uri(world, 2), uri(world, 3)); assert(serd_model_size(model) == serd_model_size(different)); assert(!serd_model_equals(model, different)); serd_model_free(model); serd_model_free(empty); serd_model_free(different); return 0; } static int test_find_past_end(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); const SerdNode* s = uri(world, 1); const SerdNode* p = uri(world, 2); const SerdNode* o = uri(world, 3); assert(!serd_model_add(model, s, p, o, 0)); assert(serd_model_ask(model, s, p, o, 0)); const SerdNode* huge = uri(world, 999); SerdCursor* range = serd_model_find(NULL, model, huge, huge, huge, 0); assert(serd_cursor_is_end(range)); serd_cursor_free(NULL, range); serd_model_free(model); return 0; } static int test_find_unknown_node(SerdWorld* world, const unsigned n_quads) { (void)n_quads; const SerdNode* const s = uri(world, 1); const SerdNode* const p = uri(world, 2); const SerdNode* const o = uri(world, 3); SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); // Add one statement assert(!serd_model_add(model, s, p, o, NULL)); assert(serd_model_ask(model, s, p, o, NULL)); /* Test searching for statements that contain a non-existent node. This is semantically equivalent to any other non-matching pattern, but can be implemented with a fast path that avoids searching a statement index entirely. */ const SerdNode* const q = uri(world, 42); assert(!serd_model_ask(model, s, p, o, q)); assert(!serd_model_ask(model, s, p, q, NULL)); assert(!serd_model_ask(model, s, q, o, NULL)); assert(!serd_model_ask(model, q, p, o, NULL)); serd_model_free(model); return 0; } static int test_find_graph(SerdWorld* world, const unsigned n_quads) { (void)n_quads; const SerdNode* const s = uri(world, 1); const SerdNode* const p = uri(world, 2); const SerdNode* const o1 = uri(world, 3); const SerdNode* const o2 = uri(world, 4); const SerdNode* const g = uri(world, 5); for (unsigned indexed = 0U; indexed < 2U; ++indexed) { SerdModel* const model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); if (indexed) { serd_model_add_index(model, SERD_ORDER_GSPO); } // Add one statement in a named graph and one in the default graph assert(!serd_model_add(model, s, p, o1, NULL)); assert(!serd_model_add(model, s, p, o2, g)); // Both statements can be found in the default graph assert(serd_model_ask(model, s, p, o1, NULL)); assert(serd_model_ask(model, s, p, o2, NULL)); // Only the one statement can be found in the named graph assert(!serd_model_ask(model, s, p, o1, g)); assert(serd_model_ask(model, s, p, o2, g)); serd_model_free(model); } return 0; } static int test_range(SerdWorld* world, const unsigned n_quads) { SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); generate(world, model, n_quads, NULL); SerdCursor* range1 = serd_model_begin(NULL, model); SerdCursor* range2 = serd_model_begin(NULL, model); assert(!serd_cursor_is_end(range1)); assert(serd_cursor_is_end(NULL)); assert(serd_cursor_equals(NULL, NULL)); assert(!serd_cursor_equals(range1, NULL)); assert(!serd_cursor_equals(NULL, range1)); assert(serd_cursor_equals(range1, range2)); assert(!serd_cursor_advance(range2)); assert(!serd_cursor_equals(range1, range2)); serd_cursor_free(NULL, range2); serd_cursor_free(NULL, range1); serd_model_free(model); return 0; } static int test_triple_index_read(SerdWorld* world, const unsigned n_quads) { serd_set_log_func(world, ignore_only_index_error, NULL); for (unsigned i = 0; i < 6; ++i) { SerdModel* model = serd_model_new(world, (SerdStatementOrder)i, 0U); generate(world, model, n_quads, 0); assert(!test_read(world, model, 0, n_quads)); serd_model_free(model); } return 0; } static int test_quad_index_read(SerdWorld* world, const unsigned n_quads) { serd_set_log_func(world, ignore_only_index_error, NULL); for (unsigned i = 0; i < 6; ++i) { SerdModel* model = serd_model_new(world, (SerdStatementOrder)i, SERD_STORE_GRAPHS); const SerdNode* graph = uri(world, 42); generate(world, model, n_quads, graph); assert(!test_read(world, model, graph, n_quads)); serd_model_free(model); } return 0; } static int test_remove_graph(SerdWorld* world, const unsigned n_quads) { (void)n_quads; SerdModel* model = serd_model_new(world, SERD_ORDER_GSPO, SERD_STORE_GRAPHS); // Generate a couple of graphs const SerdNode* graph42 = uri(world, 42); const SerdNode* graph43 = uri(world, 43); generate(world, model, 1, graph42); generate(world, model, 1, graph43); // Find the start of graph43 SerdCursor* range = serd_model_find(NULL, model, NULL, NULL, NULL, graph43); assert(range); // Remove the entire range of statements in the graph SerdStatus st = serd_model_erase_statements(model, range); assert(!st); serd_cursor_free(NULL, range); // Erase the first tuple (an element in the default graph) SerdCursor* iter = serd_model_begin(NULL, model); assert(!serd_model_erase(model, iter)); serd_cursor_free(NULL, iter); // Ensure only the other graph is left Quad pat = {0, 0, 0, graph42}; for (iter = serd_model_begin(NULL, model); !serd_cursor_equals(iter, serd_model_end(model)); serd_cursor_advance(iter)) { const SerdStatement* const s = serd_cursor_get(iter); assert(s); assert(serd_statement_matches(s, pat[0], pat[1], pat[2], pat[3])); } serd_cursor_free(NULL, iter); serd_model_free(model); return 0; } static int test_default_graph(SerdWorld* world, const unsigned n_quads) { (void)n_quads; const SerdNode* s = uri(world, 1); const SerdNode* p = uri(world, 2); const SerdNode* o = uri(world, 3); const SerdNode* g1 = uri(world, 101); const SerdNode* g2 = uri(world, 102); { // Make a model that does not store graphs SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); // Insert a statement into a graph (which will be dropped) assert(!serd_model_add(model, s, p, o, g1)); // Attempt to insert the same statement into another graph assert(serd_model_add(model, s, p, o, g2) == SERD_FAILURE); // Ensure that we only see the statement once assert(serd_model_count(model, s, p, o, NULL) == 1); serd_model_free(model); } { // Make a model that stores graphs SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); // Insert the same statement into two graphs assert(!serd_model_add(model, s, p, o, g1)); assert(!serd_model_add(model, s, p, o, g2)); // Ensure we see the statement twice assert(serd_model_count(model, s, p, o, NULL) == 2); serd_model_free(model); } return 0; } static int test_write_flat_range(SerdWorld* world, const unsigned n_quads) { (void)n_quads; ZixAllocator* const alloc = serd_world_allocator(world); SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); SerdNodes* nodes = serd_nodes_new(alloc); const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); const SerdNode* b1 = serd_nodes_get(nodes, serd_a_blank(zix_string("b1"))); const SerdNode* b2 = serd_nodes_get(nodes, serd_a_blank(zix_string("b2"))); const SerdNode* o = serd_nodes_get(nodes, serd_a_uri_string("urn:o")); serd_model_add(model, s, p, b1, NULL); serd_model_add(model, b1, p, o, NULL); serd_model_add(model, s, p, b2, NULL); serd_model_add(model, b2, p, o, NULL); SerdBuffer buffer = {NULL, NULL, 0}; SerdEnv* env = serd_env_new(alloc, zix_empty_string()); SerdOutputStream out = serd_open_output_buffer(&buffer); SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); assert(writer); SerdCursor* all = serd_model_begin(NULL, model); for (const SerdStatement* t = NULL; (t = serd_cursor_get(all)); serd_cursor_advance(all)) { serd_sink_write_statement(serd_writer_sink(writer), 0U, t); } serd_cursor_free(NULL, all); serd_writer_finish(writer); serd_close_output(&out); const char* const str = (const char*)buffer.buf; static const char* const expected = "\n" "\t _:b1 ,\n" "\t\t_:b2 .\n" "\n" "_:b1\n" "\t .\n" "\n" "_:b2\n" "\t .\n"; assert(str); assert(!strcmp(str, expected)); zix_free(NULL, buffer.buf); serd_writer_free(writer); serd_model_free(model); serd_env_free(env); serd_nodes_free(nodes); return 0; } static int test_write_bad_list(SerdWorld* world, const unsigned n_quads) { (void)n_quads; ZixAllocator* const alloc = serd_world_allocator(world); SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); SerdNodes* nodes = serd_nodes_new(alloc); serd_model_add_index(model, SERD_ORDER_OPS); const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); const SerdNode* list1 = serd_nodes_get(nodes, serd_a_blank(zix_string("l1"))); const SerdNode* list2 = serd_nodes_get(nodes, serd_a_blank(zix_string("l2"))); const SerdNode* nofirst = serd_nodes_get(nodes, serd_a_blank(zix_string("nof"))); const SerdNode* norest = serd_nodes_get(nodes, serd_a_blank(zix_string("nor"))); const SerdNode* pfirst = serd_nodes_get(nodes, serd_a_uri_string(RDF_FIRST)); const SerdNode* prest = serd_nodes_get(nodes, serd_a_uri_string(RDF_REST)); const SerdNode* val1 = serd_nodes_get(nodes, serd_a_string("a")); const SerdNode* val2 = serd_nodes_get(nodes, serd_a_string("b")); // List where second node has no rdf:first serd_model_add(model, s, p, list1, NULL); serd_model_add(model, list1, pfirst, val1, NULL); serd_model_add(model, list1, prest, nofirst, NULL); // List where second node has no rdf:rest serd_model_add(model, s, p, list2, NULL); serd_model_add(model, list2, pfirst, val1, NULL); serd_model_add(model, list2, prest, norest, NULL); serd_model_add(model, norest, pfirst, val2, NULL); SerdBuffer buffer = {NULL, NULL, 0}; SerdEnv* env = serd_env_new(alloc, zix_empty_string()); SerdOutputStream out = serd_open_output_buffer(&buffer); SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); assert(writer); SerdCursor* all = serd_model_begin(NULL, model); serd_describe_range(NULL, all, serd_writer_sink(writer), 0); serd_cursor_free(NULL, all); serd_writer_finish(writer); serd_close_output(&out); const char* str = (const char*)buffer.buf; const char* expected = "\n" " (\n" " \"a\"\n" " ) , (\n" " \"a\"\n" " \"b\"\n" " ) .\n"; assert(str); assert(!strcmp(str, expected)); zix_free(NULL, buffer.buf); serd_writer_free(writer); serd_close_output(&out); serd_model_free(model); serd_env_free(env); serd_nodes_free(nodes); return 0; } static int test_write_infinite_list(SerdWorld* world, const unsigned n_quads) { (void)n_quads; ZixAllocator* const alloc = serd_world_allocator(world); SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_GRAPHS); SerdNodes* nodes = serd_nodes_new(alloc); serd_model_add_index(model, SERD_ORDER_OPS); const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); const SerdNode* list1 = serd_nodes_get(nodes, serd_a_blank(zix_string("l1"))); const SerdNode* list2 = serd_nodes_get(nodes, serd_a_blank(zix_string("l2"))); const SerdNode* pfirst = serd_nodes_get(nodes, serd_a_uri_string(RDF_FIRST)); const SerdNode* prest = serd_nodes_get(nodes, serd_a_uri_string(RDF_REST)); const SerdNode* val1 = serd_nodes_get(nodes, serd_a_string("a")); const SerdNode* val2 = serd_nodes_get(nodes, serd_a_string("b")); // List with a cycle: list1 -> list2 -> list1 -> list2 ... serd_model_add(model, s, p, list1, NULL); serd_model_add(model, list1, pfirst, val1, NULL); serd_model_add(model, list1, prest, list2, NULL); serd_model_add(model, list2, pfirst, val2, NULL); serd_model_add(model, list2, prest, list1, NULL); SerdBuffer buffer = {NULL, NULL, 0}; SerdEnv* env = serd_env_new(alloc, zix_empty_string()); SerdOutputStream out = serd_open_output_buffer(&buffer); SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); assert(writer); serd_env_set_prefix( env, zix_string("rdf"), zix_string("http://www.w3.org/1999/02/22-rdf-syntax-ns#")); SerdCursor* all = serd_model_begin(NULL, model); serd_describe_range(NULL, all, serd_writer_sink(writer), 0); serd_cursor_free(NULL, all); serd_writer_finish(writer); serd_close_output(&out); const char* str = (const char*)buffer.buf; const char* expected = "\n" " _:l1 .\n" "\n" "_:l1\n" " rdf:first \"a\" ;\n" " rdf:rest [\n" " rdf:first \"b\" ;\n" " rdf:rest _:l1\n" " ] .\n"; assert(str); assert(!strcmp(str, expected)); zix_free(NULL, buffer.buf); serd_writer_free(writer); serd_close_output(&out); serd_model_free(model); serd_env_free(env); serd_nodes_free(nodes); return 0; } typedef struct { size_t n_written; size_t max_successes; } FailingWriteFuncState; /// Write function that fails after a certain number of writes static size_t failing_write_func(const void* buf, size_t size, size_t nmemb, void* stream) { (void)buf; (void)size; (void)nmemb; FailingWriteFuncState* state = (FailingWriteFuncState*)stream; return (++state->n_written > state->max_successes) ? 0 : nmemb; } static int test_write_error_in_list_subject(SerdWorld* world, const unsigned n_quads) { (void)n_quads; ZixAllocator* const alloc = serd_world_allocator(world); serd_set_log_func(world, expected_error, NULL); SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); SerdNodes* nodes = serd_nodes_new(alloc); serd_model_add_index(model, SERD_ORDER_OPS); const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); const SerdNode* o = serd_nodes_get(nodes, serd_a_uri_string("urn:o")); const SerdNode* l1 = serd_nodes_get(nodes, serd_a_blank(zix_string("l1"))); const SerdNode* one = serd_nodes_get(nodes, serd_a_integer(1)); const SerdNode* l2 = serd_nodes_get(nodes, serd_a_blank(zix_string("l2"))); const SerdNode* two = serd_nodes_get(nodes, serd_a_integer(2)); const SerdNode* rdf_first = serd_nodes_get(nodes, serd_a_uri_string(RDF_FIRST)); const SerdNode* rdf_rest = serd_nodes_get(nodes, serd_a_uri_string(RDF_REST)); const SerdNode* rdf_nil = serd_nodes_get(nodes, serd_a_uri_string(RDF_NIL)); serd_model_add(model, l1, rdf_first, one, NULL); serd_model_add(model, l1, rdf_rest, l2, NULL); serd_model_add(model, l2, rdf_first, two, NULL); serd_model_add(model, l2, rdf_rest, rdf_nil, NULL); serd_model_add(model, l1, p, o, NULL); SerdEnv* env = serd_env_new(alloc, zix_empty_string()); for (size_t max_successes = 0; max_successes < 18; ++max_successes) { FailingWriteFuncState state = {0, max_successes}; SerdOutputStream out = serd_open_output_stream(failing_write_func, NULL, NULL, &state); SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); const SerdSink* const sink = serd_writer_sink(writer); SerdCursor* const all = serd_model_begin(NULL, model); const SerdStatus st = serd_describe_range(NULL, all, sink, 0); serd_cursor_free(NULL, all); assert(st == SERD_BAD_WRITE); serd_writer_free(writer); serd_close_output(&out); } serd_env_free(env); serd_model_free(model); serd_nodes_free(nodes); return 0; } static int test_write_error_in_list_object(SerdWorld* world, const unsigned n_quads) { (void)n_quads; ZixAllocator* const alloc = serd_world_allocator(world); serd_set_log_func(world, expected_error, NULL); SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); SerdNodes* nodes = serd_nodes_new(alloc); serd_model_add_index(model, SERD_ORDER_OPS); const SerdNode* s = serd_nodes_get(nodes, serd_a_uri_string("urn:s")); const SerdNode* p = serd_nodes_get(nodes, serd_a_uri_string("urn:p")); const SerdNode* l1 = serd_nodes_get(nodes, serd_a_blank(zix_string("l1"))); const SerdNode* one = serd_nodes_get(nodes, serd_a_integer(1)); const SerdNode* l2 = serd_nodes_get(nodes, serd_a_blank(zix_string("l2"))); const SerdNode* two = serd_nodes_get(nodes, serd_a_integer(2)); const SerdNode* rdf_first = serd_nodes_get(nodes, serd_a_uri_string(RDF_FIRST)); const SerdNode* rdf_rest = serd_nodes_get(nodes, serd_a_uri_string(RDF_REST)); const SerdNode* rdf_nil = serd_nodes_get(nodes, serd_a_uri_string(RDF_NIL)); serd_model_add(model, s, p, l1, NULL); serd_model_add(model, l1, rdf_first, one, NULL); serd_model_add(model, l1, rdf_rest, l2, NULL); serd_model_add(model, l2, rdf_first, two, NULL); serd_model_add(model, l2, rdf_rest, rdf_nil, NULL); SerdEnv* env = serd_env_new(alloc, zix_empty_string()); for (size_t max_successes = 0; max_successes < 21; ++max_successes) { FailingWriteFuncState state = {0, max_successes}; SerdOutputStream out = serd_open_output_stream(failing_write_func, NULL, NULL, &state); SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, &out, 1); const SerdSink* const sink = serd_writer_sink(writer); SerdCursor* const all = serd_model_begin(NULL, model); const SerdStatus st = serd_describe_range(NULL, all, sink, 0); serd_cursor_free(NULL, all); assert(st == SERD_BAD_WRITE); serd_writer_free(writer); serd_close_output(&out); } serd_env_free(env); serd_model_free(model); serd_nodes_free(nodes); return 0; } int main(void) { static const unsigned n_quads = 300; serd_model_free(NULL); // Shouldn't crash typedef int (*TestFunc)(SerdWorld*, unsigned); const TestFunc tests[] = {test_failed_new_alloc, test_free_null, test_get_world, test_get_default_order, test_get_flags, test_all_begin, test_begin_ordered, test_add_with_iterator, test_add_remove_nodes, test_add_index, test_remove_index, test_inserter, test_erase_with_iterator, test_add_erase, test_add_bad_statement, test_add_with_caret, test_erase_all, test_clear, test_copy, test_equals, test_find_past_end, test_find_unknown_node, test_find_graph, test_range, test_triple_index_read, test_quad_index_read, test_remove_graph, test_default_graph, test_write_flat_range, test_write_bad_list, test_write_infinite_list, test_write_error_in_list_subject, test_write_error_in_list_object, NULL}; SerdWorld* world = serd_world_new(NULL); int ret = 0; for (const TestFunc* t = tests; *t; ++t) { serd_set_log_func(world, NULL, NULL); ret += (*t)(world, n_quads); } serd_world_free(world); return ret; }