diff options
author | Daniel P. Berrange <berrange@redhat.com> | 2016-09-30 15:45:25 +0100 |
---|---|---|
committer | Markus Armbruster <armbru@redhat.com> | 2016-10-25 17:56:14 +0200 |
commit | 603476c25c4d588a557ec2139352028f3157cba4 (patch) | |
tree | 84400c4830c048177e388504b271aa8408c4ebb6 /tests/check-qdict.c | |
parent | b1d2e5f1b0612178cdc2752fb0f60407dd87960e (diff) |
qdict: implement a qdict_crumple method for un-flattening a dict
The qdict_flatten() method will take a dict whose elements are
further nested dicts/lists and flatten them by concatenating
keys.
The qdict_crumple() method aims to do the reverse, taking a flat
qdict, and turning it into a set of nested dicts/lists. It will
apply nesting based on the key name, with a '.' indicating a
new level in the hierarchy. If the keys in the nested structure
are all numeric, it will create a list, otherwise it will create
a dict.
If the keys are a mixture of numeric and non-numeric, or the
numeric keys are not in strictly ascending order, an error will
be reported.
As an example, a flat dict containing
{
'foo.0.bar': 'one',
'foo.0.wizz': '1',
'foo.1.bar': 'two',
'foo.1.wizz': '2'
}
will get turned into a dict with one element 'foo' whose
value is a list. The list elements will each in turn be
dicts.
{
'foo': [
{ 'bar': 'one', 'wizz': '1' },
{ 'bar': 'two', 'wizz': '2' }
],
}
If the key is intended to contain a literal '.', then it must
be escaped as '..'. ie a flat dict
{
'foo..bar': 'wizz',
'bar.foo..bar': 'eek',
'bar.hello': 'world'
}
Will end up as
{
'foo.bar': 'wizz',
'bar': {
'foo.bar': 'eek',
'hello': 'world'
}
}
The intent of this function is that it allows a set of QemuOpts
to be turned into a nested data structure that mirrors the nesting
used when the same object is defined over QMP.
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
Message-Id: <1475246744-29302-3-git-send-email-berrange@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Parameter recursive dropped along with its tests; whitespace style
touched up]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Diffstat (limited to 'tests/check-qdict.c')
-rw-r--r-- | tests/check-qdict.c | 158 |
1 files changed, 150 insertions, 8 deletions
diff --git a/tests/check-qdict.c b/tests/check-qdict.c index 42da1e65a5..07b1c798d8 100644 --- a/tests/check-qdict.c +++ b/tests/check-qdict.c @@ -14,6 +14,7 @@ #include "qapi/qmp/qint.h" #include "qapi/qmp/qdict.h" #include "qapi/qmp/qstring.h" +#include "qapi/error.h" #include "qemu-common.h" /* @@ -412,7 +413,6 @@ static void qdict_array_split_test(void) QDECREF(test_dict); - /* * Test the split of * @@ -518,7 +518,6 @@ static void qdict_join_test(void) dict1 = qdict_new(); dict2 = qdict_new(); - /* Test everything once without overwrite and once with */ do { @@ -528,7 +527,6 @@ static void qdict_join_test(void) g_assert(qdict_size(dict1) == 0); g_assert(qdict_size(dict2) == 0); - /* First iteration: Test movement */ /* Second iteration: Test empty source and non-empty destination */ qdict_put(dict2, "foo", qint_from_int(42)); @@ -542,7 +540,6 @@ static void qdict_join_test(void) g_assert(qdict_get_int(dict1, "foo") == 42); } - /* Test non-empty source and destination without conflict */ qdict_put(dict2, "bar", qint_from_int(23)); @@ -554,7 +551,6 @@ static void qdict_join_test(void) g_assert(qdict_get_int(dict1, "foo") == 42); g_assert(qdict_get_int(dict1, "bar") == 23); - /* Test conflict */ qdict_put(dict2, "foo", qint_from_int(84)); @@ -570,7 +566,6 @@ static void qdict_join_test(void) g_assert(qdict_get_int(dict2, "foo") == 84); } - /* Check the references */ g_assert(qdict_get(dict1, "foo")->refcnt == 1); g_assert(qdict_get(dict1, "bar")->refcnt == 1); @@ -579,7 +574,6 @@ static void qdict_join_test(void) g_assert(qdict_get(dict2, "foo")->refcnt == 1); } - /* Clean up */ qdict_del(dict1, "foo"); qdict_del(dict1, "bar"); @@ -590,11 +584,152 @@ static void qdict_join_test(void) } while (overwrite ^= true); - QDECREF(dict1); QDECREF(dict2); } +static void qdict_crumple_test_recursive(void) +{ + QDict *src, *dst, *rule, *vnc, *acl, *listen; + QObject *child, *res; + QList *rules; + + src = qdict_new(); + qdict_put(src, "vnc.listen.addr", qstring_from_str("127.0.0.1")); + qdict_put(src, "vnc.listen.port", qstring_from_str("5901")); + qdict_put(src, "vnc.acl.rules.0.match", qstring_from_str("fred")); + qdict_put(src, "vnc.acl.rules.0.policy", qstring_from_str("allow")); + qdict_put(src, "vnc.acl.rules.1.match", qstring_from_str("bob")); + qdict_put(src, "vnc.acl.rules.1.policy", qstring_from_str("deny")); + qdict_put(src, "vnc.acl.default", qstring_from_str("deny")); + qdict_put(src, "vnc.acl..name", qstring_from_str("acl0")); + qdict_put(src, "vnc.acl.rule..name", qstring_from_str("acl0")); + + res = qdict_crumple(src, &error_abort); + + g_assert_cmpint(qobject_type(res), ==, QTYPE_QDICT); + + dst = qobject_to_qdict(res); + + g_assert_cmpint(qdict_size(dst), ==, 1); + + child = qdict_get(dst, "vnc"); + g_assert_cmpint(qobject_type(child), ==, QTYPE_QDICT); + vnc = qobject_to_qdict(child); + + child = qdict_get(vnc, "listen"); + g_assert_cmpint(qobject_type(child), ==, QTYPE_QDICT); + listen = qobject_to_qdict(child); + g_assert_cmpstr("127.0.0.1", ==, qdict_get_str(listen, "addr")); + g_assert_cmpstr("5901", ==, qdict_get_str(listen, "port")); + + child = qdict_get(vnc, "acl"); + g_assert_cmpint(qobject_type(child), ==, QTYPE_QDICT); + acl = qobject_to_qdict(child); + + child = qdict_get(acl, "rules"); + g_assert_cmpint(qobject_type(child), ==, QTYPE_QLIST); + rules = qobject_to_qlist(child); + g_assert_cmpint(qlist_size(rules), ==, 2); + + rule = qobject_to_qdict(qlist_pop(rules)); + g_assert_cmpint(qdict_size(rule), ==, 2); + g_assert_cmpstr("fred", ==, qdict_get_str(rule, "match")); + g_assert_cmpstr("allow", ==, qdict_get_str(rule, "policy")); + QDECREF(rule); + + rule = qobject_to_qdict(qlist_pop(rules)); + g_assert_cmpint(qdict_size(rule), ==, 2); + g_assert_cmpstr("bob", ==, qdict_get_str(rule, "match")); + g_assert_cmpstr("deny", ==, qdict_get_str(rule, "policy")); + QDECREF(rule); + + /* With recursive crumpling, we should see all names unescaped */ + g_assert_cmpstr("acl0", ==, qdict_get_str(vnc, "acl.name")); + child = qdict_get(vnc, "acl"); + g_assert_cmpint(qobject_type(child), ==, QTYPE_QDICT); + acl = qdict_get_qdict(vnc, "acl"); + g_assert_cmpstr("acl0", ==, qdict_get_str(acl, "rule.name")); + + QDECREF(src); + QDECREF(dst); +} + +static void qdict_crumple_test_empty(void) +{ + QDict *src, *dst; + + src = qdict_new(); + + dst = (QDict *)qdict_crumple(src, &error_abort); + + g_assert_cmpint(qdict_size(dst), ==, 0); + + QDECREF(src); + QDECREF(dst); +} + +static void qdict_crumple_test_bad_inputs(void) +{ + QDict *src; + Error *error = NULL; + + src = qdict_new(); + /* rule.0 can't be both a string and a dict */ + qdict_put(src, "rule.0", qstring_from_str("fred")); + qdict_put(src, "rule.0.policy", qstring_from_str("allow")); + + g_assert(qdict_crumple(src, &error) == NULL); + g_assert(error != NULL); + error_free(error); + error = NULL; + QDECREF(src); + + src = qdict_new(); + /* rule can't be both a list and a dict */ + qdict_put(src, "rule.0", qstring_from_str("fred")); + qdict_put(src, "rule.a", qstring_from_str("allow")); + + g_assert(qdict_crumple(src, &error) == NULL); + g_assert(error != NULL); + error_free(error); + error = NULL; + QDECREF(src); + + src = qdict_new(); + /* The input should be flat, ie no dicts or lists */ + qdict_put(src, "rule.a", qdict_new()); + qdict_put(src, "rule.b", qstring_from_str("allow")); + + g_assert(qdict_crumple(src, &error) == NULL); + g_assert(error != NULL); + error_free(error); + error = NULL; + QDECREF(src); + + src = qdict_new(); + /* List indexes must not have gaps */ + qdict_put(src, "rule.0", qstring_from_str("deny")); + qdict_put(src, "rule.3", qstring_from_str("allow")); + + g_assert(qdict_crumple(src, &error) == NULL); + g_assert(error != NULL); + error_free(error); + error = NULL; + QDECREF(src); + + src = qdict_new(); + /* List indexes must be in %zu format */ + qdict_put(src, "rule.0", qstring_from_str("deny")); + qdict_put(src, "rule.+1", qstring_from_str("allow")); + + g_assert(qdict_crumple(src, &error) == NULL); + g_assert(error != NULL); + error_free(error); + error = NULL; + QDECREF(src); +} + /* * Errors test-cases */ @@ -742,6 +877,13 @@ int main(int argc, char **argv) g_test_add_func("/errors/put_exists", qdict_put_exists_test); g_test_add_func("/errors/get_not_exists", qdict_get_not_exists_test); + g_test_add_func("/public/crumple/recursive", + qdict_crumple_test_recursive); + g_test_add_func("/public/crumple/empty", + qdict_crumple_test_empty); + g_test_add_func("/public/crumple/bad_inputs", + qdict_crumple_test_bad_inputs); + /* The Big one */ if (g_test_slow()) { g_test_add_func("/stress/test", qdict_stress_test); |