1 /*
2 Copyright 2011-2020 David Robillard <d@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 #undef NDEBUG
18
19 #include "serd/serd.h"
20
21 #include <assert.h>
22 #include <stdbool.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #define USTR(s) ((const uint8_t*)(s))
29
30 typedef struct {
31 int n_statements;
32 const SerdNode* graph;
33 } ReaderTest;
34
35 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)36 test_sink(void* handle,
37 SerdStatementFlags flags,
38 const SerdNode* graph,
39 const SerdNode* subject,
40 const SerdNode* predicate,
41 const SerdNode* object,
42 const SerdNode* object_datatype,
43 const SerdNode* object_lang)
44 {
45 (void)flags;
46 (void)subject;
47 (void)predicate;
48 (void)object;
49 (void)object_datatype;
50 (void)object_lang;
51
52 ReaderTest* rt = (ReaderTest*)handle;
53 ++rt->n_statements;
54 rt->graph = graph;
55 return SERD_SUCCESS;
56 }
57
58 /// Returns EOF after a statement, then succeeds again (like a socket)
59 static size_t
eof_test_read(void * buf,size_t size,size_t nmemb,void * stream)60 eof_test_read(void* buf, size_t size, size_t nmemb, void* stream)
61 {
62 assert(nmemb == 1);
63
64 static const char* const string = "_:s1 <http://example.org/p> _:o1 .\n"
65 "_:s2 <http://example.org/p> _:o2 .\n";
66
67 size_t* count = (size_t*)stream;
68 if (*count == 34 || *count == 35 || *count + nmemb >= strlen(string)) {
69 ++*count;
70 return 0;
71 }
72
73 memcpy((char*)buf, string + *count, size * nmemb);
74 *count += nmemb;
75 return nmemb;
76 }
77
78 static int
eof_test_error(void * stream)79 eof_test_error(void* stream)
80 {
81 (void)stream;
82 return 0;
83 }
84
85 static void
test_read_chunks(void)86 test_read_chunks(void)
87 {
88 ReaderTest* const rt = (ReaderTest*)calloc(1, sizeof(ReaderTest));
89 FILE* const f = tmpfile();
90 static const char null = 0;
91 SerdReader* const reader =
92 serd_reader_new(SERD_TURTLE, rt, free, NULL, NULL, test_sink, NULL);
93
94 assert(reader);
95 assert(serd_reader_get_handle(reader) == rt);
96 assert(f);
97
98 SerdStatus st = serd_reader_start_stream(reader, f, NULL, false);
99 assert(st == SERD_SUCCESS);
100
101 // Write two statement separated by null characters
102 fprintf(f, "@prefix eg: <http://example.org/> .\n");
103 fprintf(f, "eg:s eg:p eg:o1 .\n");
104 fwrite(&null, sizeof(null), 1, f);
105 fprintf(f, "eg:s eg:p eg:o2 .\n");
106 fwrite(&null, sizeof(null), 1, f);
107 fseek(f, 0, SEEK_SET);
108
109 // Read prefix
110 st = serd_reader_read_chunk(reader);
111 assert(st == SERD_SUCCESS);
112 assert(rt->n_statements == 0);
113
114 // Read first statement
115 st = serd_reader_read_chunk(reader);
116 assert(st == SERD_SUCCESS);
117 assert(rt->n_statements == 1);
118
119 // Read terminator
120 st = serd_reader_read_chunk(reader);
121 assert(st == SERD_FAILURE);
122 assert(rt->n_statements == 1);
123
124 // Read second statement (after null terminator)
125 st = serd_reader_read_chunk(reader);
126 assert(st == SERD_SUCCESS);
127 assert(rt->n_statements == 2);
128
129 // Read terminator
130 st = serd_reader_read_chunk(reader);
131 assert(st == SERD_FAILURE);
132 assert(rt->n_statements == 2);
133
134 // EOF
135 st = serd_reader_read_chunk(reader);
136 assert(st == SERD_FAILURE);
137 assert(rt->n_statements == 2);
138
139 serd_reader_free(reader);
140 fclose(f);
141 }
142
143 static void
test_read_string(void)144 test_read_string(void)
145 {
146 ReaderTest* rt = (ReaderTest*)calloc(1, sizeof(ReaderTest));
147 SerdReader* reader =
148 serd_reader_new(SERD_TURTLE, rt, free, NULL, NULL, test_sink, NULL);
149
150 assert(reader);
151 assert(serd_reader_get_handle(reader) == rt);
152
153 // Test reading a string that ends exactly at the end of input (no newline)
154 const SerdStatus st = serd_reader_read_string(
155 reader,
156 USTR("<http://example.org/s> <http://example.org/p> "
157 "<http://example.org/o> ."));
158
159 assert(!st);
160 assert(rt->n_statements == 1);
161
162 serd_reader_free(reader);
163 }
164
165 static void
test_writer(const char * const path)166 test_writer(const char* const path)
167 {
168 FILE* fd = fopen(path, "wb");
169 SerdEnv* env = serd_env_new(NULL);
170 assert(fd);
171
172 SerdWriter* writer =
173 serd_writer_new(SERD_TURTLE, (SerdStyle)0, env, NULL, serd_file_sink, fd);
174 assert(writer);
175
176 serd_writer_chop_blank_prefix(writer, USTR("tmp"));
177 serd_writer_chop_blank_prefix(writer, NULL);
178
179 const SerdNode lit = serd_node_from_string(SERD_LITERAL, USTR("hello"));
180
181 assert(serd_writer_set_base_uri(writer, &lit));
182 assert(serd_writer_set_prefix(writer, &lit, &lit));
183 assert(serd_writer_end_anon(writer, NULL));
184 assert(serd_writer_get_env(writer) == env);
185
186 uint8_t buf[] = {0x80, 0, 0, 0, 0};
187 SerdNode s = serd_node_from_string(SERD_URI, USTR(""));
188 SerdNode p = serd_node_from_string(SERD_URI, USTR("http://example.org/pred"));
189 SerdNode o = serd_node_from_string(SERD_LITERAL, buf);
190
191 // Write 3 invalid statements (should write nothing)
192 const SerdNode* junk[][5] = {{&s, &p, &SERD_NODE_NULL, NULL, NULL},
193 {&s, &SERD_NODE_NULL, &o, NULL, NULL},
194 {&SERD_NODE_NULL, &p, &o, NULL, NULL},
195 {&s, &o, &o, NULL, NULL},
196 {&o, &p, &o, NULL, NULL},
197 {&s, &p, &SERD_NODE_NULL, NULL, NULL}};
198 for (size_t i = 0; i < sizeof(junk) / (sizeof(SerdNode*) * 5); ++i) {
199 assert(serd_writer_write_statement(writer,
200 0,
201 NULL,
202 junk[i][0],
203 junk[i][1],
204 junk[i][2],
205 junk[i][3],
206 junk[i][4]));
207 }
208
209 const SerdNode t = serd_node_from_string(SERD_URI, USTR("urn:Type"));
210 const SerdNode l = serd_node_from_string(SERD_LITERAL, USTR("en"));
211 const SerdNode* good[][5] = {{&s, &p, &o, NULL, NULL},
212 {&s, &p, &o, &SERD_NODE_NULL, &SERD_NODE_NULL},
213 {&s, &p, &o, &t, NULL},
214 {&s, &p, &o, NULL, &l},
215 {&s, &p, &o, &t, &l},
216 {&s, &p, &o, &t, &SERD_NODE_NULL},
217 {&s, &p, &o, &SERD_NODE_NULL, &l},
218 {&s, &p, &o, NULL, &SERD_NODE_NULL},
219 {&s, &p, &o, &SERD_NODE_NULL, NULL},
220 {&s, &p, &o, &SERD_NODE_NULL, NULL}};
221 for (size_t i = 0; i < sizeof(good) / (sizeof(SerdNode*) * 5); ++i) {
222 assert(!serd_writer_write_statement(writer,
223 0,
224 NULL,
225 good[i][0],
226 good[i][1],
227 good[i][2],
228 good[i][3],
229 good[i][4]));
230 }
231
232 // Write statements with bad UTF-8 (should be replaced)
233 const uint8_t bad_str[] = {0xFF, 0x90, 'h', 'i', 0};
234 SerdNode bad_lit = serd_node_from_string(SERD_LITERAL, bad_str);
235 SerdNode bad_uri = serd_node_from_string(SERD_URI, bad_str);
236 assert(!serd_writer_write_statement(
237 writer, 0, NULL, &s, &p, &bad_lit, NULL, NULL));
238 assert(!serd_writer_write_statement(
239 writer, 0, NULL, &s, &p, &bad_uri, NULL, NULL));
240
241 // Write 1 valid statement
242 o = serd_node_from_string(SERD_LITERAL, USTR("hello"));
243 assert(!serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL));
244
245 serd_writer_free(writer);
246
247 // Test chunk sink
248 SerdChunk chunk = {NULL, 0};
249 writer = serd_writer_new(
250 SERD_TURTLE, (SerdStyle)0, env, NULL, serd_chunk_sink, &chunk);
251
252 o = serd_node_from_string(SERD_URI, USTR("http://example.org/base"));
253 assert(!serd_writer_set_base_uri(writer, &o));
254
255 serd_writer_free(writer);
256 uint8_t* out = serd_chunk_sink_finish(&chunk);
257
258 assert(!strcmp((const char*)out, "@base <http://example.org/base> .\n"));
259 serd_free(out);
260
261 // Test writing empty node
262 SerdNode nothing = serd_node_from_string(SERD_NOTHING, USTR(""));
263 FILE* const empty = tmpfile();
264
265 writer = serd_writer_new(
266 SERD_TURTLE, (SerdStyle)0, env, NULL, serd_file_sink, empty);
267
268 // FIXME: error handling
269 serd_writer_write_statement(writer, 0, NULL, &s, &p, ¬hing, NULL, NULL);
270
271 assert((size_t)ftell(empty) == strlen("<>\n\t<http://example.org/pred> "));
272
273 serd_writer_free(writer);
274 fclose(empty);
275
276 serd_env_free(env);
277 fclose(fd);
278 }
279
280 static void
test_reader(const char * path)281 test_reader(const char* path)
282 {
283 ReaderTest* rt = (ReaderTest*)calloc(1, sizeof(ReaderTest));
284 SerdReader* reader =
285 serd_reader_new(SERD_TURTLE, rt, free, NULL, NULL, test_sink, NULL);
286 assert(reader);
287 assert(serd_reader_get_handle(reader) == rt);
288
289 SerdNode g = serd_node_from_string(SERD_URI, USTR("http://example.org/"));
290 serd_reader_set_default_graph(reader, &g);
291 serd_reader_add_blank_prefix(reader, USTR("tmp"));
292
293 #if defined(__GNUC__)
294 # pragma GCC diagnostic push
295 # pragma GCC diagnostic ignored "-Wnonnull"
296 #endif
297 serd_reader_add_blank_prefix(reader, NULL);
298 #if defined(__GNUC__)
299 # pragma GCC diagnostic pop
300 #endif
301
302 assert(serd_reader_read_file(reader, USTR("http://notafile")));
303 assert(serd_reader_read_file(reader, USTR("file:///better/not/exist")));
304 assert(serd_reader_read_file(reader, USTR("file://")));
305
306 const SerdStatus st = serd_reader_read_file(reader, USTR(path));
307 assert(!st);
308 assert(rt->n_statements == 13);
309 assert(rt->graph && rt->graph->buf &&
310 !strcmp((const char*)rt->graph->buf, "http://example.org/"));
311
312 assert(serd_reader_read_string(reader, USTR("This isn't Turtle at all.")));
313
314 // A read of a big page hits EOF then fails to read chunks immediately
315 {
316 FILE* temp = tmpfile();
317 assert(temp);
318 fprintf(temp, "_:s <http://example.org/p> _:o .\n");
319 fflush(temp);
320 fseek(temp, 0L, SEEK_SET);
321
322 serd_reader_start_stream(reader, temp, NULL, true);
323
324 assert(serd_reader_read_chunk(reader) == SERD_SUCCESS);
325 assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
326 assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
327
328 serd_reader_end_stream(reader);
329 fclose(temp);
330 }
331
332 // A byte-wise reader that hits EOF once then continues (like a socket)
333 {
334 size_t n_reads = 0;
335 serd_reader_start_source_stream(reader,
336 (SerdSource)eof_test_read,
337 (SerdStreamErrorFunc)eof_test_error,
338 &n_reads,
339 NULL,
340 1);
341
342 assert(serd_reader_read_chunk(reader) == SERD_SUCCESS);
343 assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
344 assert(serd_reader_read_chunk(reader) == SERD_SUCCESS);
345 assert(serd_reader_read_chunk(reader) == SERD_FAILURE);
346 }
347
348 serd_reader_free(reader);
349 }
350
351 int
main(void)352 main(void)
353 {
354 test_read_chunks();
355 test_read_string();
356
357 const char* const path = "serd_test.ttl";
358 test_writer(path);
359 test_reader(path);
360
361 printf("Success\n");
362 return 0;
363 }
364