1 /* poke-json.c -- Unit tests for the MI-JSON interface in poke */
2
3 /* Copyright (C) 2020, 2021 Jose E. Marchesi */
4
5 /* This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <dirent.h>
25 #include <err.h>
26 #include <json.h>
27
28 /* DejaGnu should not use gnulib's vsnprintf replacement here. */
29 #undef vsnprintf
30 #include <dejagnu.h>
31
32 #include "pk-mi-msg.h"
33 #include "pk-mi-json.h"
34 #include "libpoke.h"
35 #include "../poke.libpoke/term-if.h"
36
37 #define PASS 1
38 #define FAIL 0
39
40 void
test_json_to_msg()41 test_json_to_msg ()
42 {
43 pk_mi_msg msg;
44
45 /* Invalid JSON should result in NULL. */
46 msg = pk_mi_json_to_msg ("\"foo");
47 if (msg == NULL)
48 pass ("json_to_msg_1");
49 else
50 fail ("json_to_msg_1");
51 }
52
53 int
parse_json_str_object(const char * json_str,json_object ** pk_obj)54 parse_json_str_object (const char *json_str, json_object **pk_obj)
55 {
56 struct json_tokener *tok = json_tokener_new ();
57 enum json_tokener_error jerr;
58
59 do
60 {
61 *pk_obj = json_tokener_parse_ex (tok, json_str,
62 strlen (json_str));
63 }
64 while ((jerr = json_tokener_get_error (tok)) == json_tokener_continue);
65
66 json_tokener_free (tok);
67
68 return jerr == json_tokener_success ? PASS : FAIL;
69 }
70
71 /* The following functions check if a JSON representation and a pk_val match.
72
73 In order to test pk_mi_json_to_val and pk_mi_val_to_json we do the following:
74 * Read the .json file which contains a pk_val and its JSON representation.
75 * Call pk_mi_json_to_val to get the pk_val to be tested.
76 * Call pk_mi_val_to_json to get the JSON representation to be tested.
77 * Based on the type of the object, we call the following functions twice.
78 * The first call is used to check if the pk_val returned from
79 pk_mi_json_to_val matches with the JSON representation of the .json file.
80 * The second call is used to check if the JSON representation returned from
81 pk_mi_val_to_json matches with the pk_val of the .json file. */
82
83
84 int test_json_pk_val (json_object *obj, pk_val val);
85
86 int
test_json_pk_int(json_object * pk_int_obj,pk_val pk_int)87 test_json_pk_int (json_object *pk_int_obj, pk_val pk_int)
88 {
89 json_object *current;
90 const char *typename;
91
92 /* Poke integers properties are : "type", "value" and "size". */
93 if (json_object_object_length (pk_int_obj) != 3)
94 return FAIL;
95
96 if (!json_object_object_get_ex (pk_int_obj, "type", ¤t))
97 return FAIL;
98
99 typename = json_object_get_string (current);
100 if (strncmp (typename, "Integer", strlen ("Integer")))
101 return FAIL;
102
103 if (!json_object_object_get_ex (pk_int_obj, "value", ¤t))
104 return FAIL;
105
106 if (json_object_get_int64 (current) != pk_int_value (pk_int))
107 return FAIL;
108
109 if (!json_object_object_get_ex (pk_int_obj, "size", ¤t))
110 return FAIL;
111
112 if (json_object_get_int (current) != pk_int_size (pk_int))
113 return FAIL;
114 return PASS;
115 }
116
117 int
test_json_pk_uint(json_object * pk_uint_obj,pk_val pk_uint)118 test_json_pk_uint (json_object *pk_uint_obj, pk_val pk_uint)
119 {
120 json_object *current;
121 const char *typename;
122
123 /* Poke unsigned integers properties are : "type", "value" and "size". */
124 if (json_object_object_length (pk_uint_obj) != 3)
125 return FAIL;
126
127 if (!json_object_object_get_ex (pk_uint_obj, "type", ¤t))
128 return FAIL;
129
130 typename = json_object_get_string (current);
131 if (strncmp (typename, "UnsignedInteger",
132 strlen ("UnsignedInteger")))
133 return FAIL;
134
135 if (!json_object_object_get_ex (pk_uint_obj, "value", ¤t))
136 return FAIL;
137
138 /* NOTE: This version of libjson-c does not support *_get_uint64 so we use
139 the int64 version and always cast to the unsigned type. */
140 if ((uint64_t) json_object_get_int64 (current) != pk_uint_value (pk_uint))
141 return FAIL;
142
143 if (!json_object_object_get_ex (pk_uint_obj, "size", ¤t))
144 return FAIL;
145
146 if (json_object_get_int (current) != pk_uint_size (pk_uint))
147 return FAIL;
148
149 return PASS;
150 }
151
152 int
test_json_pk_string(json_object * pk_string_obj,pk_val pk_string)153 test_json_pk_string (json_object *pk_string_obj, pk_val pk_string)
154 {
155 json_object *current;
156 const char *typename, *json_str_value, *pk_string_value;
157
158 /* Poke string properties are : "type" and "value". */
159 if (json_object_object_length (pk_string_obj) != 2)
160 return FAIL;
161
162 if (!json_object_object_get_ex (pk_string_obj, "type", ¤t))
163 return FAIL;
164
165 typename = json_object_get_string (current);
166 if (strncmp (typename, "String", strlen ("String")))
167 return FAIL;
168
169 if (!json_object_object_get_ex (pk_string_obj, "value", ¤t))
170 return FAIL;
171
172 json_str_value = json_object_get_string (current);
173 pk_string_value = pk_string_str (pk_string);
174 if (strncmp (json_str_value, pk_string_value, strlen (pk_string_value)))
175 return FAIL;
176
177 return PASS;
178 }
179
180 int
test_json_pk_offset(json_object * pk_offset_obj,pk_val pk_offset)181 test_json_pk_offset (json_object *pk_offset_obj, pk_val pk_offset)
182 {
183 json_object *current;
184 pk_val pk_magnitude, pk_unit;
185 const char *typename;
186 int signed_p;
187
188 /* Poke offset properties are : "type", "magnitude" and "unit". */
189 if (json_object_object_length (pk_offset_obj) != 3)
190 return FAIL;
191
192 if (!json_object_object_get_ex (pk_offset_obj, "type", ¤t))
193 return FAIL;
194
195 typename = json_object_get_string (current);
196 if (strncmp (typename, "Offset", strlen ("Offset")))
197 return FAIL;
198
199 if (!json_object_object_get_ex (pk_offset_obj, "magnitude", ¤t))
200 return FAIL;
201
202 /* "magnitude" is either an UnsignedInteger or an Integer.
203 Check if its UnsignedInteger or Integer. */
204 pk_magnitude = pk_offset_magnitude (pk_offset);
205 signed_p = pk_int_value (pk_integral_type_signed_p (pk_typeof (pk_magnitude)));
206 if (signed_p && test_json_pk_int (current, pk_magnitude) == FAIL)
207 return FAIL;
208
209 if (!signed_p && test_json_pk_uint (current, pk_magnitude) == FAIL)
210 return FAIL;
211
212 /* "unit" is an UnsignedInteger. */
213 if (!json_object_object_get_ex (pk_offset_obj, "unit", ¤t))
214 return FAIL;
215
216 pk_unit = pk_offset_unit (pk_offset);
217 if (test_json_pk_uint (current, pk_unit) == FAIL)
218 return FAIL;
219
220 return PASS;
221 }
222
223 int
test_json_pk_null(json_object * pk_null_obj,pk_val pk_null)224 test_json_pk_null (json_object *pk_null_obj, pk_val pk_null)
225 {
226 json_object *current;
227 const char *typename;
228
229 /* Poke null properties are : "type" and "value". */
230 if (json_object_object_length (pk_null_obj) != 2)
231 return FAIL;
232
233 if (!json_object_object_get_ex (pk_null_obj, "type", ¤t))
234 return FAIL;
235
236 typename = json_object_get_string (current);
237 if (strncmp (typename, "Null", strlen ("Null")))
238 return FAIL;
239
240 if (!json_object_object_get_ex (pk_null_obj, "value", ¤t))
241 return FAIL;
242
243 if (current != NULL)
244 return FAIL;
245
246 return PASS;
247 }
248
249 int
test_json_pk_sct(json_object * pk_sct_obj,pk_val pk_sct)250 test_json_pk_sct (json_object *pk_sct_obj, pk_val pk_sct)
251 {
252 json_object *current, *pk_sct_fields_obj, *pk_sct_field_obj;
253 pk_val pk_sct_name, pk_sct_fname, pk_sct_fboffset, pk_sct_fvalue;
254 const char *typename;
255
256 /* Poke struct properties are : "type", "name", "fields" and "mapping". */
257 if (json_object_object_length (pk_sct_obj) != 4)
258 return FAIL;
259
260 if (!json_object_object_get_ex (pk_sct_obj, "type", ¤t))
261 return FAIL;
262
263 typename = json_object_get_string (current);
264 if (strncmp (typename, "Struct", strlen ("Struct")))
265 return FAIL;
266
267 if (!json_object_object_get_ex (pk_sct_obj, "name", ¤t))
268 return FAIL;
269
270 pk_sct_name = pk_struct_type_name (pk_struct_type (pk_sct));
271
272 if (test_json_pk_string (current, pk_sct_name) == FAIL)
273 return FAIL;
274
275 /* Get the fields of a struct and check them. */
276 if (!json_object_object_get_ex (pk_sct_obj, "fields", &pk_sct_fields_obj))
277 return FAIL;
278
279 for (size_t i = 0 ; i < pk_uint_value (pk_struct_nfields (pk_sct)) ; i++)
280 pk_sct_fboffset = pk_struct_field_boffset (pk_sct, i);
281
282 for (size_t i = 0 ; i < pk_uint_value (pk_struct_nfields (pk_sct)) ; i++)
283 {
284 pk_sct_fname = pk_struct_field_name (pk_sct, i);
285 pk_sct_fboffset = pk_struct_field_boffset (pk_sct, i);
286 pk_sct_fvalue = pk_struct_field_value (pk_sct, i);
287
288 pk_sct_field_obj = json_object_array_get_idx (pk_sct_fields_obj, i);
289
290 if (!json_object_object_get_ex (pk_sct_field_obj, "name", ¤t))
291 return FAIL;
292
293 if (test_json_pk_string (current, pk_sct_fname) == FAIL)
294 return FAIL;
295
296 if (!json_object_object_get_ex (pk_sct_field_obj, "boffset", ¤t))
297 return FAIL;
298
299 if (test_json_pk_uint (current, pk_sct_fboffset) == FAIL)
300 return FAIL;
301
302 if (!json_object_object_get_ex (pk_sct_field_obj, "value", ¤t))
303 return FAIL;
304
305 if (test_json_pk_val (current, pk_sct_fvalue) == FAIL)
306 return FAIL;
307 }
308
309 /* TODO: add test for mapping when its added on pk-mi-json.c. */
310 return PASS;
311 }
312
313 int
test_json_pk_array(json_object * pk_array_obj,pk_val pk_array)314 test_json_pk_array (json_object *pk_array_obj, pk_val pk_array)
315 {
316 json_object *current, *pk_array_elems_obj, *pk_array_elem_obj;
317 pk_val pk_array_elem_value, pk_array_elem_boff;
318 const char *typename;
319
320 /* Poke array properties are : "type", "elements" and "mapping". */
321 if (json_object_object_length (pk_array_obj) != 3)
322 return FAIL;
323
324 if (!json_object_object_get_ex (pk_array_obj, "type", ¤t))
325 return FAIL;
326
327 typename = json_object_get_string (current);
328 if (strncmp (typename, "Array", strlen ("Array")))
329 return FAIL;
330
331 if (!json_object_object_get_ex (pk_array_obj, "elements", &pk_array_elems_obj))
332 return FAIL;
333
334 /* Access every element of the array and check it. */
335 for (size_t i = 0 ; i < pk_uint_value (pk_array_nelem (pk_array)) ; i++)
336 {
337 pk_array_elem_value = pk_array_elem_val (pk_array, i);
338 pk_array_elem_boff = pk_array_elem_boffset (pk_array, i);
339
340 pk_array_elem_obj = json_object_array_get_idx (pk_array_elems_obj, i);
341
342 if (!json_object_object_get_ex (pk_array_elem_obj, "boffset", ¤t))
343 return FAIL;
344
345 if (test_json_pk_uint (current, pk_array_elem_boff) == FAIL)
346 return FAIL;
347
348 if (!json_object_object_get_ex (pk_array_elem_obj, "value", ¤t))
349 return FAIL;
350
351 if (test_json_pk_val (current, pk_array_elem_value) == FAIL)
352 return FAIL;
353 }
354
355 /* TODO: add test for mapping when its added on pk-mi-json.c. */
356 return PASS;
357 }
358
359 int
test_json_pk_val(json_object * obj,pk_val val)360 test_json_pk_val (json_object *obj, pk_val val)
361 {
362 if (val == PK_NULL) {
363 return test_json_pk_null (obj, val);
364 }
365
366 switch (pk_type_code (pk_typeof (val)))
367 {
368 case PK_INT:
369 return test_json_pk_int (obj, val);
370 case PK_UINT:
371 return test_json_pk_uint (obj, val);
372 case PK_STRING:
373 return test_json_pk_string (obj, val);
374 case PK_OFFSET:
375 return test_json_pk_offset (obj, val);
376 case PK_STRUCT:
377 return test_json_pk_sct (obj, val);
378 case PK_ARRAY:
379 return test_json_pk_array (obj, val);
380 case PK_CLOSURE:
381 case PK_ANY:
382 default:
383 return FAIL;
384 }
385 }
386
387 int
compile_initial_poke_code(FILE * ifp,pk_compiler pkc)388 compile_initial_poke_code (FILE *ifp, pk_compiler pkc)
389 {
390 ssize_t nread, s_nread = 0;
391 char *line = NULL, *poke_code = NULL;
392 size_t len = 0;
393 int error = PK_OK;
394
395 while (1)
396 {
397 nread = getline (&line, &len, ifp);
398 /* That should not happen on a correct file.
399 We should prolly check ferror (ifp), postpone it for now. */
400 if (nread == -1)
401 return PK_ERROR;
402
403 /* If we reached the next section of the file, break. */
404 if (nread == 3 && line[0] == '#' && line[1] == '#')
405 break;
406
407 line[nread - 1] = '\0';
408 s_nread += nread - 1;
409
410 if (poke_code == NULL)
411 {
412 poke_code = (char *) malloc (nread);
413 memcpy (poke_code, line, nread);
414 }
415 else
416 {
417 poke_code = (char *) realloc (poke_code, s_nread + 1);
418 strncat (poke_code, line, nread + 1);
419 }
420 }
421
422 if (poke_code)
423 {
424 error = pk_compile_buffer (pkc, poke_code, NULL);
425 free (poke_code);
426 }
427
428 free (line);
429 return error;
430 }
431
432 /* Returns a C array containing the Poke values that were compiled.
433 Returns NULL on error. */
434 int
compile_poke_expression(FILE * ifp,pk_compiler pkc,pk_val * val)435 compile_poke_expression (FILE *ifp, pk_compiler pkc, pk_val *val)
436 {
437 ssize_t nread;
438 char *line = NULL;
439 size_t len = 0;
440
441 while (1)
442 {
443 nread = getline (&line, &len, ifp);
444 if (nread == -1)
445 return PK_ERROR;
446
447 if (nread == 3 && line[0] == '#' && line[1] == '#')
448 break;
449
450 line[nread - 1] = '\0';
451
452 if (pk_compile_expression (pkc, (const char *)line, NULL, val) != PK_OK)
453 goto error;
454 }
455
456 free (line);
457 return PK_OK;
458
459 error:
460 free (line);
461 return PK_ERROR;
462 }
463
464 const char *
read_json_object(FILE * ifp)465 read_json_object (FILE *ifp)
466 {
467 ssize_t nread, s_read = 0;
468 size_t len = 0;
469 char *line = NULL, *json_str = NULL;
470 ssize_t cap = 1024;
471
472 /* Optimistic allocation, to avoid multiple reallocations. */
473 json_str = (char *) malloc (cap);
474 if (json_str == NULL)
475 return NULL;
476 json_str[0] = '\0';
477
478 while ((nread = getline (&line, &len, ifp)) != -1)
479 {
480 s_read += nread;
481
482 if (s_read >= cap)
483 {
484 cap *= 2;
485 json_str = (char *) realloc (json_str, cap);
486 }
487
488 strncat (json_str, line, nread);
489 }
490
491 return json_str;
492 }
493
494 int
test_json_to_val(pk_compiler pk,const char * pk_obj_str,pk_val val)495 test_json_to_val (pk_compiler pk, const char *pk_obj_str, pk_val val)
496 {
497 pk_val pk_test_val;
498 json_object *pk_obj, *current;
499 char *errmsg;
500
501 if (pk_mi_json_to_val (&pk_test_val, pk_obj_str, &errmsg) == -1)
502 return FAIL;
503
504 /* Check if the pk_val returned from pk_mi_json_to_val
505 is the same as the pk_val that we read from the test file. */
506
507 if (!pk_val_equal_p (pk_test_val, val))
508 {
509 printf ("Expected value:\n");
510 pk_print_val_with_params (pk, val, 0, 0, 16, 2, 0, PK_PRINT_F_MAPS);
511 printf ("\n");
512 printf ("Parsed value:\n");
513 pk_print_val_with_params (pk, pk_test_val, 0, 0, 16, 2, 0, PK_PRINT_F_MAPS);
514 printf ("\n");
515 return FAIL;
516 }
517
518 if (parse_json_str_object (pk_obj_str, &pk_obj) == FAIL)
519 return FAIL;
520
521 if (json_object_object_get_ex (pk_obj, "PokeValue", ¤t) == 0)
522 return FAIL;
523
524 if (test_json_pk_val (current, pk_test_val) == FAIL)
525 return FAIL;
526
527 if (json_object_put (pk_obj) != 1)
528 return FAIL;
529
530 return PASS;
531 }
532
533 int
test_val_to_json(const char * pk_obj_str,pk_val val)534 test_val_to_json (const char *pk_obj_str, pk_val val)
535 {
536 const char *pk_test_obj_str;
537 json_object *pk_test_obj, *current;
538
539 if ((pk_test_obj_str = pk_mi_val_to_json (val, NULL)) == NULL)
540 return FAIL;
541
542 if (parse_json_str_object (pk_test_obj_str, &pk_test_obj) == FAIL)
543 return FAIL;
544
545 if (json_object_object_get_ex (pk_test_obj, "PokeValue", ¤t) == 0)
546 return FAIL;
547
548 if (test_json_pk_val (current, val) == FAIL)
549 return FAIL;
550
551 if (json_object_put (pk_test_obj) != 1)
552 return FAIL;
553
554 return PASS;
555 }
556
557 void
test_json_file(const char * filename,FILE * ifp)558 test_json_file (const char *filename, FILE *ifp)
559 {
560 const char *json_obj_str;
561 pk_compiler pkc;
562 pk_val val;
563
564 pkc = pk_compiler_new (&poke_term_if);
565
566 if (pkc == NULL)
567 goto error;
568
569 if (compile_initial_poke_code (ifp, pkc) != PK_OK)
570 goto error;
571
572 if (compile_poke_expression (ifp, pkc, &val) != PK_OK)
573 goto error;
574
575 if ((json_obj_str = read_json_object (ifp)) == NULL)
576 goto error;
577
578 if (test_json_to_val (pkc, json_obj_str, val) == FAIL)
579 goto error;
580
581 if (test_val_to_json (json_obj_str, val) == FAIL)
582 goto error;
583
584 pass (filename);
585 return;
586
587 error:
588 fail (filename);
589 }
590
591 void
test_json_to_val_to_json()592 test_json_to_val_to_json ()
593 {
594 FILE *ifp;
595 DIR *directory;
596 struct dirent *dir;
597 const char *extension;
598 char *testfile;
599
600 directory = opendir (TESTDIR);
601 if (!directory)
602 err (1, "opendir (%s) failed", TESTDIR);
603
604 while ((dir = readdir (directory)) != NULL)
605 {
606 /* Ignore files without `.json` extension */
607 extension = strrchr (dir->d_name, '.');
608 if (!extension)
609 continue;
610 if (strncmp (extension + 1, "json", 4) != 0)
611 continue;
612
613 if (asprintf (&testfile, "%s/%s", TESTDIR, dir->d_name) == -1)
614 err (1, "asprintf () failed");
615
616 ifp = fopen (testfile, "r");
617 if (ifp)
618 {
619 test_json_file (dir->d_name, ifp);
620 fclose (ifp);
621 }
622
623 free (testfile);
624 }
625
626 closedir (directory);
627 }
628
629 void
pk_fatal(const char * msg)630 pk_fatal (const char *msg)
631 {
632 if (msg)
633 printf ("fatal error: %s\n", msg);
634 exit (EXIT_FAILURE);
635 }
636
637 int
main(int argc,char * argv[])638 main (int argc, char *argv[])
639 {
640 test_json_to_msg ();
641 test_json_to_val_to_json ();
642 totals ();
643 return 0;
644 }
645