]> git.proxmox.com Git - mirror_qemu.git/blame - tests/test-visitor-serialization.c
qapi: Unit tests for visitor-based serialization
[mirror_qemu.git] / tests / test-visitor-serialization.c
CommitLineData
2d496105
MR
1/*
2 * Unit-tests for visitor-based serialization
3 *
4 * Copyright IBM, Corp. 2012
5 *
6 * Authors:
7 * Michael Roth <mdroth@linux.vnet.ibm.com>
8 *
9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
10 * See the COPYING file in the top-level directory.
11 */
12
13#include <glib.h>
14#include <stdlib.h>
15#include <stdint.h>
16#include <float.h>
17#include "test-qapi-types.h"
18#include "test-qapi-visit.h"
19#include "qemu-objects.h"
20#include "qapi/qmp-input-visitor.h"
21#include "qapi/qmp-output-visitor.h"
22
23typedef struct PrimitiveType {
24 union {
25 const char *string;
26 bool boolean;
27 double number;
28 int64_t integer;
29 uint8_t u8;
30 uint16_t u16;
31 uint32_t u32;
32 uint64_t u64;
33 int8_t s8;
34 int16_t s16;
35 int32_t s32;
36 int64_t s64;
37 intmax_t max;
38 } value;
39 enum {
40 PTYPE_STRING = 0,
41 PTYPE_BOOLEAN,
42 PTYPE_NUMBER,
43 PTYPE_INTEGER,
44 PTYPE_U8,
45 PTYPE_U16,
46 PTYPE_U32,
47 PTYPE_U64,
48 PTYPE_S8,
49 PTYPE_S16,
50 PTYPE_S32,
51 PTYPE_S64,
52 PTYPE_EOL,
53 } type;
54 const char *description;
55} PrimitiveType;
56
57/* test helpers */
58
59static void visit_primitive_type(Visitor *v, void **native, Error **errp)
60{
61 PrimitiveType *pt = *native;
62 switch(pt->type) {
63 case PTYPE_STRING:
64 visit_type_str(v, (char **)&pt->value.string, NULL, errp);
65 break;
66 case PTYPE_BOOLEAN:
67 visit_type_bool(v, &pt->value.boolean, NULL, errp);
68 break;
69 case PTYPE_NUMBER:
70 visit_type_number(v, &pt->value.number, NULL, errp);
71 break;
72 case PTYPE_INTEGER:
73 visit_type_int(v, &pt->value.integer, NULL, errp);
74 break;
75 case PTYPE_U8:
76 visit_type_uint8(v, &pt->value.u8, NULL, errp);
77 break;
78 case PTYPE_U16:
79 visit_type_uint16(v, &pt->value.u16, NULL, errp);
80 break;
81 case PTYPE_U32:
82 visit_type_uint32(v, &pt->value.u32, NULL, errp);
83 break;
84 case PTYPE_U64:
85 visit_type_uint64(v, &pt->value.u64, NULL, errp);
86 break;
87 case PTYPE_S8:
88 visit_type_int8(v, &pt->value.s8, NULL, errp);
89 break;
90 case PTYPE_S16:
91 visit_type_int16(v, &pt->value.s16, NULL, errp);
92 break;
93 case PTYPE_S32:
94 visit_type_int32(v, &pt->value.s32, NULL, errp);
95 break;
96 case PTYPE_S64:
97 visit_type_int64(v, &pt->value.s64, NULL, errp);
98 break;
99 case PTYPE_EOL:
100 g_assert(false);
101 }
102}
103
104typedef struct TestStruct
105{
106 int64_t integer;
107 bool boolean;
108 char *string;
109} TestStruct;
110
111static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
112 const char *name, Error **errp)
113{
114 visit_start_struct(v, (void **)obj, NULL, name, sizeof(TestStruct), errp);
115
116 visit_type_int(v, &(*obj)->integer, "integer", errp);
117 visit_type_bool(v, &(*obj)->boolean, "boolean", errp);
118 visit_type_str(v, &(*obj)->string, "string", errp);
119
120 visit_end_struct(v, errp);
121}
122
123static TestStruct *struct_create(void)
124{
125 TestStruct *ts = g_malloc0(sizeof(*ts));
126 ts->integer = -42;
127 ts->boolean = true;
128 ts->string = strdup("test string");
129 return ts;
130}
131
132static void struct_compare(TestStruct *ts1, TestStruct *ts2)
133{
134 g_assert(ts1);
135 g_assert(ts2);
136 g_assert_cmpint(ts1->integer, ==, ts2->integer);
137 g_assert(ts1->boolean == ts2->boolean);
138 g_assert_cmpstr(ts1->string, ==, ts2->string);
139}
140
141static void struct_cleanup(TestStruct *ts)
142{
143 g_free(ts->string);
144 g_free(ts);
145}
146
147static void visit_struct(Visitor *v, void **native, Error **errp)
148{
149 visit_type_TestStruct(v, (TestStruct **)native, NULL, errp);
150}
151
152static UserDefNested *nested_struct_create(void)
153{
154 UserDefNested *udnp = g_malloc0(sizeof(*udnp));
155 udnp->string0 = strdup("test_string0");
156 udnp->dict1.string1 = strdup("test_string1");
157 udnp->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne));
158 udnp->dict1.dict2.userdef1->integer = 42;
159 udnp->dict1.dict2.userdef1->string = strdup("test_string");
160 udnp->dict1.dict2.string2 = strdup("test_string2");
161 udnp->dict1.has_dict3 = true;
162 udnp->dict1.dict3.userdef2 = g_malloc0(sizeof(UserDefOne));
163 udnp->dict1.dict3.userdef2->integer = 43;
164 udnp->dict1.dict3.userdef2->string = strdup("test_string");
165 udnp->dict1.dict3.string3 = strdup("test_string3");
166 return udnp;
167}
168
169static void nested_struct_compare(UserDefNested *udnp1, UserDefNested *udnp2)
170{
171 g_assert(udnp1);
172 g_assert(udnp2);
173 g_assert_cmpstr(udnp1->string0, ==, udnp2->string0);
174 g_assert_cmpstr(udnp1->dict1.string1, ==, udnp2->dict1.string1);
175 g_assert_cmpint(udnp1->dict1.dict2.userdef1->integer, ==,
176 udnp2->dict1.dict2.userdef1->integer);
177 g_assert_cmpstr(udnp1->dict1.dict2.userdef1->string, ==,
178 udnp2->dict1.dict2.userdef1->string);
179 g_assert_cmpstr(udnp1->dict1.dict2.string2, ==, udnp2->dict1.dict2.string2);
180 g_assert(udnp1->dict1.has_dict3 == udnp2->dict1.has_dict3);
181 g_assert_cmpint(udnp1->dict1.dict3.userdef2->integer, ==,
182 udnp2->dict1.dict3.userdef2->integer);
183 g_assert_cmpstr(udnp1->dict1.dict3.userdef2->string, ==,
184 udnp2->dict1.dict3.userdef2->string);
185 g_assert_cmpstr(udnp1->dict1.dict3.string3, ==, udnp2->dict1.dict3.string3);
186}
187
188static void nested_struct_cleanup(UserDefNested *udnp)
189{
190 qapi_free_UserDefNested(udnp);
191}
192
193static void visit_nested_struct(Visitor *v, void **native, Error **errp)
194{
195 visit_type_UserDefNested(v, (UserDefNested **)native, NULL, errp);
196}
197
198static void visit_nested_struct_list(Visitor *v, void **native, Error **errp)
199{
200 visit_type_UserDefNestedList(v, (UserDefNestedList **)native, NULL, errp);
201}
202
203/* test cases */
204
205typedef void (*VisitorFunc)(Visitor *v, void **native, Error **errp);
206
207typedef enum VisitorCapabilities {
208 VCAP_PRIMITIVES = 1,
209 VCAP_STRUCTURES = 2,
210 VCAP_LISTS = 4,
211} VisitorCapabilities;
212
213typedef struct SerializeOps {
214 void (*serialize)(void *native_in, void **datap,
215 VisitorFunc visit, Error **errp);
216 void (*deserialize)(void **native_out, void *datap,
217 VisitorFunc visit, Error **errp);
218 void (*cleanup)(void *datap);
219 const char *type;
220 VisitorCapabilities caps;
221} SerializeOps;
222
223typedef struct TestArgs {
224 const SerializeOps *ops;
225 void *test_data;
226} TestArgs;
227
228#define FLOAT_STRING_PRECISION 6 /* corresponding to n in %.nf formatting */
229static gsize calc_float_string_storage(double value)
230{
231 int whole_value = value;
232 gsize i = 0;
233 do {
234 i++;
235 } while (whole_value /= 10);
236 return i + 2 + FLOAT_STRING_PRECISION;
237}
238
239static void test_primitives(gconstpointer opaque)
240{
241 TestArgs *args = (TestArgs *) opaque;
242 const SerializeOps *ops = args->ops;
243 PrimitiveType *pt = args->test_data;
244 PrimitiveType *pt_copy = g_malloc0(sizeof(*pt_copy));
245 Error *err = NULL;
246 void *serialize_data;
247 char *double1, *double2;
248
249 pt_copy->type = pt->type;
250 ops->serialize(pt, &serialize_data, visit_primitive_type, &err);
251 ops->deserialize((void **)&pt_copy, serialize_data, visit_primitive_type, &err);
252
253 g_assert(err == NULL);
254 g_assert(pt_copy != NULL);
255 if (pt->type == PTYPE_STRING) {
256 g_assert_cmpstr(pt->value.string, ==, pt_copy->value.string);
257 } else if (pt->type == PTYPE_NUMBER) {
258 /* we serialize with %f for our reference visitors, so rather than fuzzy
259 * floating math to test "equality", just compare the formatted values
260 */
261 double1 = g_malloc0(calc_float_string_storage(pt->value.number));
262 double2 = g_malloc0(calc_float_string_storage(pt_copy->value.number));
263 g_assert_cmpstr(double1, ==, double2);
264 g_free(double1);
265 g_free(double2);
266 } else if (pt->type == PTYPE_BOOLEAN) {
267 g_assert_cmpint(!!pt->value.max, ==, !!pt->value.max);
268 } else {
269 g_assert_cmpint(pt->value.max, ==, pt_copy->value.max);
270 }
271
272 ops->cleanup(serialize_data);
273 g_free(args);
274}
275
276static void test_struct(gconstpointer opaque)
277{
278 TestArgs *args = (TestArgs *) opaque;
279 const SerializeOps *ops = args->ops;
280 TestStruct *ts = struct_create();
281 TestStruct *ts_copy = NULL;
282 Error *err = NULL;
283 void *serialize_data;
284
285 ops->serialize(ts, &serialize_data, visit_struct, &err);
286 ops->deserialize((void **)&ts_copy, serialize_data, visit_struct, &err);
287
288 g_assert(err == NULL);
289 struct_compare(ts, ts_copy);
290
291 struct_cleanup(ts);
292 struct_cleanup(ts_copy);
293
294 ops->cleanup(serialize_data);
295 g_free(args);
296}
297
298static void test_nested_struct(gconstpointer opaque)
299{
300 TestArgs *args = (TestArgs *) opaque;
301 const SerializeOps *ops = args->ops;
302 UserDefNested *udnp = nested_struct_create();
303 UserDefNested *udnp_copy = NULL;
304 Error *err = NULL;
305 void *serialize_data;
306
307 ops->serialize(udnp, &serialize_data, visit_nested_struct, &err);
308 ops->deserialize((void **)&udnp_copy, serialize_data, visit_nested_struct, &err);
309
310 g_assert(err == NULL);
311 nested_struct_compare(udnp, udnp_copy);
312
313 nested_struct_cleanup(udnp);
314 nested_struct_cleanup(udnp_copy);
315
316 ops->cleanup(serialize_data);
317 g_free(args);
318}
319
320static void test_nested_struct_list(gconstpointer opaque)
321{
322 TestArgs *args = (TestArgs *) opaque;
323 const SerializeOps *ops = args->ops;
324 UserDefNestedList *listp = NULL, *tmp, *tmp_copy, *listp_copy = NULL;
325 Error *err = NULL;
326 void *serialize_data;
327 int i = 0;
328
329 for (i = 0; i < 8; i++) {
330 tmp = g_malloc0(sizeof(UserDefNestedList));
331 tmp->value = nested_struct_create();
332 tmp->next = listp;
333 listp = tmp;
334 }
335
336 ops->serialize(listp, &serialize_data, visit_nested_struct_list, &err);
337 ops->deserialize((void **)&listp_copy, serialize_data,
338 visit_nested_struct_list, &err);
339
340 g_assert(err == NULL);
341
342 tmp = listp;
343 tmp_copy = listp_copy;
344 while (listp_copy) {
345 g_assert(listp);
346 nested_struct_compare(listp->value, listp_copy->value);
347 listp = listp->next;
348 listp_copy = listp_copy->next;
349 }
350
351 qapi_free_UserDefNestedList(tmp);
352 qapi_free_UserDefNestedList(tmp_copy);
353
354 ops->cleanup(serialize_data);
355 g_free(args);
356}
357
358PrimitiveType pt_values[] = {
359 /* string tests */
360 {
361 .description = "string_empty",
362 .type = PTYPE_STRING,
363 .value.string = "",
364 },
365 {
366 .description = "string_whitespace",
367 .type = PTYPE_STRING,
368 .value.string = "a b c\td",
369 },
370 {
371 .description = "string_newlines",
372 .type = PTYPE_STRING,
373 .value.string = "a\nb\n",
374 },
375 {
376 .description = "string_commas",
377 .type = PTYPE_STRING,
378 .value.string = "a,b, c,d",
379 },
380 {
381 .description = "string_single_quoted",
382 .type = PTYPE_STRING,
383 .value.string = "'a b',cd",
384 },
385 {
386 .description = "string_double_quoted",
387 .type = PTYPE_STRING,
388 .value.string = "\"a b\",cd",
389 },
390 /* boolean tests */
391 {
392 .description = "boolean_true1",
393 .type = PTYPE_BOOLEAN,
394 .value.boolean = true,
395 },
396 {
397 .description = "boolean_true2",
398 .type = PTYPE_BOOLEAN,
399 .value.boolean = 8,
400 },
401 {
402 .description = "boolean_true3",
403 .type = PTYPE_BOOLEAN,
404 .value.boolean = -1,
405 },
406 {
407 .description = "boolean_false1",
408 .type = PTYPE_BOOLEAN,
409 .value.boolean = false,
410 },
411 {
412 .description = "boolean_false2",
413 .type = PTYPE_BOOLEAN,
414 .value.boolean = 0,
415 },
416 /* number tests (double) */
417 /* note: we format these to %.6f before comparing, since that's how
418 * we serialize them and it doesn't make sense to check precision
419 * beyond that.
420 */
421 {
422 .description = "number_sanity1",
423 .type = PTYPE_NUMBER,
424 .value.number = -1,
425 },
426 {
427 .description = "number_sanity2",
428 .type = PTYPE_NUMBER,
429 .value.number = 3.14159265,
430 },
431 {
432 .description = "number_min",
433 .type = PTYPE_NUMBER,
434 .value.number = DBL_MIN,
435 },
436 {
437 .description = "number_max",
438 .type = PTYPE_NUMBER,
439 .value.number = DBL_MAX,
440 },
441 /* integer tests (int64) */
442 {
443 .description = "integer_sanity1",
444 .type = PTYPE_INTEGER,
445 .value.integer = -1,
446 },
447 {
448 .description = "integer_sanity2",
449 .type = PTYPE_INTEGER,
450 .value.integer = INT64_MAX / 2 + 1,
451 },
452 {
453 .description = "integer_min",
454 .type = PTYPE_INTEGER,
455 .value.integer = INT64_MIN,
456 },
457 {
458 .description = "integer_max",
459 .type = PTYPE_INTEGER,
460 .value.integer = INT64_MAX,
461 },
462 /* uint8 tests */
463 {
464 .description = "uint8_sanity1",
465 .type = PTYPE_U8,
466 .value.u8 = 1,
467 },
468 {
469 .description = "uint8_sanity2",
470 .type = PTYPE_U8,
471 .value.u8 = UINT8_MAX / 2 + 1,
472 },
473 {
474 .description = "uint8_min",
475 .type = PTYPE_U8,
476 .value.u8 = 0,
477 },
478 {
479 .description = "uint8_max",
480 .type = PTYPE_U8,
481 .value.u8 = UINT8_MAX,
482 },
483 /* uint16 tests */
484 {
485 .description = "uint16_sanity1",
486 .type = PTYPE_U16,
487 .value.u16 = 1,
488 },
489 {
490 .description = "uint16_sanity2",
491 .type = PTYPE_U16,
492 .value.u16 = UINT16_MAX / 2 + 1,
493 },
494 {
495 .description = "uint16_min",
496 .type = PTYPE_U16,
497 .value.u16 = 0,
498 },
499 {
500 .description = "uint16_max",
501 .type = PTYPE_U16,
502 .value.u16 = UINT16_MAX,
503 },
504 /* uint32 tests */
505 {
506 .description = "uint32_sanity1",
507 .type = PTYPE_U32,
508 .value.u32 = 1,
509 },
510 {
511 .description = "uint32_sanity2",
512 .type = PTYPE_U32,
513 .value.u32 = UINT32_MAX / 2 + 1,
514 },
515 {
516 .description = "uint32_min",
517 .type = PTYPE_U32,
518 .value.u32 = 0,
519 },
520 {
521 .description = "uint32_max",
522 .type = PTYPE_U32,
523 .value.u32 = UINT32_MAX,
524 },
525 /* uint64 tests */
526 {
527 .description = "uint64_sanity1",
528 .type = PTYPE_U64,
529 .value.u64 = 1,
530 },
531 {
532 .description = "uint64_sanity2",
533 .type = PTYPE_U64,
534 .value.u64 = UINT64_MAX / 2 + 1,
535 },
536 {
537 .description = "uint64_min",
538 .type = PTYPE_U64,
539 .value.u64 = 0,
540 },
541 {
542 .description = "uint64_max",
543 .type = PTYPE_U64,
544 .value.u64 = UINT64_MAX,
545 },
546 /* int8 tests */
547 {
548 .description = "int8_sanity1",
549 .type = PTYPE_S8,
550 .value.s8 = -1,
551 },
552 {
553 .description = "int8_sanity2",
554 .type = PTYPE_S8,
555 .value.s8 = INT8_MAX / 2 + 1,
556 },
557 {
558 .description = "int8_min",
559 .type = PTYPE_S8,
560 .value.s8 = INT8_MIN,
561 },
562 {
563 .description = "int8_max",
564 .type = PTYPE_S8,
565 .value.s8 = INT8_MAX,
566 },
567 /* int16 tests */
568 {
569 .description = "int16_sanity1",
570 .type = PTYPE_S16,
571 .value.s16 = -1,
572 },
573 {
574 .description = "int16_sanity2",
575 .type = PTYPE_S16,
576 .value.s16 = INT16_MAX / 2 + 1,
577 },
578 {
579 .description = "int16_min",
580 .type = PTYPE_S16,
581 .value.s16 = INT16_MIN,
582 },
583 {
584 .description = "int16_max",
585 .type = PTYPE_S16,
586 .value.s16 = INT16_MAX,
587 },
588 /* int32 tests */
589 {
590 .description = "int32_sanity1",
591 .type = PTYPE_S32,
592 .value.s32 = -1,
593 },
594 {
595 .description = "int32_sanity2",
596 .type = PTYPE_S32,
597 .value.s32 = INT32_MAX / 2 + 1,
598 },
599 {
600 .description = "int32_min",
601 .type = PTYPE_S32,
602 .value.s32 = INT32_MIN,
603 },
604 {
605 .description = "int32_max",
606 .type = PTYPE_S32,
607 .value.s32 = INT32_MAX,
608 },
609 /* int64 tests */
610 {
611 .description = "int64_sanity1",
612 .type = PTYPE_S64,
613 .value.s64 = -1,
614 },
615 {
616 .description = "int64_sanity2",
617 .type = PTYPE_S64,
618 .value.s64 = INT64_MAX / 2 + 1,
619 },
620 {
621 .description = "int64_min",
622 .type = PTYPE_S64,
623 .value.s64 = INT64_MIN,
624 },
625 {
626 .description = "int64_max",
627 .type = PTYPE_S64,
628 .value.s64 = INT64_MAX,
629 },
630 { .type = PTYPE_EOL }
631};
632
633/* visitor-specific op implementations */
634
635typedef struct QmpSerializeData {
636 QmpOutputVisitor *qov;
637 QmpInputVisitor *qiv;
638} QmpSerializeData;
639
640static void qmp_serialize(void *native_in, void **datap,
641 VisitorFunc visit, Error **errp)
642{
643 QmpSerializeData *d = g_malloc0(sizeof(*d));
644
645 d->qov = qmp_output_visitor_new();
646 visit(qmp_output_get_visitor(d->qov), &native_in, errp);
647 *datap = d;
648}
649
650static void qmp_deserialize(void **native_out, void *datap,
651 VisitorFunc visit, Error **errp)
652{
653 QmpSerializeData *d = datap;
654 QString *output_json = qobject_to_json(qmp_output_get_qobject(d->qov));
655 QObject *obj = qobject_from_json(qstring_get_str(output_json));
656
657 QDECREF(output_json);
658 d->qiv = qmp_input_visitor_new(obj);
659 visit(qmp_input_get_visitor(d->qiv), native_out, errp);
660}
661
662static void qmp_cleanup(void *datap)
663{
664 QmpSerializeData *d = datap;
665 qmp_output_visitor_cleanup(d->qov);
666 qmp_input_visitor_cleanup(d->qiv);
667}
668
669/* visitor registration, test harness */
670
671/* note: to function interchangeably as a serialization mechanism your
672 * visitor test implementation should pass the test cases for all visitor
673 * capabilities: primitives, structures, and lists
674 */
675static const SerializeOps visitors[] = {
676 {
677 .type = "QMP",
678 .serialize = qmp_serialize,
679 .deserialize = qmp_deserialize,
680 .cleanup = qmp_cleanup,
681 .caps = VCAP_PRIMITIVES | VCAP_STRUCTURES | VCAP_LISTS
682 },
683 { NULL }
684};
685
686static void add_visitor_type(const SerializeOps *ops)
687{
688 char testname_prefix[128];
689 char testname[128];
690 TestArgs *args;
691 int i = 0;
692
693 sprintf(testname_prefix, "/visitor/serialization/%s", ops->type);
694
695 if (ops->caps & VCAP_PRIMITIVES) {
696 while (pt_values[i].type != PTYPE_EOL) {
697 sprintf(testname, "%s/primitives/%s", testname_prefix,
698 pt_values[i].description);
699 args = g_malloc0(sizeof(*args));
700 args->ops = ops;
701 args->test_data = &pt_values[i];
702 g_test_add_data_func(testname, args, test_primitives);
703 i++;
704 }
705 }
706
707 if (ops->caps & VCAP_STRUCTURES) {
708 sprintf(testname, "%s/struct", testname_prefix);
709 args = g_malloc0(sizeof(*args));
710 args->ops = ops;
711 args->test_data = NULL;
712 g_test_add_data_func(testname, args, test_struct);
713
714 sprintf(testname, "%s/nested_struct", testname_prefix);
715 args = g_malloc0(sizeof(*args));
716 args->ops = ops;
717 args->test_data = NULL;
718 g_test_add_data_func(testname, args, test_nested_struct);
719 }
720
721 if (ops->caps & VCAP_LISTS) {
722 sprintf(testname, "%s/nested_struct_list", testname_prefix);
723 args = g_malloc0(sizeof(*args));
724 args->ops = ops;
725 args->test_data = NULL;
726 g_test_add_data_func(testname, args, test_nested_struct_list);
727 }
728}
729
730int main(int argc, char **argv)
731{
732 int i = 0;
733
734 g_test_init(&argc, &argv, NULL);
735
736 while (visitors[i].type != NULL) {
737 add_visitor_type(&visitors[i]);
738 i++;
739 }
740
741 g_test_run();
742
743 return 0;
744}