[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v4 24/28] qapi: Add JSON output visitor
From: |
Eric Blake |
Subject: |
[Qemu-devel] [PATCH v4 24/28] qapi: Add JSON output visitor |
Date: |
Wed, 18 May 2016 22:41:10 -0600 |
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, support for visit_type_any(),
and conversion of 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, via abort().
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 <address@hidden>
---
v4: retitle, split off inf/NaN handling, rebase to visit_complete
and visit_free changes, defer type_any, address other findings from
Markus
v3: retitle, rebase to master, minor cleanups
v2: rebase to qapi subset E v8; add test of error outputting
infinity; use unsigned depth
---
include/qapi/visitor.h | 33 ++--
include/qapi/json-output-visitor.h | 28 +++
qapi/json-output-visitor.c | 193 +++++++++++++++++++
tests/test-json-output-visitor.c | 383 +++++++++++++++++++++++++++++++++++++
tests/test-qmp-output-visitor.c | 5 +
qapi/Makefile.objs | 2 +-
tests/.gitignore | 1 +
tests/Makefile | 5 +-
8 files changed, 632 insertions(+), 18 deletions(-)
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/visitor.h b/include/qapi/visitor.h
index 3f46921..c097507 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -26,17 +26,17 @@
*
* There are four kinds of visitor classes: input visitors (QMP,
* string, and QemuOpts) parse an external representation and build
- * the corresponding QAPI graph, output visitors (QMP and string) take
- * a completed QAPI graph and generate an external representation, the
- * dealloc visitor can take a QAPI graph (possibly partially
- * constructed) and recursively free its resources, and the clone
- * visitor performs a deep clone of one QAPI object to another. While
- * the dealloc and QMP input/output visitors are general, the string,
- * QemuOpts, and clone visitors have some implementation limitations;
- * see the documentation for each visitor for more details on what it
- * supports. Also, see visitor-impl.h for the callback contracts
- * implemented by each visitor, and docs/qapi-code-gen.txt for more
- * about the QAPI code generator.
+ * the corresponding QAPI graph, output visitors (QMP, string, and
+ * JSON) take a completed QAPI graph and generate an external
+ * representation, the dealloc visitor can take a QAPI graph (possibly
+ * partially constructed) and recursively free its resources, and the
+ * clone visitor performs a deep clone of one QAPI object to another.
+ * While the dealloc, JSON output, and QMP input/output visitors are
+ * general, the string, QemuOpts, and clone visitors have some
+ * implementation limitations; see the documentation for each visitor
+ * for more details on what it supports. Also, see visitor-impl.h for
+ * the callback contracts implemented by each visitor, and
+ * docs/qapi-code-gen.txt for more about the QAPI code generator.
*
* All of the visitors are created via:
*
@@ -59,11 +59,12 @@
* visitors are declared here; the remaining visitors are generated in
* qapi-visit.h.
*
- * The @name parameter of visit_type_FOO() describes the relation
- * between this QAPI value and its parent container. When visiting
- * the root of a tree, @name is ignored; when visiting a member of an
- * object, @name is the key associated with the value; and when
- * visiting a member of a list, @name is NULL.
+ * The @name parameter of visit_type_FOO() and visit_start_OBJECT()
+ * describes the relation between this QAPI value and its parent
+ * container. When visiting the root of a tree, @name is ignored;
+ * when visiting a member of an object, @name is the key associated
+ * with the value; and when visiting a member of a list, @name is
+ * NULL.
*
* FIXME: Clients must pass NULL for @name when visiting a member of a
* list, but this leads to poor error messages; it might be nicer to
diff --git a/include/qapi/json-output-visitor.h
b/include/qapi/json-output-visitor.h
new file mode 100644
index 0000000..41c79f4
--- /dev/null
+++ b/include/qapi/json-output-visitor.h
@@ -0,0 +1,28 @@
+/*
+ * JSON Output Visitor
+ *
+ * Copyright (C) 2015-2016 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.
+ *
+ */
+
+#ifndef JSON_OUTPUT_VISITOR_H
+#define JSON_OUTPUT_VISITOR_H
+
+#include "qapi/visitor.h"
+
+typedef struct JsonOutputVisitor JsonOutputVisitor;
+
+/*
+ * Create a new JSON output visitor.
+ *
+ * If everything else succeeds, pass @result to visit_complete() to
+ * collect the result of the visit.
+ *
+ * For now, this cannot be used to visit the 'any' type.
+ */
+Visitor *json_output_visitor_new(char **result);
+
+#endif
diff --git a/qapi/json-output-visitor.c b/qapi/json-output-visitor.c
new file mode 100644
index 0000000..7010ff8
--- /dev/null
+++ b/qapi/json-output-visitor.c
@@ -0,0 +1,193 @@
+/*
+ * Convert QAPI to JSON
+ *
+ * Copyright (C) 2015-2016 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 "qemu/osdep.h"
+#include "qapi/json-output-visitor.h"
+#include "qapi/visitor-impl.h"
+#include "qapi/qmp/qstring.h"
+#include "qapi/qmp/qobject-json.h"
+
+struct JsonOutputVisitor {
+ Visitor visitor;
+ QString *str;
+ bool comma;
+ unsigned int depth;
+ char **result;
+};
+
+static JsonOutputVisitor *to_jov(Visitor *v)
+{
+ return container_of(v, JsonOutputVisitor, visitor);
+}
+
+static void json_output_name(JsonOutputVisitor *jov, const char *name)
+{
+ 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 void json_output_start_struct(Visitor *v, const char *name, void **obj,
+ size_t unused, Error **errp)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ json_output_name(jov, name);
+ qstring_append(jov->str, "{");
+ jov->comma = false;
+ jov->depth++;
+}
+
+static void json_output_end_struct(Visitor *v, void **obj)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ assert(jov->depth);
+ jov->depth--;
+ qstring_append(jov->str, "}");
+ jov->comma = true;
+}
+
+static void json_output_start_list(Visitor *v, const char *name,
+ GenericList **listp, size_t size,
+ Error **errp)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ json_output_name(jov, name);
+ qstring_append(jov->str, "[");
+ jov->comma = false;
+ jov->depth++;
+}
+
+static GenericList *json_output_next_list(Visitor *v, GenericList *tail,
+ size_t size)
+{
+ return tail->next;
+}
+
+static void json_output_end_list(Visitor *v, void **obj)
+{
+ 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, const char *name, int64_t *obj,
+ Error **errp)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ json_output_name(jov, name);
+ qstring_append_printf(jov->str, "%" PRId64, *obj);
+}
+
+static void json_output_type_uint64(Visitor *v, const char *name,
+ uint64_t *obj, Error **errp)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ json_output_name(jov, name);
+ qstring_append_printf(jov->str, "%" PRIu64, *obj);
+}
+
+static void json_output_type_bool(Visitor *v, const char *name, bool *obj,
+ 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, const char *name, char **obj,
+ Error **errp)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ assert(*obj);
+ json_output_name(jov, name);
+ /* FIXME: report invalid UTF-8 encoding */
+ qstring_append_json_string(jov->str, *obj);
+}
+
+static void json_output_type_number(Visitor *v, const char *name, double *obj,
+ Error **errp)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ json_output_name(jov, name);
+ /* FIXME: report Inf/NaN problems */
+ qstring_append_json_number(jov->str, *obj);
+}
+
+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");
+}
+
+static void json_output_complete(Visitor *v, void *result)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ assert(!jov->depth);
+ assert(qstring_get_length(jov->str));
+ assert(jov->result == result);
+ *jov->result = qstring_consume_str(jov->str);
+ jov->str = NULL;
+ jov->result = NULL;
+}
+
+static void json_output_free(Visitor *v)
+{
+ JsonOutputVisitor *jov = to_jov(v);
+
+ QDECREF(jov->str);
+ g_free(jov);
+}
+
+Visitor *json_output_visitor_new(char **result)
+{
+ JsonOutputVisitor *v;
+
+ v = g_malloc0(sizeof(*v));
+ v->result = result;
+ *result = NULL;
+ v->str = qstring_new();
+
+ v->visitor.type = VISITOR_OUTPUT;
+ 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_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_null = json_output_type_null;
+ v->visitor.complete = json_output_complete;
+ v->visitor.free = json_output_free;
+
+ return &v->visitor;
+}
diff --git a/tests/test-json-output-visitor.c b/tests/test-json-output-visitor.c
new file mode 100644
index 0000000..3c77a61
--- /dev/null
+++ b/tests/test-json-output-visitor.c
@@ -0,0 +1,383 @@
+/*
+ * JSON Output Visitor unit-tests.
+ *
+ * Copyright (C) 2015-2016 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.
+ */
+
+/*
+ * Similar in design to test-qmp-output-visitor; if you add tests
+ * here, consider adding tests there as well.
+ */
+
+#include "qemu/osdep.h"
+#include <glib.h>
+
+#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"
+#include "qapi/error.h"
+
+typedef struct TestOutputVisitorData {
+ Visitor *ov;
+ char *str;
+} TestOutputVisitorData;
+
+static void visitor_output_setup(TestOutputVisitorData *data,
+ const void *unused)
+{
+ data->ov = json_output_visitor_new(&data->str);
+ g_assert(data->ov);
+}
+
+static void visitor_output_teardown(TestOutputVisitorData *data,
+ const void *unused)
+{
+ visit_free(data->ov);
+ data->ov = NULL;
+ g_free(data->str);
+ data->str = NULL;
+}
+
+static const char *visitor_get(TestOutputVisitorData *data)
+{
+ visit_complete(data->ov, &data->str);
+ g_assert(data->str);
+ return data->str;
+}
+
+static void visitor_reset(TestOutputVisitorData *data)
+{
+ visitor_output_teardown(data, NULL);
+ visitor_output_setup(data, NULL);
+}
+
+static void test_visitor_out_int(TestOutputVisitorData *data,
+ const void *unused)
+{
+ int64_t value = -42;
+ const char *out;
+
+ visit_type_int(data->ov, NULL, &value, &error_abort);
+
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==, "-42");
+}
+
+static void test_visitor_out_bool(TestOutputVisitorData *data,
+ const void *unused)
+{
+ bool value = true;
+ const char *out;
+
+ visit_type_bool(data->ov, NULL, &value, &error_abort);
+
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==, "true");
+}
+
+static void test_visitor_out_number(TestOutputVisitorData *data,
+ const void *unused)
+{
+ double value = 3.14;
+ const char *out;
+
+ visit_type_number(data->ov, NULL, &value, &error_abort);
+
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==, "3.14");
+
+ /* FIXME: JSON requires finite numbers */
+}
+
+static void test_visitor_out_string(TestOutputVisitorData *data,
+ const void *unused)
+{
+ char *string = (char *) "Q E M U";
+ const char *out;
+
+ visit_type_str(data->ov, NULL, &string, &error_abort);
+
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==, "\"Q E M U\"");
+}
+
+static void test_visitor_out_enum(TestOutputVisitorData *data,
+ const void *unused)
+{
+ const char *out;
+ EnumOne i;
+ size_t len;
+
+ for (i = 0; i < ENUM_ONE__MAX; i++) {
+ visit_type_EnumOne(data->ov, "unused", &i, &error_abort);
+
+ out = visitor_get(data);
+ g_assert(*out == '"');
+ len = strlen(out);
+ g_assert_cmpint(len, >, 2);
+ g_assert(out[len - 1] == '"');
+ g_assert_cmpint(memcmp(out + 1, EnumOne_lookup[i], len - 2), ==, 0);
+ visitor_reset(data);
+ }
+}
+
+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, "unused", &bad_values[i], &err);
+ error_free_or_abort(&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;
+ const char *out;
+
+ visit_type_TestStruct(data->ov, NULL, &p, &error_abort);
+
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==,
+ "{"
+ "\"integer\": 42, "
+ "\"boolean\": false, "
+ "\"string\": \"foo\""
+ "}");
+}
+
+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" };
+ const 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, "unused", &ud2, &error_abort);
+
+ out = visitor_get(data);
+ 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);
+}
+
+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, "unused", &pu, &err);
+ error_free_or_abort(&err);
+ visitor_reset(data);
+ }
+}
+
+
+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;
+ const 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, NULL, &head, &error_abort);
+
+ out = visitor_get(data);
+ 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);
+}
+
+static void test_visitor_out_union_flat(TestOutputVisitorData *data,
+ const void *unused)
+{
+ const char *out;
+ UserDefFlatUnion *tmp = g_malloc0(sizeof(UserDefFlatUnion));
+
+ tmp->enum1 = ENUM_ONE_VALUE1;
+ tmp->string = g_strdup("str");
+ tmp->integer = 41;
+ tmp->u.value1.boolean = true;
+
+ visit_type_UserDefFlatUnion(data->ov, NULL, &tmp, &error_abort);
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==,
+ "{"
+ "\"integer\": 41, "
+ "\"string\": \"str\", "
+ "\"enum1\": \"value1\", "
+ "\"boolean\": true"
+ "}");
+ qapi_free_UserDefFlatUnion(tmp);
+}
+
+static void test_visitor_out_alternate(TestOutputVisitorData *data,
+ const void *unused)
+{
+ UserDefAlternate *tmp;
+ const char *out;
+
+ tmp = g_new0(UserDefAlternate, 1);
+ tmp->type = QTYPE_QINT;
+ tmp->u.i = 42;
+
+ visit_type_UserDefAlternate(data->ov, NULL, &tmp, &error_abort);
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==, "42");
+ qapi_free_UserDefAlternate(tmp);
+
+ visitor_reset(data);
+ tmp = g_new0(UserDefAlternate, 1);
+ tmp->type = QTYPE_QSTRING;
+ tmp->u.s = g_strdup("hello");
+
+ visit_type_UserDefAlternate(data->ov, NULL, &tmp, &error_abort);
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==, "\"hello\"");
+ qapi_free_UserDefAlternate(tmp);
+}
+
+static void test_visitor_out_null(TestOutputVisitorData *data,
+ const void *unused)
+{
+ const char *out;
+
+ visit_type_null(data->ov, NULL, &error_abort);
+ out = visitor_get(data);
+ g_assert_cmpstr(out, ==, "null");
+}
+
+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/union-flat",
+ test_visitor_out_union_flat);
+ output_visitor_test_add("/visitor/json/alternate",
+ test_visitor_out_alternate);
+ output_visitor_test_add("/visitor/json/null", test_visitor_out_null);
+
+ g_test_run();
+
+ return 0;
+}
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index df8a495..0db0b19 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -10,6 +10,11 @@
* See the COPYING file in the top-level directory.
*/
+/*
+ * Similar in design to test-json-output-visitor; if you add tests
+ * here, consider adding tests there as well.
+ */
+
#include "qemu/osdep.h"
#include <glib.h>
diff --git a/qapi/Makefile.objs b/qapi/Makefile.objs
index 7ea4aeb..0a08492 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 qapi-clone-visitor.o
+util-obj-y += opts-visitor.o qapi-clone-visitor.o json-output-visitor.o
util-obj-y += qmp-event.o
util-obj-y += qapi-util.o
diff --git a/tests/.gitignore b/tests/.gitignore
index 346d75d..60ff7cc 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -41,6 +41,7 @@ test-io-channel-file.txt
test-io-channel-socket
test-io-channel-tls
test-io-task
+test-json-output-visitor
test-logging
test-mul64
test-opts-visitor
diff --git a/tests/Makefile b/tests/Makefile
index d596493..0d06406 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -24,6 +24,8 @@ 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-clone-visitor$(EXESUF)
gcov-files-test-clone-visitor-y = qapi/qapi-clone-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)
@@ -390,7 +392,7 @@ test-obj-y = tests/check-qint.o tests/check-qstring.o
tests/check-qdict.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-clone-visitor.o \
+ tests/test-clone-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 \
@@ -482,6 +484,7 @@ tests/test-string-input-visitor$(EXESUF):
tests/test-string-input-visitor.o $(te
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-clone-visitor$(EXESUF): tests/test-clone-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)
--
2.5.5
- [Qemu-devel] [PATCH v4 14/28] sockets: Use new QAPI cloning, (continued)
- [Qemu-devel] [PATCH v4 14/28] sockets: Use new QAPI cloning, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 16/28] qapi: Factor out JSON string escaping, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 17/28] qapi: Factor out JSON number formatting, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 23/28] tests: Test qobject_to_json() pretty formatting, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 13/28] qapi: Add new clone visitor, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 18/28] qapi: Add qstring_append_printf(), Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 25/28] qapi: Support pretty printing in JSON output visitor, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 26/28] qobject: Implement qobject_to_json() atop JSON visitor, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 27/28] qapi: Add 'any' support to JSON output, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 20/28] qstring: Add qstring_consume_str(), Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 24/28] qapi: Add JSON output visitor,
Eric Blake <=
- [Qemu-devel] [PATCH v4 22/28] qobject: Consolidate qobject_to_json() calls, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 28/28] qemu-img: Use new JSON output formatter, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 19/28] qapi: Use qstring_append_chr() where appropriate, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 12/28] qapi: Add new visit_complete() function, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 21/28] qstring: Add qstring_wrap_str(), Eric Blake, 2016/05/19
- Re: [Qemu-devel] [PATCH v4 00/28] Add qapi-to-JSON and clone visitors, Eric Blake, 2016/05/19
- [Qemu-devel] [PATCH v4 29/28] qapi: Add strict mode to JSON output visitor, Eric Blake, 2016/05/19
- Re: [Qemu-devel] [PATCH v4 00/28] Add qapi-to-JSON and clone visitors, Markus Armbruster, 2016/05/19