1 /*
2   Copyright 2011-2016 David Robillard <http://drobilla.net>
3 
4   Permission to use, copy, modify, and/or distribute this software for any
5   purpose with or without fee is hereby granted, provided that the above
6   copyright notice and this permission notice appear in all copies.
7 
8   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 
17 #include <float.h>
18 #include <math.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "serd/serd.h"
25 
26 #define USTR(s) ((const uint8_t*)(s))
27 
28 #ifndef INFINITY
29 #    define INFINITY (DBL_MAX + DBL_MAX)
30 #endif
31 #ifndef NAN
32 #    define NAN (INFINITY - INFINITY)
33 #endif
34 
35 static int
failure(const char * fmt,...)36 failure(const char* fmt, ...)
37 {
38 	va_list args;
39 	va_start(args, fmt);
40 	fprintf(stderr, "error: ");
41 	vfprintf(stderr, fmt, args);
42 	va_end(args);
43 	return 1;
44 }
45 
46 static bool
test_strtod(double dbl,double max_delta)47 test_strtod(double dbl, double max_delta)
48 {
49 	char buf[1024];
50 	snprintf(buf, sizeof(buf), "%f", dbl);
51 
52 	char* endptr = NULL;
53 	const double out = serd_strtod(buf, &endptr);
54 
55 	const double diff = fabs(out - dbl);
56 	if (diff > max_delta) {
57 		return !failure("Parsed %lf != %lf (delta %lf)\n", dbl, out, diff);
58 	}
59 	return true;
60 }
61 
62 static SerdStatus
count_prefixes(void * handle,const SerdNode * name,const SerdNode * uri)63 count_prefixes(void* handle, const SerdNode* name, const SerdNode* uri)
64 {
65 	++*(int*)handle;
66 	return SERD_SUCCESS;
67 }
68 
69 typedef struct {
70 	int             n_statements;
71 	const SerdNode* graph;
72 } ReaderTest;
73 
74 static SerdStatus
test_sink(void * handle,SerdStatementFlags flags,const SerdNode * graph,const SerdNode * subject,const SerdNode * predicate,const SerdNode * object,const SerdNode * object_datatype,const SerdNode * object_lang)75 test_sink(void*              handle,
76           SerdStatementFlags flags,
77           const SerdNode*    graph,
78           const SerdNode*    subject,
79           const SerdNode*    predicate,
80           const SerdNode*    object,
81           const SerdNode*    object_datatype,
82           const SerdNode*    object_lang)
83 {
84 	ReaderTest* rt = (ReaderTest*)handle;
85 	++rt->n_statements;
86 	rt->graph = graph;
87 	return SERD_SUCCESS;
88 }
89 
90 int
main(void)91 main(void)
92 {
93 #define MAX       1000000
94 #define NUM_TESTS 1000
95 	for (int i = 0; i < NUM_TESTS; ++i) {
96 		double dbl = rand() % MAX;
97 		dbl += (rand() % MAX) / (double)MAX;
98 
99 		if (!test_strtod(dbl, 1 / (double)MAX)) {
100 			return 1;
101 		}
102 	}
103 
104 	const double expt_test_nums[] = {
105 		2.0E18, -5e19, +8e20, 2e+24, -5e-5, 8e0, 9e-0, 2e+0
106 	};
107 
108 	const char* expt_test_strs[] = {
109 		"02e18", "-5e019", "+8e20", "2E+24", "-5E-5", "8E0", "9e-0", " 2e+0"
110 	};
111 
112 	for (unsigned i = 0; i < sizeof(expt_test_nums) / sizeof(double); ++i) {
113 		const double num   = serd_strtod(expt_test_strs[i], NULL);
114 		const double delta = fabs(num - expt_test_nums[i]);
115 		if (delta > DBL_EPSILON) {
116 			return failure("Parsed `%s' %lf != %lf (delta %lf)\n",
117 			               expt_test_strs[i], num, expt_test_nums[i], delta);
118 		}
119 	}
120 
121 	// Test serd_node_new_decimal
122 
123 	const double dbl_test_nums[] = {
124 		0.0, 9.0, 10.0, .01, 2.05, -16.00001, 5.000000005, 0.0000000001, NAN, INFINITY
125 	};
126 
127 	const char* dbl_test_strs[] = {
128 		"0.0", "9.0", "10.0", "0.01", "2.05", "-16.00001", "5.00000001", "0.0", NULL, NULL
129 	};
130 
131 	for (unsigned i = 0; i < sizeof(dbl_test_nums) / sizeof(double); ++i) {
132 		SerdNode   node = serd_node_new_decimal(dbl_test_nums[i], 8);
133 		const bool pass = (node.buf && dbl_test_strs[i])
134 			? !strcmp((const char*)node.buf, (const char*)dbl_test_strs[i])
135 			: ((const char*)node.buf == dbl_test_strs[i]);
136 		if (!pass) {
137 			return failure("Serialised `%s' != %s\n",
138 			               node.buf, dbl_test_strs[i]);
139 		}
140 		const size_t len = node.buf ? strlen((const char*)node.buf) : 0;
141 		if (node.n_bytes != len || node.n_chars != len) {
142 			return failure("Length %zu,%zu != %zu\n",
143 			               node.n_bytes, node.n_chars, len);
144 		}
145 		serd_node_free(&node);
146 	}
147 
148 	// Test serd_node_new_integer
149 
150 	const long int_test_nums[] = {
151 		0, -0, -23, 23, -12340, 1000, -1000
152 	};
153 
154 	const char* int_test_strs[] = {
155 		"0", "0", "-23", "23", "-12340", "1000", "-1000"
156 	};
157 
158 	for (unsigned i = 0; i < sizeof(int_test_nums) / sizeof(double); ++i) {
159 		SerdNode node = serd_node_new_integer(int_test_nums[i]);
160 		if (strcmp((const char*)node.buf, (const char*)int_test_strs[i])) {
161 			return failure("Serialised `%s' != %s\n",
162 			               node.buf, int_test_strs[i]);
163 		}
164 		const size_t len = strlen((const char*)node.buf);
165 		if (node.n_bytes != len || node.n_chars != len) {
166 			return failure("Length %zu,%zu != %zu\n",
167 			               node.n_bytes, node.n_chars, len);
168 		}
169 		serd_node_free(&node);
170 	}
171 
172 	// Test serd_node_new_blob
173 	for (size_t size = 0; size < 256; ++size) {
174 		uint8_t* data = (uint8_t*)malloc(size);
175 		for (size_t i = 0; i < size; ++i) {
176 			data[i] = (uint8_t)(rand() % 256);
177 		}
178 
179 		SerdNode blob = serd_node_new_blob(data, size, size % 5);
180 
181 		if (blob.n_bytes != blob.n_chars) {
182 			return failure("Blob %zu bytes != %zu chars\n",
183 			               blob.n_bytes, blob.n_chars);
184 		}
185 
186 		size_t   out_size;
187 		uint8_t* out = (uint8_t*)serd_base64_decode(
188 			blob.buf, blob.n_bytes, &out_size);
189 		if (out_size != size) {
190 			return failure("Blob size %zu != %zu\n", out_size, size);
191 		}
192 
193 		for (size_t i = 0; i < size; ++i) {
194 			if (out[i] != data[i]) {
195 				return failure("Corrupt blob at byte %zu\n", i);
196 			}
197 		}
198 
199 		serd_node_free(&blob);
200 		free(out);
201 		free(data);
202 	}
203 
204 	// Test serd_strlen
205 
206 	const uint8_t str[] = { '"', '5', 0xE2, 0x82, 0xAC, '"', '\n', 0 };
207 
208 	size_t        n_bytes;
209 	SerdNodeFlags flags;
210 	size_t        len = serd_strlen(str, &n_bytes, &flags);
211 	if (len != 5 || n_bytes != 7
212 	    || flags != (SERD_HAS_QUOTE|SERD_HAS_NEWLINE)) {
213 		return failure("Bad serd_strlen(%s) len=%zu n_bytes=%zu flags=%u\n",
214 		        str, len, n_bytes, flags);
215 	}
216 	len = serd_strlen(str, NULL, &flags);
217 	if (len != 5) {
218 		return failure("Bad serd_strlen(%s) len=%zu flags=%u\n",
219 		        str, len, flags);
220 	}
221 
222 	// Test serd_strerror
223 
224 	const uint8_t* msg = NULL;
225 	if (strcmp((const char*)(msg = serd_strerror(SERD_SUCCESS)), "Success")) {
226 		return failure("Bad message `%s' for SERD_SUCCESS\n", msg);
227 	}
228 	for (int i = SERD_FAILURE; i <= SERD_ERR_INTERNAL; ++i) {
229 		msg = serd_strerror((SerdStatus)i);
230 		if (!strcmp((const char*)msg, "Success")) {
231 			return failure("Bad message `%s' for (SerdStatus)%d\n", msg, i);
232 		}
233 	}
234 	msg = serd_strerror((SerdStatus)-1);
235 
236 	// Test serd_uri_to_path
237 
238 	const uint8_t* uri = (const uint8_t*)"file:///home/user/foo.ttl";
239 	if (strcmp((const char*)serd_uri_to_path(uri), "/home/user/foo.ttl")) {
240 		return failure("Bad path %s for %s\n", serd_uri_to_path(uri), uri);
241 	}
242 	uri = (const uint8_t*)"file://localhost/home/user/foo.ttl";
243 	if (strcmp((const char*)serd_uri_to_path(uri), "/home/user/foo.ttl")) {
244 		return failure("Bad path %s for %s\n", serd_uri_to_path(uri), uri);
245 	}
246 	uri = (const uint8_t*)"file:illegal/file/uri";
247 	if (serd_uri_to_path(uri)) {
248 		return failure("Converted invalid URI `%s' to path `%s'\n",
249 		        uri, serd_uri_to_path(uri));
250 	}
251 	uri = (const uint8_t*)"file:///c:/awful/system";
252 	if (strcmp((const char*)serd_uri_to_path(uri), "c:/awful/system")) {
253 		return failure("Bad path %s for %s\n", serd_uri_to_path(uri), uri);
254 	}
255 	uri = (const uint8_t*)"file:///c:awful/system";
256 	if (strcmp((const char*)serd_uri_to_path(uri), "/c:awful/system")) {
257 		return failure("Bad path %s for %s\n", serd_uri_to_path(uri), uri);
258 	}
259 	uri = (const uint8_t*)"file:///0/1";
260 	if (strcmp((const char*)serd_uri_to_path(uri), "/0/1")) {
261 		return failure("Bad path %s for %s\n", serd_uri_to_path(uri), uri);
262 	}
263 	uri = (const uint8_t*)"C:\\Windows\\Sucks";
264 	if (strcmp((const char*)serd_uri_to_path(uri), "C:\\Windows\\Sucks")) {
265 		return failure("Bad path %s for %s\n", serd_uri_to_path(uri), uri);
266 	}
267 	uri = (const uint8_t*)"C|/Windows/Sucks";
268 	if (strcmp((const char*)serd_uri_to_path(uri), "C|/Windows/Sucks")) {
269 		return failure("Bad path %s for %s\n", serd_uri_to_path(uri), uri);
270 	}
271 
272 	// Test serd_node_new_file_uri and serd_file_uri_parse
273 	SerdURI        furi;
274 	const uint8_t* path_str  = USTR("C:/My 100%");
275 	SerdNode       file_node = serd_node_new_file_uri(path_str, 0, &furi, true);
276 	uint8_t*       hostname  = NULL;
277 	uint8_t*       out_path  = serd_file_uri_parse(file_node.buf, &hostname);
278 	if (strcmp((const char*)file_node.buf, "file:///C:/My%20100%%")) {
279 		return failure("Bad URI %s\n", file_node.buf);
280 	} else if (hostname) {
281 		return failure("hostname `%s' shouldn't exist\n", hostname);
282 	} else if (strcmp((const char*)path_str, (const char*)out_path)) {
283 		return failure("path=>URI=>path failure %s => %s => %s\n",
284 		               path_str, file_node.buf, out_path);
285 	}
286 	free(out_path);
287 	serd_node_free(&file_node);
288 
289 	path_str  = USTR("C:\\Pointless Space");
290 	file_node = serd_node_new_file_uri(path_str, USTR("pwned"), 0, true);
291 	hostname  = NULL;
292 	out_path  = serd_file_uri_parse(file_node.buf, &hostname);
293 	if (strcmp((const char*)file_node.buf, "file://pwned/C:/Pointless%20Space")) {
294 		return failure("Bad URI %s\n", file_node.buf);
295 	} else if (!hostname || strcmp((const char*)hostname, "pwned")) {
296 		return failure("Bad hostname `%s'\n", hostname);
297 	} else if (strcmp((const char*)out_path, "C:/Pointless Space")) {
298 		return failure("path=>URI=>path failure %s => %s => %s\n",
299 		               path_str, file_node.buf, out_path);
300 	}
301 	free(hostname);
302 	free(out_path);
303 	serd_node_free(&file_node);
304 
305 	path_str  = USTR("/foo/bar");
306 	file_node = serd_node_new_file_uri(path_str, 0, 0, true);
307 	hostname  = NULL;
308 	out_path  = serd_file_uri_parse(file_node.buf, &hostname);
309 	if (strcmp((const char*)file_node.buf, "file:///foo/bar")) {
310 		return failure("Bad URI %s\n", file_node.buf);
311 	} else if (hostname) {
312 		return failure("hostname `%s' shouldn't exist\n", hostname);
313 	} else if (strcmp((const char*)path_str, (const char*)out_path)) {
314 		return failure("path=>URI=>path failure %s => %s => %s\n",
315 		               path_str, file_node.buf, out_path);
316 	}
317 	free(out_path);
318 	serd_node_free(&file_node);
319 
320 	path_str  = USTR("/foo/bar");
321 	file_node = serd_node_new_file_uri(path_str, USTR("localhost"), 0, true);
322 	out_path  = serd_file_uri_parse(file_node.buf, &hostname);
323 	if (strcmp((const char*)file_node.buf, "file://localhost/foo/bar")) {
324 		return failure("Bad URI %s\n", file_node.buf);
325 	} else if (strcmp((const char*)hostname, "localhost")) {
326 		return failure("incorrect hostname `%s'\n", hostname);
327 	} else if (strcmp((const char*)path_str, (const char*)out_path)) {
328 		return failure("path=>URI=>path failure %s => %s => %s\n",
329 		               path_str, file_node.buf, out_path);
330 	}
331 	free(hostname);
332 	free(out_path);
333 	serd_node_free(&file_node);
334 
335 	path_str  = USTR("a/relative path");
336 	file_node = serd_node_new_file_uri(path_str, 0, 0, false);
337 	out_path  = serd_file_uri_parse(file_node.buf, &hostname);
338 	if (strcmp((const char*)file_node.buf, "a/relative path")) {
339 		return failure("Bad URI %s\n", file_node.buf);
340 	} else if (hostname) {
341 		return failure("hostname `%s' shouldn't exist\n", hostname);
342 	} else if (strcmp((const char*)path_str, (const char*)out_path)) {
343 		return failure("path=>URI=>path failure %s => %s => %s\n",
344 		               path_str, file_node.buf, out_path);
345 	}
346 	free(hostname);
347 	free(out_path);
348 	serd_node_free(&file_node);
349 
350 	if (serd_file_uri_parse(USTR("file://invalid"), NULL)) {
351 		return failure("successfully parsed bogus URI <file://invalid>\n");
352 	}
353 
354 	out_path = serd_file_uri_parse(USTR("file://host/foo/%XYbar"), NULL);
355 	if (strcmp((const char*)out_path, "/foo/bar")) {
356 		return failure("bad tolerance of junk escape: `%s'\n", out_path);
357 	}
358 	free(out_path);
359 	out_path = serd_file_uri_parse(USTR("file://host/foo/%0Abar"), NULL);
360 	if (strcmp((const char*)out_path, "/foo/bar")) {
361 		return failure("bad tolerance of junk escape: `%s'\n", out_path);
362 	}
363 	free(out_path);
364 
365 	// Test serd_node_equals
366 
367 	const uint8_t replacement_char_str[] = { 0xEF, 0xBF, 0xBD, 0 };
368 	SerdNode lhs = serd_node_from_string(SERD_LITERAL, replacement_char_str);
369 	SerdNode rhs = serd_node_from_string(SERD_LITERAL, USTR("123"));
370 	if (serd_node_equals(&lhs, &rhs)) {
371 		return failure("%s == %s\n", lhs.buf, rhs.buf);
372 	}
373 
374 	SerdNode qnode = serd_node_from_string(SERD_CURIE, USTR("foo:bar"));
375 	if (serd_node_equals(&lhs, &qnode)) {
376 		return failure("%s == %s\n", lhs.buf, qnode.buf);
377 	}
378 
379 	if (!serd_node_equals(&lhs, &lhs)) {
380 		return failure("%s != %s\n", lhs.buf, lhs.buf);
381 	}
382 
383 	SerdNode null_copy = serd_node_copy(&SERD_NODE_NULL);
384 	if (!serd_node_equals(&SERD_NODE_NULL, &null_copy)) {
385 		return failure("copy of null node != null node\n");
386 	}
387 
388 	// Test serd_node_from_string
389 
390 	SerdNode node = serd_node_from_string(SERD_LITERAL, (const uint8_t*)"hello\"");
391 	if (node.n_bytes != 6 || node.n_chars != 6 || node.flags != SERD_HAS_QUOTE
392 	    || strcmp((const char*)node.buf, "hello\"")) {
393 		return failure("Bad node %s %zu %zu %d %d\n",
394 		        node.buf, node.n_bytes, node.n_chars, node.flags, node.type);
395 	}
396 
397 	node = serd_node_from_string(SERD_URI, NULL);
398 	if (!serd_node_equals(&node, &SERD_NODE_NULL)) {
399 		return failure("Creating node from NULL string failed\n");
400 	}
401 
402 	// Test serd_node_new_uri_from_string
403 
404 	SerdNode nonsense = serd_node_new_uri_from_string(NULL, NULL, NULL);
405 	if (nonsense.type != SERD_NOTHING) {
406 		return failure("Successfully created NULL URI\n");
407 	}
408 
409 	SerdURI base_uri;
410 	SerdNode base = serd_node_new_uri_from_string(USTR("http://example.org/"),
411 	                                              NULL, &base_uri);
412 	SerdNode nil = serd_node_new_uri_from_string(NULL, &base_uri, NULL);
413 	SerdNode nil2 = serd_node_new_uri_from_string(USTR(""), &base_uri, NULL);
414 	if (nil.type != SERD_URI || strcmp((const char*)nil.buf, (const char*)base.buf) ||
415 	    nil2.type != SERD_URI || strcmp((const char*)nil2.buf, (const char*)base.buf)) {
416 		return failure("URI %s != base %s\n", nil.buf, base.buf);
417 	}
418 	serd_node_free(&nil);
419 	serd_node_free(&nil2);
420 
421 	// Test serd_node_new_relative_uri
422 	SerdNode abs = serd_node_from_string(SERD_URI, USTR("http://example.org/foo/bar"));
423 	SerdURI  abs_uri;
424 	serd_uri_parse(abs.buf, &abs_uri);
425 
426 	SerdURI  rel_uri;
427 	SerdNode rel = serd_node_new_relative_uri(&abs_uri, &base_uri, NULL, &rel_uri);
428 	if (strcmp((const char*)rel.buf, "/foo/bar")) {
429 		return failure("Bad relative URI %s (expected '/foo/bar')\n", rel.buf);
430 	}
431 
432 	serd_node_free(&rel);
433 	serd_node_free(&base);
434 
435 	// Test SerdEnv
436 
437 	SerdNode u   = serd_node_from_string(SERD_URI, USTR("http://example.org/foo"));
438 	SerdNode b   = serd_node_from_string(SERD_CURIE, USTR("invalid"));
439 	SerdNode c   = serd_node_from_string(SERD_CURIE, USTR("eg.2:b"));
440 	SerdEnv* env = serd_env_new(NULL);
441 	serd_env_set_prefix_from_strings(env, USTR("eg.2"), USTR("http://example.org/"));
442 
443 	if (!serd_env_set_base_uri(env, NULL)) {
444 		return failure("Successfully set NULL base URI\n");
445 	}
446 
447 	if (!serd_env_set_base_uri(env, &node)) {
448 		return failure("Set base URI to %s\n", node.buf);
449 	}
450 
451 	if (!serd_node_equals(serd_env_get_base_uri(env, NULL), &node)) {
452 		return failure("Base URI mismatch\n");
453 	}
454 
455 	SerdChunk prefix, suffix;
456 	if (!serd_env_expand(env, &b, &prefix, &suffix)) {
457 		return failure("Expanded invalid curie %s\n", b.buf);
458 	}
459 
460 	SerdNode xnode = serd_env_expand_node(env, &node);
461 	if (!serd_node_equals(&xnode, &SERD_NODE_NULL)) {
462 		return failure("Expanded %s to %s\n", c.buf, xnode.buf);
463 	}
464 
465 	SerdNode xu = serd_env_expand_node(env, &u);
466 	if (strcmp((const char*)xu.buf, "http://example.org/foo")) {
467 		return failure("Expanded %s to %s\n", c.buf, xu.buf);
468 	}
469 	serd_node_free(&xu);
470 
471 	SerdNode badpre = serd_node_from_string(SERD_CURIE, USTR("hm:what"));
472 	SerdNode xbadpre = serd_env_expand_node(env, &badpre);
473 	if (!serd_node_equals(&xbadpre, &SERD_NODE_NULL)) {
474 		return failure("Expanded invalid curie %s\n", badpre.buf);
475 	}
476 
477 	SerdNode xc = serd_env_expand_node(env, &c);
478 	if (strcmp((const char*)xc.buf, "http://example.org/b")) {
479 		return failure("Expanded %s to %s\n", c.buf, xc.buf);
480 	}
481 	serd_node_free(&xc);
482 
483 	if (!serd_env_set_prefix(env, &SERD_NODE_NULL, &SERD_NODE_NULL)) {
484 		return failure("Set NULL prefix\n");
485 	}
486 
487 	const SerdNode lit = serd_node_from_string(SERD_LITERAL, USTR("hello"));
488 	if (!serd_env_set_prefix(env, &b, &lit)) {
489 		return failure("Set prefix to literal\n");
490 	}
491 
492 	int n_prefixes = 0;
493 	serd_env_set_prefix_from_strings(env, USTR("eg.2"), USTR("http://example.org/"));
494 	serd_env_foreach(env, count_prefixes, &n_prefixes);
495 	if (n_prefixes != 1) {
496 		return failure("Bad prefix count %d\n", n_prefixes);
497 	}
498 
499 	SerdNode shorter_uri = serd_node_from_string(SERD_URI, USTR("urn:foo"));
500 	SerdNode prefix_name;
501 	if (serd_env_qualify(env, &shorter_uri, &prefix_name, &suffix)) {
502 		return failure("Qualified %s\n", shorter_uri.buf);
503 	}
504 
505 	// Test SerdReader and SerdWriter
506 
507 	const char* path = "serd_test.ttl";
508 	FILE* fd = fopen(path, "w");
509 	if (!fd) {
510 		return failure("Failed to open file %s\n", path);
511 	}
512 
513 	SerdWriter* writer = serd_writer_new(
514 		SERD_TURTLE, (SerdStyle)0, env, NULL, serd_file_sink, fd);
515 	if (!writer) {
516 		return failure("Failed to create writer\n");
517 	}
518 
519 	serd_writer_chop_blank_prefix(writer, USTR("tmp"));
520 	serd_writer_chop_blank_prefix(writer, NULL);
521 
522 	if (!serd_writer_set_base_uri(writer, &lit)) {
523 		return failure("Set base URI to %s\n", lit.buf);
524 	}
525 
526 	if (!serd_writer_set_prefix(writer, &lit, &lit)) {
527 		return failure("Set prefix %s to %s\n", lit.buf, lit.buf);
528 	}
529 
530 	if (!serd_writer_end_anon(writer, NULL)) {
531 		return failure("Ended non-existent anonymous node\n");
532 	}
533 
534 	if (serd_writer_get_env(writer) != env) {
535 		return failure("Writer has incorrect env\n");
536 	}
537 
538 	uint8_t buf[] = { 0x80, 0, 0, 0, 0 };
539 	SerdNode s = serd_node_from_string(SERD_URI, USTR(""));
540 	SerdNode p = serd_node_from_string(SERD_URI, USTR("http://example.org/pred"));
541 	SerdNode o = serd_node_from_string(SERD_LITERAL, buf);
542 
543 	// Write 3 invalid statements (should write nothing)
544 	const SerdNode* junk[][5] = { { &s, &p, NULL, NULL, NULL },
545 	                              { &s, NULL, &o, NULL, NULL },
546 	                              { NULL, &p, &o, NULL, NULL },
547 	                              { &s, &p, &SERD_NODE_NULL, NULL, NULL },
548 	                              { &s, &SERD_NODE_NULL, &o, NULL, NULL },
549 	                              { &SERD_NODE_NULL, &p, &o, NULL, NULL },
550 	                              { &s, &o, &o, NULL, NULL },
551 	                              { &o, &p, &o, NULL, NULL },
552 	                              { NULL, NULL, NULL, NULL, NULL } };
553 	for (unsigned i = 0; i < sizeof(junk) / (sizeof(SerdNode*) * 5); ++i) {
554 		if (!serd_writer_write_statement(
555 			    writer, 0, NULL,
556 			    junk[i][0], junk[i][1], junk[i][2], junk[i][3], junk[i][4])) {
557 			return failure("Successfully wrote junk statement %d\n", i);
558 		}
559 	}
560 
561 	const SerdNode t = serd_node_from_string(SERD_URI, USTR("urn:Type"));
562 	const SerdNode l = serd_node_from_string(SERD_LITERAL, USTR("en"));
563 	const SerdNode* good[][5] = { { &s, &p, &o, NULL, NULL },
564 	                              { &s, &p, &o, &SERD_NODE_NULL, &SERD_NODE_NULL },
565 	                              { &s, &p, &o, &t, NULL },
566 	                              { &s, &p, &o, NULL, &l },
567 	                              { &s, &p, &o, &t, &l },
568 	                              { &s, &p, &o, &t, &SERD_NODE_NULL },
569 	                              { &s, &p, &o, &SERD_NODE_NULL, &l },
570 	                              { &s, &p, &o, NULL, &SERD_NODE_NULL },
571 	                              { &s, &p, &o, &SERD_NODE_NULL, NULL },
572 	                              { &s, &p, &o, &SERD_NODE_NULL, NULL } };
573 	for (unsigned i = 0; i < sizeof(good) / (sizeof(SerdNode*) * 5); ++i) {
574 		if (serd_writer_write_statement(
575 			    writer, 0, NULL,
576 			    good[i][0], good[i][1], good[i][2], good[i][3], good[i][4])) {
577 			return failure("Failed to write good statement %d\n", i);
578 		}
579 	}
580 
581 	// Write 1 statement with bad UTF-8 (should be replaced)
582 	if (serd_writer_write_statement(writer, 0, NULL,
583 	                                &s, &p, &o, NULL, NULL)) {
584 		return failure("Failed to write junk UTF-8\n");
585 	}
586 
587 	// Write 1 valid statement
588 	o = serd_node_from_string(SERD_LITERAL, USTR("hello"));
589 	if (serd_writer_write_statement(writer, 0, NULL,
590 	                                &s, &p, &o, NULL, NULL)) {
591 		return failure("Failed to write valid statement\n");
592 	}
593 
594 	serd_writer_free(writer);
595 
596 	// Test chunk sink
597 	SerdChunk chunk = { NULL, 0 };
598 	writer = serd_writer_new(
599 		SERD_TURTLE, (SerdStyle)0, env, NULL, serd_chunk_sink, &chunk);
600 
601 	o = serd_node_from_string(SERD_URI, USTR("http://example.org/base"));
602 	if (serd_writer_set_base_uri(writer, &o)) {
603 		return failure("Failed to write to chunk sink\n");
604 	}
605 
606 	serd_writer_free(writer);
607 	uint8_t* out = serd_chunk_sink_finish(&chunk);
608 
609 	if (strcmp((const char*)out, "@base <http://example.org/base> .\n")) {
610 		return failure("Incorrect chunk output:\n%s\n", chunk.buf);
611 	}
612 
613 	free(out);
614 
615 	// Rewind and test reader
616 	fseek(fd, 0, SEEK_SET);
617 
618 	ReaderTest* rt     = (ReaderTest*)calloc(1, sizeof(ReaderTest));
619 	SerdReader* reader = serd_reader_new(
620 		SERD_TURTLE, rt, free,
621 		NULL, NULL, test_sink, NULL);
622 	if (!reader) {
623 		return failure("Failed to create reader\n");
624 	}
625 	if (serd_reader_get_handle(reader) != rt) {
626 		return failure("Corrupt reader handle\n");
627 	}
628 
629 	SerdNode g = serd_node_from_string(SERD_URI, USTR("http://example.org/"));
630 	serd_reader_set_default_graph(reader, &g);
631 	serd_reader_add_blank_prefix(reader, USTR("tmp"));
632 	serd_reader_add_blank_prefix(reader, NULL);
633 
634 	if (!serd_reader_read_file(reader, USTR("http://notafile"))) {
635 		return failure("Apparently read an http URI\n");
636 	}
637 	if (!serd_reader_read_file(reader, USTR("file:///better/not/exist"))) {
638 		return failure("Apparently read a non-existent file\n");
639 	}
640 	if (!serd_reader_read_file(reader, USTR("file://"))) {
641 		return failure("Apparently read a file with no path\n");
642 	}
643 	SerdStatus st = serd_reader_read_file(reader, USTR(path));
644 	if (st) {
645 		return failure("Error reading file (%s)\n", serd_strerror(st));
646 	}
647 
648 	if (rt->n_statements != 12) {
649 		return failure("Bad statement count %d\n", rt->n_statements);
650 	} else if (!rt->graph || !rt->graph->buf ||
651 	           strcmp((const char*)rt->graph->buf, "http://example.org/")) {
652 		return failure("Bad graph %p\n", rt->graph);
653 	}
654 
655 	if (!serd_reader_read_string(reader, USTR("This isn't Turtle at all."))) {
656 		return failure("Parsed invalid string successfully.\n");
657 	}
658 
659 	serd_reader_free(reader);
660 	fclose(fd);
661 
662 	serd_env_free(env);
663 
664 	printf("Success\n");
665 	return 0;
666 }
667