From d0d4e0ed487ea23aaf0d023513c0a4d86901b79b Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Fri, 4 Oct 2019 16:16:47 -0400 Subject: [PATCH] Add parameter validation utility This patch adds a utility to help component classes validate the parameters they receive (either for component initialization or query). The definition of what constitutes valid parameters for a component class is done by filling C structures describing each expected value (see included test for examples). Since this is made for parameter validation, and not general-purpose value validation, the entry point bt_param_validation_validate assumes that the root value is a map (and is therefore validated as a map, as described below). For parameters that have simple validation rules, it is enough to fill the `type` field of bt_param_validation_value_descr, and perhaps the additional fields associated to the type. For array values, the description includes the minimum and maximum sizes the array can have (use BT_PARAM_VALIDATION_INFINITE if there's no max). It also includes a pointer to the description of its element values. For map values, the description includes a list of possible map entries. For each entry, we have the key, whether the entry is mandatory and the description of the value. For string values, the description may include a list of choices. If it does, the string value must be in this list. For parameters that have more complex validation rules, for example parameters whose value can be of different types, it is possible to set the `validation_func` field. If it is set, this function will be called and is responsible for validating the value. If the validation fails, an error message is produced, which includes the full scope where the error occured (again, see the test for an example). Change-Id: If729415fdd8ce97fa94b79e8bf79461e46ebf2bc Signed-off-by: Simon Marchi Reviewed-on: https://review.lttng.org/c/babeltrace/+/2119 Tested-by: jenkins --- configure.ac | 2 + src/param-parse/param-parse.h | 2 + src/plugins/common/Makefile.am | 2 +- .../common/param-validation/Makefile.am | 5 + .../param-validation/param-validation.c | 421 ++++++++++++++++++ .../param-validation/param-validation.h | 110 +++++ tests/Makefile.am | 9 +- tests/param-validation/Makefile.am | 9 + .../param-validation/test_param_validation.c | 369 +++++++++++++++ 9 files changed, 927 insertions(+), 2 deletions(-) create mode 100644 src/plugins/common/param-validation/Makefile.am create mode 100644 src/plugins/common/param-validation/param-validation.c create mode 100644 src/plugins/common/param-validation/param-validation.h create mode 100644 tests/param-validation/Makefile.am create mode 100644 tests/param-validation/test_param_validation.c diff --git a/configure.ac b/configure.ac index 851d0a55..0a630558 100644 --- a/configure.ac +++ b/configure.ac @@ -698,6 +698,7 @@ AC_CONFIG_FILES([ src/Makefile src/plugins/common/Makefile src/plugins/common/muxing/Makefile + src/plugins/common/param-validation/Makefile src/plugins/ctf/common/bfcr/Makefile src/plugins/ctf/common/Makefile src/plugins/ctf/common/metadata/Makefile @@ -727,6 +728,7 @@ AC_CONFIG_FILES([ tests/lib/Makefile tests/lib/test-plugin-plugins/Makefile tests/Makefile + tests/param-validation/Makefile tests/plugins/Makefile tests/plugins/src.ctf.fs/Makefile tests/plugins/src.ctf.fs/succeed/Makefile diff --git a/src/param-parse/param-parse.h b/src/param-parse/param-parse.h index 301b7e22..845d978b 100644 --- a/src/param-parse/param-parse.h +++ b/src/param-parse/param-parse.h @@ -25,6 +25,8 @@ #include +#include + #include "common/macros.h" BT_HIDDEN diff --git a/src/plugins/common/Makefile.am b/src/plugins/common/Makefile.am index 3616b5d7..4cfd7394 100644 --- a/src/plugins/common/Makefile.am +++ b/src/plugins/common/Makefile.am @@ -1 +1 @@ -SUBDIRS = muxing +SUBDIRS = muxing param-validation diff --git a/src/plugins/common/param-validation/Makefile.am b/src/plugins/common/param-validation/Makefile.am new file mode 100644 index 00000000..1ce84672 --- /dev/null +++ b/src/plugins/common/param-validation/Makefile.am @@ -0,0 +1,5 @@ +noinst_LTLIBRARIES = libbabeltrace2-param-validation.la + +libbabeltrace2_param_validation_la_SOURCES = \ + param-validation.c \ + param-validation.h diff --git a/src/plugins/common/param-validation/param-validation.c b/src/plugins/common/param-validation/param-validation.c new file mode 100644 index 00000000..755310e5 --- /dev/null +++ b/src/plugins/common/param-validation/param-validation.c @@ -0,0 +1,421 @@ +/* + * Copyright 2019 EfficiOS Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "param-validation.h" + +#include +#include +#include + +#include "common/common.h" + +struct bt_param_validation_context { + gchar *error; + GArray *scope_stack; +}; + +struct validate_ctx_stack_element { + enum { + VALIDATE_CTX_STACK_ELEMENT_MAP, + VALIDATE_CTX_STACK_ELEMENT_ARRAY, + } type; + + union { + const char *map_key_name; + uint64_t array_index; + }; +}; + +static +void validate_ctx_push_map_scope( + struct bt_param_validation_context *ctx, + const char *key) +{ + struct validate_ctx_stack_element stack_element = { + .type = VALIDATE_CTX_STACK_ELEMENT_MAP, + .map_key_name = key, + }; + + g_array_append_val(ctx->scope_stack, stack_element); +} + +static +void validate_ctx_push_array_scope( + struct bt_param_validation_context *ctx, uint64_t index) +{ + struct validate_ctx_stack_element stack_element = { + .type = VALIDATE_CTX_STACK_ELEMENT_ARRAY, + .array_index = index, + }; + + g_array_append_val(ctx->scope_stack, stack_element); +} + +static +void validate_ctx_pop_scope(struct bt_param_validation_context *ctx) +{ + BT_ASSERT(ctx->scope_stack->len > 0); + + g_array_remove_index_fast(ctx->scope_stack, ctx->scope_stack->len - 1); +} + +static +void append_scope_to_string(GString *str, + const struct validate_ctx_stack_element *elem, + bool first) +{ + switch (elem->type) { + case VALIDATE_CTX_STACK_ELEMENT_MAP: + if (!first) { + g_string_append_c(str, '.'); + } + + g_string_append(str, elem->map_key_name); + break; + case VALIDATE_CTX_STACK_ELEMENT_ARRAY: + g_string_append_printf(str, "[%" PRIu64 "]", elem->array_index); + break; + default: + abort(); + } +} + +enum bt_param_validation_status bt_param_validation_error( + struct bt_param_validation_context *ctx, + const char *format, ...) { + va_list ap; + enum bt_param_validation_status status; + + GString *str = g_string_new(NULL); + if (!str) { + status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR; + goto end; + } + + if (ctx->scope_stack->len > 0) { + guint i; + + g_string_assign(str, "Error validating parameter `"); + + append_scope_to_string(str, &g_array_index(ctx->scope_stack, + struct validate_ctx_stack_element, 0), true); + + for (i = 1; i < ctx->scope_stack->len; i++) { + append_scope_to_string(str, + &g_array_index(ctx->scope_stack, + struct validate_ctx_stack_element, i), false); + } + + g_string_append(str, "`: "); + } else { + g_string_assign(str, "Error validating parameters: "); + } + + va_start(ap, format); + g_string_append_vprintf(str, format, ap); + va_end(ap); + + ctx->error = g_string_free(str, FALSE); + status = BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR; + +end: + return status; +} + +struct validate_map_value_data +{ + GPtrArray *available_keys; + enum bt_param_validation_status status; + struct bt_param_validation_context *ctx; +}; + +static +enum bt_param_validation_status validate_value( + const bt_value *value, + const struct bt_param_validation_value_descr *descr, + struct bt_param_validation_context *ctx); + +static +bt_bool validate_map_value_entry(const char *key, + const bt_value *value, void *v_data) +{ + struct validate_map_value_data *data = v_data; + const struct bt_param_validation_map_value_entry_descr *candidate; + guint i; + + /* Check if this key is in the available keys. */ + for (i = 0; i < data->available_keys->len; i++) { + candidate = g_ptr_array_index(data->available_keys, i); + + if (g_str_equal(key, candidate->key)) { + break; + } + } + + if (i < data->available_keys->len) { + /* Key was found in available keys. */ + g_ptr_array_remove_index_fast(data->available_keys, i); + + /* Push key name as the scope. */ + validate_ctx_push_map_scope(data->ctx, key); + + /* Validate the value of the entry. */ + data->status = validate_value(value, &candidate->value_descr, + data->ctx); + + validate_ctx_pop_scope(data->ctx); + } else { + data->status = bt_param_validation_error(data->ctx, + "unexpected key `%s`.", key); + } + + /* Continue iterating if everything is good so far. */ + return data->status == BT_PARAM_VALIDATION_STATUS_OK; +} + +static +enum bt_param_validation_status validate_map_value( + const struct bt_param_validation_map_value_descr *descr, + const bt_value *map, + struct bt_param_validation_context *ctx) { + enum bt_param_validation_status status; + struct validate_map_value_data data; + bt_value_map_foreach_entry_const_status foreach_entry_status; + GPtrArray *available_keys = NULL; + const struct bt_param_validation_map_value_entry_descr *descr_iter; + guint i; + + BT_ASSERT(bt_value_get_type(map) == BT_VALUE_TYPE_MAP); + + available_keys = g_ptr_array_new(); + if (!available_keys) { + status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR; + goto end; + } + + for (descr_iter = descr->entries; descr_iter->key; descr_iter++) { + g_ptr_array_add(available_keys, (gpointer) descr_iter); + } + + /* Initialize `status` to OK, in case the map is empty. */ + data.status = BT_PARAM_VALIDATION_STATUS_OK; + data.available_keys = available_keys; + data.ctx = ctx; + + foreach_entry_status = bt_value_map_foreach_entry_const(map, + validate_map_value_entry, &data); + if (foreach_entry_status == BT_VALUE_MAP_FOREACH_ENTRY_CONST_STATUS_MEMORY_ERROR) { + status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR; + goto end; + } + + if (data.status != BT_PARAM_VALIDATION_STATUS_OK) { + status = data.status; + goto end; + } + + for (i = 0; i < data.available_keys->len; i++) { + const struct bt_param_validation_map_value_entry_descr *entry = + g_ptr_array_index(data.available_keys, i); + + if (!entry->is_optional) { + status = bt_param_validation_error(ctx, + "missing mandatory entry `%s`", + entry->key); + goto end; + } + } + + status = BT_PARAM_VALIDATION_STATUS_OK; + +end: + g_ptr_array_free(available_keys, TRUE); + return status; +} + +static +enum bt_param_validation_status validate_array_value( + const struct bt_param_validation_array_value_descr *descr, + const bt_value *array, + struct bt_param_validation_context *ctx) { + enum bt_param_validation_status status; + uint64_t i; + + BT_ASSERT(bt_value_get_type(array) == BT_VALUE_TYPE_ARRAY); + + if (bt_value_array_get_length(array) < descr->min_length) { + status = bt_param_validation_error(ctx, + "array is smaller than the minimum length: " + "array-length=%" PRIu64 ", min-length=%" PRIu64, + bt_value_array_get_length(array), + descr->min_length); + goto end; + } + + if (bt_value_array_get_length(array) > descr->max_length) { + status = bt_param_validation_error(ctx, + "array is larger than the maximum length: " + "array-length=%" PRIu64 ", max-length=%" PRIu64, + bt_value_array_get_length(array), + descr->max_length); + goto end; + } + + for (i = 0; i < bt_value_array_get_length(array); i++) { + const bt_value *element = + bt_value_array_borrow_element_by_index_const(array, i); + + validate_ctx_push_array_scope(ctx, i); + + status = validate_value(element, descr->element_type, ctx); + + validate_ctx_pop_scope(ctx); + + if (status != BT_PARAM_VALIDATION_STATUS_OK) { + goto end; + } + } + + status = BT_PARAM_VALIDATION_STATUS_OK; + +end: + return status; +} + +static +enum bt_param_validation_status validate_string_value( + const struct bt_param_validation_string_value_descr *descr, + const bt_value *string, + struct bt_param_validation_context *ctx) { + enum bt_param_validation_status status; + const char *s = bt_value_string_get(string); + gchar *joined_choices = NULL; + + BT_ASSERT(bt_value_get_type(string) == BT_VALUE_TYPE_STRING); + + if (descr->choices) { + const char **choice; + + for (choice = descr->choices; *choice; choice++) { + if (strcmp(s, *choice) == 0) { + break; + } + } + + if (!*choice) { + /* + * g_strjoinv takes a gchar **, but it doesn't modify + * the array of the strings (yet). + */ + joined_choices = g_strjoinv(", ", (gchar **) descr->choices); + if (!joined_choices) { + status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR; + goto end; + } + + status = bt_param_validation_error(ctx, + "string is not amongst the available choices: " + "string=%s, choices=[%s]", s, joined_choices); + goto end; + } + } + + status = BT_PARAM_VALIDATION_STATUS_OK; +end: + g_free(joined_choices); + + return status; +} + +static +enum bt_param_validation_status validate_value( + const bt_value *value, + const struct bt_param_validation_value_descr *descr, + struct bt_param_validation_context *ctx) { + enum bt_param_validation_status status; + + /* If there is a custom validation func, we call it and ignore the rest. */ + if (descr->validation_func) { + status = descr->validation_func(value, ctx); + + if (status == BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR) { + BT_ASSERT(ctx->error); + } + + goto end; + } + + if (bt_value_get_type(value) != descr->type) { + bt_param_validation_error(ctx, + "unexpected type: expected-type=%s, actual-type=%s", + bt_common_value_type_string(descr->type), + bt_common_value_type_string(bt_value_get_type(value))); + status = BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR; + goto end; + } + + switch (bt_value_get_type(value)) { + case BT_VALUE_TYPE_MAP: + status = validate_map_value(&descr->map, value, ctx); + break; + case BT_VALUE_TYPE_ARRAY: + status = validate_array_value(&descr->array, value, ctx); + break; + case BT_VALUE_TYPE_STRING: + status = validate_string_value(&descr->string, value, ctx); + break; + default: + status = BT_PARAM_VALIDATION_STATUS_OK; + break; + } + +end: + return status; +} + +enum bt_param_validation_status bt_param_validation_validate( + const bt_value *params, + const struct bt_param_validation_map_value_entry_descr *entries, + gchar **error) { + struct bt_param_validation_context ctx; + struct bt_param_validation_map_value_descr map_value_descr; + enum bt_param_validation_status status; + + ctx.error = NULL; + ctx.scope_stack = g_array_new(FALSE, FALSE, + sizeof(struct validate_ctx_stack_element)); + g_ptr_array_new_with_free_func(g_free); + if (!ctx.scope_stack) { + status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR; + goto end; + } + + map_value_descr.entries = entries; + + status = validate_map_value(&map_value_descr, params, &ctx); + +end: + *error = ctx.error; + ctx.error = NULL; + + return status; +} diff --git a/src/plugins/common/param-validation/param-validation.h b/src/plugins/common/param-validation/param-validation.h new file mode 100644 index 00000000..a78f997b --- /dev/null +++ b/src/plugins/common/param-validation/param-validation.h @@ -0,0 +1,110 @@ +#ifndef BABELTRACE_PLUGINS_COMMON_PARAM_VALIDATION_PARAM_VALIDATION_H +#define BABELTRACE_PLUGINS_COMMON_PARAM_VALIDATION_PARAM_VALIDATION_H + +/* + * Copyright 2019 EfficiOS Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +#include /* For __MINGW_PRINTF_FORMAT. */ + +#ifdef __MINGW_PRINTF_FORMAT +# define BT_PRINTF_FORMAT __MINGW_PRINTF_FORMAT +#else +# define BT_PRINTF_FORMAT printf +#endif + +struct bt_param_validation_context; +struct bt_param_validation_value_descr; + +#define BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END { NULL, 0, {} } + +struct bt_param_validation_map_value_descr { + const struct bt_param_validation_map_value_entry_descr *entries; +}; + +#define BT_PARAM_VALIDATION_INFINITE UINT64_MAX + +struct bt_param_validation_array_value_descr { + uint64_t min_length; + uint64_t max_length; /* Use BT_PARAM_VALIDATION_INFINITE if there's no max. */ + const struct bt_param_validation_value_descr *element_type; +}; + +struct bt_param_validation_string_value_descr { + /* NULL-terminated array of choices. Unused if NULL. */ + const char **choices; +}; + +enum bt_param_validation_status { + BT_PARAM_VALIDATION_STATUS_OK = 0, + BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR = -1, + BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR = -2, +}; + +typedef enum bt_param_validation_status + (bt_param_validation_func)(const bt_value *value, + struct bt_param_validation_context *); + +struct bt_param_validation_value_descr { + bt_value_type type; + + /* Additional checks dependent on the type. */ + union { + struct bt_param_validation_array_value_descr array; + struct bt_param_validation_map_value_descr map; + struct bt_param_validation_string_value_descr string; + }; + + /* + * If set, call this function, which is responsible of validating the + * value. The other fields are ignored. + * + * If validation fails, this function must call + * `bt_param_validation_error` with the provided context + * to set the error string. + */ + bt_param_validation_func *validation_func; +}; + +#define BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_OPTIONAL true +#define BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY false + +struct bt_param_validation_map_value_entry_descr { + const char *key; + bool is_optional; + + const struct bt_param_validation_value_descr value_descr; +}; + +enum bt_param_validation_status bt_param_validation_validate( + const bt_value *params, + const struct bt_param_validation_map_value_entry_descr *entries, + gchar **error); + +__attribute__((format(BT_PRINTF_FORMAT, 2, 3))) +enum bt_param_validation_status bt_param_validation_error( + struct bt_param_validation_context *ctx, + const char *format, ...); + +#endif /* BABELTRACE_PLUGINS_COMMON_PARAM_VALIDATION_PARAM_VALIDATION_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 92ff89e1..11b932c8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,4 +1,11 @@ -SUBDIRS = utils lib bitfield ctf-writer plugins argpar +SUBDIRS = \ + utils \ + lib \ + bitfield \ + ctf-writer \ + plugins \ + argpar \ + param-validation # Directories added to EXTRA_DIST will be recursively copied to the distribution. EXTRA_DIST = $(srcdir)/data \ diff --git a/tests/param-validation/Makefile.am b/tests/param-validation/Makefile.am new file mode 100644 index 00000000..7dbf58ac --- /dev/null +++ b/tests/param-validation/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS += -I$(top_srcdir)/tests/utils + +noinst_PROGRAMS = test_param_validation +test_param_validation_SOURCES = test_param_validation.c +test_param_validation_LDADD = \ + $(top_builddir)/src/param-parse/libbabeltrace2-param-parse.la \ + $(top_builddir)/src/plugins/common/param-validation/libbabeltrace2-param-validation.la \ + $(top_builddir)/src/lib/libbabeltrace2.la \ + $(top_builddir)/tests/utils/tap/libtap.la diff --git a/tests/param-validation/test_param_validation.c b/tests/param-validation/test_param_validation.c new file mode 100644 index 00000000..200b5514 --- /dev/null +++ b/tests/param-validation/test_param_validation.c @@ -0,0 +1,369 @@ +/* + * Copyright (c) EfficiOS Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; under version 2 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "tap/tap.h" +#include "param-parse/param-parse.h" +#include "plugins/common/param-validation/param-validation.h" + +#include +#include +#include + +static +enum bt_param_validation_status run_test( + const char *params_str, + const struct bt_param_validation_map_value_entry_descr *entries, + const char *test_name, + const char *expected_error) +{ + GString *err = g_string_new(NULL); + const bt_value *params; + enum bt_param_validation_status status; + gchar *validate_error = NULL; + + if (!err) { + fprintf(stderr, "Failed to allocated a GString.\n"); + abort(); + } + + params = bt_param_parse(params_str, err); + + if (!params) { + fprintf(stderr, "Could not parse params: `%s`, %s\n", + params_str, err->str); + abort(); + } + + status = bt_param_validation_validate(params, entries, &validate_error); + + if (expected_error) { + const char *fmt; + + /* We expect a failure. */ + ok(status == BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR, + "%s: validation fails", test_name); + ok(validate_error, "%s: error string is not NULL", test_name); + + fmt = "%s: error string contains expected string"; + if (validate_error && strstr(validate_error, expected_error)) { + pass(fmt, test_name); + } else { + fail(fmt, test_name); + diag("could not find `%s` in `%s`", expected_error, validate_error); + } + + g_free(validate_error); + } else { + /* We expect a success. */ + ok(status == BT_PARAM_VALIDATION_STATUS_OK, "%s: validation succeeds", test_name); + ok(!validate_error, "%s: error string is NULL", test_name); + } + + bt_value_put_ref(params); + g_string_free(err, TRUE); + + return status; +} + +static +void test_map_valid(void) +{ + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } }, + { "fenouil", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_OPTIONAL, { .type = BT_VALUE_TYPE_STRING } }, + { "panais", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_OPTIONAL, { .type = BT_VALUE_TYPE_BOOL } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carotte=2,fenouil=\"miam\"", entries, "valid map", NULL); +} + +static +void test_map_missing_key(void) +{ + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } }, + { "tomate", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carotte=2", entries, "missing key in map", + "Error validating parameters: missing mandatory entry `tomate`"); +} + +static +void test_map_unexpected_key(void) +{ + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("tomate=2", entries, "unexpected key in map", "unexpected key `tomate`"); +} + +static +void test_map_invalid_entry_value_type(void) +{ + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carottes", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carottes=\"orange\"", entries, "map entry with unexpected type", + "Error validating parameter `carottes`: unexpected type: expected-type=SIGNED_INTEGER, actual-type=STRING"); +} + +static +void test_nested_error(void) +{ + const struct bt_param_validation_map_value_entry_descr poireau_entries[] = { + { "navet", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END, + }; + + const struct bt_param_validation_map_value_entry_descr carottes_elem_entries[] = { + { "poireau", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_MAP, .map = { + .entries = poireau_entries, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END, + }; + + const struct bt_param_validation_value_descr carottes_elem = { + .type = BT_VALUE_TYPE_MAP, + .map = { + .entries = carottes_elem_entries, + } + }; + + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carottes", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = { + .min_length = 0, + .max_length = BT_PARAM_VALIDATION_INFINITE, + .element_type = &carottes_elem, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carottes=[{poireau={navet=7}}, {poireau={}}]", entries, "error nested in maps and arrays", + "Error validating parameter `carottes[1].poireau`: missing mandatory entry `navet`"); +} + +static +void test_array_valid(void) +{ + const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} }; + + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = { + .min_length = 2, + .max_length = 22, + .element_type = &carotte_elem, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carotte=[true, false, true]", entries, "valid array", NULL); +} + +static +void test_array_empty_valid(void) +{ + const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} }; + + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = { + .min_length = 0, + .max_length = 2, + .element_type = &carotte_elem, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carotte=[]", entries, "valid empty array", NULL); +} + +static +void test_array_invalid_too_small(void) +{ + const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} }; + + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = { + .min_length = 1, + .max_length = 100, + .element_type = &carotte_elem, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carotte=[]", entries, "array too small", + "Error validating parameter `carotte`: array is smaller than the minimum length: array-length=0, min-length=1"); +} + +static +void test_array_invalid_too_large(void) +{ + const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} }; + + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = { + .min_length = 2, + .max_length = 2, + .element_type = &carotte_elem, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carotte=[true, false, false]", entries, "array too large", + "Error validating parameter `carotte`: array is larger than the maximum length: array-length=3, max-length=2"); +} + +static +void test_array_invalid_elem_type(void) +{ + const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} }; + + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = { + .min_length = 3, + .max_length = 3, + .element_type = &carotte_elem, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("carotte=[true, false, 2]", entries, "array with invalid element type", + "Error validating parameter `carotte[2]`: unexpected type: expected-type=BOOL, actual-type=SIGNED_INTEGER"); +} + +static +void test_string_valid_without_choices(void) +{ + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "haricot", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_STRING, { } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("haricot=\"vert\"", entries, "valid string without choices", NULL); +} + +static +void test_string_valid_with_choices(void) +{ + const char *haricot_choices[] = {"vert", "jaune", "rouge", NULL}; + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "haricot", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_STRING, .string = { + .choices = haricot_choices, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("haricot=\"jaune\"", entries, "valid string with choices", NULL); +} + +static +void test_string_invalid_choice(void) +{ + const char *haricot_choices[] = {"vert", "jaune", "rouge", NULL}; + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "haricot", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_STRING, .string = { + .choices = haricot_choices, + } } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("haricot=\"violet\"", entries, "string with invalid choice", + "Error validating parameter `haricot`: string is not amongst the available choices: string=violet, choices=[vert, jaune, rouge]"); +} + +static +enum bt_param_validation_status custom_validation_func_valid( + const bt_value *value, + struct bt_param_validation_context *context) +{ + ok(bt_value_get_type(value) == BT_VALUE_TYPE_UNSIGNED_INTEGER, + "type of value passed to custom function is as expected"); + ok(bt_value_integer_unsigned_get(value) == 1234, + "value passed to custom function is as expected"); + return BT_PARAM_VALIDATION_STATUS_OK; +} + +static +void test_custom_validation_func_valid(void) +{ + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "navet", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { + .validation_func = custom_validation_func_valid, + } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("navet=+1234", entries, "custom validation function with valid value", NULL); +} + +static +enum bt_param_validation_status custom_validation_func_invalid( + const bt_value *value, + struct bt_param_validation_context *context) +{ + return bt_param_validation_error(context, "wrooooong"); +} + +static +void test_custom_validation_func_invalid(void) +{ + const struct bt_param_validation_map_value_entry_descr entries[] = { + { "navet", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { + .validation_func = custom_validation_func_invalid, + } }, + BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END + }; + + run_test("navet=+1234", entries, "custom validation function with invalid value", + "Error validating parameter `navet`: wrooooong"); +} + +int main(void) +{ + plan_tests(34); + + test_map_valid(); + + test_map_missing_key(); + test_map_unexpected_key(); + test_map_invalid_entry_value_type(); + + test_array_valid(); + test_array_empty_valid(); + + test_array_invalid_too_small(); + test_array_invalid_too_large(); + test_array_invalid_elem_type(); + + test_string_valid_without_choices(); + test_string_valid_with_choices(); + + test_string_invalid_choice(); + + test_custom_validation_func_valid(); + test_custom_validation_func_invalid(); + + test_nested_error(); + + return exit_status(); +} -- 2.34.1