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