From a6cd7dd91d93015ec118286b868c3fd43133f3ac Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 9 Apr 2023 11:54:11 -0400 Subject: Gracefully handle errors when writing text --- test/test_reader_writer.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++ test/test_string.c | 2 +- test/test_writer.c | 66 ++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/test_reader_writer.c b/test/test_reader_writer.c index 32367bf7..83ee7b2f 100644 --- a/test/test_reader_writer.c +++ b/test/test_reader_writer.c @@ -10,6 +10,7 @@ #endif #include +#include #include #include #include @@ -18,6 +19,11 @@ #define USTR(s) ((const uint8_t*)(s)) +typedef struct { + size_t n_written; + size_t error_offset; +} ErrorContext; + typedef struct { int n_base; int n_prefix; @@ -26,6 +32,29 @@ typedef struct { const SerdNode* graph; } ReaderTest; +static const char* const doc_string = + "@base .\n" + "@prefix eg: .\n" + "eg:g {\n" + " eg:p \"l\\n\\\"it\" ,\n" + " \"\"\"long\"\"\" ,\n" + " \"lang\"@en ;\n" + " eg:p .\n" + "}\n" + "eg:s\n" + " [\n" + " eg:p 3.0 ,\n" + " 4 ,\n" + " \"lit\" ,\n" + " _:n42 ,\n" + " \"t\"^^eg:T\n" + " ] ;\n" + " eg:p () ;\n" + " eg:p\\!q (\"s\" 1 2.0 \"l\"@en eg:o) .\n" + "[] eg:p eg:o .\n" + "[ eg:p eg:o ] eg:q eg:r .\n" + "( eg:o ) eg:t eg:u .\n"; + static SerdStatus test_base_sink(void* handle, const SerdNode* uri) { @@ -250,6 +279,73 @@ test_read_string(void) serd_reader_free(reader); } +static size_t +faulty_sink(const void* const buf, const size_t len, void* const stream) +{ + (void)buf; + (void)len; + + ErrorContext* const ctx = (ErrorContext*)stream; + const size_t new_n_written = ctx->n_written + len; + if (new_n_written >= ctx->error_offset) { + errno = EINVAL; + return 0U; + } + + ctx->n_written += len; + errno = 0; + return len; +} + +static SerdStatus +quiet_error_sink(void* const handle, const SerdError* const e) +{ + (void)handle; + (void)e; + return SERD_SUCCESS; +} + +static void +test_write_errors(void) +{ + ErrorContext ctx = {0U, 0U}; + const SerdStyle style = (SerdStyle)(SERD_STYLE_STRICT | SERD_STYLE_CURIED); + + const size_t max_offsets[] = {0, 386, 1911, 2003, 386}; + + // Test errors at different offsets to hit different code paths + for (unsigned s = 1; s <= (unsigned)SERD_TRIG; ++s) { + const SerdSyntax syntax = (SerdSyntax)s; + for (size_t o = 0; o < max_offsets[s]; ++o) { + ctx.n_written = 0; + ctx.error_offset = o; + + SerdEnv* const env = serd_env_new(NULL); + SerdWriter* const writer = + serd_writer_new(syntax, style, env, NULL, faulty_sink, &ctx); + + SerdReader* const reader = + serd_reader_new(SERD_TRIG, + writer, + NULL, + (SerdBaseSink)serd_writer_set_base_uri, + (SerdPrefixSink)serd_writer_set_prefix, + (SerdStatementSink)serd_writer_write_statement, + (SerdEndSink)serd_writer_end_anon); + + serd_reader_set_error_sink(reader, quiet_error_sink, NULL); + serd_writer_set_error_sink(writer, quiet_error_sink, NULL); + + const SerdStatus st = serd_reader_read_string(reader, USTR(doc_string)); + assert(st == SERD_ERR_BAD_WRITE); + + serd_reader_free(reader); + serd_writer_free(writer); + serd_env_free(env); + } + } +} + static void test_writer(const char* const path) { @@ -470,6 +566,7 @@ main(void) test_read_chunks(path); test_read_string(); + test_write_errors(); test_writer(path); test_reader(path); diff --git a/test/test_string.c b/test/test_string.c index 86ccf982..6767e5ae 100644 --- a/test/test_string.c +++ b/test/test_string.c @@ -31,7 +31,7 @@ test_strerror(void) { const uint8_t* msg = serd_strerror(SERD_SUCCESS); assert(!strcmp((const char*)msg, "Success")); - for (int i = SERD_FAILURE; i <= SERD_ERR_BAD_WRITE; ++i) { + for (int i = SERD_FAILURE; i <= SERD_ERR_BAD_TEXT; ++i) { msg = serd_strerror((SerdStatus)i); assert(strcmp((const char*)msg, "Success")); } diff --git a/test/test_writer.c b/test/test_writer.c index 356dbfcb..d236d7e6 100644 --- a/test/test_writer.c +++ b/test/test_writer.c @@ -84,16 +84,82 @@ test_writer_cleanup(void) // Finish writing without terminating nodes assert(!(st = serd_writer_finish(writer))); + // Set the base to an empty URI + assert(!(st = serd_writer_set_base_uri(writer, NULL))); + // Free (which could leak if the writer doesn't clean up the stack properly) serd_writer_free(writer); serd_env_free(env); } +static void +test_strict_write(void) +{ + const char* const path = "serd_strict_write_test.ttl"; + FILE* const fd = fopen(path, "wb"); + assert(fd); + + SerdEnv* const env = serd_env_new(NULL); + SerdWriter* const writer = serd_writer_new( + SERD_TURTLE, (SerdStyle)SERD_STYLE_STRICT, env, NULL, null_sink, fd); + + assert(writer); + + const uint8_t bad_str[] = {0xFF, 0x90, 'h', 'i', 0}; + + SerdNode s = serd_node_from_string(SERD_URI, USTR("http://example.org/s")); + SerdNode p = serd_node_from_string(SERD_URI, USTR("http://example.org/p")); + + SerdNode bad_lit = serd_node_from_string(SERD_LITERAL, bad_str); + SerdNode bad_uri = serd_node_from_string(SERD_URI, bad_str); + + assert(serd_writer_write_statement( + writer, 0, NULL, &s, &p, &bad_lit, NULL, NULL) == SERD_ERR_BAD_TEXT); + + assert(serd_writer_write_statement( + writer, 0, NULL, &s, &p, &bad_uri, NULL, NULL) == SERD_ERR_BAD_TEXT); + + serd_writer_free(writer); + serd_env_free(env); + fclose(fd); +} + +// Produce a write error without setting errno +static size_t +error_sink(const void* const buf, const size_t len, void* const stream) +{ + (void)buf; + (void)len; + (void)stream; + return 0U; +} + +static void +test_write_error(void) +{ + SerdEnv* const env = serd_env_new(NULL); + SerdWriter* writer = NULL; + SerdStatus st = SERD_SUCCESS; + + SerdNode u = serd_node_from_string(SERD_URI, USTR("http://example.com/u")); + + writer = + serd_writer_new(SERD_TURTLE, (SerdStyle)0, env, NULL, error_sink, NULL); + assert(writer); + st = serd_writer_write_statement(writer, 0U, NULL, &u, &u, &u, NULL, NULL); + assert(st == SERD_ERR_BAD_WRITE); + serd_writer_free(writer); + + serd_env_free(env); +} + int main(void) { test_write_long_literal(); test_writer_cleanup(); + test_strict_write(); + test_write_error(); return 0; } -- cgit v1.2.1