Merge remote-tracking branch 'qmp/queue/qmp' into staging
* qmp/queue/qmp: qmp: document strict parsing qmp: parse commands in strict mode qmp: add and use q type specifier qapi: add strict mode to input visitor qapi: place outermost object on qiv stack qapi: untangle next_list qapi: allow freeing partially-allocated objects qapi: shortcut visits on errors qapi: fix memory leak on error qapi: fail hard on stack imbalance qapi: add a test case for type errors qapi: add struct-errors test case to test-qmp-output-visitor qapi: fix double free in qmp_output_visitor_cleanup()
This commit is contained in:
commit
c76d1a9b3f
|
@ -209,13 +209,27 @@ incompatible way are disabled by default and will be advertised by the
|
|||
capabilities array (section '2.2 Server Greeting'). Thus, Clients can check
|
||||
that array and enable the capabilities they support.
|
||||
|
||||
Additionally, Clients must not assume any particular:
|
||||
The QMP Server performs a type check on the arguments to a command. It
|
||||
generates an error if a value does not have the expected type for its
|
||||
key, or if it does not understand a key that the Client included. The
|
||||
strictness of the Server catches wrong assumptions of Clients about
|
||||
the Server's schema. Clients can assume that, when such validation
|
||||
errors occur, they will be reported before the command generated any
|
||||
side effect.
|
||||
|
||||
- Size of json-objects or length of json-arrays
|
||||
However, Clients must not assume any particular:
|
||||
|
||||
- Length of json-arrays
|
||||
- Size of json-objects; in particular, future versions of QEMU may add
|
||||
new keys and Clients should be able to ignore them.
|
||||
- Order of json-object members or json-array elements
|
||||
- Amount of errors generated by a command, that is, new errors can be added
|
||||
to any existing command in newer versions of the Server
|
||||
|
||||
Of course, the Server does guarantee to send valid JSON. But apart from
|
||||
this, a Client should be "conservative in what they send, and liberal in
|
||||
what they accept".
|
||||
|
||||
6. Downstream extension of QMP
|
||||
------------------------------
|
||||
|
||||
|
|
|
@ -194,11 +194,11 @@ Example:
|
|||
|
||||
void visit_type_UserDefOneList(Visitor *m, UserDefOneList ** obj, const char *name, Error **errp)
|
||||
{
|
||||
GenericList *i;
|
||||
GenericList *i, **prev = (GenericList **)obj;
|
||||
|
||||
visit_start_list(m, name, errp);
|
||||
|
||||
for (i = visit_next_list(m, (GenericList **)obj, errp); i; i = visit_next_list(m, &i, errp)) {
|
||||
for (; (i = visit_next_list(m, prev, errp)) != NULL; prev = &i) {
|
||||
UserDefOneList *native_i = (UserDefOneList *)i;
|
||||
visit_type_UserDefOne(m, &native_i->value, NULL, errp);
|
||||
}
|
||||
|
|
|
@ -4157,6 +4157,9 @@ static int check_client_args_type(const QDict *client_args,
|
|||
case 'O':
|
||||
assert(flags & QMP_ACCEPT_UNKNOWNS);
|
||||
break;
|
||||
case 'q':
|
||||
/* Any QObject can be passed. */
|
||||
break;
|
||||
case '/':
|
||||
case '.':
|
||||
/*
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
# for testing nested structs
|
||||
{ 'type': 'UserDefOne',
|
||||
'data': { 'integer': 'int', 'string': 'str' } }
|
||||
'data': { 'integer': 'int', 'string': 'str', '*enum1': 'EnumOne' } }
|
||||
|
||||
{ 'type': 'UserDefTwo',
|
||||
'data': { 'string': 'str',
|
||||
|
|
|
@ -22,16 +22,17 @@
|
|||
|
||||
typedef struct StackObject
|
||||
{
|
||||
const QObject *obj;
|
||||
const QListEntry *entry;
|
||||
QObject *obj;
|
||||
const QListEntry *entry;
|
||||
GHashTable *h;
|
||||
} StackObject;
|
||||
|
||||
struct QmpInputVisitor
|
||||
{
|
||||
Visitor visitor;
|
||||
QObject *obj;
|
||||
StackObject stack[QIV_STACK_SIZE];
|
||||
int nb_stack;
|
||||
bool strict;
|
||||
};
|
||||
|
||||
static QmpInputVisitor *to_qiv(Visitor *v)
|
||||
|
@ -39,21 +40,18 @@ static QmpInputVisitor *to_qiv(Visitor *v)
|
|||
return container_of(v, QmpInputVisitor, visitor);
|
||||
}
|
||||
|
||||
static const QObject *qmp_input_get_object(QmpInputVisitor *qiv,
|
||||
const char *name)
|
||||
static QObject *qmp_input_get_object(QmpInputVisitor *qiv,
|
||||
const char *name)
|
||||
{
|
||||
const QObject *qobj;
|
||||
|
||||
if (qiv->nb_stack == 0) {
|
||||
qobj = qiv->obj;
|
||||
} else {
|
||||
qobj = qiv->stack[qiv->nb_stack - 1].obj;
|
||||
}
|
||||
QObject *qobj = qiv->stack[qiv->nb_stack - 1].obj;
|
||||
|
||||
if (qobj) {
|
||||
if (name && qobject_type(qobj) == QTYPE_QDICT) {
|
||||
if (qiv->stack[qiv->nb_stack - 1].h) {
|
||||
g_hash_table_remove(qiv->stack[qiv->nb_stack - 1].h, name);
|
||||
}
|
||||
return qdict_get(qobject_to_qdict(qobj), name);
|
||||
} else if (qiv->nb_stack > 0 && qobject_type(qobj) == QTYPE_QLIST) {
|
||||
} else if (qiv->stack[qiv->nb_stack - 1].entry) {
|
||||
return qlist_entry_obj(qiv->stack[qiv->nb_stack - 1].entry);
|
||||
}
|
||||
}
|
||||
|
@ -61,34 +59,57 @@ static const QObject *qmp_input_get_object(QmpInputVisitor *qiv,
|
|||
return qobj;
|
||||
}
|
||||
|
||||
static void qmp_input_push(QmpInputVisitor *qiv, const QObject *obj, Error **errp)
|
||||
static void qdict_add_key(const char *key, QObject *obj, void *opaque)
|
||||
{
|
||||
qiv->stack[qiv->nb_stack].obj = obj;
|
||||
if (qobject_type(obj) == QTYPE_QLIST) {
|
||||
qiv->stack[qiv->nb_stack].entry = qlist_first(qobject_to_qlist(obj));
|
||||
}
|
||||
qiv->nb_stack++;
|
||||
GHashTable *h = opaque;
|
||||
g_hash_table_insert(h, (gpointer) key, NULL);
|
||||
}
|
||||
|
||||
static void qmp_input_push(QmpInputVisitor *qiv, QObject *obj, Error **errp)
|
||||
{
|
||||
GHashTable *h;
|
||||
|
||||
if (qiv->nb_stack >= QIV_STACK_SIZE) {
|
||||
error_set(errp, QERR_BUFFER_OVERRUN);
|
||||
return;
|
||||
}
|
||||
|
||||
qiv->stack[qiv->nb_stack].obj = obj;
|
||||
qiv->stack[qiv->nb_stack].entry = NULL;
|
||||
qiv->stack[qiv->nb_stack].h = NULL;
|
||||
|
||||
if (qiv->strict && qobject_type(obj) == QTYPE_QDICT) {
|
||||
h = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
qdict_iter(qobject_to_qdict(obj), qdict_add_key, h);
|
||||
qiv->stack[qiv->nb_stack].h = h;
|
||||
}
|
||||
|
||||
qiv->nb_stack++;
|
||||
}
|
||||
|
||||
static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp)
|
||||
{
|
||||
qiv->nb_stack--;
|
||||
if (qiv->nb_stack < 0) {
|
||||
error_set(errp, QERR_BUFFER_OVERRUN);
|
||||
return;
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
|
||||
if (qiv->strict && qiv->stack[qiv->nb_stack - 1].h) {
|
||||
g_hash_table_iter_init(&iter, qiv->stack[qiv->nb_stack - 1].h);
|
||||
if (g_hash_table_iter_next(&iter, &key, NULL)) {
|
||||
error_set(errp, QERR_QMP_EXTRA_MEMBER, (char *) key);
|
||||
}
|
||||
g_hash_table_unref(qiv->stack[qiv->nb_stack - 1].h);
|
||||
}
|
||||
|
||||
assert(qiv->nb_stack > 0);
|
||||
qiv->nb_stack--;
|
||||
}
|
||||
|
||||
static void qmp_input_start_struct(Visitor *v, void **obj, const char *kind,
|
||||
const char *name, size_t size, Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
const QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
Error *err = NULL;
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QDICT) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
|
@ -96,8 +117,9 @@ static void qmp_input_start_struct(Visitor *v, void **obj, const char *kind,
|
|||
return;
|
||||
}
|
||||
|
||||
qmp_input_push(qiv, qobj, errp);
|
||||
if (error_is_set(errp)) {
|
||||
qmp_input_push(qiv, qobj, &err);
|
||||
if (err) {
|
||||
error_propagate(errp, err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -116,7 +138,7 @@ static void qmp_input_end_struct(Visitor *v, Error **errp)
|
|||
static void qmp_input_start_list(Visitor *v, const char *name, Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
const QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QLIST) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
|
@ -133,18 +155,24 @@ static GenericList *qmp_input_next_list(Visitor *v, GenericList **list,
|
|||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
GenericList *entry;
|
||||
StackObject *so = &qiv->stack[qiv->nb_stack - 1];
|
||||
bool first;
|
||||
|
||||
if (so->entry == NULL) {
|
||||
so->entry = qlist_first(qobject_to_qlist(so->obj));
|
||||
first = true;
|
||||
} else {
|
||||
so->entry = qlist_next(so->entry);
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (so->entry == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry = g_malloc0(sizeof(*entry));
|
||||
if (*list) {
|
||||
so->entry = qlist_next(so->entry);
|
||||
if (so->entry == NULL) {
|
||||
g_free(entry);
|
||||
return NULL;
|
||||
}
|
||||
if (first) {
|
||||
*list = entry;
|
||||
} else {
|
||||
(*list)->next = entry;
|
||||
}
|
||||
|
||||
|
@ -162,7 +190,7 @@ static void qmp_input_type_int(Visitor *v, int64_t *obj, const char *name,
|
|||
Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
const QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QINT) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
|
@ -177,7 +205,7 @@ static void qmp_input_type_bool(Visitor *v, bool *obj, const char *name,
|
|||
Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
const QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QBOOL) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
|
@ -192,7 +220,7 @@ static void qmp_input_type_str(Visitor *v, char **obj, const char *name,
|
|||
Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
const QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QSTRING) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
|
@ -207,7 +235,7 @@ static void qmp_input_type_number(Visitor *v, double *obj, const char *name,
|
|||
Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
const QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
|
||||
if (!qobj || qobject_type(qobj) != QTYPE_QFLOAT) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
|
||||
|
@ -222,7 +250,7 @@ static void qmp_input_start_optional(Visitor *v, bool *present,
|
|||
const char *name, Error **errp)
|
||||
{
|
||||
QmpInputVisitor *qiv = to_qiv(v);
|
||||
const QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
QObject *qobj = qmp_input_get_object(qiv, name);
|
||||
|
||||
if (!qobj) {
|
||||
*present = false;
|
||||
|
@ -239,7 +267,7 @@ Visitor *qmp_input_get_visitor(QmpInputVisitor *v)
|
|||
|
||||
void qmp_input_visitor_cleanup(QmpInputVisitor *v)
|
||||
{
|
||||
qobject_decref(v->obj);
|
||||
qobject_decref(v->stack[0].obj);
|
||||
g_free(v);
|
||||
}
|
||||
|
||||
|
@ -261,8 +289,18 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
|
|||
v->visitor.type_number = qmp_input_type_number;
|
||||
v->visitor.start_optional = qmp_input_start_optional;
|
||||
|
||||
v->obj = obj;
|
||||
qobject_incref(v->obj);
|
||||
qmp_input_push(v, obj, NULL);
|
||||
qobject_incref(obj);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj)
|
||||
{
|
||||
QmpInputVisitor *v;
|
||||
|
||||
v = qmp_input_visitor_new(obj);
|
||||
v->strict = true;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
typedef struct QmpInputVisitor QmpInputVisitor;
|
||||
|
||||
QmpInputVisitor *qmp_input_visitor_new(QObject *obj);
|
||||
QmpInputVisitor *qmp_input_visitor_new_strict(QObject *obj);
|
||||
|
||||
void qmp_input_visitor_cleanup(QmpInputVisitor *v);
|
||||
|
||||
Visitor *qmp_input_get_visitor(QmpInputVisitor *v);
|
||||
|
|
|
@ -199,14 +199,16 @@ void qmp_output_visitor_cleanup(QmpOutputVisitor *v)
|
|||
{
|
||||
QStackEntry *e, *tmp;
|
||||
|
||||
/* The bottom QStackEntry, if any, owns the root QObject. See the
|
||||
* qmp_output_push_obj() invocations in qmp_output_add_obj(). */
|
||||
QObject *root = QTAILQ_EMPTY(&v->stack) ? NULL : qmp_output_first(v);
|
||||
|
||||
QTAILQ_FOREACH_SAFE(e, &v->stack, node, tmp) {
|
||||
QTAILQ_REMOVE(&v->stack, e, node);
|
||||
if (e->value) {
|
||||
qobject_decref(e->value);
|
||||
}
|
||||
g_free(e);
|
||||
}
|
||||
|
||||
qobject_decref(root);
|
||||
g_free(v);
|
||||
}
|
||||
|
||||
|
|
|
@ -708,7 +708,7 @@ EQMP
|
|||
},
|
||||
{
|
||||
.name = "transaction",
|
||||
.args_type = "actions:O",
|
||||
.args_type = "actions:q",
|
||||
.mhandler.cmd_new = qmp_marshal_input_transaction,
|
||||
},
|
||||
|
||||
|
@ -2125,7 +2125,7 @@ EQMP
|
|||
|
||||
{
|
||||
.name = "qom-set",
|
||||
.args_type = "path:s,property:s,opts:O",
|
||||
.args_type = "path:s,property:s,value:q",
|
||||
.mhandler.cmd_new = qmp_qom_set,
|
||||
},
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ v = qapi_dealloc_get_visitor(md);
|
|||
''')
|
||||
else:
|
||||
ret += mcgen('''
|
||||
mi = qmp_input_visitor_new(%(obj)s);
|
||||
mi = qmp_input_visitor_new_strict(%(obj)s);
|
||||
v = qmp_input_get_visitor(mi);
|
||||
''',
|
||||
obj=obj)
|
||||
|
|
|
@ -61,7 +61,13 @@ def generate_visit_struct(name, members):
|
|||
|
||||
void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
|
||||
{
|
||||
if (error_is_set(errp)) {
|
||||
return;
|
||||
}
|
||||
visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), errp);
|
||||
if (obj && !*obj) {
|
||||
goto end;
|
||||
}
|
||||
''',
|
||||
name=name)
|
||||
push_indent()
|
||||
|
@ -69,6 +75,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
|
|||
pop_indent()
|
||||
|
||||
ret += mcgen('''
|
||||
end:
|
||||
visit_end_struct(m, errp);
|
||||
}
|
||||
''')
|
||||
|
@ -79,11 +86,14 @@ def generate_visit_list(name, members):
|
|||
|
||||
void visit_type_%(name)sList(Visitor *m, %(name)sList ** obj, const char *name, Error **errp)
|
||||
{
|
||||
GenericList *i, **head = (GenericList **)obj;
|
||||
GenericList *i, **prev = (GenericList **)obj;
|
||||
|
||||
if (error_is_set(errp)) {
|
||||
return;
|
||||
}
|
||||
visit_start_list(m, name, errp);
|
||||
|
||||
for (*head = i = visit_next_list(m, head, errp); i; i = visit_next_list(m, &i, errp)) {
|
||||
for (; (i = visit_next_list(m, prev, errp)) != NULL; prev = &i) {
|
||||
%(name)sList *native_i = (%(name)sList *)i;
|
||||
visit_type_%(name)s(m, &native_i->value, NULL, errp);
|
||||
}
|
||||
|
@ -112,7 +122,13 @@ void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
|
|||
{
|
||||
Error *err = NULL;
|
||||
|
||||
if (error_is_set(errp)) {
|
||||
return;
|
||||
}
|
||||
visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
|
||||
if (obj && !*obj) {
|
||||
goto end;
|
||||
}
|
||||
visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
|
||||
if (err) {
|
||||
error_propagate(errp, err);
|
||||
|
|
234
test-qmp-input-strict.c
Normal file
234
test-qmp-input-strict.c
Normal file
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* QMP Input Visitor unit-tests (strict mode).
|
||||
*
|
||||
* Copyright (C) 2011-2012 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Luiz Capitulino <lcapitulino@redhat.com>
|
||||
* Paolo Bonzini <pbonzini@redhat.com>
|
||||
*
|
||||
* 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 <glib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "qapi/qmp-input-visitor.h"
|
||||
#include "test-qapi-types.h"
|
||||
#include "test-qapi-visit.h"
|
||||
#include "qemu-objects.h"
|
||||
|
||||
typedef struct TestInputVisitorData {
|
||||
QObject *obj;
|
||||
QmpInputVisitor *qiv;
|
||||
} TestInputVisitorData;
|
||||
|
||||
static void validate_teardown(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
qobject_decref(data->obj);
|
||||
data->obj = NULL;
|
||||
|
||||
if (data->qiv) {
|
||||
qmp_input_visitor_cleanup(data->qiv);
|
||||
data->qiv = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is provided instead of a test setup function so that the JSON
|
||||
string used by the tests are kept in the test functions (and not
|
||||
int main()) */
|
||||
static GCC_FMT_ATTR(2, 3)
|
||||
Visitor *validate_test_init(TestInputVisitorData *data,
|
||||
const char *json_string, ...)
|
||||
{
|
||||
Visitor *v;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, json_string);
|
||||
data->obj = qobject_from_jsonv(json_string, &ap);
|
||||
va_end(ap);
|
||||
|
||||
g_assert(data->obj != NULL);
|
||||
|
||||
data->qiv = qmp_input_visitor_new_strict(data->obj);
|
||||
g_assert(data->qiv != NULL);
|
||||
|
||||
v = qmp_input_get_visitor(data->qiv);
|
||||
g_assert(v != NULL);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
typedef struct TestStruct
|
||||
{
|
||||
int64_t integer;
|
||||
bool boolean;
|
||||
char *string;
|
||||
} TestStruct;
|
||||
|
||||
static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
|
||||
const char *name, Error **errp)
|
||||
{
|
||||
visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
|
||||
errp);
|
||||
|
||||
visit_type_int(v, &(*obj)->integer, "integer", errp);
|
||||
visit_type_bool(v, &(*obj)->boolean, "boolean", errp);
|
||||
visit_type_str(v, &(*obj)->string, "string", errp);
|
||||
|
||||
visit_end_struct(v, errp);
|
||||
}
|
||||
|
||||
static void test_validate_struct(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
TestStruct *p = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = validate_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo' }");
|
||||
|
||||
visit_type_TestStruct(v, &p, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
g_free(p->string);
|
||||
g_free(p);
|
||||
}
|
||||
|
||||
static void test_validate_struct_nested(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefNested *udp = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string' }, 'string2': 'string2'}}}");
|
||||
|
||||
visit_type_UserDefNested(v, &udp, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
qapi_free_UserDefNested(udp);
|
||||
}
|
||||
|
||||
static void test_validate_list(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefOneList *head = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = validate_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44 } ]");
|
||||
|
||||
visit_type_UserDefOneList(v, &head, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
qapi_free_UserDefOneList(head);
|
||||
}
|
||||
|
||||
static void test_validate_union(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefUnion *tmp = NULL;
|
||||
Visitor *v;
|
||||
Error *errp = NULL;
|
||||
|
||||
v = validate_test_init(data, "{ 'type': 'b', 'data' : { 'integer': 42 } }");
|
||||
|
||||
visit_type_UserDefUnion(v, &tmp, NULL, &errp);
|
||||
g_assert(!error_is_set(&errp));
|
||||
qapi_free_UserDefUnion(tmp);
|
||||
}
|
||||
|
||||
static void test_validate_fail_struct(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
TestStruct *p = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = validate_test_init(data, "{ 'integer': -42, 'boolean': true, 'string': 'foo', 'extra': 42 }");
|
||||
|
||||
visit_type_TestStruct(v, &p, NULL, &errp);
|
||||
g_assert(error_is_set(&errp));
|
||||
if (p) {
|
||||
g_free(p->string);
|
||||
}
|
||||
g_free(p);
|
||||
}
|
||||
|
||||
static void test_validate_fail_struct_nested(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefNested *udp = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string', 'extra': [42, 23, {'foo':'bar'}] }, 'string2': 'string2'}}}");
|
||||
|
||||
visit_type_UserDefNested(v, &udp, NULL, &errp);
|
||||
g_assert(error_is_set(&errp));
|
||||
qapi_free_UserDefNested(udp);
|
||||
}
|
||||
|
||||
static void test_validate_fail_list(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefOneList *head = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = validate_test_init(data, "[ { 'string': 'string0', 'integer': 42 }, { 'string': 'string1', 'integer': 43 }, { 'string': 'string2', 'integer': 44, 'extra': 'ggg' } ]");
|
||||
|
||||
visit_type_UserDefOneList(v, &head, NULL, &errp);
|
||||
g_assert(error_is_set(&errp));
|
||||
qapi_free_UserDefOneList(head);
|
||||
}
|
||||
|
||||
static void test_validate_fail_union(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
UserDefUnion *tmp = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = validate_test_init(data, "{ 'type': 'b', 'data' : { 'integer': 42 }, 'extra': 'yyy' }");
|
||||
|
||||
visit_type_UserDefUnion(v, &tmp, NULL, &errp);
|
||||
g_assert(error_is_set(&errp));
|
||||
qapi_free_UserDefUnion(tmp);
|
||||
}
|
||||
|
||||
static void validate_test_add(const char *testpath,
|
||||
TestInputVisitorData *data,
|
||||
void (*test_func)(TestInputVisitorData *data, const void *user_data))
|
||||
{
|
||||
g_test_add(testpath, TestInputVisitorData, data, NULL, test_func,
|
||||
validate_teardown);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
TestInputVisitorData testdata;
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
validate_test_add("/visitor/input-strict/pass/struct",
|
||||
&testdata, test_validate_struct);
|
||||
validate_test_add("/visitor/input-strict/pass/struct-nested",
|
||||
&testdata, test_validate_struct_nested);
|
||||
validate_test_add("/visitor/input-strict/pass/list",
|
||||
&testdata, test_validate_list);
|
||||
validate_test_add("/visitor/input-strict/pass/union",
|
||||
&testdata, test_validate_union);
|
||||
validate_test_add("/visitor/input-strict/fail/struct",
|
||||
&testdata, test_validate_fail_struct);
|
||||
validate_test_add("/visitor/input-strict/fail/struct-nested",
|
||||
&testdata, test_validate_fail_struct_nested);
|
||||
validate_test_add("/visitor/input-strict/fail/list",
|
||||
&testdata, test_validate_fail_list);
|
||||
validate_test_add("/visitor/input-strict/fail/union",
|
||||
&testdata, test_validate_fail_union);
|
||||
|
||||
g_test_run();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -258,6 +258,23 @@ static void input_visitor_test_add(const char *testpath,
|
|||
visitor_input_teardown);
|
||||
}
|
||||
|
||||
static void test_visitor_in_errors(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
TestStruct *p = NULL;
|
||||
Error *errp = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init(data, "{ 'integer': false, 'boolean': 'foo', 'string': -42 }");
|
||||
|
||||
visit_type_TestStruct(v, &p, NULL, &errp);
|
||||
g_assert(error_is_set(&errp));
|
||||
g_assert(p->string == NULL);
|
||||
|
||||
g_free(p->string);
|
||||
g_free(p);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
TestInputVisitorData in_visitor_data;
|
||||
|
@ -282,6 +299,8 @@ int main(int argc, char **argv)
|
|||
&in_visitor_data, test_visitor_in_list);
|
||||
input_visitor_test_add("/visitor/input/union",
|
||||
&in_visitor_data, test_visitor_in_union);
|
||||
input_visitor_test_add("/visitor/input/errors",
|
||||
&in_visitor_data, test_visitor_in_errors);
|
||||
|
||||
g_test_run();
|
||||
|
||||
|
|
|
@ -274,6 +274,24 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data,
|
|||
qapi_free_UserDefNested(ud2);
|
||||
}
|
||||
|
||||
static void test_visitor_out_struct_errors(TestOutputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
EnumOne bad_values[] = { ENUM_ONE_MAX, -1 };
|
||||
UserDefOne u = { 0 }, *pu = &u;
|
||||
Error *errp;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bad_values) ; i++) {
|
||||
errp = NULL;
|
||||
u.has_enum1 = true;
|
||||
u.enum1 = bad_values[i];
|
||||
visit_type_UserDefOne(data->ov, &pu, "unused", &errp);
|
||||
g_assert(error_is_set(&errp) == true);
|
||||
error_free(errp);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct TestStructList
|
||||
{
|
||||
TestStruct *value;
|
||||
|
@ -444,6 +462,8 @@ int main(int argc, char **argv)
|
|||
&out_visitor_data, test_visitor_out_struct);
|
||||
output_visitor_test_add("/visitor/output/struct-nested",
|
||||
&out_visitor_data, test_visitor_out_struct_nested);
|
||||
output_visitor_test_add("/visitor/output/struct-errors",
|
||||
&out_visitor_data, test_visitor_out_struct_errors);
|
||||
output_visitor_test_add("/visitor/output/list",
|
||||
&out_visitor_data, test_visitor_out_list);
|
||||
output_visitor_test_add("/visitor/output/list-qapi-free",
|
||||
|
|
|
@ -16,7 +16,7 @@ check-qfloat: check-qfloat.o qfloat.o $(tools-obj-y)
|
|||
check-qjson: check-qjson.o $(qobject-obj-y) $(tools-obj-y)
|
||||
test-coroutine: test-coroutine.o qemu-timer-common.o async.o $(coroutine-obj-y) $(tools-obj-y)
|
||||
|
||||
test-qmp-input-visitor.o test-qmp-output-visitor.o \
|
||||
test-qmp-input-visitor.o test-qmp-output-visitor.o test-qmp-input-strict.o \
|
||||
test-string-input-visitor.o test-string-output-visitor.o \
|
||||
test-qmp-commands.o: QEMU_CFLAGS += -I $(qapi-dir)
|
||||
|
||||
|
@ -37,6 +37,9 @@ test-string-output-visitor: test-string-output-visitor.o $(qobject-obj-y) $(qapi
|
|||
test-string-input-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
|
||||
test-string-input-visitor: test-string-input-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
|
||||
|
||||
test-qmp-input-strict.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
|
||||
test-qmp-input-strict: test-qmp-input-strict.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
|
||||
|
||||
test-qmp-output-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
|
||||
test-qmp-output-visitor: test-qmp-output-visitor.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
|
||||
|
||||
|
|
Loading…
Reference in a new issue