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, &nothing, 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