From patchwork Thu Dec 10 23:53:37 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Blake X-Patchwork-Id: 555392 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 42678140281 for ; Fri, 11 Dec 2015 10:56:39 +1100 (AEDT) Received: from localhost ([::1]:44975 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1a7B4e-0002J4-VA for incoming@patchwork.ozlabs.org; Thu, 10 Dec 2015 18:56:36 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:59176) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1a7B25-0005v4-FO for qemu-devel@nongnu.org; Thu, 10 Dec 2015 18:54:00 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1a7B22-0001sY-OA for qemu-devel@nongnu.org; Thu, 10 Dec 2015 18:53:57 -0500 Received: from mx1.redhat.com ([209.132.183.28]:52088) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1a7B22-0001sJ-Cy for qemu-devel@nongnu.org; Thu, 10 Dec 2015 18:53:54 -0500 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) by mx1.redhat.com (Postfix) with ESMTPS id 0386A67C2B; Thu, 10 Dec 2015 23:53:54 +0000 (UTC) Received: from red.redhat.com (ovpn-113-183.phx2.redhat.com [10.3.113.183]) by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id tBANrirm017109; Thu, 10 Dec 2015 18:53:53 -0500 From: Eric Blake To: qemu-devel@nongnu.org Date: Thu, 10 Dec 2015 16:53:37 -0700 Message-Id: <1449791621-16185-8-git-send-email-eblake@redhat.com> In-Reply-To: <1449791621-16185-1-git-send-email-eblake@redhat.com> References: <1449791621-16185-1-git-send-email-eblake@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.26 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 209.132.183.28 Cc: Markus Armbruster , Michael Roth Subject: [Qemu-devel] [PATCH 07/11] qapi: add json output visitor X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org We have several places that want to go from qapi to JSON; right now, they have to create an intermediate QObject to do the work. That also has the drawback that the JSON formatting of a QDict will rearrange keys (according to a deterministic, but unpredictable, hash), when humans have an easier time if dicts are produced in the same order as the qapi type. For these reasons, it is time to add a new JSON output visitor. This patch just adds the basic visitor and tests that it works; later patches will add pretty-printing, and convert clients to use the visitor. Design choices: Unlike the QMP output visitor, the JSON visitor refuses to visit a required string with a NULL value (abort), as well as a non-finite number (raises an error message). Reusing QString to grow the contents means that we easily share code with both qobject-json.c and qjson.c; although it might be nice to enhance things to take an optional output callback function so that the output can truly be streamed instead of collected in memory. Signed-off-by: Eric Blake --- include/qapi/json-output-visitor.h | 25 +++ qapi/Makefile.objs | 2 +- qapi/json-output-visitor.c | 202 ++++++++++++++++++ tests/.gitignore | 1 + tests/Makefile | 4 + tests/test-json-output-visitor.c | 410 +++++++++++++++++++++++++++++++++++++ 6 files changed, 643 insertions(+), 1 deletion(-) create mode 100644 include/qapi/json-output-visitor.h create mode 100644 qapi/json-output-visitor.c create mode 100644 tests/test-json-output-visitor.c diff --git a/include/qapi/json-output-visitor.h b/include/qapi/json-output-visitor.h new file mode 100644 index 0000000..5be5a13 --- /dev/null +++ b/include/qapi/json-output-visitor.h @@ -0,0 +1,25 @@ +/* + * JSON Output Visitor + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef JSON_OUTPUT_VISITOR_H +#define JSON_OUTPUT_VISITOR_H + +#include "qapi/visitor.h" + +typedef struct JsonOutputVisitor JsonOutputVisitor; + +JsonOutputVisitor *json_output_visitor_new(void); +void json_output_visitor_cleanup(JsonOutputVisitor *v); +void json_output_visitor_reset(JsonOutputVisitor *v); + +char *json_output_get_string(JsonOutputVisitor *v); +Visitor *json_output_get_visitor(JsonOutputVisitor *v); + +#endif diff --git a/qapi/Makefile.objs b/qapi/Makefile.objs index 2278970..b60e11b 100644 --- a/qapi/Makefile.objs +++ b/qapi/Makefile.objs @@ -1,6 +1,6 @@ util-obj-y = qapi-visit-core.o qapi-dealloc-visitor.o qmp-input-visitor.o util-obj-y += qmp-output-visitor.o qmp-registry.o qmp-dispatch.o util-obj-y += string-input-visitor.o string-output-visitor.o -util-obj-y += opts-visitor.o +util-obj-y += opts-visitor.o json-output-visitor.o util-obj-y += qmp-event.o util-obj-y += qapi-util.o diff --git a/qapi/json-output-visitor.c b/qapi/json-output-visitor.c new file mode 100644 index 0000000..0d759c4 --- /dev/null +++ b/qapi/json-output-visitor.c @@ -0,0 +1,202 @@ +/* + * Convert QAPI to JSON + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qapi/json-output-visitor.h" +#include "qapi/visitor-impl.h" +#include "qemu/queue.h" +#include "qemu-common.h" +#include "qapi/qmp/qstring.h" +#include "qapi/qmp/qobject-json.h" + +struct JsonOutputVisitor { + Visitor visitor; + QString *str; + bool comma; + int depth; +}; + +static JsonOutputVisitor *to_jov(Visitor *v) +{ + return container_of(v, JsonOutputVisitor, visitor); +} + +static void json_output_name(JsonOutputVisitor *jov, const char *name) +{ + if (!jov->str) { + jov->str = qstring_new(); + } + if (jov->comma) { + qstring_append(jov->str, ", "); + } else { + jov->comma = true; + } + if (name && jov->depth) { + qstring_append_json_string(jov->str, name); + qstring_append(jov->str, ": "); + } +} + +static bool json_output_start_struct(Visitor *v, void **obj, size_t unused, + const char *name, Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + json_output_name(jov, name); + qstring_append(jov->str, "{"); + jov->comma = false; + jov->depth++; + return false; +} + +static void json_output_end_struct(Visitor *v) +{ + JsonOutputVisitor *jov = to_jov(v); + assert(jov->depth); + jov->depth--; + qstring_append(jov->str, "}"); + jov->comma = true; +} + +static bool json_output_start_list(Visitor *v, GenericList **listp, + size_t size, const char *name, Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + json_output_name(jov, name); + qstring_append(jov->str, "["); + jov->comma = false; + jov->depth++; + return false; +} + +static GenericList *json_output_next_list(Visitor *v, GenericList *list, + size_t size, Error **errp) +{ + return list->next; +} + +static void json_output_end_list(Visitor *v) +{ + JsonOutputVisitor *jov = to_jov(v); + assert(jov->depth); + jov->depth--; + qstring_append(jov->str, "]"); + jov->comma = true; +} + +static void json_output_type_int64(Visitor *v, int64_t *obj, const char *name, + Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + json_output_name(jov, name); + qstring_append_format(jov->str, "%" PRId64, *obj); +} + +static void json_output_type_uint64(Visitor *v, uint64_t *obj, + const char *name, Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + json_output_name(jov, name); + qstring_append_format(jov->str, "%" PRIu64, *obj); +} + +static void json_output_type_bool(Visitor *v, bool *obj, const char *name, + Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + json_output_name(jov, name); + qstring_append(jov->str, *obj ? "true" : "false"); +} + +static void json_output_type_str(Visitor *v, char **obj, const char *name, + Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + assert(*obj); + json_output_name(jov, name); + qstring_append_json_string(jov->str, *obj); +} + +static void json_output_type_number(Visitor *v, double *obj, const char *name, + Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + json_output_name(jov, name); + qstring_append_json_number(jov->str, *obj, errp); +} + +static void json_output_type_any(Visitor *v, QObject **obj, const char *name, + Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + QString *str = qobject_to_json(*obj); + assert(str); + json_output_name(jov, name); + qstring_append(jov->str, qstring_get_str(str)); + QDECREF(str); +} + +static void json_output_type_null(Visitor *v, const char *name, Error **errp) +{ + JsonOutputVisitor *jov = to_jov(v); + json_output_name(jov, name); + qstring_append(jov->str, "null"); +} + +/* Finish building, and return the resulting string. Will not be NULL. */ +char *json_output_get_string(JsonOutputVisitor *jov) +{ + char *result; + + assert(!jov->depth); + result = g_strdup(qstring_get_str(jov->str)); + json_output_visitor_reset(jov); + return result; +} + +Visitor *json_output_get_visitor(JsonOutputVisitor *v) +{ + return &v->visitor; +} + +void json_output_visitor_reset(JsonOutputVisitor *v) +{ + QDECREF(v->str); + v->str = NULL; + v->depth = 0; + v->comma = false; +} + +void json_output_visitor_cleanup(JsonOutputVisitor *v) +{ + json_output_visitor_reset(v); + g_free(v); +} + +JsonOutputVisitor *json_output_visitor_new(void) +{ + JsonOutputVisitor *v; + + v = g_malloc0(sizeof(*v)); + + v->visitor.start_struct = json_output_start_struct; + v->visitor.end_struct = json_output_end_struct; + v->visitor.start_list = json_output_start_list; + v->visitor.next_list = json_output_next_list; + v->visitor.end_list = json_output_end_list; + v->visitor.type_enum = output_type_enum; + v->visitor.type_int64 = json_output_type_int64; + v->visitor.type_uint64 = json_output_type_uint64; + v->visitor.type_bool = json_output_type_bool; + v->visitor.type_str = json_output_type_str; + v->visitor.type_number = json_output_type_number; + v->visitor.type_any = json_output_type_any; + v->visitor.type_null = json_output_type_null; + + return v; +} diff --git a/tests/.gitignore b/tests/.gitignore index 6d3416d..b4f371c 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -24,6 +24,7 @@ test-cutils test-hbitmap test-int128 test-iov +test-json-output-visitor test-mul64 test-opts-visitor test-qapi-event.[ch] diff --git a/tests/Makefile b/tests/Makefile index 17d74e2..f617e2d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -20,6 +20,8 @@ check-unit-y += tests/check-qobject-json$(EXESUF) gcov-files-check-qobject-json-y = qobject/qobject-json.c check-unit-y += tests/test-qmp-output-visitor$(EXESUF) gcov-files-test-qmp-output-visitor-y = qapi/qmp-output-visitor.c +check-unit-y += tests/test-json-output-visitor$(EXESUF) +gcov-files-test-json-output-visitor-y = qapi/json-output-visitor.c check-unit-y += tests/test-qmp-input-visitor$(EXESUF) gcov-files-test-qmp-input-visitor-y = qapi/qmp-input-visitor.c check-unit-y += tests/test-qmp-input-strict$(EXESUF) @@ -363,6 +365,7 @@ test-obj-y = tests/check-qint.o tests/check-qstring.o tests/check-qdict.o \ tests/check-qlist.o tests/check-qfloat.o tests/check-qobject-json.o \ tests/test-coroutine.o tests/test-string-output-visitor.o \ tests/test-string-input-visitor.o tests/test-qmp-output-visitor.o \ + tests/test-json-output-visitor.o \ tests/test-qmp-input-visitor.o tests/test-qmp-input-strict.o \ tests/test-qmp-commands.o tests/test-visitor-serialization.o \ tests/test-x86-cpuid.o tests/test-mul64.o tests/test-int128.o \ @@ -447,6 +450,7 @@ tests/test-string-output-visitor$(EXESUF): tests/test-string-output-visitor.o $( tests/test-string-input-visitor$(EXESUF): tests/test-string-input-visitor.o $(test-qapi-obj-y) tests/test-qmp-event$(EXESUF): tests/test-qmp-event.o $(test-qapi-obj-y) tests/test-qmp-output-visitor$(EXESUF): tests/test-qmp-output-visitor.o $(test-qapi-obj-y) +tests/test-json-output-visitor$(EXESUF): tests/test-json-output-visitor.o $(test-qapi-obj-y) tests/test-qmp-input-visitor$(EXESUF): tests/test-qmp-input-visitor.o $(test-qapi-obj-y) tests/test-qmp-input-strict$(EXESUF): tests/test-qmp-input-strict.o $(test-qapi-obj-y) tests/test-qmp-commands$(EXESUF): tests/test-qmp-commands.o tests/test-qmp-marshal.o $(test-qapi-obj-y) diff --git a/tests/test-json-output-visitor.c b/tests/test-json-output-visitor.c new file mode 100644 index 0000000..e47571a --- /dev/null +++ b/tests/test-json-output-visitor.c @@ -0,0 +1,410 @@ +/* + * JSON Output Visitor unit-tests. + * + * Copyright (C) 2011, 2015 Red Hat Inc. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include + +#include "qemu-common.h" +#include "qapi/json-output-visitor.h" +#include "test-qapi-types.h" +#include "test-qapi-visit.h" +#include "qapi/qmp/types.h" + +typedef struct TestOutputVisitorData { + JsonOutputVisitor *jov; + Visitor *ov; +} TestOutputVisitorData; + +static void visitor_output_setup(TestOutputVisitorData *data, + const void *unused) +{ + data->jov = json_output_visitor_new(); + g_assert(data->jov); + + data->ov = json_output_get_visitor(data->jov); + g_assert(data->ov); +} + +static void visitor_output_teardown(TestOutputVisitorData *data, + const void *unused) +{ + json_output_visitor_cleanup(data->jov); + data->jov = NULL; + data->ov = NULL; +} + +static void test_visitor_out_int(TestOutputVisitorData *data, + const void *unused) +{ + int64_t value = -42; + char *out; + + visit_type_int(data->ov, &value, NULL, &error_abort); + + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, "-42"); + g_free(out); +} + +static void test_visitor_out_bool(TestOutputVisitorData *data, + const void *unused) +{ + bool value = true; + char *out; + + visit_type_bool(data->ov, &value, NULL, &error_abort); + + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, "true"); + g_free(out); +} + +static void test_visitor_out_number(TestOutputVisitorData *data, + const void *unused) +{ + double value = 3.14; + char *out; + + visit_type_number(data->ov, &value, NULL, &error_abort); + + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, "3.14"); + g_free(out); +} + +static void test_visitor_out_string(TestOutputVisitorData *data, + const void *unused) +{ + char *string = (char *) "Q E M U"; + char *out; + + visit_type_str(data->ov, &string, NULL, &error_abort); + + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, "\"Q E M U\""); + g_free(out); +} + +static void test_visitor_out_enum(TestOutputVisitorData *data, + const void *unused) +{ + char *out; + char *tmp; + EnumOne i; + size_t len; + + for (i = 0; i < ENUM_ONE__MAX; i++) { + visit_type_EnumOne(data->ov, &i, "unused", &error_abort); + + out = json_output_get_string(data->jov); + g_assert(*out == '"'); + len = strlen(out); + g_assert(out[len - 1] == '"'); + tmp = out + 1; + out[len - 1] = 0; + g_assert_cmpstr(tmp, ==, EnumOne_lookup[i]); + g_free(out); + } +} + +static void test_visitor_out_enum_errors(TestOutputVisitorData *data, + const void *unused) +{ + EnumOne i, bad_values[] = { ENUM_ONE__MAX, -1 }; + Error *err; + + for (i = 0; i < ARRAY_SIZE(bad_values) ; i++) { + err = NULL; + visit_type_EnumOne(data->ov, &bad_values[i], "unused", &err); + g_assert(err); + error_free(err); + } +} + + +static void test_visitor_out_struct(TestOutputVisitorData *data, + const void *unused) +{ + TestStruct test_struct = { .integer = 42, + .boolean = false, + .string = (char *) "foo"}; + TestStruct *p = &test_struct; + char *out; + + visit_type_TestStruct(data->ov, &p, NULL, &error_abort); + + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, + "{" + "\"integer\": 42, " + "\"boolean\": false, " + "\"string\": \"foo\"" + "}"); + g_free(out); +} + +static void test_visitor_out_struct_nested(TestOutputVisitorData *data, + const void *unused) +{ + int64_t value = 42; + UserDefTwo *ud2; + const char *string = "user def string"; + const char *strings[] = { "forty two", "forty three", "forty four", + "forty five" }; + char *out; + + ud2 = g_malloc0(sizeof(*ud2)); + ud2->string0 = g_strdup(strings[0]); + + ud2->dict1 = g_malloc0(sizeof(*ud2->dict1)); + ud2->dict1->string1 = g_strdup(strings[1]); + + ud2->dict1->dict2 = g_malloc0(sizeof(*ud2->dict1->dict2)); + ud2->dict1->dict2->userdef = g_new0(UserDefOne, 1); + ud2->dict1->dict2->userdef->string = g_strdup(string); + ud2->dict1->dict2->userdef->integer = value; + ud2->dict1->dict2->string = g_strdup(strings[2]); + + ud2->dict1->dict3 = g_malloc0(sizeof(*ud2->dict1->dict3)); + ud2->dict1->has_dict3 = true; + ud2->dict1->dict3->userdef = g_new0(UserDefOne, 1); + ud2->dict1->dict3->userdef->string = g_strdup(string); + ud2->dict1->dict3->userdef->integer = value; + ud2->dict1->dict3->string = g_strdup(strings[3]); + + visit_type_UserDefTwo(data->ov, &ud2, "unused", &error_abort); + + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, + "{" + "\"string0\": \"forty two\", " + "\"dict1\": {" + "\"string1\": \"forty three\", " + "\"dict2\": {" + "\"userdef\": {" + "\"integer\": 42, " + "\"string\": \"user def string\"" + "}, " + "\"string\": \"forty four\"" + "}, " + "\"dict3\": {" + "\"userdef\": {" + "\"integer\": 42, " + "\"string\": \"user def string\"" + "}, " + "\"string\": \"forty five\"" + "}" + "}" + "}"); + qapi_free_UserDefTwo(ud2); + g_free(out); +} + +static void test_visitor_out_struct_errors(TestOutputVisitorData *data, + const void *unused) +{ + EnumOne bad_values[] = { ENUM_ONE__MAX, -1 }; + UserDefOne u = { .string = (char *)"" }; + UserDefOne *pu = &u; + Error *err; + int i; + + for (i = 0; i < ARRAY_SIZE(bad_values) ; i++) { + err = NULL; + u.has_enum1 = true; + u.enum1 = bad_values[i]; + visit_type_UserDefOne(data->ov, &pu, "unused", &err); + g_assert(err); + error_free(err); + json_output_visitor_reset(data->jov); + } +} + + +static void test_visitor_out_list(TestOutputVisitorData *data, + const void *unused) +{ + const char *value_str = "list value"; + TestStructList *p, *head = NULL; + const int max_items = 10; + bool value_bool = true; + int value_int = 10; + int i; + char *out; + + for (i = 0; i < max_items; i++) { + p = g_malloc0(sizeof(*p)); + p->value = g_malloc0(sizeof(*p->value)); + p->value->integer = value_int + (max_items - i - 1); + p->value->boolean = value_bool; + p->value->string = g_strdup(value_str); + + p->next = head; + head = p; + } + + visit_type_TestStructList(data->ov, &head, NULL, &error_abort); + + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, + "[" + "{\"integer\": 10, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 11, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 12, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 13, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 14, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 15, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 16, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 17, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 18, \"boolean\": true, " + "\"string\": \"list value\"}, " + "{\"integer\": 19, \"boolean\": true, " + "\"string\": \"list value\"}" + "]"); + qapi_free_TestStructList(head); + g_free(out); +} + +static void test_visitor_out_any(TestOutputVisitorData *data, + const void *unused) +{ + QObject *qobj; + QDict *qdict; + char *out; + + qobj = QOBJECT(qint_from_int(-42)); + visit_type_any(data->ov, &qobj, NULL, &error_abort); + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, "-42"); + qobject_decref(qobj); + g_free(out); + + qdict = qdict_new(); + qdict_put(qdict, "integer", qint_from_int(-42)); + qdict_put(qdict, "boolean", qbool_from_bool(true)); + qdict_put(qdict, "string", qstring_from_str("foo")); + qobj = QOBJECT(qdict); + visit_type_any(data->ov, &qobj, NULL, &error_abort); + qobject_decref(qobj); + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, + "{" + "\"integer\": -42, " + "\"boolean\": true, " + "\"string\": \"foo\"" + "}"); + g_free(out); +} + +static void test_visitor_out_union_flat(TestOutputVisitorData *data, + const void *unused) +{ + char *out; + UserDefFlatUnion *tmp = g_malloc0(sizeof(UserDefFlatUnion)); + + tmp->enum1 = ENUM_ONE_VALUE1; + tmp->string = g_strdup("str"); + tmp->u.value1 = g_malloc0(sizeof(UserDefA)); + tmp->integer = 41; + tmp->u.value1->boolean = true; + + visit_type_UserDefFlatUnion(data->ov, &tmp, NULL, &error_abort); + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, + "{" + "\"integer\": 41, " + "\"string\": \"str\", " + "\"enum1\": \"value1\", " + "\"boolean\": true" + "}"); + g_free(out); + qapi_free_UserDefFlatUnion(tmp); +} + +static void test_visitor_out_alternate(TestOutputVisitorData *data, + const void *unused) +{ + UserDefAlternate *tmp; + char *out; + + tmp = g_new0(UserDefAlternate, 1); + tmp->type = QTYPE_QINT; + tmp->u.i = 42; + + visit_type_UserDefAlternate(data->ov, &tmp, NULL, &error_abort); + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, "42"); + g_free(out); + qapi_free_UserDefAlternate(tmp); + + tmp = g_new0(UserDefAlternate, 1); + tmp->type = QTYPE_QSTRING; + tmp->u.s = g_strdup("hello"); + + visit_type_UserDefAlternate(data->ov, &tmp, NULL, &error_abort); + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, "\"hello\""); + g_free(out); + qapi_free_UserDefAlternate(tmp); +} + +static void test_visitor_out_empty(TestOutputVisitorData *data, + const void *unused) +{ + char *out; + + visit_type_null(data->ov, NULL, &error_abort); + out = json_output_get_string(data->jov); + g_assert_cmpstr(out, ==, "null"); + g_free(out); +} + +static void output_visitor_test_add(const char *testpath, + void (*test_func)(TestOutputVisitorData *, + const void *)) +{ + g_test_add(testpath, TestOutputVisitorData, NULL, visitor_output_setup, + test_func, visitor_output_teardown); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + output_visitor_test_add("/visitor/json/int", test_visitor_out_int); + output_visitor_test_add("/visitor/json/bool", test_visitor_out_bool); + output_visitor_test_add("/visitor/json/number", test_visitor_out_number); + output_visitor_test_add("/visitor/json/string", test_visitor_out_string); + output_visitor_test_add("/visitor/json/enum", test_visitor_out_enum); + output_visitor_test_add("/visitor/json/enum-errors", + test_visitor_out_enum_errors); + output_visitor_test_add("/visitor/json/struct", test_visitor_out_struct); + output_visitor_test_add("/visitor/json/struct-nested", + test_visitor_out_struct_nested); + output_visitor_test_add("/visitor/json/struct-errors", + test_visitor_out_struct_errors); + output_visitor_test_add("/visitor/json/list", test_visitor_out_list); + output_visitor_test_add("/visitor/json/any", test_visitor_out_any); + output_visitor_test_add("/visitor/json/union-flat", + test_visitor_out_union_flat); + output_visitor_test_add("/visitor/json/alternate", + test_visitor_out_alternate); + output_visitor_test_add("/visitor/json/empty", test_visitor_out_empty); + + g_test_run(); + + return 0; +}