/* Copyright 2011-2018 David Robillard 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 #include #include #include #include "serd/serd.h" #include "../src/log.h" #include "test_utils.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" 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_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_new_string("hello"); SerdNode* hello_gb = serd_new_plain_literal("hello", "en-gb"); SerdNode* hello_us = serd_new_plain_literal("hello", "en-us"); SerdNode* hello_t4 = serd_new_typed_literal("hello", uri(world, 4)); SerdNode* hello_t5 = serd_new_typed_literal("hello", uri(world, 5)); 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_new_plain_literal("bonjour", "fr"); SerdNode* salut = serd_new_plain_literal("salut", "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_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) { SerdIter* iter = serd_model_begin(model); for (; !serd_iter_equals(iter, serd_model_end(model)); 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_new_string(s); SerdNode* type4_hello = serd_new_typed_literal(s, uri(world, 4)); SerdNode* type5_hello = serd_new_typed_literal(s, uri(world, 5)); SerdNode* gb_hello = serd_new_plain_literal(s, "en-gb"); SerdNode* us_hello = serd_new_plain_literal(s, "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 }; SerdRange* range = serd_model_range(model, pat[0], pat[1], pat[2], pat[3]); int num_results = 0; for (; !serd_range_empty(range); serd_range_next(range)) { ++num_results; if (!serd_statement_matches(serd_range_front(range), pat[0], pat[1], pat[2], pat[3])) { serd_iter_free(iter); FAILF("Query result " TUP_FMT " does not match pattern " TUP_FMT "\n", STATEMENT_FMT_ARGS(serd_range_front(range)), TUP_FMT_ARGS(pat)); } } serd_range_free(range); 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_new_blank("ablank"), 0, 0 }; int num_results = 0; SerdRange* range = serd_model_range(model, pat[0], pat[1], pat[2], pat[3]); for (; !serd_range_empty(range); serd_range_next(range)) { ++num_results; const SerdStatement* statement = serd_range_front(range); if (!serd_statement_matches( statement, pat[0], pat[1], pat[2], pat[3])) { serd_range_free(range); FAILF("Result for " TUP_FMT " does not match pattern\n", TUP_FMT_ARGS(pat)); } } serd_range_free(range); if (num_results != 2) { FAIL("Blank node subject query failed\n"); } // Test nested queries const SerdNode* last_subject = 0; range = serd_model_range(model, NULL, NULL, NULL, NULL); for (; !serd_range_empty(range); serd_range_next(range)) { const SerdStatement* statement = serd_range_front(range); const SerdNode* subject = serd_statement_get_subject(statement); if (subject == last_subject) { continue; } Quad subpat = { subject, 0, 0 }; SerdRange* subrange = serd_model_range( model, subpat[0], subpat[1], subpat[2], subpat[3]); const SerdStatement* substatement = serd_range_front(subrange); uint64_t num_sub_results = 0; if (serd_statement_get_subject(substatement) != subject) { FAIL("Incorrect initial submatch\n"); } for (; !serd_range_empty(subrange); serd_range_next(subrange)) { if (!serd_statement_matches(serd_range_front(subrange), subpat[0], subpat[1], subpat[2], subpat[3])) { serd_range_free(range); serd_range_free(subrange); FAIL("Nested query result does not match pattern\n"); } ++num_sub_results; } serd_range_free(subrange); 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_range_free(range); 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_all_begin(SerdWorld* world, const size_t n_quads) { SerdModel* model = serd_model_new(world, SERD_SPO, false); SerdRange* all = serd_model_all(model); SerdIter* begin = serd_model_find(model, NULL, NULL, NULL, NULL); if (!serd_iter_equals(serd_range_begin(all), begin)) { FAIL("Model range does not start with begin iterator\n"); } serd_range_free(all); serd_iter_free(begin); 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_empty(model)) { 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_new_string("hello"); 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_new_string("hi"); 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_size(model) != 1) { FAILF("Expected 1 node after erasure, not %zu\n", serd_model_size(model)); } serd_iter_free(iter); // Check that erased statement can not be found SerdRange* empty = serd_model_range(model, s, p, hi, NULL); if (!serd_range_empty(empty)) { FAIL("Found removed statement (s p \"hi\")\n"); } serd_range_free(empty); 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_equals(iter, serd_model_end(model))) { 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_copy(SerdWorld* world, const size_t n_quads) { SerdModel* model = serd_model_new(world, SERD_SPO, false); generate(world, model, n_quads, NULL); SerdModel* copy = serd_model_copy(model); if (!serd_model_equals(model, copy)) { FAIL("Model copy does not equal original\n"); } serd_model_free(model); serd_model_free(copy); return 0; } static int test_equals(SerdWorld* world, const size_t n_quads) { SerdModel* model = serd_model_new(world, SERD_SPO, false); generate(world, model, n_quads, NULL); serd_model_add( model, uri(world, 0), uri(world, 1), uri(world, 2), uri(world, 3)); CHECK(serd_model_equals(NULL, NULL)); CHECK(!serd_model_equals(NULL, model)); CHECK(!serd_model_equals(model, NULL)); SerdModel* empty = serd_model_new(world, SERD_SPO, false); if (serd_model_equals(model, empty)) { FAIL("Non-empty model equals empty model\n"); } SerdModel* different = serd_model_new(world, SERD_SPO, false); generate(world, different, n_quads, NULL); serd_model_add( different, uri(world, 1), uri(world, 1), uri(world, 2), uri(world, 3)); if (serd_model_size(model) != serd_model_size(different)) { FAIL("Models with same number of statements report different size\n"); } else if (serd_model_equals(model, different)) { FAIL("Models with different statements are equal\n"); } serd_model_free(model); serd_model_free(empty); serd_model_free(different); 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_range_empty(serd_model_range(model, huge, huge, huge, 0))) { FAIL("Found statement past end of model\n"); } serd_model_free(model); return 0; } static int test_iter_comparison(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* iter1 = serd_model_begin(model); SerdIter* iter2 = serd_model_begin(model); if (!serd_iter_equals(iter1, iter2)) { FAIL("Begin iterators are not equal\n"); } serd_iter_next(iter1); if (serd_iter_equals(iter1, iter2)) { FAIL("Begin iterator equals incremented begin iterator\n"); } const SerdIter* end = serd_model_end(model); if (!serd_iter_equals(iter1, end)) { FAIL("Iterator incremented to end does not equal end iterator\n"); } serd_iter_free(iter2); serd_iter_free(iter1); 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 range SerdStatus st; SerdRange* range = serd_model_range(model, NULL, NULL, NULL, graph43); if ((st = serd_model_erase_range(model, range))) { FAILF("Remove range failed (%s)\n", serd_strerror(st)); } serd_range_free(range); // Erase the first tuple (an element in the default graph) SerdIter* 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_equals(iter, serd_model_end(model)); 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; } static int test_write_bad_list(SerdWorld* world, const size_t n_quads) { SerdModel* model = serd_model_new(world, SERD_SPO, true); SerdNodes* nodes = serd_nodes_new(); const SerdNode* s = serd_nodes_manage(nodes, serd_new_uri("urn:s")); const SerdNode* p = serd_nodes_manage(nodes, serd_new_uri("urn:p")); const SerdNode* list1 = serd_nodes_manage(nodes, serd_new_blank("l1")); const SerdNode* list2 = serd_nodes_manage(nodes, serd_new_blank("l2")); const SerdNode* nofirst = serd_nodes_manage(nodes, serd_new_blank("nof")); const SerdNode* norest = serd_nodes_manage(nodes, serd_new_blank("nor")); const SerdNode* pfirst = serd_nodes_manage(nodes, serd_new_uri(RDF_FIRST)); const SerdNode* prest = serd_nodes_manage(nodes, serd_new_uri(RDF_REST)); const SerdNode* val1 = serd_nodes_manage(nodes, serd_new_string("a")); const SerdNode* val2 = serd_nodes_manage(nodes, serd_new_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(NULL); SerdWriter* writer = serd_writer_new( world, SERD_TURTLE, 0, env, serd_buffer_sink, &buffer); SerdRange* all = serd_model_all(model); serd_range_serialise(all, serd_writer_get_sink(writer), 0); serd_range_free(all); serd_writer_finish(writer); const char* str = serd_buffer_sink_finish(&buffer); const char* expected = "\n" " (\n" " \"a\"\n" " ) , (\n" " \"a\"\n" " \"b\"\n" " ) .\n"; if (strcmp(str, expected)) { FAIL("Failed to serialise invalid list\n"); } free(buffer.buf); serd_writer_free(writer); serd_model_free(model); serd_env_free(env); serd_nodes_free(nodes); 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_all_begin, test_add_null, test_add_with_iterator, test_erase_with_iterator, test_add_erase, test_erase_all, test_copy, test_equals, test_find_past_end, test_iter_comparison, test_triple_index_read, test_quad_index_read, test_remove_graph, test_default_graph, test_write_bad_list, 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; }