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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current))
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", &current) == 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", &current) == 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