From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 5C05B439C1; Thu, 25 Jan 2024 12:57:16 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 4872D42E04; Thu, 25 Jan 2024 12:57:16 +0100 (CET) Received: from szxga04-in.huawei.com (szxga04-in.huawei.com [45.249.212.190]) by mails.dpdk.org (Postfix) with ESMTP id 5CBBD4029B for ; Thu, 25 Jan 2024 12:57:14 +0100 (CET) Received: from mail.maildlp.com (unknown [172.19.162.112]) by szxga04-in.huawei.com (SkyGuard) with ESMTP id 4TLK5W4wYwz29jmn; Thu, 25 Jan 2024 19:55:27 +0800 (CST) Received: from dggpeml500024.china.huawei.com (unknown [7.185.36.10]) by mail.maildlp.com (Postfix) with ESMTPS id E0EFF140667; Thu, 25 Jan 2024 19:56:57 +0800 (CST) Received: from localhost.localdomain (10.50.165.33) by dggpeml500024.china.huawei.com (7.185.36.10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.35; Thu, 25 Jan 2024 19:56:27 +0800 From: Chengwen Feng To: , , , CC: Subject: [PATCH v2 3/8] argparse: support verify argument config Date: Thu, 25 Jan 2024 11:52:24 +0000 Message-ID: <20240125115229.41402-4-fengchengwen@huawei.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20240125115229.41402-1-fengchengwen@huawei.com> References: <20231121122651.7078-1-fengchengwen@huawei.com> <20240125115229.41402-1-fengchengwen@huawei.com> MIME-Version: 1.0 Content-Type: text/plain X-Originating-IP: [10.50.165.33] X-ClientProxiedBy: dggems704-chm.china.huawei.com (10.3.19.181) To dggpeml500024.china.huawei.com (7.185.36.10) X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org This commit supports verify argument config. Signed-off-by: Chengwen Feng --- MAINTAINERS | 1 + app/test/meson.build | 1 + app/test/test_argparse.c | 327 ++++++++++++++++++++++++++++++++++++ lib/argparse/rte_argparse.c | 307 ++++++++++++++++++++++++++++++++- 4 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 app/test/test_argparse.c diff --git a/MAINTAINERS b/MAINTAINERS index 09fdb87a25..a32b941e78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1653,6 +1653,7 @@ Other libraries Argument parsing M: Chengwen Feng F: lib/argparse/ +F: app/test/test_argparse.c Configuration file M: Cristian Dumitrescu diff --git a/app/test/meson.build b/app/test/meson.build index dcc93f4a43..864b79d39f 100644 --- a/app/test/meson.build +++ b/app/test/meson.build @@ -27,6 +27,7 @@ source_file_deps = { # the various test_*.c files 'test_acl.c': ['net', 'acl'], 'test_alarm.c': [], + 'test_argparse.c': ['argparse'], 'test_atomic.c': ['hash'], 'test_barrier.c': [], 'test_bitcount.c': [], diff --git a/app/test/test_argparse.c b/app/test/test_argparse.c new file mode 100644 index 0000000000..31c46ecccf --- /dev/null +++ b/app/test/test_argparse.c @@ -0,0 +1,327 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 HiSilicon Limited + */ + +#include +#include + +#include + +#include "test.h" + +static int default_argc; +static char *default_argv[1]; + +/* + * Define strdup wrapper. + * 1. Mainly to fix compile error "warning: assignment discards 'const' + * qualifier from pointer target type [-Wdiscarded-qualifiers]" for + * following code: + * argv[x] = "100"; + * 2. Because this is a test, the memory release which allocated by this + * wrapper in the subtest is not considered. + */ +static char * +test_strdup(const char *str) +{ + char *s = strdup(str); + if (s == NULL) + exit(-ENOMEM); + return s; +} + +static int +test_argparse_setup(void) +{ + default_argc = 1; + default_argv[0] = test_strdup("test_argparse"); + return 0; +} + +static void +test_argparse_teardown(void) +{ + free(default_argv[0]); +} + +static int +test_argparse_callback(uint32_t index, const char *value, void *opaque) +{ + RTE_SET_USED(index); + RTE_SET_USED(value); + RTE_SET_USED(opaque); + return 0; +} + +/* valid templater, must contain at least two args. */ +#define argparse_templater() { \ + .prog_name = "test_argparse", \ + .usage = "-a xx -b yy", \ + .descriptor = NULL, \ + .epilog = NULL, \ + .exit_on_error = false, \ + .callback = test_argparse_callback, \ + .args = { \ + { "--abc", "-a", "abc argument", (void *)1, (void *)1, RTE_ARGPARSE_ARG_NO_VALUE | RTE_ARGPARSE_ARG_VALUE_INT }, \ + { "--xyz", "-x", "xyz argument", (void *)1, (void *)2, RTE_ARGPARSE_ARG_NO_VALUE | RTE_ARGPARSE_ARG_VALUE_INT }, \ + ARGPARSE_ARG_END(), \ + }, \ +} + +static void +test_argparse_copy(struct rte_argparse *dst, struct rte_argparse *src) +{ + uint32_t i; + memcpy(dst, src, sizeof(*src)); + for (i = 0; /* NULL */; i++) { + memcpy(&dst->args[i], &src->args[i], sizeof(src->args[i])); + if (src->args[i].name_long == NULL) + break; + } +} + +static struct rte_argparse * +test_argparse_init_obj(void) +{ + static struct rte_argparse backup = argparse_templater(); + static struct rte_argparse obj = argparse_templater(); + test_argparse_copy(&obj, &backup); + return &obj; +} + +static int +test_argparse_invalid_basic_param(void) +{ + struct rte_argparse *obj; + int ret; + + obj = test_argparse_init_obj(); + obj->prog_name = NULL; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->usage = NULL; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + return TEST_SUCCESS; +} + +static int +test_argparse_invalid_arg_name(void) +{ + struct rte_argparse *obj; + int ret; + + obj = test_argparse_init_obj(); + obj->args[0].name_long = "-ab"; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].name_long = "-abc"; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].name_long = "---c"; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].name_long = "abc"; + obj->args[0].name_short = "-a"; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].name_short = "a"; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].name_short = "abc"; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].name_short = "ab"; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + return 0; +} + +static int +test_argparse_invalid_arg_help(void) +{ + struct rte_argparse *obj; + int ret; + + obj = test_argparse_init_obj(); + obj->args[0].help = NULL; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + return 0; +} + +static int +test_argparse_invalid_has_val(void) +{ + uint32_t set_mask[] = { 0, + RTE_ARGPARSE_ARG_NO_VALUE, + RTE_ARGPARSE_ARG_OPTIONAL_VALUE + }; + struct rte_argparse *obj; + uint32_t index; + int ret; + + obj = test_argparse_init_obj(); + obj->args[0].flags &= ~0x3u; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + for (index = 0; index < RTE_DIM(set_mask); index++) { + obj = test_argparse_init_obj(); + obj->args[0].name_long = "abc"; + obj->args[0].name_short = NULL; + obj->args[0].flags &= ~0x3u; + obj->args[0].flags |= set_mask[index]; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + } + + return 0; +} + +static int +test_argparse_invalid_arg_saver(void) +{ + struct rte_argparse *obj; + int ret; + + /* test saver == NULL with val-type != 0. */ + obj = test_argparse_init_obj(); + obj->args[0].val_saver = NULL; + obj->args[0].flags = RTE_ARGPARSE_ARG_NO_VALUE | RTE_ARGPARSE_ARG_VALUE_INT; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + /* test saver == NULL with callback is NULL. */ + obj = test_argparse_init_obj(); + obj->args[0].val_saver = NULL; + obj->args[0].flags = RTE_ARGPARSE_ARG_NO_VALUE; + obj->callback = NULL; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + /* test saver != NULL with val-type is zero! */ + obj = test_argparse_init_obj(); + obj->args[0].val_saver = (void *)1; + obj->args[0].val_set = (void *)1; + obj->args[0].flags = RTE_ARGPARSE_ARG_NO_VALUE; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + /* test saver != NULL with val-type is max. */ + obj = test_argparse_init_obj(); + obj->args[0].val_saver = (void *)1; + obj->args[0].val_set = (void *)1; + obj->args[0].flags = RTE_ARGPARSE_ARG_NO_VALUE | RTE_ARGPARSE_ARG_VALUE_MAX; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + /* test saver != NULL with required value, but val-set is not NULL. */ + obj = test_argparse_init_obj(); + obj->args[0].val_saver = (void *)1; + obj->args[0].val_set = (void *)1; + obj->args[0].flags = RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + return 0; +} + +static int +test_argparse_invalid_arg_flags(void) +{ + struct rte_argparse *obj; + int ret; + + obj = test_argparse_init_obj(); + obj->args[0].flags |= ~0x107FFu; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].name_long = "positional"; + obj->args[0].name_short = NULL; + obj->args[0].val_saver = (void *)1; + obj->args[0].val_set = (void *)1; + obj->args[0].flags = RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_VALUE_INT | + RTE_ARGPARSE_ARG_SUPPORT_MULTI; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].flags |= RTE_ARGPARSE_ARG_SUPPORT_MULTI; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + obj = test_argparse_init_obj(); + obj->args[0].val_saver = NULL; + obj->args[0].flags = RTE_ARGPARSE_ARG_REQUIRED_VALUE | RTE_ARGPARSE_ARG_SUPPORT_MULTI; + obj->callback = NULL; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + return 0; +} + +static int +test_argparse_invalid_arg_repeat(void) +{ + struct rte_argparse *obj; + int ret; + + /* test for long name repeat! */ + obj = test_argparse_init_obj(); + obj->args[1].name_long = obj->args[0].name_long; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + /* test for short name repeat! */ + obj = test_argparse_init_obj(); + obj->args[1].name_short = obj->args[0].name_short; + ret = rte_argparse_parse(obj, default_argc, default_argv); + TEST_ASSERT(ret == -EINVAL, "Argparse parse expect failed!"); + + return 0; +} + +static struct unit_test_suite argparse_test_suite = { + .suite_name = "Argparse Unit Test Suite", + .setup = test_argparse_setup, + .teardown = test_argparse_teardown, + .unit_test_cases = { + TEST_CASE(test_argparse_invalid_basic_param), + TEST_CASE(test_argparse_invalid_arg_name), + TEST_CASE(test_argparse_invalid_arg_help), + TEST_CASE(test_argparse_invalid_has_val), + TEST_CASE(test_argparse_invalid_arg_saver), + TEST_CASE(test_argparse_invalid_arg_flags), + TEST_CASE(test_argparse_invalid_arg_repeat), + + TEST_CASES_END() /**< NULL terminate unit test array */ + } +}; + +static int +test_argparse(void) +{ + return unit_test_suite_runner(&argparse_test_suite); +} + +REGISTER_FAST_TEST(argparse_autotest, true, true, test_argparse); diff --git a/lib/argparse/rte_argparse.c b/lib/argparse/rte_argparse.c index 3471c5e757..9c516c4bea 100644 --- a/lib/argparse/rte_argparse.c +++ b/lib/argparse/rte_argparse.c @@ -2,13 +2,318 @@ * Copyright(c) 2024 HiSilicon Limited */ +#include +#include +#include + +#include + #include "rte_argparse.h" +RTE_LOG_REGISTER_DEFAULT(rte_argparse_logtype, INFO); +#define RTE_LOGTYPE_ARGPARSE rte_argparse_logtype +#define ARGPARSE_LOG(level, ...) \ + RTE_LOG_LINE(level, ARGPARSE, "" __VA_ARGS__) + +#define ARG_ATTR_HAS_VAL_MASK RTE_GENMASK64(1, 0) +#define ARG_ATTR_VAL_TYPE_MASK RTE_GENMASK64(9, 2) +#define ARG_ATTR_SUPPORT_MULTI_MASK RTE_BIT64(10) +#define ARG_ATTR_FLAG_PARSED_MASK RTE_BIT64(63) + +static inline bool +is_arg_optional(const struct rte_argparse_arg *arg) +{ + return arg->name_long[0] == '-'; +} + +static inline bool +is_arg_positional(const struct rte_argparse_arg *arg) +{ + return arg->name_long[0] != '-'; +} + +static inline uint32_t +arg_attr_has_val(const struct rte_argparse_arg *arg) +{ + return RTE_FIELD_GET64(ARG_ATTR_HAS_VAL_MASK, arg->flags); +} + +static inline uint32_t +arg_attr_val_type(const struct rte_argparse_arg *arg) +{ + return RTE_FIELD_GET64(ARG_ATTR_VAL_TYPE_MASK, arg->flags); +} + +static inline bool +arg_attr_flag_multi(const struct rte_argparse_arg *arg) +{ + return RTE_FIELD_GET64(ARG_ATTR_SUPPORT_MULTI_MASK, arg->flags); +} + +static inline uint32_t +arg_attr_unused_bits(const struct rte_argparse_arg *arg) +{ +#define USED_BIT_MASK (ARG_ATTR_HAS_VAL_MASK | ARG_ATTR_VAL_TYPE_MASK | \ + ARG_ATTR_SUPPORT_MULTI_MASK) + return arg->flags & ~USED_BIT_MASK; +} + +static int +verify_arg_name(const struct rte_argparse_arg *arg) +{ + if (is_arg_optional(arg)) { + if (strlen(arg->name_long) <= 3) { + ARGPARSE_LOG(ERR, "optional long name %s too short!", arg->name_long); + return -EINVAL; + } + if (arg->name_long[1] != '-') { + ARGPARSE_LOG(ERR, "optional long name %s must only start with '--'", + arg->name_long); + return -EINVAL; + } + if (arg->name_long[2] == '-') { + ARGPARSE_LOG(ERR, "optional long name %s should not start with '---'", + arg->name_long); + return -EINVAL; + } + } + + if (arg->name_short == NULL) + return 0; + + if (!is_arg_optional(arg)) { + ARGPARSE_LOG(ERR, "short name %s corresponding long name must be optional!", + arg->name_short); + return -EINVAL; + } + + if (strlen(arg->name_short) != 2 || arg->name_short[0] != '-' || + arg->name_short[1] == '-') { + ARGPARSE_LOG(ERR, "short name %s must start with a hyphen (-) followed by an English letter", + arg->name_short); + return -EINVAL; + } + + return 0; +} + +static int +verify_arg_help(const struct rte_argparse_arg *arg) +{ + if (arg->help == NULL) { + ARGPARSE_LOG(ERR, "argument %s must have help info!", arg->name_long); + return -EINVAL; + } + + return 0; +} + +static int +verify_arg_has_val(const struct rte_argparse_arg *arg) +{ + uint32_t has_val = arg_attr_has_val(arg); + + if (is_arg_positional(arg)) { + if (has_val == RTE_ARGPARSE_ARG_REQUIRED_VALUE) + return 0; + ARGPARSE_LOG(ERR, "argument %s is positional, should has zero or required-val!", + arg->name_long); + return -EINVAL; + } + + if (has_val == 0) { + ARGPARSE_LOG(ERR, "argument %s is optional, has-val config wrong!", + arg->name_long); + return -EINVAL; + } + + return 0; +} + +static int +verify_arg_saver(const struct rte_argparse *obj, uint32_t index) +{ + uint32_t cmp_max = RTE_FIELD_GET64(ARG_ATTR_VAL_TYPE_MASK, RTE_ARGPARSE_ARG_VALUE_MAX); + const struct rte_argparse_arg *arg = &obj->args[index]; + uint32_t val_type = arg_attr_val_type(arg); + uint32_t has_val = arg_attr_has_val(arg); + + if (arg->val_saver == NULL) { + if (val_type != 0) { + ARGPARSE_LOG(ERR, "argument %s parse by callback, val-type must be zero!", + arg->name_long); + return -EINVAL; + } + + if (obj->callback == NULL) { + ARGPARSE_LOG(ERR, "argument %s parse by callback, but callback is NULL!", + arg->name_long); + return -EINVAL; + } + + return 0; + } + + if (val_type == 0 || val_type >= cmp_max) { + ARGPARSE_LOG(ERR, "argument %s val-type config wrong!", arg->name_long); + return -EINVAL; + } + + if (has_val == RTE_ARGPARSE_ARG_REQUIRED_VALUE && arg->val_set != NULL) { + ARGPARSE_LOG(ERR, "argument %s has required value, val-set should be NULL!", + arg->name_long); + return -EINVAL; + } + + return 0; +} + +static int +verify_arg_flags(const struct rte_argparse *obj, uint32_t index) +{ + const struct rte_argparse_arg *arg = &obj->args[index]; + uint32_t unused_bits = arg_attr_unused_bits(arg); + + if (unused_bits != 0) { + ARGPARSE_LOG(ERR, "argument %s flags set wrong!", arg->name_long); + return -EINVAL; + } + + if (!(arg->flags & RTE_ARGPARSE_ARG_SUPPORT_MULTI)) + return 0; + + if (is_arg_positional(arg)) { + ARGPARSE_LOG(ERR, "argument %s is positional, don't support multiple times!", + arg->name_long); + return -EINVAL; + } + + if (arg->val_saver != NULL) { + ARGPARSE_LOG(ERR, "argument %s could occur multiple times, should use callback to parse!", + arg->name_long); + return -EINVAL; + } + + if (obj->callback == NULL) { + ARGPARSE_LOG(ERR, "argument %s should use callback to parse, but callback is NULL!", + arg->name_long); + return -EINVAL; + } + + return 0; +} + +static int +verify_arg_repeat(const struct rte_argparse *self, uint32_t index) +{ + const struct rte_argparse_arg *arg = &self->args[index]; + uint32_t i; + + for (i = 0; i < index; i++) { + if (!strcmp(arg->name_long, self->args[i].name_long)) { + ARGPARSE_LOG(ERR, "argument %s repeat!", arg->name_long); + return -EINVAL; + } + } + + if (arg->name_short == NULL) + return 0; + + for (i = 0; i < index; i++) { + if (self->args[i].name_short == NULL) + continue; + if (!strcmp(arg->name_short, self->args[i].name_short)) { + ARGPARSE_LOG(ERR, "argument %s repeat!", arg->name_short); + return -EINVAL; + } + } + + return 0; +} + +static int +verify_argparse_arg(const struct rte_argparse *obj, uint32_t index) +{ + const struct rte_argparse_arg *arg = &obj->args[index]; + int ret; + + ret = verify_arg_name(arg); + if (ret != 0) + return ret; + + ret = verify_arg_help(arg); + if (ret != 0) + return ret; + + ret = verify_arg_has_val(arg); + if (ret != 0) + return ret; + + ret = verify_arg_saver(obj, index); + if (ret != 0) + return ret; + + ret = verify_arg_flags(obj, index); + if (ret != 0) + return ret; + + ret = verify_arg_repeat(obj, index); + if (ret != 0) + return ret; + + return 0; +} + +static int +verify_argparse(const struct rte_argparse *obj) +{ + uint32_t idx; + int ret; + + if (obj->prog_name == NULL) { + ARGPARSE_LOG(ERR, "program name is NULL!"); + return -EINVAL; + } + + if (obj->usage == NULL) { + ARGPARSE_LOG(ERR, "usage is NULL!"); + return -EINVAL; + } + + for (idx = 0; idx < RTE_DIM(obj->reserved); idx++) { + if (obj->reserved[idx] != 0) { + ARGPARSE_LOG(ERR, "reserved field must be zero!"); + return -EINVAL; + } + } + + idx = 0; + while (obj->args[idx].name_long != NULL) { + ret = verify_argparse_arg(obj, idx); + if (ret != 0) + return ret; + idx++; + } + + return 0; +} + int rte_argparse_parse(struct rte_argparse *obj, int argc, char **argv) { - (void)obj; + int ret; + (void)argc; (void)argv; + + ret = verify_argparse(obj); + if (ret != 0) + goto error; + return 0; + +error: + if (obj->exit_on_error) + exit(ret); + return ret; } -- 2.17.1