Skip to content

Commit

Permalink
keyval: Support lists
Browse files Browse the repository at this point in the history
Additionally permit non-negative integers as key components.  A
dictionary's keys must either be all integers or none.  If all keys
are integers, convert the dictionary to a list.  The set of keys must
be [0,N].

Examples:

* list.1=goner,list.0=null,list.1=eins,list.2=zwei
  is equivalent to JSON [ "null", "eins", "zwei" ]

* a.b.c=1,a.b.0=2
  is inconsistent: a.b.c clashes with a.b.0

* list.0=null,list.2=eins,list.2=zwei
  has a hole: list.1 is missing

Similar design flaw as for objects: there is no way to denote an empty
list.  While interpreting "key absent" as empty list seems natural
(removing a list member from the input string works when there are
multiple ones, so why not when there's just one), it doesn't work:
"key absent" already means "optional list absent", which isn't the
same as "empty list present".

Update the keyval object visitor to use this a.0 syntax in error
messages rather than the usual a[0].

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1488317230-26248-25-git-send-email-armbru@redhat.com>
[Off-by-one fix squashed in, as per Kevin's review]
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
  • Loading branch information
Markus Armbruster committed Mar 7, 2017
1 parent 79f7598 commit 0b2c1be
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 13 deletions.
6 changes: 5 additions & 1 deletion qapi/qobject-input-visitor.c
Expand Up @@ -41,6 +41,7 @@ struct QObjectInputVisitor {

/* Root of visit at visitor creation. */
QObject *root;
bool keyval; /* Assume @root made with keyval_parse() */

/* Stack of objects being visited (all entries will be either
* QDict or QList). */
Expand Down Expand Up @@ -73,7 +74,9 @@ static const char *full_name_nth(QObjectInputVisitor *qiv, const char *name,
g_string_prepend(qiv->errname, name ?: "<anonymous>");
g_string_prepend_c(qiv->errname, '.');
} else {
snprintf(buf, sizeof(buf), "[%u]", so->index);
snprintf(buf, sizeof(buf),
qiv->keyval ? ".%u" : "[%u]",
so->index);
g_string_prepend(qiv->errname, buf);
}
name = so->name;
Expand Down Expand Up @@ -673,6 +676,7 @@ Visitor *qobject_input_visitor_new_keyval(QObject *obj)
v->visitor.type_any = qobject_input_type_any;
v->visitor.type_null = qobject_input_type_null;
v->visitor.type_size = qobject_input_type_size_keyval;
v->keyval = true;

return &v->visitor;
}
Expand Down
122 changes: 122 additions & 0 deletions tests/test-keyval.c
Expand Up @@ -12,6 +12,7 @@

#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/qmp/qstring.h"
#include "qapi/qobject-input-visitor.h"
#include "qemu/cutils.h"
#include "qemu/option.h"
Expand Down Expand Up @@ -183,6 +184,72 @@ static void test_keyval_parse(void)
g_assert(!qdict);
}

static void check_list012(QList *qlist)
{
static const char *expected[] = { "null", "eins", "zwei" };
int i;
QString *qstr;

g_assert(qlist);
for (i = 0; i < ARRAY_SIZE(expected); i++) {
qstr = qobject_to_qstring(qlist_pop(qlist));
g_assert(qstr);
g_assert_cmpstr(qstring_get_str(qstr), ==, expected[i]);
QDECREF(qstr);
}
g_assert(qlist_empty(qlist));
}

static void test_keyval_parse_list(void)
{
Error *err = NULL;
QDict *qdict, *sub_qdict;

/* Root can't be a list */
qdict = keyval_parse("0=1", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);

/* List elements need not be in order */
qdict = keyval_parse("list.0=null,list.2=zwei,list.1=eins",
NULL, &error_abort);
g_assert_cmpint(qdict_size(qdict), ==, 1);
check_list012(qdict_get_qlist(qdict, "list"));
QDECREF(qdict);

/* Multiple indexes, last one wins */
qdict = keyval_parse("list.1=goner,list.0=null,list.1=eins,list.2=zwei",
NULL, &error_abort);
g_assert_cmpint(qdict_size(qdict), ==, 1);
check_list012(qdict_get_qlist(qdict, "list"));
QDECREF(qdict);

/* List at deeper nesting */
qdict = keyval_parse("a.list.1=eins,a.list.0=null,a.list.2=zwei",
NULL, &error_abort);
g_assert_cmpint(qdict_size(qdict), ==, 1);
sub_qdict = qdict_get_qdict(qdict, "a");
g_assert_cmpint(qdict_size(sub_qdict), ==, 1);
check_list012(qdict_get_qlist(sub_qdict, "list"));
QDECREF(qdict);

/* Inconsistent dotted keys: both list and dictionary */
qdict = keyval_parse("a.b.c=1,a.b.0=2", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
qdict = keyval_parse("a.0.c=1,a.b.c=2", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);

/* Missing list indexes */
qdict = keyval_parse("list.2=lonely", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
qdict = keyval_parse("list.0=null,list.2=eins,list.02=zwei", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
}

static void test_keyval_visit_bool(void)
{
Error *err = NULL;
Expand Down Expand Up @@ -459,6 +526,59 @@ static void test_keyval_visit_dict(void)
visit_free(v);
}

static void test_keyval_visit_list(void)
{
Error *err = NULL;
Visitor *v;
QDict *qdict;
char *s;

qdict = keyval_parse("a.0=,a.1=I,a.2.0=II", NULL, &error_abort);
/* TODO empty list */
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
QDECREF(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_start_list(v, "a", NULL, 0, &error_abort);
visit_type_str(v, NULL, &s, &error_abort);
g_assert_cmpstr(s, ==, "");
g_free(s);
visit_type_str(v, NULL, &s, &error_abort);
g_assert_cmpstr(s, ==, "I");
g_free(s);
visit_start_list(v, NULL, NULL, 0, &error_abort);
visit_type_str(v, NULL, &s, &error_abort);
g_assert_cmpstr(s, ==, "II");
g_free(s);
visit_check_list(v, &error_abort);
visit_end_list(v, NULL);
visit_check_list(v, &error_abort);
visit_end_list(v, NULL);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);

qdict = keyval_parse("a.0=,b.0.0=head", NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
QDECREF(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_start_list(v, "a", NULL, 0, &error_abort);
visit_check_list(v, &err); /* a[0] unexpected */
error_free_or_abort(&err);
visit_end_list(v, NULL);
visit_start_list(v, "b", NULL, 0, &error_abort);
visit_start_list(v, NULL, NULL, 0, &error_abort);
visit_type_str(v, NULL, &s, &error_abort);
g_assert_cmpstr(s, ==, "head");
g_free(s);
visit_type_str(v, NULL, &s, &err); /* b[0][1] missing */
error_free_or_abort(&err);
visit_end_list(v, NULL);
visit_end_list(v, NULL);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
}

static void test_keyval_visit_optional(void)
{
Visitor *v;
Expand Down Expand Up @@ -492,10 +612,12 @@ int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/keyval/keyval_parse", test_keyval_parse);
g_test_add_func("/keyval/keyval_parse/list", test_keyval_parse_list);
g_test_add_func("/keyval/visit/bool", test_keyval_visit_bool);
g_test_add_func("/keyval/visit/number", test_keyval_visit_number);
g_test_add_func("/keyval/visit/size", test_keyval_visit_size);
g_test_add_func("/keyval/visit/dict", test_keyval_visit_dict);
g_test_add_func("/keyval/visit/list", test_keyval_visit_list);
g_test_add_func("/keyval/visit/optional", test_keyval_visit_optional);
g_test_run();
return 0;
Expand Down

0 comments on commit 0b2c1be

Please sign in to comment.