aboutsummaryrefslogtreecommitdiffstats
path: root/test/test_reader.c
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2024-06-26 10:21:52 -0400
committerDavid Robillard <d@drobilla.net>2024-06-27 08:03:17 -0400
commitac7f3a28687d03876482130b9e91ed4be6df3579 (patch)
treee03c33951445294bafca788c805d4455f83ddd9f /test/test_reader.c
parent7d6ae82233b3a4351e2dfce7a8471d341e538e7c (diff)
downloadserd-ac7f3a28687d03876482130b9e91ed4be6df3579.tar.gz
serd-ac7f3a28687d03876482130b9e91ed4be6df3579.tar.bz2
serd-ac7f3a28687d03876482130b9e91ed4be6df3579.zip
Split out simple reader unit tests
Some of the simpler tests here only test the reader directly, while others are higher level tests that test both the writer and reader together. Split the simple reader-only tests into a separate file so things can be cleaned up and simplified for each kind of test.
Diffstat (limited to 'test/test_reader.c')
-rw-r--r--test/test_reader.c437
1 files changed, 437 insertions, 0 deletions
diff --git a/test/test_reader.c b/test/test_reader.c
new file mode 100644
index 00000000..b102f8a6
--- /dev/null
+++ b/test/test_reader.c
@@ -0,0 +1,437 @@
+// Copyright 2011-2024 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#undef NDEBUG
+
+#include "serd/serd.h"
+
+#ifdef _WIN32
+# include <windows.h>
+#endif
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define USTR(s) ((const uint8_t*)(s))
+
+typedef struct {
+ int n_base;
+ int n_prefix;
+ int n_statement;
+ int n_end;
+} ReaderTest;
+
+static SerdStatus
+test_base_sink(void* const handle, const SerdNode* const uri)
+{
+ (void)uri;
+
+ ReaderTest* const rt = (ReaderTest*)handle;
+ ++rt->n_base;
+ return SERD_SUCCESS;
+}
+
+static SerdStatus
+test_prefix_sink(void* const handle,
+ const SerdNode* const name,
+ const SerdNode* const uri)
+{
+ (void)name;
+ (void)uri;
+
+ ReaderTest* const rt = (ReaderTest*)handle;
+ ++rt->n_prefix;
+ return SERD_SUCCESS;
+}
+
+static SerdStatus
+test_statement_sink(void* const handle,
+ SerdStatementFlags flags,
+ const SerdNode* const graph,
+ const SerdNode* const subject,
+ const SerdNode* const predicate,
+ const SerdNode* const object,
+ const SerdNode* const object_datatype,
+ const SerdNode* const object_lang)
+{
+ (void)flags;
+ (void)graph;
+ (void)subject;
+ (void)predicate;
+ (void)object;
+ (void)object_datatype;
+ (void)object_lang;
+
+ ReaderTest* const rt = (ReaderTest*)handle;
+ ++rt->n_statement;
+ return SERD_SUCCESS;
+}
+
+static SerdStatus
+test_end_sink(void* const handle, const SerdNode* const node)
+{
+ (void)node;
+
+ ReaderTest* const rt = (ReaderTest*)handle;
+ ++rt->n_end;
+ return SERD_SUCCESS;
+}
+
+static void
+test_read_string(void)
+{
+ ReaderTest rt = {0, 0, 0, 0};
+ SerdReader* const reader = serd_reader_new(SERD_TURTLE,
+ &rt,
+ NULL,
+ test_base_sink,
+ test_prefix_sink,
+ test_statement_sink,
+ test_end_sink);
+
+ assert(reader);
+ assert(serd_reader_get_handle(reader) == &rt);
+
+ // Test reading a string that ends exactly at the end of input (no newline)
+ const SerdStatus st = serd_reader_read_string(
+ reader,
+ USTR("<http://example.org/s> <http://example.org/p> "
+ "<http://example.org/o> ."));
+
+ assert(!st);
+ assert(rt.n_base == 0);
+ assert(rt.n_prefix == 0);
+ assert(rt.n_statement == 1);
+ assert(rt.n_end == 0);
+
+ serd_reader_free(reader);
+}
+
+/// Reads a null byte after a statement, then succeeds again (like a socket)
+static size_t
+eof_test_read(void* buf, size_t size, size_t nmemb, void* stream)
+{
+ assert(size == 1);
+ assert(nmemb == 1);
+ (void)size;
+
+ static const char* const string = "_:s1 <http://example.org/p> _:o1 .\n"
+ "_:s2 <http://example.org/p> _:o2 .\n";
+
+ size_t* const count = (size_t*)stream;
+
+ // Normal reading for the first statement
+ if (*count < 35) {
+ *(char*)buf = string[*count];
+ ++*count;
+ return nmemb;
+ }
+
+ // EOF for the first read at the start of the second statement
+ if (*count == 35) {
+ assert(string[*count] == '_');
+ ++*count;
+ return 0;
+ }
+
+ if (*count >= strlen(string)) {
+ return 0;
+ }
+
+ // Normal reading after the EOF, adjusting for the skipped index 35
+ *(char*)buf = string[*count - 1];
+ ++*count;
+ return nmemb;
+}
+
+static int
+eof_test_error(void* stream)
+{
+ (void)stream;
+ return 0;
+}
+
+/// A read of a big page hits EOF then fails to read chunks immediately
+static void
+test_read_eof_by_page(const char* const path)
+{
+ FILE* const f = fopen(path, "w+b");
+ assert(f);
+
+ fprintf(f, "_:s <http://example.org/p> _:o .\n");
+ fflush(f);
+ fseek(f, 0L, SEEK_SET);
+
+ ReaderTest rt = {0, 0, 0, 0};
+ SerdReader* const reader = serd_reader_new(SERD_TURTLE,
+ &rt,
+ NULL,
+ test_base_sink,
+ test_prefix_sink,
+ test_statement_sink,
+ test_end_sink);
+
+ serd_reader_start_stream(reader, f, (const uint8_t*)"test", true);
+
+ assert(serd_reader_read_chunk(reader) == SERD_SUCCESS);
+ assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
+ assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
+
+ serd_reader_end_stream(reader);
+ serd_reader_free(reader);
+ fclose(f);
+}
+
+// A byte-wise reader hits EOF once then continues (like a socket)
+static void
+test_read_eof_by_byte(void)
+{
+ ReaderTest rt = {0, 0, 0, 0};
+ SerdReader* const reader = serd_reader_new(SERD_TURTLE,
+ &rt,
+ NULL,
+ test_base_sink,
+ test_prefix_sink,
+ test_statement_sink,
+ test_end_sink);
+
+ size_t n_reads = 0U;
+ serd_reader_start_source_stream(reader,
+ eof_test_read,
+ eof_test_error,
+ &n_reads,
+ (const uint8_t*)"test",
+ 1U);
+
+ assert(serd_reader_read_chunk(reader) == SERD_SUCCESS);
+ assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
+ assert(serd_reader_read_chunk(reader) == SERD_SUCCESS);
+ assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
+
+ serd_reader_free(reader);
+}
+
+static void
+test_read_nquads_chunks(const char* const path)
+{
+ static const char null = 0;
+
+ FILE* const f = fopen(path, "w+b");
+
+ // Write two statements, a null separator, then another statement
+
+ fprintf(f,
+ "<http://example.org/s> <http://example.org/p1> "
+ "<http://example.org/o1> .\n");
+
+ fprintf(f,
+ "<http://example.org/s> <http://example.org/p2> "
+ "<http://example.org/o2> .\n");
+
+ fwrite(&null, sizeof(null), 1, f);
+
+ fprintf(f,
+ "<http://example.org/s> <http://example.org/p3> "
+ "<http://example.org/o3> .\n");
+
+ fseek(f, 0, SEEK_SET);
+
+ ReaderTest rt = {0, 0, 0, 0};
+ SerdReader* const reader = serd_reader_new(SERD_NQUADS,
+ &rt,
+ NULL,
+ test_base_sink,
+ test_prefix_sink,
+ test_statement_sink,
+ test_end_sink);
+
+ assert(reader);
+ assert(serd_reader_get_handle(reader) == &rt);
+ assert(f);
+
+ SerdStatus st = serd_reader_start_stream(reader, f, NULL, false);
+ assert(st == SERD_SUCCESS);
+
+ // Read first statement
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_SUCCESS);
+ assert(rt.n_base == 0);
+ assert(rt.n_prefix == 0);
+ assert(rt.n_statement == 1);
+ assert(rt.n_end == 0);
+
+ // Read second statement
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_SUCCESS);
+ assert(rt.n_base == 0);
+ assert(rt.n_prefix == 0);
+ assert(rt.n_statement == 2);
+ assert(rt.n_end == 0);
+
+ // Read terminator
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_FAILURE);
+ assert(rt.n_base == 0);
+ assert(rt.n_prefix == 0);
+ assert(rt.n_statement == 2);
+ assert(rt.n_end == 0);
+
+ // Read last statement
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_SUCCESS);
+ assert(rt.n_base == 0);
+ assert(rt.n_prefix == 0);
+ assert(rt.n_statement == 3);
+ assert(rt.n_end == 0);
+
+ // EOF
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_FAILURE);
+ assert(rt.n_base == 0);
+ assert(rt.n_prefix == 0);
+ assert(rt.n_statement == 3);
+ assert(rt.n_end == 0);
+
+ assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
+
+ serd_reader_free(reader);
+ fclose(f);
+ remove(path);
+}
+
+static void
+test_read_turtle_chunks(const char* const path)
+{
+ static const char null = 0;
+
+ FILE* const f = fopen(path, "w+b");
+
+ // Write two statements separated by null characters
+ fprintf(f, "@base <http://example.org/base/> .\n");
+ fprintf(f, "@prefix eg: <http://example.org/> .\n");
+ fprintf(f, "eg:s eg:p1 eg:o1 ;\n");
+ fprintf(f, " eg:p2 eg:o2 .\n");
+ fwrite(&null, sizeof(null), 1, f);
+ fprintf(f, "eg:s eg:p [ eg:sp eg:so ] .\n");
+ fwrite(&null, sizeof(null), 1, f);
+ fseek(f, 0, SEEK_SET);
+
+ ReaderTest rt = {0, 0, 0, 0};
+ SerdReader* const reader = serd_reader_new(SERD_TURTLE,
+ &rt,
+ NULL,
+ test_base_sink,
+ test_prefix_sink,
+ test_statement_sink,
+ test_end_sink);
+
+ assert(reader);
+ assert(serd_reader_get_handle(reader) == &rt);
+ assert(f);
+
+ SerdStatus st = serd_reader_start_stream(reader, f, NULL, false);
+ assert(st == SERD_SUCCESS);
+
+ // Read base
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_SUCCESS);
+ assert(rt.n_base == 1);
+ assert(rt.n_prefix == 0);
+ assert(rt.n_statement == 0);
+ assert(rt.n_end == 0);
+
+ // Read prefix
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_SUCCESS);
+ assert(rt.n_base == 1);
+ assert(rt.n_prefix == 1);
+ assert(rt.n_statement == 0);
+ assert(rt.n_end == 0);
+
+ // Read first two statements
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_SUCCESS);
+ assert(rt.n_base == 1);
+ assert(rt.n_prefix == 1);
+ assert(rt.n_statement == 2);
+ assert(rt.n_end == 0);
+
+ // Read terminator
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_FAILURE);
+ assert(rt.n_base == 1);
+ assert(rt.n_prefix == 1);
+ assert(rt.n_statement == 2);
+ assert(rt.n_end == 0);
+
+ // Read statements after null terminator
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_SUCCESS);
+ assert(rt.n_base == 1);
+ assert(rt.n_prefix == 1);
+ assert(rt.n_statement == 4);
+ assert(rt.n_end == 1);
+
+ // Read terminator
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_FAILURE);
+ assert(rt.n_base == 1);
+ assert(rt.n_prefix == 1);
+ assert(rt.n_statement == 4);
+ assert(rt.n_end == 1);
+
+ // EOF
+ st = serd_reader_read_chunk(reader);
+ assert(st == SERD_FAILURE);
+ assert(rt.n_base == 1);
+ assert(rt.n_prefix == 1);
+ assert(rt.n_statement == 4);
+ assert(rt.n_end == 1);
+
+ assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
+
+ serd_reader_free(reader);
+ fclose(f);
+ remove(path);
+}
+
+int
+main(void)
+{
+#ifdef _WIN32
+ char tmp[MAX_PATH] = {0};
+ const size_t tmp_len = (size_t)GetTempPath(sizeof(tmp), tmp);
+#else
+ const char* const env_tmp = getenv("TMPDIR");
+ const char* const tmp = env_tmp ? env_tmp : "/tmp";
+ const size_t tmp_len = strlen(tmp);
+#endif
+
+ const char* const ttl_name = "serd_test_reader.ttl";
+ const char* const nq_name = "serd_test_reader.nq";
+ const size_t ttl_name_len = strlen(ttl_name);
+ const size_t nq_name_len = strlen(nq_name);
+ const size_t path_len = tmp_len + 1 + ttl_name_len;
+ char* const path = (char*)calloc(path_len + 1, 1);
+
+ memcpy(path, tmp, tmp_len + 1);
+ path[tmp_len] = '/';
+
+ memcpy(path + tmp_len + 1, nq_name, nq_name_len + 1);
+ test_read_nquads_chunks(path);
+
+ memcpy(path + tmp_len + 1, ttl_name, ttl_name_len + 1);
+ test_read_turtle_chunks(path);
+
+ test_read_string();
+ test_read_eof_by_page(path);
+ test_read_eof_by_byte();
+ assert(!remove(path));
+
+ free(path);
+ return 0;
+}