qemu-patch-raspberry4/tests/test-qemu-opts.c
Eric Blake cf923b783e utils: Improve qemu_strtosz() to have 64 bits of precision
We have multiple clients of qemu_strtosz (qemu-io, the opts visitor,
the keyval visitor), and it gets annoying that edge-case testing is
impacted by implicit rounding to 53 bits of precision due to parsing
with strtod().  As an example posted by Rich Jones:
 $ nbdkit memory $(( 2**63 - 2**30 )) --run \
   'build/qemu-io -f raw "$uri" -c "w -P 3 $(( 2**63 - 2**30 - 512 )) 512" '
 write failed: Input/output error

because 9223372035781033472 got rounded to 0x7fffffffc0000000 which is
out of bounds.

It is also worth noting that our existing parser, by virtue of using
strtod(), accepts decimal AND hex numbers, even though test-cutils
previously lacked any coverage of the latter until the previous patch.
We do have existing clients that expect a hex parse to work (for
example, iotest 33 using qemu-io -c "write -P 0xa 0x200 0x400"), but
strtod() parses "08" as 8 rather than as an invalid octal number, so
we know there are no clients that depend on octal.  Our use of
strtod() also means that "0x1.8k" would actually parse as 1536 (the
fraction is 8/16), rather than 1843 (if the fraction were 8/10); but
as this was not covered in the testsuite, I have no qualms forbidding
hex fractions as invalid, so this patch declares that the use of
fractions is only supported with decimal input, and enhances the
testsuite to document that.

Our previous use of strtod() meant that -1 parsed as a negative; now
that we parse with strtoull(), negative values can wrap around modulo
2^64, so we have to explicitly check whether the user passed in a '-';
and make it consistent to also reject '-0'.  This has the minor effect
of treating negative values as EINVAL (with no change to endptr)
rather than ERANGE (with endptr advanced to what was parsed), visible
in the updated iotest output.

We also had no testsuite coverage of "1.1e0k", which happened to parse
under strtod() but is unlikely to occur in practice; as long as we are
making things more robust, it is easy enough to reject the use of
exponents in a strtod parse.

The fix is done by breaking the parse into an integer prefix (no loss
in precision), rejecting negative values (since we can no longer rely
on strtod() to do that), determining if a decimal or hexadecimal parse
was intended (with the new restriction that a fractional hex parse is
not allowed), and where appropriate, using a floating point fractional
parse (where we also scan to reject use of exponents in the fraction).
The bulk of the patch is then updates to the testsuite to match our
new precision, as well as adding new cases we reject (whether they
were rejected or inadvertently accepted before).

Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <20210211204438.1184395-3-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2021-03-08 13:36:12 -06:00

1058 lines
34 KiB
C

/*
* QemuOpts unit-tests.
*
* Copyright (C) 2014 Leandro Dorileo <l@dorileo.org>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/units.h"
#include "qemu/option.h"
#include "qemu/option_int.h"
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qstring.h"
#include "qemu/config-file.h"
static QemuOptsList opts_list_01 = {
.name = "opts_list_01",
.head = QTAILQ_HEAD_INITIALIZER(opts_list_01.head),
.desc = {
{
.name = "str1",
.type = QEMU_OPT_STRING,
.help = "Help texts are preserved in qemu_opts_append",
.def_value_str = "default",
},{
.name = "str2",
.type = QEMU_OPT_STRING,
},{
.name = "str3",
.type = QEMU_OPT_STRING,
},{
.name = "number1",
.type = QEMU_OPT_NUMBER,
.help = "Having help texts only for some options is okay",
},{
.name = "number2",
.type = QEMU_OPT_NUMBER,
},
{ /* end of list */ }
},
};
static QemuOptsList opts_list_02 = {
.name = "opts_list_02",
.head = QTAILQ_HEAD_INITIALIZER(opts_list_02.head),
.desc = {
{
.name = "str1",
.type = QEMU_OPT_STRING,
},{
.name = "str2",
.type = QEMU_OPT_STRING,
},{
.name = "bool1",
.type = QEMU_OPT_BOOL,
},{
.name = "bool2",
.type = QEMU_OPT_BOOL,
},{
.name = "size1",
.type = QEMU_OPT_SIZE,
},{
.name = "size2",
.type = QEMU_OPT_SIZE,
},{
.name = "size3",
.type = QEMU_OPT_SIZE,
},
{ /* end of list */ }
},
};
static QemuOptsList opts_list_03 = {
.name = "opts_list_03",
.implied_opt_name = "implied",
.head = QTAILQ_HEAD_INITIALIZER(opts_list_03.head),
.desc = {
/* no elements => accept any params */
{ /* end of list */ }
},
};
static QemuOptsList opts_list_04 = {
.name = "opts_list_04",
.head = QTAILQ_HEAD_INITIALIZER(opts_list_04.head),
.merge_lists = true,
.desc = {
{
.name = "str3",
.type = QEMU_OPT_STRING,
},
{ /* end of list */ }
},
};
static void register_opts(void)
{
qemu_add_opts(&opts_list_01);
qemu_add_opts(&opts_list_02);
qemu_add_opts(&opts_list_03);
qemu_add_opts(&opts_list_04);
}
static void test_find_unknown_opts(void)
{
QemuOptsList *list;
Error *err = NULL;
/* should not return anything, we don't have an "unknown" option */
list = qemu_find_opts_err("unknown", &err);
g_assert(list == NULL);
error_free_or_abort(&err);
}
static void test_qemu_find_opts(void)
{
QemuOptsList *list;
/* we have an "opts_list_01" option, should return it */
list = qemu_find_opts("opts_list_01");
g_assert(list != NULL);
g_assert_cmpstr(list->name, ==, "opts_list_01");
}
static void test_qemu_opts_create(void)
{
QemuOptsList *list;
QemuOpts *opts;
list = qemu_find_opts("opts_list_01");
g_assert(list != NULL);
g_assert(QTAILQ_EMPTY(&list->head));
g_assert_cmpstr(list->name, ==, "opts_list_01");
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
/* create the opts */
opts = qemu_opts_create(list, NULL, 0, &error_abort);
g_assert(opts != NULL);
g_assert(!QTAILQ_EMPTY(&list->head));
/* now we've create the opts, must find it */
opts = qemu_opts_find(list, NULL);
g_assert(opts != NULL);
qemu_opts_del(opts);
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
}
static void test_qemu_opt_get(void)
{
QemuOptsList *list;
QemuOpts *opts;
const char *opt = NULL;
list = qemu_find_opts("opts_list_01");
g_assert(list != NULL);
g_assert(QTAILQ_EMPTY(&list->head));
g_assert_cmpstr(list->name, ==, "opts_list_01");
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
/* create the opts */
opts = qemu_opts_create(list, NULL, 0, &error_abort);
g_assert(opts != NULL);
g_assert(!QTAILQ_EMPTY(&list->head));
/* haven't set anything to str2 yet */
opt = qemu_opt_get(opts, "str2");
g_assert(opt == NULL);
qemu_opt_set(opts, "str2", "value", &error_abort);
/* now we have set str2, should know about it */
opt = qemu_opt_get(opts, "str2");
g_assert_cmpstr(opt, ==, "value");
qemu_opt_set(opts, "str2", "value2", &error_abort);
/* having reset the value, the returned should be the reset one */
opt = qemu_opt_get(opts, "str2");
g_assert_cmpstr(opt, ==, "value2");
qemu_opts_del(opts);
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
}
static void test_qemu_opt_get_bool(void)
{
QemuOptsList *list;
QemuOpts *opts;
bool opt;
list = qemu_find_opts("opts_list_02");
g_assert(list != NULL);
g_assert(QTAILQ_EMPTY(&list->head));
g_assert_cmpstr(list->name, ==, "opts_list_02");
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
/* create the opts */
opts = qemu_opts_create(list, NULL, 0, &error_abort);
g_assert(opts != NULL);
g_assert(!QTAILQ_EMPTY(&list->head));
/* haven't set anything to bool1 yet, so defval should be returned */
opt = qemu_opt_get_bool(opts, "bool1", false);
g_assert(opt == false);
qemu_opt_set_bool(opts, "bool1", true, &error_abort);
/* now we have set bool1, should know about it */
opt = qemu_opt_get_bool(opts, "bool1", false);
g_assert(opt == true);
/* having reset the value, opt should be the reset one not defval */
qemu_opt_set_bool(opts, "bool1", false, &error_abort);
opt = qemu_opt_get_bool(opts, "bool1", true);
g_assert(opt == false);
qemu_opts_del(opts);
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
}
static void test_qemu_opt_get_number(void)
{
QemuOptsList *list;
QemuOpts *opts;
uint64_t opt;
list = qemu_find_opts("opts_list_01");
g_assert(list != NULL);
g_assert(QTAILQ_EMPTY(&list->head));
g_assert_cmpstr(list->name, ==, "opts_list_01");
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
/* create the opts */
opts = qemu_opts_create(list, NULL, 0, &error_abort);
g_assert(opts != NULL);
g_assert(!QTAILQ_EMPTY(&list->head));
/* haven't set anything to number1 yet, so defval should be returned */
opt = qemu_opt_get_number(opts, "number1", 5);
g_assert(opt == 5);
qemu_opt_set_number(opts, "number1", 10, &error_abort);
/* now we have set number1, should know about it */
opt = qemu_opt_get_number(opts, "number1", 5);
g_assert(opt == 10);
/* having reset it, the returned should be the reset one not defval */
qemu_opt_set_number(opts, "number1", 15, &error_abort);
opt = qemu_opt_get_number(opts, "number1", 5);
g_assert(opt == 15);
qemu_opts_del(opts);
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
}
static void test_qemu_opt_get_size(void)
{
QemuOptsList *list;
QemuOpts *opts;
uint64_t opt;
QDict *dict;
list = qemu_find_opts("opts_list_02");
g_assert(list != NULL);
g_assert(QTAILQ_EMPTY(&list->head));
g_assert_cmpstr(list->name, ==, "opts_list_02");
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
/* create the opts */
opts = qemu_opts_create(list, NULL, 0, &error_abort);
g_assert(opts != NULL);
g_assert(!QTAILQ_EMPTY(&list->head));
/* haven't set anything to size1 yet, so defval should be returned */
opt = qemu_opt_get_size(opts, "size1", 5);
g_assert(opt == 5);
dict = qdict_new();
g_assert(dict != NULL);
qdict_put_str(dict, "size1", "10");
qemu_opts_absorb_qdict(opts, dict, &error_abort);
g_assert(error_abort == NULL);
/* now we have set size1, should know about it */
opt = qemu_opt_get_size(opts, "size1", 5);
g_assert(opt == 10);
/* reset value */
qdict_put_str(dict, "size1", "15");
qemu_opts_absorb_qdict(opts, dict, &error_abort);
g_assert(error_abort == NULL);
/* test the reset value */
opt = qemu_opt_get_size(opts, "size1", 5);
g_assert(opt == 15);
qdict_del(dict, "size1");
g_free(dict);
qemu_opts_del(opts);
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
}
static void test_qemu_opt_unset(void)
{
QemuOpts *opts;
const char *value;
int ret;
/* dynamically initialized (parsed) opts */
opts = qemu_opts_parse(&opts_list_03, "key=value", false, NULL);
g_assert(opts != NULL);
/* check default/parsed value */
value = qemu_opt_get(opts, "key");
g_assert_cmpstr(value, ==, "value");
/* reset it to value2 */
qemu_opt_set(opts, "key", "value2", &error_abort);
value = qemu_opt_get(opts, "key");
g_assert_cmpstr(value, ==, "value2");
/* unset, valid only for "accept any" */
ret = qemu_opt_unset(opts, "key");
g_assert(ret == 0);
/* after reset the value should be the parsed/default one */
value = qemu_opt_get(opts, "key");
g_assert_cmpstr(value, ==, "value");
qemu_opts_del(opts);
}
static void test_qemu_opts_reset(void)
{
QemuOptsList *list;
QemuOpts *opts;
uint64_t opt;
list = qemu_find_opts("opts_list_01");
g_assert(list != NULL);
g_assert(QTAILQ_EMPTY(&list->head));
g_assert_cmpstr(list->name, ==, "opts_list_01");
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
/* create the opts */
opts = qemu_opts_create(list, NULL, 0, &error_abort);
g_assert(opts != NULL);
g_assert(!QTAILQ_EMPTY(&list->head));
/* haven't set anything to number1 yet, so defval should be returned */
opt = qemu_opt_get_number(opts, "number1", 5);
g_assert(opt == 5);
qemu_opt_set_number(opts, "number1", 10, &error_abort);
/* now we have set number1, should know about it */
opt = qemu_opt_get_number(opts, "number1", 5);
g_assert(opt == 10);
qemu_opts_reset(list);
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
}
static void test_qemu_opts_set(void)
{
QemuOptsList *list;
QemuOpts *opts;
const char *opt;
list = qemu_find_opts("opts_list_04");
g_assert(list != NULL);
g_assert(QTAILQ_EMPTY(&list->head));
g_assert_cmpstr(list->name, ==, "opts_list_04");
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
/* implicitly create opts and set str3 value */
qemu_opts_set(list, "str3", "value", &error_abort);
g_assert(!QTAILQ_EMPTY(&list->head));
/* get the just created opts */
opts = qemu_opts_find(list, NULL);
g_assert(opts != NULL);
/* check the str3 value */
opt = qemu_opt_get(opts, "str3");
g_assert_cmpstr(opt, ==, "value");
qemu_opts_del(opts);
/* should not find anything at this point */
opts = qemu_opts_find(list, NULL);
g_assert(opts == NULL);
}
static int opts_count_iter(void *opaque, const char *name, const char *value,
Error **errp)
{
(*(size_t *)opaque)++;
return 0;
}
static size_t opts_count(QemuOpts *opts)
{
size_t n = 0;
qemu_opt_foreach(opts, opts_count_iter, &n, NULL);
return n;
}
static void test_opts_parse(void)
{
Error *err = NULL;
QemuOpts *opts;
/* Nothing */
opts = qemu_opts_parse(&opts_list_03, "", false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 0);
/* Empty key */
opts = qemu_opts_parse(&opts_list_03, "=val", false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpstr(qemu_opt_get(opts, ""), ==, "val");
/* Multiple keys, last one wins */
opts = qemu_opts_parse(&opts_list_03, "a=1,b=2,,x,a=3",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 3);
g_assert_cmpstr(qemu_opt_get(opts, "a"), ==, "3");
g_assert_cmpstr(qemu_opt_get(opts, "b"), ==, "2,x");
/* Except when it doesn't */
opts = qemu_opts_parse(&opts_list_03, "id=foo,id=bar",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 0);
g_assert_cmpstr(qemu_opts_id(opts), ==, "foo");
/* TODO Cover low-level access to repeated keys */
/* Trailing comma is ignored */
opts = qemu_opts_parse(&opts_list_03, "x=y,", false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpstr(qemu_opt_get(opts, "x"), ==, "y");
/* Except when it isn't */
opts = qemu_opts_parse(&opts_list_03, ",", false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpstr(qemu_opt_get(opts, ""), ==, "on");
/* Duplicate ID */
opts = qemu_opts_parse(&opts_list_03, "x=y,id=foo", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
/* TODO Cover .merge_lists = true */
/* Buggy ID recognition (fixed) */
opts = qemu_opts_parse(&opts_list_03, "x=,,id=bar", false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert(!qemu_opts_id(opts));
g_assert_cmpstr(qemu_opt_get(opts, "x"), ==, ",id=bar");
/* Anti-social ID */
opts = qemu_opts_parse(&opts_list_01, "id=666", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
/* Implied value (qemu_opts_parse warns but accepts it) */
opts = qemu_opts_parse(&opts_list_03, "an,noaus,noaus=",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 3);
g_assert_cmpstr(qemu_opt_get(opts, "an"), ==, "on");
g_assert_cmpstr(qemu_opt_get(opts, "aus"), ==, "off");
g_assert_cmpstr(qemu_opt_get(opts, "noaus"), ==, "");
/* Implied value, negated empty key */
opts = qemu_opts_parse(&opts_list_03, "no", false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpstr(qemu_opt_get(opts, ""), ==, "off");
/* Implied key */
opts = qemu_opts_parse(&opts_list_03, "an,noaus,noaus=", true,
&error_abort);
g_assert_cmpuint(opts_count(opts), ==, 3);
g_assert_cmpstr(qemu_opt_get(opts, "implied"), ==, "an");
g_assert_cmpstr(qemu_opt_get(opts, "aus"), ==, "off");
g_assert_cmpstr(qemu_opt_get(opts, "noaus"), ==, "");
/* Implied key with empty value */
opts = qemu_opts_parse(&opts_list_03, ",", true, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpstr(qemu_opt_get(opts, "implied"), ==, "");
/* Implied key with comma value */
opts = qemu_opts_parse(&opts_list_03, ",,,a=1", true, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 2);
g_assert_cmpstr(qemu_opt_get(opts, "implied"), ==, ",");
g_assert_cmpstr(qemu_opt_get(opts, "a"), ==, "1");
/* Empty key is not an implied key */
opts = qemu_opts_parse(&opts_list_03, "=val", true, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpstr(qemu_opt_get(opts, ""), ==, "val");
/* Unknown key */
opts = qemu_opts_parse(&opts_list_01, "nonexistent=", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
qemu_opts_reset(&opts_list_01);
qemu_opts_reset(&opts_list_03);
}
static void test_opts_parse_bool(void)
{
Error *err = NULL;
QemuOpts *opts;
opts = qemu_opts_parse(&opts_list_02, "bool1=on,bool2=off",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 2);
g_assert(qemu_opt_get_bool(opts, "bool1", false));
g_assert(!qemu_opt_get_bool(opts, "bool2", true));
opts = qemu_opts_parse(&opts_list_02, "bool1=offer", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
qemu_opts_reset(&opts_list_02);
}
static void test_opts_parse_number(void)
{
Error *err = NULL;
QemuOpts *opts;
/* Lower limit zero */
opts = qemu_opts_parse(&opts_list_01, "number1=0", false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpuint(qemu_opt_get_number(opts, "number1", 1), ==, 0);
/* Upper limit 2^64-1 */
opts = qemu_opts_parse(&opts_list_01,
"number1=18446744073709551615,number2=-1",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 2);
g_assert_cmphex(qemu_opt_get_number(opts, "number1", 1), ==, UINT64_MAX);
g_assert_cmphex(qemu_opt_get_number(opts, "number2", 0), ==, UINT64_MAX);
/* Above upper limit */
opts = qemu_opts_parse(&opts_list_01, "number1=18446744073709551616",
false, &err);
error_free_or_abort(&err);
g_assert(!opts);
/* Below lower limit */
opts = qemu_opts_parse(&opts_list_01, "number1=-18446744073709551616",
false, &err);
error_free_or_abort(&err);
g_assert(!opts);
/* Hex and octal */
opts = qemu_opts_parse(&opts_list_01, "number1=0x2a,number2=052",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 2);
g_assert_cmpuint(qemu_opt_get_number(opts, "number1", 1), ==, 42);
g_assert_cmpuint(qemu_opt_get_number(opts, "number2", 0), ==, 42);
/* Invalid */
opts = qemu_opts_parse(&opts_list_01, "number1=", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
opts = qemu_opts_parse(&opts_list_01, "number1=eins", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
/* Leading whitespace */
opts = qemu_opts_parse(&opts_list_01, "number1= \t42",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpuint(qemu_opt_get_number(opts, "number1", 1), ==, 42);
/* Trailing crap */
opts = qemu_opts_parse(&opts_list_01, "number1=3.14", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
opts = qemu_opts_parse(&opts_list_01, "number1=08", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
opts = qemu_opts_parse(&opts_list_01, "number1=0 ", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
qemu_opts_reset(&opts_list_01);
}
static void test_opts_parse_size(void)
{
Error *err = NULL;
QemuOpts *opts;
/* Lower limit zero */
opts = qemu_opts_parse(&opts_list_02, "size1=0", false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmpuint(qemu_opt_get_size(opts, "size1", 1), ==, 0);
/* Note: full 64 bits of precision */
/* Around double limit of precision: 2^53-1, 2^53, 2^53+1 */
opts = qemu_opts_parse(&opts_list_02,
"size1=9007199254740991,"
"size2=9007199254740992,"
"size3=9007199254740993",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 3);
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
==, 0x1fffffffffffff);
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
==, 0x20000000000000);
g_assert_cmphex(qemu_opt_get_size(opts, "size3", 1),
==, 0x20000000000001);
/* Close to signed int limit: 2^63-1, 2^63, 2^63+1 */
opts = qemu_opts_parse(&opts_list_02,
"size1=9223372036854775807," /* 7fffffffffffffff */
"size2=9223372036854775808," /* 8000000000000000 */
"size3=9223372036854775809", /* 8000000000000001 */
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 3);
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
==, 0x7fffffffffffffff);
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
==, 0x8000000000000000);
g_assert_cmphex(qemu_opt_get_size(opts, "size3", 1),
==, 0x8000000000000001);
/* Close to actual upper limit 0xfffffffffffff800 (53 msbs set) */
opts = qemu_opts_parse(&opts_list_02,
"size1=18446744073709549568," /* fffffffffffff800 */
"size2=18446744073709550591", /* fffffffffffffbff */
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 2);
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
==, 0xfffffffffffff800);
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
==, 0xfffffffffffffbff);
/* Actual limit, 2^64-1 */
opts = qemu_opts_parse(&opts_list_02,
"size1=18446744073709551615", /* ffffffffffffffff */
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 1);
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
==, 0xffffffffffffffff);
/* Beyond limits */
opts = qemu_opts_parse(&opts_list_02, "size1=-1", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
opts = qemu_opts_parse(&opts_list_02,
"size1=18446744073709551616", /* 2^64 */
false, &err);
error_free_or_abort(&err);
g_assert(!opts);
/* Suffixes */
opts = qemu_opts_parse(&opts_list_02, "size1=8b,size2=1.5k,size3=2M",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 3);
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 0), ==, 8);
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 0), ==, 1536);
g_assert_cmphex(qemu_opt_get_size(opts, "size3", 0), ==, 2 * MiB);
opts = qemu_opts_parse(&opts_list_02, "size1=0.1G,size2=16777215T",
false, &error_abort);
g_assert_cmpuint(opts_count(opts), ==, 2);
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 0), ==, GiB / 10);
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 0), ==, 16777215ULL * TiB);
/* Beyond limit with suffix */
opts = qemu_opts_parse(&opts_list_02, "size1=16777216T",
false, &err);
error_free_or_abort(&err);
g_assert(!opts);
/* Trailing crap */
opts = qemu_opts_parse(&opts_list_02, "size1=16E", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
opts = qemu_opts_parse(&opts_list_02, "size1=16Gi", false, &err);
error_free_or_abort(&err);
g_assert(!opts);
qemu_opts_reset(&opts_list_02);
}
static void test_has_help_option(void)
{
static const struct {
const char *params;
/* expected value of qemu_opt_has_help_opt() with implied=false */
bool expect;
/* expected value of qemu_opt_has_help_opt() with implied=true */
bool expect_implied;
} test[] = {
{ "help", true, false },
{ "?", true, false },
{ "helpme", false, false },
{ "?me", false, false },
{ "a,help", true, true },
{ "a,?", true, true },
{ "a=0,help,b", true, true },
{ "a=0,?,b", true, true },
{ "help,b=1", true, false },
{ "?,b=1", true, false },
{ "a,b,,help", true, true },
{ "a,b,,?", true, true },
};
int i;
QemuOpts *opts;
for (i = 0; i < ARRAY_SIZE(test); i++) {
g_assert_cmpint(has_help_option(test[i].params),
==, test[i].expect);
opts = qemu_opts_parse(&opts_list_03, test[i].params, false,
&error_abort);
g_assert_cmpint(qemu_opt_has_help_opt(opts),
==, test[i].expect);
qemu_opts_del(opts);
opts = qemu_opts_parse(&opts_list_03, test[i].params, true,
&error_abort);
g_assert_cmpint(qemu_opt_has_help_opt(opts),
==, test[i].expect_implied);
qemu_opts_del(opts);
}
}
static void append_verify_list_01(QemuOptDesc *desc, bool with_overlapping)
{
int i = 0;
if (with_overlapping) {
g_assert_cmpstr(desc[i].name, ==, "str1");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
g_assert_cmpstr(desc[i].help, ==,
"Help texts are preserved in qemu_opts_append");
g_assert_cmpstr(desc[i].def_value_str, ==, "default");
i++;
g_assert_cmpstr(desc[i].name, ==, "str2");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
}
g_assert_cmpstr(desc[i].name, ==, "str3");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, "number1");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_NUMBER);
g_assert_cmpstr(desc[i].help, ==,
"Having help texts only for some options is okay");
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, "number2");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_NUMBER);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, NULL);
}
static void append_verify_list_02(QemuOptDesc *desc)
{
int i = 0;
g_assert_cmpstr(desc[i].name, ==, "str1");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, "str2");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_STRING);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, "bool1");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_BOOL);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, "bool2");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_BOOL);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, "size1");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_SIZE);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, "size2");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_SIZE);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
i++;
g_assert_cmpstr(desc[i].name, ==, "size3");
g_assert_cmpint(desc[i].type, ==, QEMU_OPT_SIZE);
g_assert_cmpstr(desc[i].help, ==, NULL);
g_assert_cmpstr(desc[i].def_value_str, ==, NULL);
}
static void test_opts_append_to_null(void)
{
QemuOptsList *merged;
merged = qemu_opts_append(NULL, &opts_list_01);
g_assert(merged != &opts_list_01);
g_assert_cmpstr(merged->name, ==, NULL);
g_assert_cmpstr(merged->implied_opt_name, ==, NULL);
g_assert_false(merged->merge_lists);
append_verify_list_01(merged->desc, true);
qemu_opts_free(merged);
}
static void test_opts_append(void)
{
QemuOptsList *first, *merged;
first = qemu_opts_append(NULL, &opts_list_02);
merged = qemu_opts_append(first, &opts_list_01);
g_assert(first != &opts_list_02);
g_assert(merged != &opts_list_01);
g_assert_cmpstr(merged->name, ==, NULL);
g_assert_cmpstr(merged->implied_opt_name, ==, NULL);
g_assert_false(merged->merge_lists);
append_verify_list_02(&merged->desc[0]);
append_verify_list_01(&merged->desc[7], false);
qemu_opts_free(merged);
}
static void test_opts_to_qdict_basic(void)
{
QemuOpts *opts;
QDict *dict;
opts = qemu_opts_parse(&opts_list_01, "str1=foo,str2=,str3=bar,number1=42",
false, &error_abort);
g_assert(opts != NULL);
dict = qemu_opts_to_qdict(opts, NULL);
g_assert(dict != NULL);
g_assert_cmpstr(qdict_get_str(dict, "str1"), ==, "foo");
g_assert_cmpstr(qdict_get_str(dict, "str2"), ==, "");
g_assert_cmpstr(qdict_get_str(dict, "str3"), ==, "bar");
g_assert_cmpstr(qdict_get_str(dict, "number1"), ==, "42");
g_assert_false(qdict_haskey(dict, "number2"));
qobject_unref(dict);
qemu_opts_del(opts);
}
static void test_opts_to_qdict_filtered(void)
{
QemuOptsList *first, *merged;
QemuOpts *opts;
QDict *dict;
first = qemu_opts_append(NULL, &opts_list_02);
merged = qemu_opts_append(first, &opts_list_01);
opts = qemu_opts_parse(merged,
"str1=foo,str2=,str3=bar,bool1=off,number1=42",
false, &error_abort);
g_assert(opts != NULL);
/* Convert to QDict without deleting from opts */
dict = qemu_opts_to_qdict_filtered(opts, NULL, &opts_list_01, false);
g_assert(dict != NULL);
g_assert_cmpstr(qdict_get_str(dict, "str1"), ==, "foo");
g_assert_cmpstr(qdict_get_str(dict, "str2"), ==, "");
g_assert_cmpstr(qdict_get_str(dict, "str3"), ==, "bar");
g_assert_cmpstr(qdict_get_str(dict, "number1"), ==, "42");
g_assert_false(qdict_haskey(dict, "number2"));
g_assert_false(qdict_haskey(dict, "bool1"));
qobject_unref(dict);
dict = qemu_opts_to_qdict_filtered(opts, NULL, &opts_list_02, false);
g_assert(dict != NULL);
g_assert_cmpstr(qdict_get_str(dict, "str1"), ==, "foo");
g_assert_cmpstr(qdict_get_str(dict, "str2"), ==, "");
g_assert_cmpstr(qdict_get_str(dict, "bool1"), ==, "off");
g_assert_false(qdict_haskey(dict, "str3"));
g_assert_false(qdict_haskey(dict, "number1"));
g_assert_false(qdict_haskey(dict, "number2"));
qobject_unref(dict);
/* Now delete converted options from opts */
dict = qemu_opts_to_qdict_filtered(opts, NULL, &opts_list_01, true);
g_assert(dict != NULL);
g_assert_cmpstr(qdict_get_str(dict, "str1"), ==, "foo");
g_assert_cmpstr(qdict_get_str(dict, "str2"), ==, "");
g_assert_cmpstr(qdict_get_str(dict, "str3"), ==, "bar");
g_assert_cmpstr(qdict_get_str(dict, "number1"), ==, "42");
g_assert_false(qdict_haskey(dict, "number2"));
g_assert_false(qdict_haskey(dict, "bool1"));
qobject_unref(dict);
dict = qemu_opts_to_qdict_filtered(opts, NULL, &opts_list_02, true);
g_assert(dict != NULL);
g_assert_cmpstr(qdict_get_str(dict, "bool1"), ==, "off");
g_assert_false(qdict_haskey(dict, "str1"));
g_assert_false(qdict_haskey(dict, "str2"));
g_assert_false(qdict_haskey(dict, "str3"));
g_assert_false(qdict_haskey(dict, "number1"));
g_assert_false(qdict_haskey(dict, "number2"));
qobject_unref(dict);
g_assert_true(QTAILQ_EMPTY(&opts->head));
qemu_opts_del(opts);
qemu_opts_free(merged);
}
static void test_opts_to_qdict_duplicates(void)
{
QemuOpts *opts;
QemuOpt *opt;
QDict *dict;
opts = qemu_opts_parse(&opts_list_03, "foo=a,foo=b", false, &error_abort);
g_assert(opts != NULL);
/* Verify that opts has two options with the same name */
opt = QTAILQ_FIRST(&opts->head);
g_assert_cmpstr(opt->name, ==, "foo");
g_assert_cmpstr(opt->str , ==, "a");
opt = QTAILQ_NEXT(opt, next);
g_assert_cmpstr(opt->name, ==, "foo");
g_assert_cmpstr(opt->str , ==, "b");
opt = QTAILQ_NEXT(opt, next);
g_assert(opt == NULL);
/* In the conversion to QDict, the last one wins */
dict = qemu_opts_to_qdict(opts, NULL);
g_assert(dict != NULL);
g_assert_cmpstr(qdict_get_str(dict, "foo"), ==, "b");
qobject_unref(dict);
/* The last one still wins if entries are deleted, and both are deleted */
dict = qemu_opts_to_qdict_filtered(opts, NULL, NULL, true);
g_assert(dict != NULL);
g_assert_cmpstr(qdict_get_str(dict, "foo"), ==, "b");
qobject_unref(dict);
g_assert_true(QTAILQ_EMPTY(&opts->head));
qemu_opts_del(opts);
}
int main(int argc, char *argv[])
{
register_opts();
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qemu-opts/find_unknown_opts", test_find_unknown_opts);
g_test_add_func("/qemu-opts/find_opts", test_qemu_find_opts);
g_test_add_func("/qemu-opts/opts_create", test_qemu_opts_create);
g_test_add_func("/qemu-opts/opt_get", test_qemu_opt_get);
g_test_add_func("/qemu-opts/opt_get_bool", test_qemu_opt_get_bool);
g_test_add_func("/qemu-opts/opt_get_number", test_qemu_opt_get_number);
g_test_add_func("/qemu-opts/opt_get_size", test_qemu_opt_get_size);
g_test_add_func("/qemu-opts/opt_unset", test_qemu_opt_unset);
g_test_add_func("/qemu-opts/opts_reset", test_qemu_opts_reset);
g_test_add_func("/qemu-opts/opts_set", test_qemu_opts_set);
g_test_add_func("/qemu-opts/opts_parse/general", test_opts_parse);
g_test_add_func("/qemu-opts/opts_parse/bool", test_opts_parse_bool);
g_test_add_func("/qemu-opts/opts_parse/number", test_opts_parse_number);
g_test_add_func("/qemu-opts/opts_parse/size", test_opts_parse_size);
g_test_add_func("/qemu-opts/has_help_option", test_has_help_option);
g_test_add_func("/qemu-opts/append_to_null", test_opts_append_to_null);
g_test_add_func("/qemu-opts/append", test_opts_append);
g_test_add_func("/qemu-opts/to_qdict/basic", test_opts_to_qdict_basic);
g_test_add_func("/qemu-opts/to_qdict/filtered", test_opts_to_qdict_filtered);
g_test_add_func("/qemu-opts/to_qdict/duplicates", test_opts_to_qdict_duplicates);
g_test_run();
return 0;
}