The `virTypedParamsValidate' function now can be instructed to allow multiple entries for some of the keys. For this flag the type with the `VIR_TYPED_PARAM_MULTIPLE' flag. Add unit tests for this new behaviour. Signed-off-by: Pavel Boldin <pboldin@xxxxxxxxxxxx> --- src/util/virtypedparam.c | 108 +++++++++++++++++++----------- src/util/virtypedparam.h | 10 +++ tests/Makefile.am | 6 ++ tests/virtypedparamtest.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+), 39 deletions(-) create mode 100644 tests/virtypedparamtest.c diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c index de2d447..43e49ca 100644 --- a/src/util/virtypedparam.c +++ b/src/util/virtypedparam.c @@ -47,11 +47,18 @@ VIR_ENUM_IMPL(virTypedParameter, VIR_TYPED_PARAM_LAST, * internal utility functions (those in libvirt_private.syms) may * report errors that the caller will dispatch. */ +static int virTypedParamsSortName(const void *left, const void *right) +{ + const virTypedParameter *param_left = left, *param_right = right; + return strcmp(param_left->field, param_right->field); +} + /* Validate that PARAMS contains only recognized parameter names with - * correct types, and with no duplicates. Pass in as many name/type - * pairs as appropriate, and pass NULL to end the list of accepted - * parameters. Return 0 on success, -1 on failure with error message - * already issued. */ + * correct types, and with no duplicates except for parameters + * specified with VIR_TYPED_PARAM_MULTIPLE flag in type. + * Pass in as many name/type pairs as appropriate, and pass NULL to end + * the list of accepted parameters. Return 0 on success, -1 on failure + * with error message already issued. */ int virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) { @@ -60,60 +67,83 @@ virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...) size_t i, j; const char *name; int type; + size_t nkeys = 0, nkeysmax = 0; + virTypedParameterPtr sorted = NULL, keys = NULL; va_start(ap, nparams); - /* Yes, this is quadratic, but since we reject duplicates and - * unknowns, it is constrained by the number of var-args passed - * in, which is expected to be small enough to not be - * noticeable. */ - for (i = 0; i < nparams; i++) { - va_end(ap); - va_start(ap, nparams); + if (VIR_ALLOC_N(sorted, nparams) < 0) + goto cleanup; - name = va_arg(ap, const char *); - while (name) { - type = va_arg(ap, int); - if (STREQ(params[i].field, name)) { - if (params[i].type != type) { - const char *badtype; - - badtype = virTypedParameterTypeToString(params[i].type); - if (!badtype) - badtype = virTypedParameterTypeToString(0); - virReportError(VIR_ERR_INVALID_ARG, - _("invalid type '%s' for parameter '%s', " - "expected '%s'"), - badtype, params[i].field, - virTypedParameterTypeToString(type)); - } - break; - } - name = va_arg(ap, const char *); - } - if (!name) { - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, - _("parameter '%s' not supported"), - params[i].field); + /* Here we intentionally don't copy values */ + memcpy(sorted, params, sizeof(*params) * nparams); + qsort(sorted, nparams, sizeof(*sorted), virTypedParamsSortName); + + name = va_arg(ap, const char *); + while (name) { + type = va_arg(ap, int); + if (VIR_RESIZE_N(keys, nkeysmax, nkeys, 1) < 0) + goto cleanup; + + if (virStrcpyStatic(keys[nkeys].field, name) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Field name '%s' too long"), name); goto cleanup; } - for (j = 0; j < i; j++) { - if (STREQ(params[i].field, params[j].field)) { + + keys[nkeys].type = type & ~VIR_TYPED_PARAM_MULTIPLE; + /* Value is not used anyway */ + keys[nkeys].value.i = type & VIR_TYPED_PARAM_MULTIPLE; + + nkeys++; + name = va_arg(ap, const char *); + } + + qsort(keys, nkeys, sizeof(*keys), virTypedParamsSortName); + + for (i = 0, j = 0; i < nparams && j < nkeys;) { + if (STRNEQ(sorted[i].field, keys[j].field)) { + j++; + } else { + if (i > j && !(keys[j].value.i & VIR_TYPED_PARAM_MULTIPLE)) { virReportError(VIR_ERR_INVALID_ARG, _("parameter '%s' occurs multiple times"), - params[i].field); + sorted[i].field); + goto cleanup; + } + if (sorted[i].type != keys[j].type) { + const char *badtype; + + badtype = virTypedParameterTypeToString(sorted[i].type); + if (!badtype) + badtype = virTypedParameterTypeToString(0); + virReportError(VIR_ERR_INVALID_ARG, + _("invalid type '%s' for parameter '%s', " + "expected '%s'"), + badtype, sorted[i].field, + virTypedParameterTypeToString(keys[j].type)); goto cleanup; } + i++; } } + if (j == nkeys && i != nparams) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("parameter '%s' not supported"), + sorted[i].field); + goto cleanup; + } + ret = 0; cleanup: va_end(ap); + VIR_FREE(sorted); + VIR_FREE(keys); return ret; - } + /* Check if params contains only specified parameter names. Return true if * only specified names are present in params, false if params contains any * unspecified parameter name. */ diff --git a/src/util/virtypedparam.h b/src/util/virtypedparam.h index 0c18504..9f2d08c 100644 --- a/src/util/virtypedparam.h +++ b/src/util/virtypedparam.h @@ -26,6 +26,16 @@ # include "internal.h" # include "virutil.h" +/** + * VIR_TYPED_PARAM_MULTIPLE: + * + * Flag indiciating that the params has multiple occurences of the parameter. + * Only used as a flag for @type argument of the virTypedParamsValidate. + */ +# define VIR_TYPED_PARAM_MULTIPLE (1 << 31) + +verify(!(VIR_TYPED_PARAM_LAST & VIR_TYPED_PARAM_MULTIPLE)); + int virTypedParamsValidate(virTypedParameterPtr params, int nparams, /* const char *name, int type ... */ ...) ATTRIBUTE_SENTINEL ATTRIBUTE_RETURN_CHECK; diff --git a/tests/Makefile.am b/tests/Makefile.am index 8e2dbec..9efb441 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -182,6 +182,7 @@ test_programs = virshtest sockettest \ virhostdevtest \ vircaps2xmltest \ virnetdevtest \ + virtypedparamtest \ $(NULL) if WITH_REMOTE @@ -1225,6 +1226,11 @@ objecteventtest_SOURCES = \ testutils.c testutils.h objecteventtest_LDADD = $(LDADDS) +virtypedparamtest_SOURCES = \ + virtypedparamtest.c testutils.h testutils.c +virtypedparamtest_LDADD = $(LDADDS) + + if WITH_LINUX fchosttest_SOURCES = \ fchosttest.c testutils.h testutils.c diff --git a/tests/virtypedparamtest.c b/tests/virtypedparamtest.c new file mode 100644 index 0000000..95e22a7 --- /dev/null +++ b/tests/virtypedparamtest.c @@ -0,0 +1,167 @@ +/* + * virtypedparamtest.c: Test typed param functions + * + * Copyright (C) 2015 Mirantis, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> + +#include <stdio.h> +#include <virtypedparam.h> + +#include "testutils.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +typedef struct _TypedParameterTest { + /* Test name for logging */ + const char *name; + /* Flags of the "foobar" parameter check */ + int foobar_flags; + /* Parameters to validate */ + virTypedParameterPtr params; + /* Amount of parameters */ + int nparams; + + /* Expected error code */ + int expected_errcode; + /* Expected error message */ + const char *expected_errmessage; +} TypedParameterTest; + +static int +testTypedParamsValidate(const void *opaque) +{ + int rv; + TypedParameterTest *test = (TypedParameterTest *)opaque; + virErrorPtr errptr; + + rv = virTypedParamsValidate( + test->params, test->nparams, + "foobar", VIR_TYPED_PARAM_STRING | test->foobar_flags, + "foo", VIR_TYPED_PARAM_INT, + "bar", VIR_TYPED_PARAM_UINT, + NULL); + + if (test->expected_errcode) { + errptr = virGetLastError(); + + rv = (errptr == NULL) || ((rv < 0) && + !(errptr->code == test->expected_errcode)); + if (errptr && test->expected_errmessage) { + rv = STRNEQ(test->expected_errmessage, errptr->message); + if (rv) + printf("%s\n", errptr->message); + } + } + + return rv; +} + +#define PARAMS_ARRAY(...) ((virTypedParameter[]){ __VA_ARGS__ }) +#define PARAMS_SIZE(...) ARRAY_CARDINALITY(PARAMS_ARRAY(__VA_ARGS__)) + +#define PARAMS(...) \ + .params = PARAMS_ARRAY(__VA_ARGS__), \ + .nparams = PARAMS_SIZE(__VA_ARGS__), + +static int +testTypedParamsValidator(void) +{ + size_t i; + int rv = 0; + + TypedParameterTest test[] = { + { + .name = "Invalid arg type", + .foobar_flags = 0, + PARAMS({ .field = "foobar", .type = VIR_TYPED_PARAM_INT }) + .expected_errcode = VIR_ERR_INVALID_ARG, + .expected_errmessage = + "invalid argument: invalid type 'int' for parameter " + "'foobar', expected 'string'" + }, + { + .name = "Extra arg", + .foobar_flags = 0, + PARAMS({ .field = "f", .type = VIR_TYPED_PARAM_INT }) + .expected_errcode = VIR_ERR_INVALID_ARG, + .expected_errmessage = + "argument unsupported: parameter 'f' not supported" + }, + { + .name = "Valid parameters", + .foobar_flags = 0, + PARAMS( + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + ) + .expected_errcode = 0, .expected_errmessage = NULL, + }, + { + .name = "Duplicates incorrect", + .foobar_flags = 0, + PARAMS( + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + ) + .expected_errcode = VIR_ERR_INVALID_ARG, + .expected_errmessage = + "invalid argument: parameter 'foobar' occurs multiple times" + }, + { + .name = "Duplicates OK for marked", + .foobar_flags = VIR_TYPED_PARAM_MULTIPLE, + PARAMS( + { .field = "bar", .type = VIR_TYPED_PARAM_UINT }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foobar", .type = VIR_TYPED_PARAM_STRING }, + { .field = "foo", .type = VIR_TYPED_PARAM_INT } + ) + .expected_errcode = 0, .expected_errmessage = NULL, + }, + { + .name = NULL + } + }; + + for (i = 0; test[i].name; ++i) { + if (virtTestRun(test[i].name, testTypedParamsValidate, &test[i]) < 0) + rv = -1; + } + + return rv; +} + +static int +mymain(void) +{ + int rv = 0; + + if (testTypedParamsValidator() < 0) + rv = -1; + + if (rv < 0) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + +VIRT_TEST_MAIN(mymain) -- 1.9.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list