keyval: New keyval_parse()
keyval_parse() parses KEY=VALUE,... into a QDict. Works like
qemu_opts_parse(), except:
* Returns a QDict instead of a QemuOpts (d'oh).
* Supports nesting, unlike QemuOpts: a KEY is split into key
fragments at '.' (dotted key convention; the block layer does
something similar on top of QemuOpts). The key fragments are QDict
keys, and the last one's value is updated to VALUE.
* Each key fragment may be up to 127 bytes long. qemu_opts_parse()
limits the entire key to 127 bytes.
* Overlong key fragments are rejected. qemu_opts_parse() silently
truncates them.
* Empty key fragments are rejected. qemu_opts_parse() happily
accepts empty keys.
* It does not store the returned value. qemu_opts_parse() stores it
in the QemuOptsList.
* It does not treat parameter "id" specially. qemu_opts_parse()
ignores all but the first "id", and fails when its value isn't
id_wellformed(), or duplicate (a QemuOpts with the same ID is
already stored). It also screws up when a value contains ",id=".
* Implied value is not supported. qemu_opts_parse() desugars "foo" to
"foo=on", and "nofoo" to "foo=off".
* An implied key's value can't be empty, and can't contain ','.
I intend to grow this into a saner replacement for QemuOpts. It'll
take time, though.
Note: keyval_parse() provides no way to do lists, and its key syntax
is incompatible with the __RFQDN_ prefix convention for downstream
extensions, because it blindly splits at '.', even in __RFQDN_. Both
issues will be addressed later in the series.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1488317230-26248-4-git-send-email-armbru@redhat.com>
2017-02-28 22:26:49 +01:00
|
|
|
/*
|
|
|
|
* Unit tests for parsing of KEY=VALUE,... strings
|
|
|
|
*
|
|
|
|
* Copyright (C) 2017 Red Hat Inc.
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Markus Armbruster <armbru@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 "qemu/osdep.h"
|
|
|
|
#include "qapi/error.h"
|
2018-02-01 12:18:39 +01:00
|
|
|
#include "qapi/qmp/qdict.h"
|
2018-02-01 12:18:38 +01:00
|
|
|
#include "qapi/qmp/qlist.h"
|
keyval: Support lists
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>
2017-02-28 22:27:10 +01:00
|
|
|
#include "qapi/qmp/qstring.h"
|
2017-02-28 22:26:51 +01:00
|
|
|
#include "qapi/qobject-input-visitor.h"
|
2017-03-20 13:55:46 +01:00
|
|
|
#include "test-qapi-visit.h"
|
2017-02-28 22:26:51 +01:00
|
|
|
#include "qemu/cutils.h"
|
keyval: New keyval_parse()
keyval_parse() parses KEY=VALUE,... into a QDict. Works like
qemu_opts_parse(), except:
* Returns a QDict instead of a QemuOpts (d'oh).
* Supports nesting, unlike QemuOpts: a KEY is split into key
fragments at '.' (dotted key convention; the block layer does
something similar on top of QemuOpts). The key fragments are QDict
keys, and the last one's value is updated to VALUE.
* Each key fragment may be up to 127 bytes long. qemu_opts_parse()
limits the entire key to 127 bytes.
* Overlong key fragments are rejected. qemu_opts_parse() silently
truncates them.
* Empty key fragments are rejected. qemu_opts_parse() happily
accepts empty keys.
* It does not store the returned value. qemu_opts_parse() stores it
in the QemuOptsList.
* It does not treat parameter "id" specially. qemu_opts_parse()
ignores all but the first "id", and fails when its value isn't
id_wellformed(), or duplicate (a QemuOpts with the same ID is
already stored). It also screws up when a value contains ",id=".
* Implied value is not supported. qemu_opts_parse() desugars "foo" to
"foo=on", and "nofoo" to "foo=off".
* An implied key's value can't be empty, and can't contain ','.
I intend to grow this into a saner replacement for QemuOpts. It'll
take time, though.
Note: keyval_parse() provides no way to do lists, and its key syntax
is incompatible with the __RFQDN_ prefix convention for downstream
extensions, because it blindly splits at '.', even in __RFQDN_. Both
issues will be addressed later in the series.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1488317230-26248-4-git-send-email-armbru@redhat.com>
2017-02-28 22:26:49 +01:00
|
|
|
#include "qemu/option.h"
|
|
|
|
|
|
|
|
static void test_keyval_parse(void)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
QDict *qdict, *sub_qdict;
|
|
|
|
char long_key[129];
|
|
|
|
char *params;
|
|
|
|
|
|
|
|
/* Nothing */
|
|
|
|
qdict = keyval_parse("", NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 0);
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Empty key (qemu_opts_parse() accepts this) */
|
|
|
|
qdict = keyval_parse("=val", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
|
|
|
/* Empty key fragment */
|
|
|
|
qdict = keyval_parse(".", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
qdict = keyval_parse("key.", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
2017-02-28 22:27:05 +01:00
|
|
|
/* Invalid non-empty key (qemu_opts_parse() doesn't care) */
|
|
|
|
qdict = keyval_parse("7up=val", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
keyval: New keyval_parse()
keyval_parse() parses KEY=VALUE,... into a QDict. Works like
qemu_opts_parse(), except:
* Returns a QDict instead of a QemuOpts (d'oh).
* Supports nesting, unlike QemuOpts: a KEY is split into key
fragments at '.' (dotted key convention; the block layer does
something similar on top of QemuOpts). The key fragments are QDict
keys, and the last one's value is updated to VALUE.
* Each key fragment may be up to 127 bytes long. qemu_opts_parse()
limits the entire key to 127 bytes.
* Overlong key fragments are rejected. qemu_opts_parse() silently
truncates them.
* Empty key fragments are rejected. qemu_opts_parse() happily
accepts empty keys.
* It does not store the returned value. qemu_opts_parse() stores it
in the QemuOptsList.
* It does not treat parameter "id" specially. qemu_opts_parse()
ignores all but the first "id", and fails when its value isn't
id_wellformed(), or duplicate (a QemuOpts with the same ID is
already stored). It also screws up when a value contains ",id=".
* Implied value is not supported. qemu_opts_parse() desugars "foo" to
"foo=on", and "nofoo" to "foo=off".
* An implied key's value can't be empty, and can't contain ','.
I intend to grow this into a saner replacement for QemuOpts. It'll
take time, though.
Note: keyval_parse() provides no way to do lists, and its key syntax
is incompatible with the __RFQDN_ prefix convention for downstream
extensions, because it blindly splits at '.', even in __RFQDN_. Both
issues will be addressed later in the series.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1488317230-26248-4-git-send-email-armbru@redhat.com>
2017-02-28 22:26:49 +01:00
|
|
|
/* Overlong key */
|
|
|
|
memset(long_key, 'a', 127);
|
|
|
|
long_key[127] = 'z';
|
|
|
|
long_key[128] = 0;
|
|
|
|
params = g_strdup_printf("k.%s=v", long_key);
|
|
|
|
qdict = keyval_parse(params + 2, NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
|
|
|
/* Overlong key fragment */
|
|
|
|
qdict = keyval_parse(params, NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
g_free(params);
|
|
|
|
|
|
|
|
/* Long key (qemu_opts_parse() accepts and truncates silently) */
|
|
|
|
params = g_strdup_printf("k.%s=v", long_key + 1);
|
|
|
|
qdict = keyval_parse(params + 2, NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, long_key + 1), ==, "v");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Long key fragment */
|
|
|
|
qdict = keyval_parse(params, NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
|
|
|
sub_qdict = qdict_get_qdict(qdict, "k");
|
|
|
|
g_assert(sub_qdict);
|
|
|
|
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(sub_qdict, long_key + 1), ==, "v");
|
|
|
|
QDECREF(qdict);
|
|
|
|
g_free(params);
|
|
|
|
|
2017-02-28 22:27:05 +01:00
|
|
|
/* Crap after valid key */
|
|
|
|
qdict = keyval_parse("key[0]=val", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
keyval: New keyval_parse()
keyval_parse() parses KEY=VALUE,... into a QDict. Works like
qemu_opts_parse(), except:
* Returns a QDict instead of a QemuOpts (d'oh).
* Supports nesting, unlike QemuOpts: a KEY is split into key
fragments at '.' (dotted key convention; the block layer does
something similar on top of QemuOpts). The key fragments are QDict
keys, and the last one's value is updated to VALUE.
* Each key fragment may be up to 127 bytes long. qemu_opts_parse()
limits the entire key to 127 bytes.
* Overlong key fragments are rejected. qemu_opts_parse() silently
truncates them.
* Empty key fragments are rejected. qemu_opts_parse() happily
accepts empty keys.
* It does not store the returned value. qemu_opts_parse() stores it
in the QemuOptsList.
* It does not treat parameter "id" specially. qemu_opts_parse()
ignores all but the first "id", and fails when its value isn't
id_wellformed(), or duplicate (a QemuOpts with the same ID is
already stored). It also screws up when a value contains ",id=".
* Implied value is not supported. qemu_opts_parse() desugars "foo" to
"foo=on", and "nofoo" to "foo=off".
* An implied key's value can't be empty, and can't contain ','.
I intend to grow this into a saner replacement for QemuOpts. It'll
take time, though.
Note: keyval_parse() provides no way to do lists, and its key syntax
is incompatible with the __RFQDN_ prefix convention for downstream
extensions, because it blindly splits at '.', even in __RFQDN_. Both
issues will be addressed later in the series.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1488317230-26248-4-git-send-email-armbru@redhat.com>
2017-02-28 22:26:49 +01:00
|
|
|
/* Multiple keys, last one wins */
|
|
|
|
qdict = keyval_parse("a=1,b=2,,x,a=3", NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 2);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "a"), ==, "3");
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "b"), ==, "2,x");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Even when it doesn't in qemu_opts_parse() */
|
|
|
|
qdict = keyval_parse("id=foo,id=bar", NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "id"), ==, "bar");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Dotted keys */
|
|
|
|
qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 2);
|
|
|
|
sub_qdict = qdict_get_qdict(qdict, "a");
|
|
|
|
g_assert(sub_qdict);
|
|
|
|
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
|
|
|
|
sub_qdict = qdict_get_qdict(sub_qdict, "b");
|
|
|
|
g_assert(sub_qdict);
|
|
|
|
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(sub_qdict, "c"), ==, "2");
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "d"), ==, "3");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Inconsistent dotted keys */
|
|
|
|
qdict = keyval_parse("a.b=1,a=2", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
qdict = keyval_parse("a.b=1,a.b.c=2", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
|
|
|
/* Trailing comma is ignored */
|
|
|
|
qdict = keyval_parse("x=y,", NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "x"), ==, "y");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Except when it isn't */
|
|
|
|
qdict = keyval_parse(",", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
|
|
|
/* Value containing ,id= not misinterpreted as qemu_opts_parse() does */
|
|
|
|
qdict = keyval_parse("x=,,id=bar", NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "x"), ==, ",id=bar");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Anti-social ID is left to caller (qemu_opts_parse() rejects it) */
|
|
|
|
qdict = keyval_parse("id=666", NULL, &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "id"), ==, "666");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Implied value not supported (unlike qemu_opts_parse()) */
|
|
|
|
qdict = keyval_parse("an,noaus,noaus=", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
|
|
|
/* Implied value, key "no" (qemu_opts_parse(): negated empty key) */
|
|
|
|
qdict = keyval_parse("no", NULL, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
|
|
|
/* Implied key */
|
|
|
|
qdict = keyval_parse("an,aus=off,noaus=", "implied", &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 3);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "an");
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "aus"), ==, "off");
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(qdict, "noaus"), ==, "");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Implied dotted key */
|
|
|
|
qdict = keyval_parse("val", "eins.zwei", &error_abort);
|
|
|
|
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
|
|
|
sub_qdict = qdict_get_qdict(qdict, "eins");
|
|
|
|
g_assert(sub_qdict);
|
|
|
|
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
|
|
|
|
g_assert_cmpstr(qdict_get_try_str(sub_qdict, "zwei"), ==, "val");
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* Implied key with empty value (qemu_opts_parse() accepts this) */
|
|
|
|
qdict = keyval_parse(",", "implied", &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
|
|
|
/* Likewise (qemu_opts_parse(): implied key with comma value) */
|
|
|
|
qdict = keyval_parse(",,,a=1", "implied", &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
|
|
|
|
/* Empty key is not an implied key */
|
|
|
|
qdict = keyval_parse("=val", "implied", &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
g_assert(!qdict);
|
|
|
|
}
|
|
|
|
|
keyval: Support lists
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>
2017-02-28 22:27:10 +01:00
|
|
|
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 */
|
2017-03-20 13:55:44 +01:00
|
|
|
qdict = keyval_parse("list.1=goner,list.0=null,list.01=eins,list.2=zwei",
|
keyval: Support lists
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>
2017-02-28 22:27:10 +01:00
|
|
|
NULL, &error_abort);
|
|
|
|
g_assert_cmpint(qdict_size(qdict), ==, 1);
|
|
|
|
check_list012(qdict_get_qlist(qdict, "list"));
|
|
|
|
QDECREF(qdict);
|
|
|
|
|
|
|
|
/* List at deeper nesting */
|
2017-03-20 13:55:44 +01:00
|
|
|
qdict = keyval_parse("a.list.1=eins,a.list.00=null,a.list.2=zwei",
|
keyval: Support lists
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>
2017-02-28 22:27:10 +01:00
|
|
|
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 */
|
2017-03-20 13:55:44 +01:00
|
|
|
qdict = keyval_parse("list.1=lonely", NULL, &err);
|
keyval: Support lists
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>
2017-02-28 22:27:10 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-02-28 22:26:51 +01:00
|
|
|
static void test_keyval_visit_bool(void)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
Visitor *v;
|
|
|
|
QDict *qdict;
|
|
|
|
bool b;
|
|
|
|
|
|
|
|
qdict = keyval_parse("bool1=on,bool2=off", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_bool(v, "bool1", &b, &error_abort);
|
|
|
|
g_assert(b);
|
|
|
|
visit_type_bool(v, "bool2", &b, &error_abort);
|
|
|
|
g_assert(!b);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
qdict = keyval_parse("bool1=offer", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_bool(v, "bool1", &b, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_keyval_visit_number(void)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
Visitor *v;
|
|
|
|
QDict *qdict;
|
|
|
|
uint64_t u;
|
|
|
|
|
|
|
|
/* Lower limit zero */
|
|
|
|
qdict = keyval_parse("number1=0", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_uint64(v, "number1", &u, &error_abort);
|
|
|
|
g_assert_cmpuint(u, ==, 0);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Upper limit 2^64-1 */
|
|
|
|
qdict = keyval_parse("number1=18446744073709551615,number2=-1",
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_uint64(v, "number1", &u, &error_abort);
|
|
|
|
g_assert_cmphex(u, ==, UINT64_MAX);
|
|
|
|
visit_type_uint64(v, "number2", &u, &error_abort);
|
|
|
|
g_assert_cmphex(u, ==, UINT64_MAX);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Above upper limit */
|
|
|
|
qdict = keyval_parse("number1=18446744073709551616",
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_uint64(v, "number1", &u, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Below lower limit */
|
|
|
|
qdict = keyval_parse("number1=-18446744073709551616",
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_uint64(v, "number1", &u, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Hex and octal */
|
|
|
|
qdict = keyval_parse("number1=0x2a,number2=052",
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_uint64(v, "number1", &u, &error_abort);
|
|
|
|
g_assert_cmpuint(u, ==, 42);
|
|
|
|
visit_type_uint64(v, "number2", &u, &error_abort);
|
|
|
|
g_assert_cmpuint(u, ==, 42);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Trailing crap */
|
|
|
|
qdict = keyval_parse("number1=3.14,number2=08",
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_uint64(v, "number1", &u, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_type_uint64(v, "number2", &u, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_keyval_visit_size(void)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
Visitor *v;
|
|
|
|
QDict *qdict;
|
|
|
|
uint64_t sz;
|
|
|
|
|
|
|
|
/* Lower limit zero */
|
|
|
|
qdict = keyval_parse("sz1=0", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_size(v, "sz1", &sz, &error_abort);
|
|
|
|
g_assert_cmpuint(sz, ==, 0);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Note: precision is 53 bits since we're parsing with strtod() */
|
|
|
|
|
|
|
|
/* Around limit of precision: 2^53-1, 2^53, 2^53+1 */
|
|
|
|
qdict = keyval_parse("sz1=9007199254740991,"
|
|
|
|
"sz2=9007199254740992,"
|
|
|
|
"sz3=9007199254740993",
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_size(v, "sz1", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 0x1fffffffffffff);
|
|
|
|
visit_type_size(v, "sz2", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 0x20000000000000);
|
|
|
|
visit_type_size(v, "sz3", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 0x20000000000000);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Close to signed upper limit 0x7ffffffffffffc00 (53 msbs set) */
|
|
|
|
qdict = keyval_parse("sz1=9223372036854774784," /* 7ffffffffffffc00 */
|
|
|
|
"sz2=9223372036854775295", /* 7ffffffffffffdff */
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_size(v, "sz1", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 0x7ffffffffffffc00);
|
|
|
|
visit_type_size(v, "sz2", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 0x7ffffffffffffc00);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Close to actual upper limit 0xfffffffffffff800 (53 msbs set) */
|
|
|
|
qdict = keyval_parse("sz1=18446744073709549568," /* fffffffffffff800 */
|
|
|
|
"sz2=18446744073709550591", /* fffffffffffffbff */
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_size(v, "sz1", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 0xfffffffffffff800);
|
|
|
|
visit_type_size(v, "sz2", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 0xfffffffffffff800);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Beyond limits */
|
|
|
|
qdict = keyval_parse("sz1=-1,"
|
|
|
|
"sz2=18446744073709550592", /* fffffffffffffc00 */
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_size(v, "sz1", &sz, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_type_size(v, "sz2", &sz, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Suffixes */
|
|
|
|
qdict = keyval_parse("sz1=8b,sz2=1.5k,sz3=2M,sz4=0.1G,sz5=16777215T",
|
|
|
|
NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_size(v, "sz1", &sz, &error_abort);
|
|
|
|
g_assert_cmpuint(sz, ==, 8);
|
|
|
|
visit_type_size(v, "sz2", &sz, &error_abort);
|
|
|
|
g_assert_cmpuint(sz, ==, 1536);
|
|
|
|
visit_type_size(v, "sz3", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 2 * M_BYTE);
|
|
|
|
visit_type_size(v, "sz4", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, G_BYTE / 10);
|
|
|
|
visit_type_size(v, "sz5", &sz, &error_abort);
|
|
|
|
g_assert_cmphex(sz, ==, 16777215 * T_BYTE);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Beyond limit with suffix */
|
|
|
|
qdict = keyval_parse("sz1=16777216T", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_size(v, "sz1", &sz, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
/* Trailing crap */
|
|
|
|
qdict = keyval_parse("sz1=16E,sz2=16Gi", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_size(v, "sz1", &sz, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_type_size(v, "sz2", &sz, &err);
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_keyval_visit_dict(void)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
Visitor *v;
|
|
|
|
QDict *qdict;
|
|
|
|
int64_t i;
|
|
|
|
|
|
|
|
qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_start_struct(v, "a", NULL, 0, &error_abort);
|
|
|
|
visit_start_struct(v, "b", NULL, 0, &error_abort);
|
|
|
|
visit_type_int(v, "c", &i, &error_abort);
|
|
|
|
g_assert_cmpint(i, ==, 2);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_type_int(v, "d", &i, &error_abort);
|
|
|
|
g_assert_cmpint(i, ==, 3);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
|
|
|
|
qdict = keyval_parse("a.b=", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_start_struct(v, "a", NULL, 0, &error_abort);
|
|
|
|
visit_type_int(v, "c", &i, &err); /* a.c missing */
|
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_check_struct(v, &err);
|
|
|
|
error_free_or_abort(&err); /* a.b unexpected */
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
}
|
|
|
|
|
keyval: Support lists
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>
2017-02-28 22:27:10 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-02-28 22:26:51 +01:00
|
|
|
static void test_keyval_visit_optional(void)
|
|
|
|
{
|
|
|
|
Visitor *v;
|
|
|
|
QDict *qdict;
|
|
|
|
bool present;
|
|
|
|
int64_t i;
|
|
|
|
|
|
|
|
qdict = keyval_parse("a.b=1", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_optional(v, "b", &present);
|
|
|
|
g_assert(!present); /* b missing */
|
|
|
|
visit_optional(v, "a", &present);
|
|
|
|
g_assert(present); /* a present */
|
|
|
|
visit_start_struct(v, "a", NULL, 0, &error_abort);
|
|
|
|
visit_optional(v, "b", &present);
|
|
|
|
g_assert(present); /* a.b present */
|
|
|
|
visit_type_int(v, "b", &i, &error_abort);
|
|
|
|
g_assert_cmpint(i, ==, 1);
|
|
|
|
visit_optional(v, "a", &present);
|
|
|
|
g_assert(!present); /* a.a missing */
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
}
|
|
|
|
|
2017-03-20 13:55:46 +01:00
|
|
|
static void test_keyval_visit_alternate(void)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
Visitor *v;
|
|
|
|
QDict *qdict;
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 18:42:15 +02:00
|
|
|
AltStrObj *aso;
|
2017-06-07 18:35:55 +02:00
|
|
|
AltNumEnum *ane;
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 18:42:15 +02:00
|
|
|
AltEnumBool *aeb;
|
2017-03-20 13:55:46 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Can't do scalar alternate variants other than string. You get
|
|
|
|
* the string variant if there is one, else an error.
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 18:42:15 +02:00
|
|
|
* TODO make it work for unambiguous cases like AltEnumBool below
|
2017-03-20 13:55:46 +01:00
|
|
|
*/
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 18:42:15 +02:00
|
|
|
qdict = keyval_parse("a=1,b=2,c=on", NULL, &error_abort);
|
2017-03-20 13:55:46 +01:00
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 18:42:15 +02:00
|
|
|
visit_type_AltStrObj(v, "a", &aso, &error_abort);
|
|
|
|
g_assert_cmpint(aso->type, ==, QTYPE_QSTRING);
|
|
|
|
g_assert_cmpstr(aso->u.s, ==, "1");
|
|
|
|
qapi_free_AltStrObj(aso);
|
2017-06-07 18:35:55 +02:00
|
|
|
visit_type_AltNumEnum(v, "b", &ane, &err);
|
qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag. That's why we
require alternate members to have distinct QTypes.
The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars. The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,... The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.
In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'. It has an integer member
and a 'QGASeek' member. The latter is an enumeration with values
'set', 'cur', 'end'. The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious. However, our current implementation
falls apart at run time for b=0, b=1, and so forth. Fixable, but not
today; add a test case and a TODO comment.
Now consider an alternate type with a string and an integer member.
What's the meaning of a=42? Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible. This isn't
just an implementation problem, it's fundamental. Our current
implementation will pick string.
So far, we haven't needed such alternates. To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:
* A string member clashes with any other scalar member
* An enumeration member clashes with bool members when it has value
'on' or 'off'.
* An enumeration member clashes with numeric members when it has a
value that starts with '-', '+', or a decimal digit. This is a
rather lazy approximation of the actual number syntax accepted by
the visitor.
Note that enumeration values starting with '-' and '+' are rejected
elsewhere already, but better safe than sorry.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-22 18:42:15 +02:00
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_type_AltEnumBool(v, "c", &aeb, &err);
|
2017-03-20 13:55:46 +01:00
|
|
|
error_free_or_abort(&err);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_keyval_visit_any(void)
|
|
|
|
{
|
|
|
|
Visitor *v;
|
|
|
|
QDict *qdict;
|
|
|
|
QObject *any;
|
|
|
|
QList *qlist;
|
|
|
|
QString *qstr;
|
|
|
|
|
|
|
|
qdict = keyval_parse("a.0=null,a.1=1", NULL, &error_abort);
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
|
|
|
QDECREF(qdict);
|
|
|
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
|
|
|
visit_type_any(v, "a", &any, &error_abort);
|
|
|
|
qlist = qobject_to_qlist(any);
|
|
|
|
g_assert(qlist);
|
|
|
|
qstr = qobject_to_qstring(qlist_pop(qlist));
|
|
|
|
g_assert_cmpstr(qstring_get_str(qstr), ==, "null");
|
2017-05-04 00:38:42 +02:00
|
|
|
QDECREF(qstr);
|
2017-03-20 13:55:46 +01:00
|
|
|
qstr = qobject_to_qstring(qlist_pop(qlist));
|
|
|
|
g_assert_cmpstr(qstring_get_str(qstr), ==, "1");
|
|
|
|
g_assert(qlist_empty(qlist));
|
2017-05-04 00:38:42 +02:00
|
|
|
QDECREF(qstr);
|
|
|
|
qobject_decref(any);
|
2017-03-20 13:55:46 +01:00
|
|
|
visit_check_struct(v, &error_abort);
|
|
|
|
visit_end_struct(v, NULL);
|
|
|
|
visit_free(v);
|
|
|
|
}
|
|
|
|
|
keyval: New keyval_parse()
keyval_parse() parses KEY=VALUE,... into a QDict. Works like
qemu_opts_parse(), except:
* Returns a QDict instead of a QemuOpts (d'oh).
* Supports nesting, unlike QemuOpts: a KEY is split into key
fragments at '.' (dotted key convention; the block layer does
something similar on top of QemuOpts). The key fragments are QDict
keys, and the last one's value is updated to VALUE.
* Each key fragment may be up to 127 bytes long. qemu_opts_parse()
limits the entire key to 127 bytes.
* Overlong key fragments are rejected. qemu_opts_parse() silently
truncates them.
* Empty key fragments are rejected. qemu_opts_parse() happily
accepts empty keys.
* It does not store the returned value. qemu_opts_parse() stores it
in the QemuOptsList.
* It does not treat parameter "id" specially. qemu_opts_parse()
ignores all but the first "id", and fails when its value isn't
id_wellformed(), or duplicate (a QemuOpts with the same ID is
already stored). It also screws up when a value contains ",id=".
* Implied value is not supported. qemu_opts_parse() desugars "foo" to
"foo=on", and "nofoo" to "foo=off".
* An implied key's value can't be empty, and can't contain ','.
I intend to grow this into a saner replacement for QemuOpts. It'll
take time, though.
Note: keyval_parse() provides no way to do lists, and its key syntax
is incompatible with the __RFQDN_ prefix convention for downstream
extensions, because it blindly splits at '.', even in __RFQDN_. Both
issues will be addressed later in the series.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1488317230-26248-4-git-send-email-armbru@redhat.com>
2017-02-28 22:26:49 +01:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
g_test_init(&argc, &argv, NULL);
|
|
|
|
g_test_add_func("/keyval/keyval_parse", test_keyval_parse);
|
keyval: Support lists
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>
2017-02-28 22:27:10 +01:00
|
|
|
g_test_add_func("/keyval/keyval_parse/list", test_keyval_parse_list);
|
2017-02-28 22:26:51 +01:00
|
|
|
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);
|
keyval: Support lists
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>
2017-02-28 22:27:10 +01:00
|
|
|
g_test_add_func("/keyval/visit/list", test_keyval_visit_list);
|
2017-02-28 22:26:51 +01:00
|
|
|
g_test_add_func("/keyval/visit/optional", test_keyval_visit_optional);
|
2017-03-20 13:55:46 +01:00
|
|
|
g_test_add_func("/keyval/visit/alternate", test_keyval_visit_alternate);
|
|
|
|
g_test_add_func("/keyval/visit/any", test_keyval_visit_any);
|
keyval: New keyval_parse()
keyval_parse() parses KEY=VALUE,... into a QDict. Works like
qemu_opts_parse(), except:
* Returns a QDict instead of a QemuOpts (d'oh).
* Supports nesting, unlike QemuOpts: a KEY is split into key
fragments at '.' (dotted key convention; the block layer does
something similar on top of QemuOpts). The key fragments are QDict
keys, and the last one's value is updated to VALUE.
* Each key fragment may be up to 127 bytes long. qemu_opts_parse()
limits the entire key to 127 bytes.
* Overlong key fragments are rejected. qemu_opts_parse() silently
truncates them.
* Empty key fragments are rejected. qemu_opts_parse() happily
accepts empty keys.
* It does not store the returned value. qemu_opts_parse() stores it
in the QemuOptsList.
* It does not treat parameter "id" specially. qemu_opts_parse()
ignores all but the first "id", and fails when its value isn't
id_wellformed(), or duplicate (a QemuOpts with the same ID is
already stored). It also screws up when a value contains ",id=".
* Implied value is not supported. qemu_opts_parse() desugars "foo" to
"foo=on", and "nofoo" to "foo=off".
* An implied key's value can't be empty, and can't contain ','.
I intend to grow this into a saner replacement for QemuOpts. It'll
take time, though.
Note: keyval_parse() provides no way to do lists, and its key syntax
is incompatible with the __RFQDN_ prefix convention for downstream
extensions, because it blindly splits at '.', even in __RFQDN_. Both
issues will be addressed later in the series.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1488317230-26248-4-git-send-email-armbru@redhat.com>
2017-02-28 22:26:49 +01:00
|
|
|
g_test_run();
|
|
|
|
return 0;
|
|
|
|
}
|