Re: [PATCH bpf-next] selftests/bpf: Improve by-name subtest selection logic in prog_tests

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Thanks for the super quick review Andrii!

> On Apr 5, 2022, at 4:14 PM, Andrii Nakryiko <andrii.nakryiko@xxxxxxxxx> wrote:
> 
> On Tue, Apr 5, 2022 at 1:45 PM Mykola Lysenko <mykolal@xxxxxx> wrote:
>> 
>> Improve subtest selection logic when using -t/-a/-d parameters.
>> In particular, more than one subtest can be specified or a
>> combination of tests / subtests.
>> 
>> -a send_signal -d send_signal/send_signal_nmi* - runs send_signal
>> test without nmi tests
>> 
>> -a send_signal/send_signal_nmi*,find_vma - runs two send_signal
>> subtests and find_vma test
>> 
> 
> Only somewhat related, but while we are at this topic. Can you please
> check whether equivalent approach works:
> 
> -a send_signal/send_signal_nmi* -a find_vma
> 
> i.e., multi -a/-d/-t/-b are concatenating their selectors.

Yes, these options can be combined. I will add a test for the sequence of parse_test_list calls in v2. 

> 
>> This will allow us to have granular control over which subtests
>> to disable in the CI system instead of disabling whole tests.
>> 
>> Also, add new selftest to avoid possible regression when
>> changing prog_test test name selection logic.
>> 
>> Signed-off-by: Mykola Lysenko <mykolal@xxxxxx>
>> ---
>> .../selftests/bpf/prog_tests/arg_parsing.c | 88 ++++++++++
>> tools/testing/selftests/bpf/test_progs.c | 157 +++++++++---------
>> tools/testing/selftests/bpf/test_progs.h | 16 +-
>> tools/testing/selftests/bpf/testing_helpers.c | 87 ++++++++++
>> tools/testing/selftests/bpf/testing_helpers.h | 6 +
>> 5 files changed, 266 insertions(+), 88 deletions(-)
>> create mode 100644 tools/testing/selftests/bpf/prog_tests/arg_parsing.c
>> 
> 
> [...]
> 
>> +static void test_parse_test_list(void)
>> +{
>> + struct test_set test_set;
>> +
>> + init_test_set(&test_set);
>> +
>> + parse_test_list("arg_parsing", &test_set, true);
> 
> ASSER_OK() return value?

Will fix

> 
>> + if (CHECK(test_set.cnt != 1, "parse_test_list subtest argument", "Unexpected number of tests in num table %d\n", test_set.cnt))
> 
> please don't use CHECK()s in new tests, we only add ASSERT_xxx() now.
> Also line length should be under 100 characters (except for the case
> when we have a long string literal, we don't break string literals, no
> matter how long).

Will fix

> 
>> + goto error;
>> + if (!ASSERT_OK_PTR(test_set.tests, "tests__initialized"))
>> + goto error;
>> + if (CHECK(!test_set.tests[0].whole_test, "parse_test_list subtest argument", "Expected test 0 to be initialized"))
>> + goto error;
>> + if (CHECK(strcmp("arg_parsing", test_set.tests[0].name), "parse_test_list subtest argument", "Expected test 0 to be initialized"))
>> + goto error;
>> + free_test_set(&test_set);
>> +
>> + parse_test_list("arg_parsing,bpf_cookie", &test_set, true);
>> + if (CHECK(test_set.cnt != 2, "parse_test_list subtest argument", "Unexpected number of tests in num table %d\n", test_set.cnt))
>> + goto error;
>> + if (!ASSERT_OK_PTR(test_set.tests, "tests__initialized"))
>> + goto error;
>> + if (CHECK(!test_set.tests[0].whole_test, "parse_test_list subtest argument", "Expected test 0 to be fully runnable"))
>> + goto error;
>> + if (CHECK(!test_set.tests[1].whole_test, "parse_test_list subtest argument", "Expected test 1 to be fully runnable"))
>> + goto error;
> 
> nit: I think there is no need to goto error after each check. We need
> goto error if subsequent checks can crash due to some invalid state.
> In this case, validating the value of few independent values is ok to
> always check without goto error jumps. Makes tests shorter and
> simpler.

Will fix

> 
> 
>> + if (CHECK(strcmp("arg_parsing", test_set.tests[0].name), "parse_test_list subtest argument", "Expected test 0 to be arg_parsing"))
>> + goto error;
>> + if (CHECK(strcmp("bpf_cookie", test_set.tests[1].name), "parse_test_list subtest argument", "Expected test 1 to be bpf_cookie"))
>> + goto error;
>> + free_test_set(&test_set);
>> +
>> + parse_test_list("arg_parsing/test_parse_test_list,bpf_cookie", &test_set, true);
>> + if (CHECK(test_set.cnt != 2, "parse_test_list subtest argument", "Unexpected number of tests in num table %d\n", test_set.cnt))
>> + goto error;
> 
> [...]
> 
>> diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c
>> index 0a4b45d7b515..671e37cada4b 100644
>> --- a/tools/testing/selftests/bpf/test_progs.c
>> +++ b/tools/testing/selftests/bpf/test_progs.c
>> @@ -3,6 +3,7 @@
>> */
>> #define _GNU_SOURCE
>> #include "test_progs.h"
>> +#include "testing_helpers.h"
>> #include "cgroup_helpers.h"
>> #include <argp.h>
>> #include <pthread.h>
>> @@ -82,14 +83,14 @@ int usleep(useconds_t usec)
>> static bool should_run(struct test_selector *sel, int num, const char *name)
>> {
>> int i;
>> -
> 
> keep empty line between variable declaration block and first statement

Will fix, curious though why it is not caught by the script. Probably because this line is not modified

> 
>> for (i = 0; i < sel->blacklist.cnt; i++) {
>> - if (glob_match(name, sel->blacklist.strs[i]))
>> + if (glob_match(name, sel->blacklist.tests[i].name) &&
>> + sel->blacklist.tests[i].whole_test)
>> return false;
>> }
>> 
>> for (i = 0; i < sel->whitelist.cnt; i++) {
>> - if (glob_match(name, sel->whitelist.strs[i]))
>> + if (glob_match(name, sel->whitelist.tests[i].name))
>> return true;
>> }
>> 
> 
> [...]
> 
>> -bool test__start_subtest(const char *name)
>> +bool test__start_subtest(const char *subtest_name)
>> {
>> struct prog_test_def *test = env.test;
>> 
>> @@ -205,17 +246,21 @@ bool test__start_subtest(const char *name)
>> 
>> test->subtest_num++;
>> 
>> - if (!name || !name[0]) {
>> + if (!subtest_name || !subtest_name[0]) {
>> fprintf(env.stderr,
>> "Subtest #%d didn't provide sub-test name!\n",
>> test->subtest_num);
>> return false;
>> }
>> 
>> - if (!should_run(&env.subtest_selector, test->subtest_num, name))
>> + if (!should_run_subtest(&env.test_selector,
>> + &env.subtest_selector,
> 
> we don't have subtest_selector anymore, do we?

Unfortunately, we do use subtest selector for -n option. I do have a change for -n option, but I want to review it separately. Otherwise it makes the change way to big

> 
> also, do you think that maybe combining should_run and
> should_rub_subtest would be a good idea? You can distinguish by having
> subtest_name NULL when you need to check only test?

Similar to above, because of -n option, combining two makes logic too convoluted. Happy to address it as a separate change once we figure out -n option behavior

> 
>> + test->subtest_num,
>> + test->test_name,
>> + subtest_name))
>> return false;
>> 
>> - test->subtest_name = strdup(name);
>> + test->subtest_name = strdup(subtest_name);
>> if (!test->subtest_name) {
>> fprintf(env.stderr,
>> "Subtest #%d: failed to copy subtest name!\n",
>> @@ -527,63 +572,29 @@ static int libbpf_print_fn(enum libbpf_print_level level,
>> return 0;
>> }
>> 
> 
> [...]
> 
>> diff --git a/tools/testing/selftests/bpf/test_progs.h b/tools/testing/selftests/bpf/test_progs.h
>> index eec4c7385b14..6a465a98341e 100644
>> --- a/tools/testing/selftests/bpf/test_progs.h
>> +++ b/tools/testing/selftests/bpf/test_progs.h
>> @@ -37,7 +37,6 @@ typedef __u16 __sum16;
>> #include <bpf/bpf_endian.h>
>> #include "trace_helpers.h"
>> #include "testing_helpers.h"
>> -#include "flow_dissector_load.h"
>> 
>> enum verbosity {
>> VERBOSE_NONE,
>> @@ -46,14 +45,21 @@ enum verbosity {
>> VERBOSE_SUPER,
>> };
>> 
>> -struct str_set {
>> - const char **strs;
>> +struct prog_test {
>> + char *name;
>> + char **subtests;
>> + int subtest_cnt;
>> + bool whole_test;
>> +};
>> +
>> +struct test_set {
>> + struct prog_test *tests;
> 
> prog_test is a bad name, IMO. it's a "test filter", right? Let's call it that?
> 
> test_set isn't most accurate as well, maybe test_filter_set or test_set_filter?

Will change

> 
>> int cnt;
>> };
>> 
>> struct test_selector {
>> - struct str_set whitelist;
>> - struct str_set blacklist;
>> + struct test_set whitelist;
>> + struct test_set blacklist;
>> bool *num_set;
>> int num_set_len;
>> };
>> diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
>> index 795b6798ccee..d2160d2a1303 100644
>> --- a/tools/testing/selftests/bpf/testing_helpers.c
>> +++ b/tools/testing/selftests/bpf/testing_helpers.c
>> @@ -6,6 +6,7 @@
>> #include <errno.h>
>> #include <bpf/bpf.h>
>> #include <bpf/libbpf.h>
>> +#include "test_progs.h"
>> #include "testing_helpers.h"
>> 
>> int parse_num_list(const char *s, bool **num_set, int *num_set_len)
>> @@ -69,6 +70,92 @@ int parse_num_list(const char *s, bool **num_set, int *num_set_len)
>> return 0;
>> }
>> 
>> +int parse_test_list(const char *s,
>> + struct test_set *test_set,
>> + bool is_glob_pattern)
>> +{
>> + char *input, *state = NULL, *next;
>> + struct prog_test *tmp, *tests = NULL;
>> + int i, j, cnt = 0;
>> +
>> + input = strdup(s);
>> + if (!input)
>> + return -ENOMEM;
>> +
>> + while ((next = strtok_r(state ? NULL : input, ",", &state))) {
>> + char *subtest_str = strchr(next, '/');
>> + char *pattern = NULL;
>> +
>> + tmp = realloc(tests, sizeof(*tests) * (cnt + 1));
>> + if (!tmp)
>> + goto err;
>> + tests = tmp;
>> +
>> + tests[cnt].subtest_cnt = 0;
>> + tests[cnt].subtests = NULL;
>> + tests[cnt].whole_test = false;
>> +
>> + if (subtest_str) {
>> + char **tmp_subtests = NULL;
> 
> need an empty line between variable declarations and statements

Will fix

> 
>> + *subtest_str = '\0';
>> + int subtest_cnt = tests[cnt].subtest_cnt;
>> +
>> + tmp_subtests = realloc(tests[cnt].subtests,
>> + sizeof(*tmp_subtests) *
>> + (subtest_cnt + 1));
>> + if (!tmp_subtests)
>> + goto err;
>> + tests[cnt].subtests = tmp_subtests;
>> +
>> + tests[cnt].subtests[subtest_cnt] = strdup(subtest_str + 1);
>> + if (!tests[cnt].subtests[subtest_cnt])
>> + goto err;
>> +
>> + tests[cnt].subtest_cnt++;
>> + } else {
>> + tests[cnt].whole_test = true;
> 
> isn't whole_test equivalent to subtest_cnt > 0? Why keeping extra bool
> of state then?

Will fix. Overthought it here a bit

> 
>> + }
>> +
>> + if (is_glob_pattern) {
>> + pattern = "%s";
>> + tests[cnt].name = malloc(strlen(next) + 1);
>> + } else {
>> + pattern = "*%s*";
>> + tests[cnt].name = malloc(strlen(next) + 2 + 1);
>> + }
>> +
>> + if (!tests[cnt].name)
>> + goto err;
>> +
>> + sprintf(tests[cnt].name, pattern, next);
>> +
>> + cnt++;
>> + }
>> +
>> + tmp = realloc(test_set->tests, sizeof(*tests) * (cnt + test_set->cnt));
>> + if (!tmp)
>> + goto err;
>> +
>> + memcpy(tmp + test_set->cnt, tests, sizeof(*tests) * cnt);
>> + test_set->tests = tmp;
>> + test_set->cnt += cnt;
>> +
>> + free(tests);
>> + free(input);
>> + return 0;
>> +
>> +err:
>> + for (i = 0; i < cnt; i++) {
>> + for (j = 0; j < tests[i].subtest_cnt; j++)
>> + free(tests[i].subtests[j]);
>> +
>> + free(tests[i].name);
>> + }
>> + free(tests);
>> + free(input);
>> + return -ENOMEM;
>> +}
>> +
>> __u32 link_info_prog_id(const struct bpf_link *link, struct bpf_link_info *info)
>> {
>> __u32 info_len = sizeof(*info);
>> diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
>> index f46ebc476ee8..d2f502184cd1 100644
>> --- a/tools/testing/selftests/bpf/testing_helpers.h
>> +++ b/tools/testing/selftests/bpf/testing_helpers.h
>> @@ -12,3 +12,9 @@ int bpf_test_load_program(enum bpf_prog_type type, const struct bpf_insn *insns,
>> size_t insns_cnt, const char *license,
>> __u32 kern_version, char *log_buf,
>> size_t log_buf_sz);
>> +
>> +/*
>> + * below function is exported for testing in prog_test test
>> + */
>> +struct test_set;
>> +int parse_test_list(const char *s, struct test_set *test_set, bool is_glob_pattern);
>> --
>> 2.30.2




[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux