From a94e48572a785d144f565ee8addbd4180693e808 Mon Sep 17 00:00:00 2001 From: Tomasz Gromadzki Date: Thu, 13 Feb 2025 10:15:21 +0100 Subject: [PATCH] DAOS-16953 tests: fix btree parameter parsing (#15850) In btree.sh the -m and -t parameters were not applied correctly because of the preceding positional argument containing spaces but not wrapped in quotes In btree.c an invalid parameter sequence was not detected due to of the partially manual parsing process. All parameters parsing process has been unified. Additionally: instead of -t and -m parameters new parameters has been proposed: -R[d] for class registration, optionally with dynamic root -M[p] for memory registration, optionally with indication to use persistent memory struct btr_test_state introduced to provide global parameters to the test (argc/argv) and avoid static variables for that purpose. Signed-off-by: Tomasz Gromadzki --- src/common/tests/btree.c | 237 +++++++++++++++++--------------- src/common/tests/btree.sh | 24 ++-- src/common/tests/btree_direct.c | 234 +++++++++++++++++-------------- 3 files changed, 270 insertions(+), 225 deletions(-) diff --git a/src/common/tests/btree.c b/src/common/tests/btree.c index 69a595d6109..c60c0bdebc8 100644 --- a/src/common/tests/btree.c +++ b/src/common/tests/btree.c @@ -1,5 +1,6 @@ /** * (C) Copyright 2016-2022 Intel Corporation. + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -62,9 +63,10 @@ struct ik_rec { umem_off_t ir_val_off; }; -static char **test_group_args; -static int test_group_start; -static int test_group_stop; +struct btr_test_state { + int argc; + char **argv; +}; #define IK_TREE_CLASS 100 #define POOL_NAME "/mnt/daos/btree-test" @@ -834,7 +836,6 @@ ik_btr_perf(void **state) D_FREE(arr); } - static void ik_btr_drain(void **state) { @@ -884,52 +885,98 @@ ik_btr_drain(void **state) D_FREE(arr); } -static struct option btr_ops[] = { - { "create", required_argument, NULL, 'C' }, - { "destroy", no_argument, NULL, 'D' }, - { "drain", no_argument, NULL, 'e' }, - { "open", no_argument, NULL, 'o' }, - { "close", no_argument, NULL, 'c' }, - { "update", required_argument, NULL, 'u' }, - { "find", required_argument, NULL, 'f' }, - { "dyn_tree", no_argument, NULL, 't' }, - { "delete", required_argument, NULL, 'd' }, - { "del_retain", required_argument, NULL, 'r' }, - { "query", no_argument, NULL, 'q' }, - { "iterate", required_argument, NULL, 'i' }, - { "batch", required_argument, NULL, 'b' }, - { "perf", required_argument, NULL, 'p' }, - { NULL, 0, NULL, 0 }, -}; - -static int -use_pmem() { +static void +ik_btr_memory_register(char *pmem_flag) +{ + int rc = 0; + if (pmem_flag && *pmem_flag != 'p') { + fail_msg("Invalid value of -M parameter: %c\n", *pmem_flag); + } + if (pmem_flag) { + D_PRINT("Using pmem\n"); + rc = utest_pmem_create(POOL_NAME, POOL_SIZE, sizeof(*ik_root), NULL, &ik_utx); + if (rc) { + fail_msg("Cannot setup pmem: %d\n", rc); + } + } else { + D_PRINT("Using vmem\n"); + rc = utest_vmem_create(sizeof(*ik_root), &ik_utx); + if (rc) { + fail_msg("Cannot setup vmem: %d\n", rc); + } + } - int rc; + ik_root = utest_utx2root(ik_utx); + ik_uma = utest_utx2uma(ik_utx); +} - D_PRINT("Using pmem\n"); - rc = utest_pmem_create(POOL_NAME, POOL_SIZE, - sizeof(*ik_root), NULL, - &ik_utx); - D_ASSERT(rc == 0); - return rc; +static void +ik_btr_class_register(char *dynamic_flag) +{ + if (dynamic_flag && *dynamic_flag != 'd') { + fail_msg("Invalid value of -R parameter: %c\n", *dynamic_flag); + } + if (dynamic_flag) { + D_PRINT("Using dynamic tree order\n"); + } + int rc = dbtree_class_register(IK_TREE_CLASS, + (dynamic_flag ? BTR_FEAT_DYNAMIC_ROOT : 0) | + BTR_FEAT_EMBED_FIRST | BTR_FEAT_UINT_KEY, + &ik_ops); + if (rc) { + fail_msg("Cannot register memory class: %d\n", rc); + } } +static struct option btr_ops[] = { + {"start-test", required_argument, NULL, 'S'}, + {"reg-class", optional_argument, NULL, 'R'}, + {"reg-memory", optional_argument, NULL, 'M'}, + {"create", required_argument, NULL, 'C'}, + {"destroy", no_argument, NULL, 'D'}, + {"drain", no_argument, NULL, 'e'}, + {"open", no_argument, NULL, 'o'}, + {"close", no_argument, NULL, 'c'}, + {"update", required_argument, NULL, 'u'}, + {"find", required_argument, NULL, 'f'}, + {"delete", required_argument, NULL, 'd'}, + {"del_retain", required_argument, NULL, 'r'}, + {"query", no_argument, NULL, 'q'}, + {"iterate", required_argument, NULL, 'i'}, + {"batch", required_argument, NULL, 'b'}, + {"perf", required_argument, NULL, 'p'}, + {NULL, 0, NULL, 0}, +}; + +#define BTR_SHORTOPTS "+S:R::M::C:Deocqu:f:d:r:qi:b:p:" + +/** + * Execute test based on the given sequence of steps. + * -S/--start-test option is ignored + * -R and -M options must be provided before -C option. + * Each argument of the -R and -M options must be specified without a space between them (e.g. -Rd). + */ static void ts_group(void **state) { + struct btr_test_state *test_state = (struct btr_test_state *)*state; + int opt = 0; + void **st = NULL; - int opt = 0; - void **st = NULL; - - while ((opt = getopt_long(test_group_stop-test_group_start+1, - test_group_args+test_group_start, - "tmC:Deocqu:d:r:f:i:b:p:", - btr_ops, + while ((opt = getopt_long(test_state->argc, test_state->argv, BTR_SHORTOPTS, btr_ops, NULL)) != -1) { tst_fn_val.optval = optarg; tst_fn_val.input = true; switch (opt) { + case 'S': + /* not part of the test sequence */ + break; + case 'R': + ik_btr_class_register(tst_fn_val.optval); + break; + case 'M': + ik_btr_memory_register(tst_fn_val.optval); + break; case 'C': ik_btr_open_create(st); break; @@ -978,27 +1025,18 @@ ts_group(void **state) { ik_btr_perf(st); break; default: - D_PRINT("Unsupported command %c\n", opt); - case 'm': - case 't': - /* handled previously */ - break; + fail_msg("Unsupported command %c\n", opt); } } } static int -run_cmd_line_test(char *test_name, char **args, int start_idx, int stop_idx) +run_cmd_line_test(char *test_name, struct btr_test_state *initial_state) { - const struct CMUnitTest btree_test[] = { - {test_name, ts_group, NULL, NULL}, + {test_name, ts_group, NULL, NULL, initial_state}, }; - test_group_args = args; - test_group_start = start_idx; - test_group_stop = stop_idx; - return cmocka_run_group_tests_name(test_name, btree_test, NULL, @@ -1011,82 +1049,57 @@ main(int argc, char **argv) struct timeval tv; int rc = 0; int opt; - int dynamic_flag = 0; - int start_idx; - char *test_name; - int stop_idx; + char *test_name = NULL; + struct btr_test_state initial_state = {.argc = argc, .argv = argv}; d_register_alt_assert(mock_assert); - gettimeofday(&tv, NULL); - srand(tv.tv_usec); - - ik_toh = DAOS_HDL_INVAL; - ik_root_off = UMOFF_NULL; - - rc = daos_debug_init(DAOS_LOG_DEFAULT); - if (rc != 0) - return rc; - if (argc == 1) { - print_message("Invalid format.\n"); + print_message("No parameters provided.\n"); return -1; } - stop_idx = argc-1; - if (strcmp(argv[1], "--start-test") == 0) { - start_idx = 2; - test_name = argv[2]; - if (strcmp(argv[3], "-t") == 0) { - D_PRINT("Using dynamic tree order\n"); - dynamic_flag = BTR_FEAT_DYNAMIC_ROOT; - if (strcmp(argv[4], "-m") == 0) - rc = use_pmem(); - } else if (strcmp(argv[3], "-m") == 0) { - rc = use_pmem(); - if (strcmp(argv[4], "-t") == 0) { - D_PRINT("Using dynamic tree order\n"); - dynamic_flag = BTR_FEAT_DYNAMIC_ROOT; - } - } - } else { - start_idx = 0; - test_name = "Btree testing tool"; - optind = 0; - /* Check for -m option first */ - while ((opt = getopt_long(argc, argv, "tmC:Deocqu:d:r:f:i:b:p:", - btr_ops, NULL)) != -1) { - if (opt == 'm') { - rc = use_pmem(); - break; - } - if (opt == 't') { - D_PRINT("Using dynamic tree order\n"); - dynamic_flag = BTR_FEAT_DYNAMIC_ROOT; - } + /* Check for --start-test parameter and verify that all parameters are in place */ + while ((opt = getopt_long(argc, argv, BTR_SHORTOPTS, btr_ops, NULL)) != -1) { + if (opt == 'S') { + test_name = optarg; + } else if (opt == '?') { + break; } } + if (opt == '?') { + /* invalid option - error message printed on stderr already */ + return -1; + } else if (argc != optind) { + fail_msg("Cannot interpret parameter: \"%s\" at optind: %d.\n", argv[optind], + optind); + } - rc = dbtree_class_register( - IK_TREE_CLASS, dynamic_flag | BTR_FEAT_EMBED_FIRST | BTR_FEAT_UINT_KEY, &ik_ops); - D_ASSERT(rc == 0); + /* getopt_long start over */ + optind = 1; - if (ik_utx == NULL) { - D_PRINT("Using vmem\n"); - rc = utest_vmem_create(sizeof(*ik_root), &ik_utx); - D_ASSERT(rc == 0); + if (test_name == NULL) { + test_name = "Btree testing tool"; } - ik_root = utest_utx2root(ik_utx); - ik_uma = utest_utx2uma(ik_utx); + gettimeofday(&tv, NULL); + srand(tv.tv_usec); - /* start over */ - optind = 0; - rc = run_cmd_line_test(test_name, argv, start_idx, stop_idx); + ik_toh = DAOS_HDL_INVAL; + ik_root_off = UMOFF_NULL; + + rc = daos_debug_init(DAOS_LOG_DEFAULT); + if (rc != 0) { + fail_msg("daos_debug_init() failed: %d\n", rc); + } + rc = run_cmd_line_test(test_name, &initial_state); daos_debug_fini(); - rc += utest_utx_destroy(ik_utx); - if (rc != 0) - printf("Error: %d\n", rc); + if (ik_utx) { + rc += utest_utx_destroy(ik_utx); + } + if (rc != 0) { + fail_msg("Error: %d\n", rc); + } - return rc; + return 0; } diff --git a/src/common/tests/btree.sh b/src/common/tests/btree.sh index 03a35d47f9e..1bc85b21ccb 100755 --- a/src/common/tests/btree.sh +++ b/src/common/tests/btree.sh @@ -31,6 +31,7 @@ Usage: btree.sh [OPTIONS] -s [num] Run with num keys dyn Run with dynamic root ukey Use integer keys + emb Use embedded value perf Run performance tests direct Use direct string key EOF @@ -39,6 +40,7 @@ EOF PERF="" UINT="" +DYN="" test_conf_pre="" while [ $# -gt 0 ]; do case "$1" in @@ -53,7 +55,7 @@ while [ $# -gt 0 ]; do test_conf_pre="${test_conf_pre} keys=${BAT_NUM}" ;; dyn) - DYN="-t" + DYN="d" shift test_conf_pre="${test_conf_pre} dyn" ;; @@ -107,9 +109,9 @@ run_test() echo "B+tree functional test..." DAOS_DEBUG="$DDEBUG" \ - eval "${VCMD}" "$BTR" --start-test \ - "btree functional ${test_conf_pre} ${test_conf} iterate=${IDIR}" \ - "${DYN}" "${PMEM}" -C "${UINT}${IPL}o:$ORDER" \ + eval "${VCMD}" "$BTR" \ + --start-test "'btree functional ${test_conf_pre} ${test_conf} iterate=${IDIR}'" \ + -R"${DYN}" -M"${PMEM}" -C "${UINT}${IPL}o:$ORDER" \ -c \ -o \ -u "$RECORDS" \ @@ -128,8 +130,8 @@ run_test() echo "B+tree batch operations test..." eval "${VCMD}" "$BTR" \ - --start-test "btree batch operations ${test_conf_pre} ${test_conf}" \ - "${DYN}" "${PMEM}" -C "${UINT}${IPL}o:$ORDER" \ + --start-test "'btree batch operations ${test_conf_pre} ${test_conf}'" \ + -R"${DYN}" -M"${PMEM}" -C "${UINT}${IPL}o:$ORDER" \ -c \ -o \ -b "$BAT_NUM" \ @@ -137,15 +139,15 @@ run_test() echo "B+tree drain test..." eval "${VCMD}" "$BTR" \ - --start-test "btree drain ${test_conf_pre} ${test_conf}" \ - "${DYN}" "${PMEM}" -C "${UINT}${IPL}o:$ORDER" \ + --start-test "'btree drain ${test_conf_pre} ${test_conf}'" \ + -R"${DYN}" -M"${PMEM}" -C "${UINT}${IPL}o:$ORDER" \ -e -D else echo "B+tree performance test..." eval "${VCMD}" "$BTR" \ - --start-test "btree performance ${test_conf_pre} ${test_conf}" \ - "${DYN}" "${PMEM}" -C "${UINT}${IPL}o:$ORDER" \ + --start-test "'btree performance ${test_conf_pre} ${test_conf}'" \ + -R"${DYN}" -M"${PMEM}" -C "${UINT}${IPL}o:$ORDER" \ -p "$BAT_NUM" \ -D fi @@ -153,7 +155,7 @@ run_test() for IPL in "i," ""; do for IDIR in "f" "b"; do - for PMEM in "-m" ""; do + for PMEM in "p" ""; do run_test done done diff --git a/src/common/tests/btree_direct.c b/src/common/tests/btree_direct.c index 8fb3fa8b6fd..d399bbd1681 100644 --- a/src/common/tests/btree_direct.c +++ b/src/common/tests/btree_direct.c @@ -1,12 +1,12 @@ /** * (C) Copyright 2018-2022 Intel Corporation. + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ #define D_LOGFAC DD_FAC(tests) #include -#include #include #include #include @@ -25,10 +25,6 @@ #include #include "utest_common.h" -static char **test_group_args; -static int test_group_start; -static int test_group_stop; - enum sk_btr_opc { BTR_OPC_UPDATE, BTR_OPC_LOOKUP, @@ -53,6 +49,11 @@ struct sk_rec { char sr_key[0]; }; +struct btr_test_state { + int argc; + char **argv; +}; + #define SK_TREE_CLASS 100 #define POOL_NAME "/mnt/daos/btree-direct-test" #define POOL_SIZE ((1024 * 1024 * 1024ULL)) @@ -285,15 +286,15 @@ sk_rec_stat(struct btr_instance *tins, struct btr_record *rec, } static btr_ops_t sk_ops = { - .to_key_cmp = sk_key_cmp, - .to_key_encode = sk_key_encode, - .to_key_decode = sk_key_decode, - .to_rec_alloc = sk_rec_alloc, - .to_rec_free = sk_rec_free, - .to_rec_fetch = sk_rec_fetch, - .to_rec_update = sk_rec_update, - .to_rec_string = sk_rec_string, - .to_rec_stat = sk_rec_stat, + .to_key_encode = sk_key_encode, + .to_key_decode = sk_key_decode, + .to_key_cmp = sk_key_cmp, + .to_rec_alloc = sk_rec_alloc, + .to_rec_free = sk_rec_free, + .to_rec_fetch = sk_rec_fetch, + .to_rec_update = sk_rec_update, + .to_rec_string = sk_rec_string, + .to_rec_stat = sk_rec_stat, }; #define SK_SEP ',' @@ -1026,37 +1027,87 @@ sk_btr_perf(void **state) D_FREE(kv); } +static void +sk_btr_memory_register(char *pmem_flag) +{ + int rc = 0; + if (pmem_flag && *pmem_flag != 'p') { + fail_msg("Invalid value of -M parameter: %c\n", *pmem_flag); + } + if (pmem_flag) { + D_PRINT("Using pmem\n"); + rc = utest_pmem_create(POOL_NAME, POOL_SIZE, sizeof(*sk_root), NULL, &sk_utx); + if (rc) { + fail_msg("Cannot setup pmem: %d\n", rc); + } + } else { + D_PRINT("Using vmem\n"); + rc = utest_vmem_create(sizeof(*sk_root), &sk_utx); + if (rc) { + fail_msg("Cannot setup vmem: %d\n", rc); + } + } + + sk_root = utest_utx2root(sk_utx); + sk_uma = utest_utx2uma(sk_utx); +} +static void +sk_btr_class_register() +{ + D_PRINT("Using dynamic tree order\n"); + int rc = dbtree_class_register(SK_TREE_CLASS, BTR_FEAT_EMBED_FIRST | BTR_FEAT_DIRECT_KEY, + &sk_ops); + if (rc) { + fail_msg("Cannot register memory class: %d\n", rc); + } +} + static struct option btr_ops[] = { - { "create", required_argument, NULL, 'C' }, - { "destroy", no_argument, NULL, 'D' }, - { "open", no_argument, NULL, 'o' }, - { "close", no_argument, NULL, 'c' }, - { "update", required_argument, NULL, 'u' }, - { "find", required_argument, NULL, 'f' }, - { "delete", required_argument, NULL, 'd' }, - { "del_retain", required_argument, NULL, 'r' }, - { "query", no_argument, NULL, 'q' }, - { "iterate", required_argument, NULL, 'i' }, - { "batch", required_argument, NULL, 'b' }, - { "perf", required_argument, NULL, 'p' }, - { NULL, 0, NULL, 0 }, + {"start-test", required_argument, NULL, 'S'}, + {"reg-class", no_argument, NULL, 'R'}, + {"reg-memory", optional_argument, NULL, 'M'}, + {"create", required_argument, NULL, 'C'}, + {"destroy", no_argument, NULL, 'D'}, + {"drain", no_argument, NULL, 'e'}, + {"open", no_argument, NULL, 'o'}, + {"close", no_argument, NULL, 'c'}, + {"update", required_argument, NULL, 'u'}, + {"find", required_argument, NULL, 'f'}, + {"delete", required_argument, NULL, 'd'}, + {"del_retain", required_argument, NULL, 'r'}, + {"query", no_argument, NULL, 'q'}, + {"iterate", required_argument, NULL, 'i'}, + {"batch", required_argument, NULL, 'b'}, + {"perf", required_argument, NULL, 'p'}, + {NULL, 0, NULL, 0}, }; +#define BTR_SHORTOPTS "+S:RM::C:Deocqu:f:d:r:qi:b:p:" + static void ts_group(void **state) { - + struct btr_test_state *test_state = (struct btr_test_state *)*state; int opt = 0; void **st = NULL; D_PRINT("--------------------------------------\n"); - while ((opt = getopt_long(test_group_stop-test_group_start+1, - test_group_args+test_group_start, - "mC:Docqu:d:r:f:i:b:p:", - btr_ops, + while ((opt = getopt_long(test_state->argc, test_state->argv, BTR_SHORTOPTS, btr_ops, NULL)) != -1) { tst_fn_val.optval = optarg; tst_fn_val.input = true; + switch (opt) { + case 'S': + /* not part of the test sequence */ + break; + case 'R': + sk_btr_class_register(); + break; + + case 'M': + sk_btr_memory_register(tst_fn_val.optval); + break; + case 'C': sk_btr_open_create(st); break; @@ -1072,6 +1123,10 @@ ts_group(void **state) { tst_fn_val.input = false; sk_btr_close_destroy(st); break; + case 'e': + /* not supported by btree_direct, + but keep here for compatibility with btree.c */ + break; case 'q': sk_btr_query(st); break; @@ -1101,104 +1156,79 @@ ts_group(void **state) { sk_btr_perf(st); break; default: - D_PRINT("Unsupported command %c\n", opt); - case 'm': - /* already handled */ - break; + fail_msg("Unsupported command %c\n", opt); } D_PRINT("--------------------------------------\n"); } } static int -run_cmd_line_test(char *test_name, char **args, int start_idx, int stop_idx) +run_cmd_line_test(char *test_name, struct btr_test_state *initial_state) { const struct CMUnitTest btree_test[] = { - {test_name, ts_group, NULL, NULL}, + {test_name, ts_group, NULL, NULL, initial_state}, }; - test_group_args = args; - test_group_start = start_idx; - test_group_stop = stop_idx; - - return cmocka_run_group_tests_name(test_name, - btree_test, - NULL, - NULL); - + return cmocka_run_group_tests_name(test_name, btree_test, NULL, NULL); } int main(int argc, char **argv) { struct timeval tv; + int rc = 0; int opt; - int rc; - int start_idx; - char *test_name; - int stop_idx; + char *test_name = NULL; + struct btr_test_state initial_state = {.argc = argc, .argv = argv}; d_register_alt_assert(mock_assert); - gettimeofday(&tv, NULL); - srand(tv.tv_usec); - - sk_toh = DAOS_HDL_INVAL; - sk_root_off = UMOFF_NULL; - - rc = daos_debug_init(DAOS_LOG_DEFAULT); - if (rc != 0) - return rc; + if (argc == 1) { + print_message("No parameters provided.\n"); + return -1; + } - rc = dbtree_class_register(SK_TREE_CLASS, BTR_FEAT_EMBED_FIRST | BTR_FEAT_DIRECT_KEY, - &sk_ops); - D_ASSERT(rc == 0); - - stop_idx = argc-1; - if (strcmp(argv[1], "--start-test") == 0) { - start_idx = 2; - test_name = argv[2]; - if (strcmp(argv[3], "-m") == 0) { - D_PRINT("Using pmem\n"); - rc = utest_pmem_create(POOL_NAME, POOL_SIZE, - sizeof(*sk_root), NULL, &sk_utx); - D_ASSERT(rc == 0); - } - } else { - start_idx = 0; - test_name = "Btree testing tool"; - optind = 0; - /* Check for -m option first */ - while ((opt = getopt_long(argc, argv, "mC:Docqu:d:r:f:i:b:p:", - btr_ops, NULL)) != -1) { - if (opt == 'm') { - D_PRINT("Using pmem\n"); - rc = utest_pmem_create(POOL_NAME, POOL_SIZE, - sizeof(*sk_root), NULL, - &sk_utx); - D_ASSERT(rc == 0); - break; - } + /* Check for --start-test parameter and verify that all parameters are in place */ + while ((opt = getopt_long(argc, argv, BTR_SHORTOPTS, btr_ops, NULL)) != -1) { + if (opt == 'S') { + test_name = optarg; + } else if (opt == '?') { + break; } } + if (opt == '?') { + /* invalid option - error message printed on stderr already */ + return -1; + } else if (argc != optind) { + fail_msg("Cannot interpret parameter: \"%s\" at optind: %d.\n", argv[optind], + optind); + } - if (sk_utx == NULL) { - D_PRINT("Using vmem\n"); - rc = utest_vmem_create(sizeof(*sk_root), &sk_utx); - D_ASSERT(rc == 0); + /* getopt_long start over */ + optind = 1; + + if (test_name == NULL) { + test_name = "Btree testing tool"; } - sk_root = utest_utx2root(sk_utx); - sk_uma = utest_utx2uma(sk_utx); + gettimeofday(&tv, NULL); + srand(tv.tv_usec); - /* start over */ - optind = 0; - rc = run_cmd_line_test(test_name, argv, start_idx, stop_idx); + sk_toh = DAOS_HDL_INVAL; + sk_root_off = UMOFF_NULL; + rc = daos_debug_init(DAOS_LOG_DEFAULT); + if (rc != 0) { + fail_msg("daos_debug_init() failed: %d\n", rc); + } + rc = run_cmd_line_test(test_name, &initial_state); daos_debug_fini(); - rc += utest_utx_destroy(sk_utx); - if (rc != 0) - printf("Error: %d\n", rc); + if (sk_utx) { + rc += utest_utx_destroy(sk_utx); + } + if (rc != 0) { + fail_msg("Error: %d\n", rc); + } - return rc; + return 0; }