All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next 0/2] selftests/bpf: test_progs can read test lists from file
@ 2023-04-25 22:53 Stephen Veiss
  2023-04-25 22:54 ` [PATCH bpf-next 1/2] selftests/bpf: extract insert_test from parse_test_list Stephen Veiss
  2023-04-25 22:54 ` [PATCH bpf-next 2/2] selftests/bpf: test_progs can read test lists from file Stephen Veiss
  0 siblings, 2 replies; 7+ messages in thread
From: Stephen Veiss @ 2023-04-25 22:53 UTC (permalink / raw)
  To: bpf; +Cc: Stephen Veiss

Hi,

BPF selftests have ALLOWLIST and DENYLIST files, used to control which
tests are run in CI. These files are currently parsed by a shell
script. [1]

This patchset allows those files to be specified directly on the
test_progs command line (eg, as -a @ALLOWLIST). 

This also fixes a bug in the existing test filter code causing
unnecessary duplicate top-level test filter entries to be created. 

Thanks,

Stephen

[1] https://github.com/kernel-patches/vmtest/blob/57feb460047b69f891cf4afe3cc860794a2ced17/ci/vmtest/run_selftests.sh#L21-L27

Stephen Veiss (2):
  selftests/bpf: extract insert_test from parse_test_list
  selftests/bpf: test_progs can read test lists from file

 .../selftests/bpf/prog_tests/arg_parsing.c    |  63 +++++
 tools/testing/selftests/bpf/test_progs.c      |  39 ++-
 tools/testing/selftests/bpf/testing_helpers.c | 225 ++++++++++++------
 tools/testing/selftests/bpf/testing_helpers.h |   3 +
 4 files changed, 249 insertions(+), 81 deletions(-)


base-commit: a0c109dcafb15b8bee187c49fb746779374f60f0
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH bpf-next 1/2] selftests/bpf: extract insert_test from parse_test_list
  2023-04-25 22:53 [PATCH bpf-next 0/2] selftests/bpf: test_progs can read test lists from file Stephen Veiss
@ 2023-04-25 22:54 ` Stephen Veiss
  2023-04-26  3:42   ` Yonghong Song
  2023-04-25 22:54 ` [PATCH bpf-next 2/2] selftests/bpf: test_progs can read test lists from file Stephen Veiss
  1 sibling, 1 reply; 7+ messages in thread
From: Stephen Veiss @ 2023-04-25 22:54 UTC (permalink / raw)
  To: bpf; +Cc: Stephen Veiss

Split the logic to insert new tests into test filter sets out from
parse_test_list.

Fix the subtest insertion logic to reuse an existing top-level test
filter, which prevents the creation of duplicate top-level test filters
each with a single subtest.

Signed-off-by: Stephen Veiss <sveiss@meta.com>
---
 .../selftests/bpf/prog_tests/arg_parsing.c    |  13 ++
 tools/testing/selftests/bpf/testing_helpers.c | 176 +++++++++++-------
 2 files changed, 117 insertions(+), 72 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
index b17bfa0e0aac..3754cd5f8c0a 100644
--- a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
+++ b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
@@ -96,6 +96,19 @@ static void test_parse_test_list(void)
 		goto error;
 	ASSERT_OK(strcmp("*bpf_cookie*", set.tests[0].name), "test name");
 	ASSERT_OK(strcmp("*trace*", set.tests[0].subtests[0]), "subtest name");
+	free_test_filter_set(&set);
+
+	ASSERT_OK(parse_test_list("t/subtest1,t/subtest2", &set, true),
+		  "parsing");
+	if (!ASSERT_EQ(set.cnt, 1, "count of test filters"))
+		goto error;
+	if (!ASSERT_OK_PTR(set.tests, "test filters initialized"))
+		goto error;
+	if (!ASSERT_EQ(set.tests[0].subtest_cnt, 2, "subtest filters count"))
+		goto error;
+	ASSERT_OK(strcmp("t", set.tests[0].name), "test name");
+	ASSERT_OK(strcmp("subtest1", set.tests[0].subtests[0]), "subtest name");
+	ASSERT_OK(strcmp("subtest2", set.tests[0].subtests[1]), "subtest name");
 error:
 	free_test_filter_set(&set);
 }
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index 0b5e0829e5be..14322371e1d8 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -70,92 +70,124 @@ int parse_num_list(const char *s, bool **num_set, int *num_set_len)
 	return 0;
 }
 
+static int do_insert_test(struct test_filter_set *set,
+			  char *test_str,
+			  char *subtest_str)
+{
+	struct test_filter *tmp, *test;
+	char **ctmp;
+	int i;
+
+	for (i = 0; i < set->cnt; i++) {
+		test = &set->tests[i];
+
+		if (strcmp(test_str, test->name) == 0) {
+			free(test_str);
+			goto subtest;
+		}
+	}
+
+	tmp = realloc(set->tests, sizeof(*test) * (set->cnt + 1));
+	if (!tmp)
+		return -ENOMEM;
+
+	set->tests = tmp;
+	test = &set->tests[set->cnt];
+
+	test->name = test_str;
+	test->subtests = NULL;
+	test->subtest_cnt = 0;
+
+	set->cnt++;
+
+subtest:
+	if (!subtest_str)
+		return 0;
+
+	for (i = 0; i < test->subtest_cnt; i++) {
+		if (strcmp(subtest_str, test->subtests[i]) == 0) {
+			free(subtest_str);
+			return 0;
+		}
+	}
+
+	ctmp = realloc(test->subtests,
+		       sizeof(*test->subtests) * (test->subtest_cnt + 1));
+	if (!ctmp)
+		return -ENOMEM;
+
+	test->subtests = ctmp;
+	test->subtests[test->subtest_cnt] = subtest_str;
+
+	test->subtest_cnt++;
+
+	return 0;
+}
+
+static int insert_test(struct test_filter_set *set,
+		       char *test_spec,
+		       bool is_glob_pattern)
+{
+	char *pattern, *subtest_str, *ext_test_str, *ext_subtest_str = NULL;
+	int glob_chars = 0;
+
+	if (is_glob_pattern) {
+		pattern = "%s";
+	} else {
+		pattern = "*%s*";
+		glob_chars = 2;
+	}
+
+	subtest_str = strchr(test_spec, '/');
+	if (subtest_str) {
+		*subtest_str = '\0';
+		subtest_str += 1;
+	}
+
+	ext_test_str = malloc(strlen(test_spec) + glob_chars + 1);
+	if (!ext_test_str)
+		goto err;
+
+	sprintf(ext_test_str, pattern, test_spec);
+
+	if (subtest_str) {
+		ext_subtest_str = malloc(strlen(subtest_str) + glob_chars + 1);
+		if (!ext_subtest_str)
+			goto err;
+
+		sprintf(ext_subtest_str, pattern, subtest_str);
+	}
+
+	return do_insert_test(set, ext_test_str, ext_subtest_str);
+
+err:
+	free(ext_test_str);
+	free(ext_subtest_str);
+
+	return -ENOMEM;
+}
+
 int parse_test_list(const char *s,
 		    struct test_filter_set *set,
 		    bool is_glob_pattern)
 {
-	char *input, *state = NULL, *next;
-	struct test_filter *tmp, *tests = NULL;
-	int i, j, cnt = 0;
+	char *input, *state = NULL, *test_spec;
+	int err;
 
 	input = strdup(s);
 	if (!input)
 		return -ENOMEM;
 
-	while ((next = strtok_r(state ? NULL : input, ",", &state))) {
-		char *subtest_str = strchr(next, '/');
-		char *pattern = NULL;
-		int glob_chars = 0;
-
-		tmp = realloc(tests, sizeof(*tests) * (cnt + 1));
-		if (!tmp)
-			goto err;
-		tests = tmp;
-
-		tests[cnt].subtest_cnt = 0;
-		tests[cnt].subtests = NULL;
-
-		if (is_glob_pattern) {
-			pattern = "%s";
-		} else {
-			pattern = "*%s*";
-			glob_chars = 2;
+	while ((test_spec = strtok_r(state ? NULL : input, ",", &state))) {
+		err = insert_test(set, test_spec, is_glob_pattern);
+		if (err) {
+			free(input);
+			return err;
 		}
-
-		if (subtest_str) {
-			char **tmp_subtests = NULL;
-			int subtest_cnt = tests[cnt].subtest_cnt;
-
-			*subtest_str = '\0';
-			subtest_str += 1;
-			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] =
-				malloc(strlen(subtest_str) + glob_chars + 1);
-			if (!tests[cnt].subtests[subtest_cnt])
-				goto err;
-			sprintf(tests[cnt].subtests[subtest_cnt],
-				pattern,
-				subtest_str);
-
-			tests[cnt].subtest_cnt++;
-		}
-
-		tests[cnt].name = malloc(strlen(next) + glob_chars + 1);
-		if (!tests[cnt].name)
-			goto err;
-		sprintf(tests[cnt].name, pattern, next);
-
-		cnt++;
 	}
 
-	tmp = realloc(set->tests, sizeof(*tests) * (cnt + set->cnt));
-	if (!tmp)
-		goto err;
-
-	memcpy(tmp +  set->cnt, tests, sizeof(*tests) * cnt);
-	set->tests = tmp;
-	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)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH bpf-next 2/2] selftests/bpf: test_progs can read test lists from file
  2023-04-25 22:53 [PATCH bpf-next 0/2] selftests/bpf: test_progs can read test lists from file Stephen Veiss
  2023-04-25 22:54 ` [PATCH bpf-next 1/2] selftests/bpf: extract insert_test from parse_test_list Stephen Veiss
@ 2023-04-25 22:54 ` Stephen Veiss
  2023-04-26  4:25   ` Yonghong Song
  1 sibling, 1 reply; 7+ messages in thread
From: Stephen Veiss @ 2023-04-25 22:54 UTC (permalink / raw)
  To: bpf; +Cc: Stephen Veiss

Improve test selection logic when using -a/-b/-d/-t options.
The list of tests to include or exclude can now be read from a file,
specified as @<filename>.

The file contains one name (or wildcard pattern) per line, and
comments beginning with # are ignored.

These options can be passed multiple times to read more than one file.

Signed-off-by: Stephen Veiss <sveiss@meta.com>
---
 .../selftests/bpf/prog_tests/arg_parsing.c    | 50 +++++++++++++++++++
 tools/testing/selftests/bpf/test_progs.c      | 39 +++++++++++----
 tools/testing/selftests/bpf/testing_helpers.c | 49 ++++++++++++++++++
 tools/testing/selftests/bpf/testing_helpers.h |  3 ++
 4 files changed, 132 insertions(+), 9 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
index 3754cd5f8c0a..e0c6ef2dda70 100644
--- a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
+++ b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
@@ -113,8 +113,58 @@ static void test_parse_test_list(void)
 	free_test_filter_set(&set);
 }
 
+static void test_parse_test_list_file(void)
+{
+	char tmpfile[80];
+	int fd;
+	FILE *fp;
+	struct test_filter_set set;
+
+	snprintf(tmpfile, sizeof(tmpfile), "/tmp/bpf_arg_parsing_test.XXXXXX");
+	fd = mkstemp(tmpfile);
+	ASSERT_GE(fd, 0, "create tmp");
+
+	fp = fdopen(fd, "w");
+
+	fprintf(fp, "# comment\n");
+	fprintf(fp, "  test_with_spaces    \n");
+	fprintf(fp, "testA/subtest    # comment\n");
+	fprintf(fp, "testB#comment with no space\n");
+	fprintf(fp, "testB # duplicate\n");
+	fprintf(fp, "testA/subtest # subtest duplicate\n");
+	fprintf(fp, "testA/subtest2\n");
+	fprintf(fp, "testC_no_eof_newline");
+
+	if (!ASSERT_OK(ferror(fp), "prepare tmp"))
+		goto error;
+
+	if (!ASSERT_OK(fclose(fp), "close tmp"))
+		goto error;
+
+	init_test_filter_set(&set);
+
+	ASSERT_OK(parse_test_list_file(tmpfile, &set, true), "parse file");
+
+	ASSERT_EQ(set.cnt, 4, "test  count");
+	ASSERT_OK(strcmp("test_with_spaces", set.tests[0].name), "test 0 name");
+	ASSERT_EQ(set.tests[0].subtest_cnt, 0, "test 0 subtest count");
+	ASSERT_OK(strcmp("testA", set.tests[1].name), "test 1 name");
+	ASSERT_EQ(set.tests[1].subtest_cnt, 2, "test 1 subtest count");
+	ASSERT_OK(strcmp("subtest", set.tests[1].subtests[0]), "test 1 subtest 0");
+	ASSERT_OK(strcmp("subtest2", set.tests[1].subtests[1]), "test 1 subtest 1");
+	ASSERT_OK(strcmp("testB", set.tests[2].name), "test 2 name");
+	ASSERT_OK(strcmp("testC_no_eof_newline", set.tests[3].name), "test 3 name");
+
+	free_test_filter_set(&set);
+
+error:
+	remove(tmpfile);
+}
+
 void test_arg_parsing(void)
 {
 	if (test__start_subtest("test_parse_test_list"))
 		test_parse_test_list();
+	if (test__start_subtest("test_parse_test_list_file"))
+		test_parse_test_list_file();
 }
diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c
index ea82921110da..cf80d28c76e8 100644
--- a/tools/testing/selftests/bpf/test_progs.c
+++ b/tools/testing/selftests/bpf/test_progs.c
@@ -714,7 +714,13 @@ static struct test_state test_states[ARRAY_SIZE(prog_test_defs)];
 
 const char *argp_program_version = "test_progs 0.1";
 const char *argp_program_bug_address = "<bpf@vger.kernel.org>";
-static const char argp_program_doc[] = "BPF selftests test runner";
+static const char argp_program_doc[] =
+"BPF selftests test runner\v"
+"Options accepting the NAMES parameter take either a comma-separated list\n"
+"of test names, or a filename prefixed with @. The file contains one name\n"
+"(or wildcard pattern) per line, and comments beginning with # are ignored.\n"
+"\n"
+"These options can be passed repeatedly to read multiple files.\n";
 
 enum ARG_KEYS {
 	ARG_TEST_NUM = 'n',
@@ -797,6 +803,7 @@ extern int extra_prog_load_log_flags;
 static error_t parse_arg(int key, char *arg, struct argp_state *state)
 {
 	struct test_env *env = state->input;
+	int err;
 
 	switch (key) {
 	case ARG_TEST_NUM: {
@@ -821,18 +828,32 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
 	}
 	case ARG_TEST_NAME_GLOB_ALLOWLIST:
 	case ARG_TEST_NAME: {
-		if (parse_test_list(arg,
-				    &env->test_selector.whitelist,
-				    key == ARG_TEST_NAME_GLOB_ALLOWLIST))
-			return -ENOMEM;
+		if (arg[0] == '@')
+			err = parse_test_list_file(arg + 1,
+						   &env->test_selector.whitelist,
+						   key == ARG_TEST_NAME_GLOB_ALLOWLIST);
+		else
+			err = parse_test_list(arg,
+					      &env->test_selector.whitelist,
+					      key == ARG_TEST_NAME_GLOB_ALLOWLIST);
+
+		if (err)
+			return err;
 		break;
 	}
 	case ARG_TEST_NAME_GLOB_DENYLIST:
 	case ARG_TEST_NAME_BLACKLIST: {
-		if (parse_test_list(arg,
-				    &env->test_selector.blacklist,
-				    key == ARG_TEST_NAME_GLOB_DENYLIST))
-			return -ENOMEM;
+		if (arg[0] == '@')
+			err = parse_test_list_file(arg + 1,
+						   &env->test_selector.blacklist,
+						   key == ARG_TEST_NAME_GLOB_DENYLIST);
+		else
+			err = parse_test_list(arg,
+					      &env->test_selector.blacklist,
+					      key == ARG_TEST_NAME_GLOB_DENYLIST);
+
+		if (err)
+			return err;
 		break;
 	}
 	case ARG_VERIFIER_STATS:
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index 14322371e1d8..d8bea5c2a4d6 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
 /* Copyright (C) 2019 Netronome Systems, Inc. */
 /* Copyright (C) 2020 Facebook, Inc. */
+#include <ctype.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
@@ -167,6 +168,54 @@ static int insert_test(struct test_filter_set *set,
 	return -ENOMEM;
 }
 
+int parse_test_list_file(const char *path,
+			 struct test_filter_set *set,
+			 bool is_glob_pattern)
+{
+	FILE *f;
+	size_t buflen = 0;
+	char *buf = NULL, *capture_start, *capture_end, *scan_end;
+	int err;
+
+	f = fopen(path, "r");
+	if (!f) {
+		err = -errno;
+		fprintf(stderr, "Failed to open '%s': %d\n", path, err);
+		return err;
+	}
+
+	while (getline(&buf, &buflen, f) != -1) {
+		capture_start = buf;
+
+		while (isspace(*capture_start))
+			++capture_start;
+
+		capture_end = capture_start;
+		scan_end = capture_start;
+
+		while (*scan_end && *scan_end != '#') {
+			if (!isspace(*scan_end))
+				capture_end = scan_end;
+
+			++scan_end;
+		}
+
+		if (capture_end == capture_start)
+			continue;
+
+		*(++capture_end) = '\0';
+
+		err = insert_test(set, capture_start, is_glob_pattern);
+		if (err) {
+			fclose(f);
+			return err;
+		}
+	}
+
+	fclose(f);
+	return 0;
+}
+
 int parse_test_list(const char *s,
 		    struct test_filter_set *set,
 		    bool is_glob_pattern)
diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
index eb8790f928e4..98f09bbae86f 100644
--- a/tools/testing/selftests/bpf/testing_helpers.h
+++ b/tools/testing/selftests/bpf/testing_helpers.h
@@ -20,5 +20,8 @@ struct test_filter_set;
 int parse_test_list(const char *s,
 		    struct test_filter_set *test_set,
 		    bool is_glob_pattern);
+int parse_test_list_file(const char *path,
+			 struct test_filter_set *test_set,
+			 bool is_glob_pattern);
 
 __u64 read_perf_max_sample_freq(void);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next 1/2] selftests/bpf: extract insert_test from parse_test_list
  2023-04-25 22:54 ` [PATCH bpf-next 1/2] selftests/bpf: extract insert_test from parse_test_list Stephen Veiss
@ 2023-04-26  3:42   ` Yonghong Song
  0 siblings, 0 replies; 7+ messages in thread
From: Yonghong Song @ 2023-04-26  3:42 UTC (permalink / raw)
  To: Stephen Veiss, bpf



On 4/25/23 3:54 PM, Stephen Veiss wrote:
> Split the logic to insert new tests into test filter sets out from
> parse_test_list.
> 
> Fix the subtest insertion logic to reuse an existing top-level test
> filter, which prevents the creation of duplicate top-level test filters
> each with a single subtest.
> 
> Signed-off-by: Stephen Veiss <sveiss@meta.com>
> ---
>   .../selftests/bpf/prog_tests/arg_parsing.c    |  13 ++
>   tools/testing/selftests/bpf/testing_helpers.c | 176 +++++++++++-------
>   2 files changed, 117 insertions(+), 72 deletions(-)
> 
> diff --git a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
> index b17bfa0e0aac..3754cd5f8c0a 100644
> --- a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
> +++ b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
> @@ -96,6 +96,19 @@ static void test_parse_test_list(void)
>   		goto error;
>   	ASSERT_OK(strcmp("*bpf_cookie*", set.tests[0].name), "test name");
>   	ASSERT_OK(strcmp("*trace*", set.tests[0].subtests[0]), "subtest name");
> +	free_test_filter_set(&set);
> +
> +	ASSERT_OK(parse_test_list("t/subtest1,t/subtest2", &set, true),
> +		  "parsing");
> +	if (!ASSERT_EQ(set.cnt, 1, "count of test filters"))
> +		goto error;
> +	if (!ASSERT_OK_PTR(set.tests, "test filters initialized"))
> +		goto error;
> +	if (!ASSERT_EQ(set.tests[0].subtest_cnt, 2, "subtest filters count"))
> +		goto error;
> +	ASSERT_OK(strcmp("t", set.tests[0].name), "test name");
> +	ASSERT_OK(strcmp("subtest1", set.tests[0].subtests[0]), "subtest name");
> +	ASSERT_OK(strcmp("subtest2", set.tests[0].subtests[1]), "subtest name");
>   error:
>   	free_test_filter_set(&set);
>   }
> diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
> index 0b5e0829e5be..14322371e1d8 100644
> --- a/tools/testing/selftests/bpf/testing_helpers.c
> +++ b/tools/testing/selftests/bpf/testing_helpers.c
> @@ -70,92 +70,124 @@ int parse_num_list(const char *s, bool **num_set, int *num_set_len)
>   	return 0;
>   }
>   
> +static int do_insert_test(struct test_filter_set *set,
> +			  char *test_str,
> +			  char *subtest_str)
> +{
> +	struct test_filter *tmp, *test;
> +	char **ctmp;
> +	int i;
> +
> +	for (i = 0; i < set->cnt; i++) {
> +		test = &set->tests[i];
> +
> +		if (strcmp(test_str, test->name) == 0) {
> +			free(test_str);
> +			goto subtest;
> +		}
> +	}
> +
> +	tmp = realloc(set->tests, sizeof(*test) * (set->cnt + 1));
> +	if (!tmp)
> +		return -ENOMEM;
> +
> +	set->tests = tmp;
> +	test = &set->tests[set->cnt];
> +
> +	test->name = test_str;
> +	test->subtests = NULL;
> +	test->subtest_cnt = 0;
> +
> +	set->cnt++;
> +
> +subtest:
> +	if (!subtest_str)
> +		return 0;
> +
> +	for (i = 0; i < test->subtest_cnt; i++) {
> +		if (strcmp(subtest_str, test->subtests[i]) == 0) {
> +			free(subtest_str);
> +			return 0;
> +		}
> +	}
> +
> +	ctmp = realloc(test->subtests,
> +		       sizeof(*test->subtests) * (test->subtest_cnt + 1));
> +	if (!ctmp)
> +		return -ENOMEM;
> +
> +	test->subtests = ctmp;
> +	test->subtests[test->subtest_cnt] = subtest_str;
> +
> +	test->subtest_cnt++;
> +
> +	return 0;
> +}
> +
> +static int insert_test(struct test_filter_set *set,
> +		       char *test_spec,
> +		       bool is_glob_pattern)
> +{
> +	char *pattern, *subtest_str, *ext_test_str, *ext_subtest_str = NULL;
> +	int glob_chars = 0;
> +
> +	if (is_glob_pattern) {
> +		pattern = "%s";
> +	} else {
> +		pattern = "*%s*";
> +		glob_chars = 2;
> +	}
> +
> +	subtest_str = strchr(test_spec, '/');
> +	if (subtest_str) {
> +		*subtest_str = '\0';
> +		subtest_str += 1;
> +	}
> +
> +	ext_test_str = malloc(strlen(test_spec) + glob_chars + 1);
> +	if (!ext_test_str)
> +		goto err;
> +
> +	sprintf(ext_test_str, pattern, test_spec);
> +
> +	if (subtest_str) {
> +		ext_subtest_str = malloc(strlen(subtest_str) + glob_chars + 1);
> +		if (!ext_subtest_str)
> +			goto err;
> +
> +		sprintf(ext_subtest_str, pattern, subtest_str);
> +	}
> +
> +	return do_insert_test(set, ext_test_str, ext_subtest_str);
> +
> +err:
> +	free(ext_test_str);
> +	free(ext_subtest_str);
> +
> +	return -ENOMEM;
> +}
> +
>   int parse_test_list(const char *s,
>   		    struct test_filter_set *set,
>   		    bool is_glob_pattern)
>   {
> -	char *input, *state = NULL, *next;
> -	struct test_filter *tmp, *tests = NULL;
> -	int i, j, cnt = 0;
> +	char *input, *state = NULL, *test_spec;
> +	int err;
>   
>   	input = strdup(s);
>   	if (!input)
>   		return -ENOMEM;
>   
> -	while ((next = strtok_r(state ? NULL : input, ",", &state))) {
> -		char *subtest_str = strchr(next, '/');
> -		char *pattern = NULL;
> -		int glob_chars = 0;
> -
> -		tmp = realloc(tests, sizeof(*tests) * (cnt + 1));
> -		if (!tmp)
> -			goto err;
> -		tests = tmp;
> -
> -		tests[cnt].subtest_cnt = 0;
> -		tests[cnt].subtests = NULL;
> -
> -		if (is_glob_pattern) {
> -			pattern = "%s";
> -		} else {
> -			pattern = "*%s*";
> -			glob_chars = 2;
> +	while ((test_spec = strtok_r(state ? NULL : input, ",", &state))) {
> +		err = insert_test(set, test_spec, is_glob_pattern);
> +		if (err) {
> +			free(input);
> +			return err;
>   		}
> -
> -		if (subtest_str) {
> -			char **tmp_subtests = NULL;
> -			int subtest_cnt = tests[cnt].subtest_cnt;
> -
> -			*subtest_str = '\0';
> -			subtest_str += 1;
> -			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] =
> -				malloc(strlen(subtest_str) + glob_chars + 1);
> -			if (!tests[cnt].subtests[subtest_cnt])
> -				goto err;
> -			sprintf(tests[cnt].subtests[subtest_cnt],
> -				pattern,
> -				subtest_str);
> -
> -			tests[cnt].subtest_cnt++;
> -		}
> -
> -		tests[cnt].name = malloc(strlen(next) + glob_chars + 1);
> -		if (!tests[cnt].name)
> -			goto err;
> -		sprintf(tests[cnt].name, pattern, next);
> -
> -		cnt++;
>   	}
>   
> -	tmp = realloc(set->tests, sizeof(*tests) * (cnt + set->cnt));
> -	if (!tmp)
> -		goto err;
> -
> -	memcpy(tmp +  set->cnt, tests, sizeof(*tests) * cnt);
> -	set->tests = tmp;
> -	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;
>   }

LGTM.
For parse_test_list() function, maybe using the following code which
is more in kernel coding style.

int parse_test_list(const char *s,
                     struct test_filter_set *set,
                     bool is_glob_pattern)
{
         char *input, *state = NULL, *test_spec;
         int err = 0;

         input = strdup(s);
         if (!input)
                 return -ENOMEM;

         while ((test_spec = strtok_r(state ? NULL : input, ",", &state))) {
                 err = insert_test(set, test_spec, is_glob_pattern);
                 if (err)
                         break;
         }

         free(input);
         return err;
}

>   
>   __u32 link_info_prog_id(const struct bpf_link *link, struct bpf_link_info *info)

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next 2/2] selftests/bpf: test_progs can read test lists from file
  2023-04-25 22:54 ` [PATCH bpf-next 2/2] selftests/bpf: test_progs can read test lists from file Stephen Veiss
@ 2023-04-26  4:25   ` Yonghong Song
  2023-04-26 16:10     ` Stephen Veiss
  0 siblings, 1 reply; 7+ messages in thread
From: Yonghong Song @ 2023-04-26  4:25 UTC (permalink / raw)
  To: Stephen Veiss, bpf



On 4/25/23 3:54 PM, Stephen Veiss wrote:
> Improve test selection logic when using -a/-b/-d/-t options.
> The list of tests to include or exclude can now be read from a file,
> specified as @<filename>.
> 
> The file contains one name (or wildcard pattern) per line, and
> comments beginning with # are ignored.
> 
> These options can be passed multiple times to read more than one file.
> 
> Signed-off-by: Stephen Veiss <sveiss@meta.com>

LGTM. A few nits below.

> ---
>   .../selftests/bpf/prog_tests/arg_parsing.c    | 50 +++++++++++++++++++
>   tools/testing/selftests/bpf/test_progs.c      | 39 +++++++++++----
>   tools/testing/selftests/bpf/testing_helpers.c | 49 ++++++++++++++++++
>   tools/testing/selftests/bpf/testing_helpers.h |  3 ++
>   4 files changed, 132 insertions(+), 9 deletions(-)
> 
> diff --git a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
> index 3754cd5f8c0a..e0c6ef2dda70 100644
> --- a/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
> +++ b/tools/testing/selftests/bpf/prog_tests/arg_parsing.c
> @@ -113,8 +113,58 @@ static void test_parse_test_list(void)
>   	free_test_filter_set(&set);
>   }
>   
> +static void test_parse_test_list_file(void)
> +{
> +	char tmpfile[80];
> +	int fd;
> +	FILE *fp;
> +	struct test_filter_set set;
> +
> +	snprintf(tmpfile, sizeof(tmpfile), "/tmp/bpf_arg_parsing_test.XXXXXX");
> +	fd = mkstemp(tmpfile);
> +	ASSERT_GE(fd, 0, "create tmp");

If ASSERT_GE(...) is not true, we should simply return.

> +
> +	fp = fdopen(fd, "w");

We should check whether fp is NULL or not, if it is we should close fd 
and return (basically go to 'error').

> +
> +	fprintf(fp, "# comment\n");
> +	fprintf(fp, "  test_with_spaces    \n");
> +	fprintf(fp, "testA/subtest    # comment\n");
> +	fprintf(fp, "testB#comment with no space\n");
> +	fprintf(fp, "testB # duplicate\n");
> +	fprintf(fp, "testA/subtest # subtest duplicate\n");
> +	fprintf(fp, "testA/subtest2\n");
> +	fprintf(fp, "testC_no_eof_newline");
> +
> +	if (!ASSERT_OK(ferror(fp), "prepare tmp"))
> +		goto error;
> +
> +	if (!ASSERT_OK(fclose(fp), "close tmp"))
> +		goto error;
> +
> +	init_test_filter_set(&set);
> +
> +	ASSERT_OK(parse_test_list_file(tmpfile, &set, true), "parse file");
> +
> +	ASSERT_EQ(set.cnt, 4, "test  count");
> +	ASSERT_OK(strcmp("test_with_spaces", set.tests[0].name), "test 0 name");
> +	ASSERT_EQ(set.tests[0].subtest_cnt, 0, "test 0 subtest count");
> +	ASSERT_OK(strcmp("testA", set.tests[1].name), "test 1 name");
> +	ASSERT_EQ(set.tests[1].subtest_cnt, 2, "test 1 subtest count");
> +	ASSERT_OK(strcmp("subtest", set.tests[1].subtests[0]), "test 1 subtest 0");
> +	ASSERT_OK(strcmp("subtest2", set.tests[1].subtests[1]), "test 1 subtest 1");
> +	ASSERT_OK(strcmp("testB", set.tests[2].name), "test 2 name");
> +	ASSERT_OK(strcmp("testC_no_eof_newline", set.tests[3].name), "test 3 name");
> +
> +	free_test_filter_set(&set);
> +
> +error:
> +	remove(tmpfile);

Maybe do 'close(fd)' instead which is more intuitive?

> +}
> +
>   void test_arg_parsing(void)
>   {
>   	if (test__start_subtest("test_parse_test_list"))
>   		test_parse_test_list();
> +	if (test__start_subtest("test_parse_test_list_file"))
> +		test_parse_test_list_file();
>   }
> diff --git a/tools/testing/selftests/bpf/test_progs.c b/tools/testing/selftests/bpf/test_progs.c
> index ea82921110da..cf80d28c76e8 100644
> --- a/tools/testing/selftests/bpf/test_progs.c
> +++ b/tools/testing/selftests/bpf/test_progs.c
> @@ -714,7 +714,13 @@ static struct test_state test_states[ARRAY_SIZE(prog_test_defs)];
>   
>   const char *argp_program_version = "test_progs 0.1";
>   const char *argp_program_bug_address = "<bpf@vger.kernel.org>";
> -static const char argp_program_doc[] = "BPF selftests test runner";
> +static const char argp_program_doc[] =
> +"BPF selftests test runner\v"

What does it mean to use "\v" here?

> +"Options accepting the NAMES parameter take either a comma-separated list\n"
> +"of test names, or a filename prefixed with @. The file contains one name\n"
> +"(or wildcard pattern) per line, and comments beginning with # are ignored.\n"
> +"\n"
> +"These options can be passed repeatedly to read multiple files.\n";
>   
>   enum ARG_KEYS {
>   	ARG_TEST_NUM = 'n',
> @@ -797,6 +803,7 @@ extern int extra_prog_load_log_flags;
>   static error_t parse_arg(int key, char *arg, struct argp_state *state)
>   {
>   	struct test_env *env = state->input;
> +	int err;

Maybe initialize 'err = 0' and change later 'return 0' to 'return err',
so we don't need 'if (err) return err' below?

>   
>   	switch (key) {
>   	case ARG_TEST_NUM: {
> @@ -821,18 +828,32 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
>   	}
>   	case ARG_TEST_NAME_GLOB_ALLOWLIST:
>   	case ARG_TEST_NAME: {
> -		if (parse_test_list(arg,
> -				    &env->test_selector.whitelist,
> -				    key == ARG_TEST_NAME_GLOB_ALLOWLIST))
> -			return -ENOMEM;
> +		if (arg[0] == '@')
> +			err = parse_test_list_file(arg + 1,
> +						   &env->test_selector.whitelist,
> +						   key == ARG_TEST_NAME_GLOB_ALLOWLIST);
> +		else
> +			err = parse_test_list(arg,
> +					      &env->test_selector.whitelist,
> +					      key == ARG_TEST_NAME_GLOB_ALLOWLIST);
> +
> +		if (err)
> +			return err;
>   		break;
>   	}
>   	case ARG_TEST_NAME_GLOB_DENYLIST:
>   	case ARG_TEST_NAME_BLACKLIST: {
> -		if (parse_test_list(arg,
> -				    &env->test_selector.blacklist,
> -				    key == ARG_TEST_NAME_GLOB_DENYLIST))
> -			return -ENOMEM;
> +		if (arg[0] == '@')
> +			err = parse_test_list_file(arg + 1,
> +						   &env->test_selector.blacklist,
> +						   key == ARG_TEST_NAME_GLOB_DENYLIST);
> +		else
> +			err = parse_test_list(arg,
> +					      &env->test_selector.blacklist,
> +					      key == ARG_TEST_NAME_GLOB_DENYLIST);
> +
> +		if (err)
> +			return err;
>   		break;
>   	}
>   	case ARG_VERIFIER_STATS:
> diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
> index 14322371e1d8..d8bea5c2a4d6 100644
> --- a/tools/testing/selftests/bpf/testing_helpers.c
> +++ b/tools/testing/selftests/bpf/testing_helpers.c
> @@ -1,6 +1,7 @@
>   // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
>   /* Copyright (C) 2019 Netronome Systems, Inc. */
>   /* Copyright (C) 2020 Facebook, Inc. */
> +#include <ctype.h>
>   #include <stdlib.h>
>   #include <string.h>
>   #include <errno.h>
> @@ -167,6 +168,54 @@ static int insert_test(struct test_filter_set *set,
>   	return -ENOMEM;
>   }
>   
> +int parse_test_list_file(const char *path,
> +			 struct test_filter_set *set,
> +			 bool is_glob_pattern)
> +{
> +	FILE *f;
> +	size_t buflen = 0;
> +	char *buf = NULL, *capture_start, *capture_end, *scan_end;
> +	int err;
> +
> +	f = fopen(path, "r");
> +	if (!f) {
> +		err = -errno;
> +		fprintf(stderr, "Failed to open '%s': %d\n", path, err);
> +		return err;
> +	}
> +
> +	while (getline(&buf, &buflen, f) != -1) {
> +		capture_start = buf;
> +
> +		while (isspace(*capture_start))
> +			++capture_start;
> +
> +		capture_end = capture_start;
> +		scan_end = capture_start;
> +
> +		while (*scan_end && *scan_end != '#') {
> +			if (!isspace(*scan_end))
> +				capture_end = scan_end;
> +
> +			++scan_end;
> +		}
> +
> +		if (capture_end == capture_start)
> +			continue;
> +
> +		*(++capture_end) = '\0';
> +
> +		err = insert_test(set, capture_start, is_glob_pattern);
> +		if (err) {
> +			fclose(f);
> +			return err;

Set initial err = 0 and later 'return 0' => 'return err', so we
just do break here to avoid calling fclose(f) in two different
places?

> +		}
> +	}
> +
> +	fclose(f);
> +	return 0;
> +}
> +
>   int parse_test_list(const char *s,
>   		    struct test_filter_set *set,
>   		    bool is_glob_pattern)
> diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
> index eb8790f928e4..98f09bbae86f 100644
> --- a/tools/testing/selftests/bpf/testing_helpers.h
> +++ b/tools/testing/selftests/bpf/testing_helpers.h
> @@ -20,5 +20,8 @@ struct test_filter_set;
>   int parse_test_list(const char *s,
>   		    struct test_filter_set *test_set,
>   		    bool is_glob_pattern);
> +int parse_test_list_file(const char *path,
> +			 struct test_filter_set *test_set,
> +			 bool is_glob_pattern);
>   
>   __u64 read_perf_max_sample_freq(void);

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next 2/2] selftests/bpf: test_progs can read test lists from file
  2023-04-26  4:25   ` Yonghong Song
@ 2023-04-26 16:10     ` Stephen Veiss
  2023-04-26 16:24       ` Yonghong Song
  0 siblings, 1 reply; 7+ messages in thread
From: Stephen Veiss @ 2023-04-26 16:10 UTC (permalink / raw)
  To: Yonghong Song; +Cc: bpf

On Tue, Apr 25, 2023 at 09:25:40PM -0700, Yonghong Song wrote:
> On 4/25/23 3:54 PM, Stephen Veiss wrote:
> > +static const char argp_program_doc[] =
> > +"BPF selftests test runner\v"
> 
> What does it mean to use "\v" here?

argp splits the documentation string on \v. The part before \v shows
up at the start of the --help output, while the part after appears
after the detailed help text for the arguments. [1]

Happy to take all your other suggestions; I'll revise and resend the
patch series later in the week.

Thanks,

Stephen

[1] https://www.gnu.org/software/libc/manual/html_node/Argp-Parsers.html

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next 2/2] selftests/bpf: test_progs can read test lists from file
  2023-04-26 16:10     ` Stephen Veiss
@ 2023-04-26 16:24       ` Yonghong Song
  0 siblings, 0 replies; 7+ messages in thread
From: Yonghong Song @ 2023-04-26 16:24 UTC (permalink / raw)
  To: Stephen Veiss; +Cc: bpf



On 4/26/23 9:10 AM, Stephen Veiss wrote:
> On Tue, Apr 25, 2023 at 09:25:40PM -0700, Yonghong Song wrote:
>> On 4/25/23 3:54 PM, Stephen Veiss wrote:
>>> +static const char argp_program_doc[] =
>>> +"BPF selftests test runner\v"
>>
>> What does it mean to use "\v" here?
> 
> argp splits the documentation string on \v. The part before \v shows
> up at the start of the --help output, while the part after appears
> after the detailed help text for the arguments. [1]

sounds good. Thanks for explanations!

> 
> Happy to take all your other suggestions; I'll revise and resend the
> patch series later in the week.
> 
> Thanks,
> 
> Stephen
> 
> [1] https://www.gnu.org/software/libc/manual/html_node/Argp-Parsers.html

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2023-04-26 16:24 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-25 22:53 [PATCH bpf-next 0/2] selftests/bpf: test_progs can read test lists from file Stephen Veiss
2023-04-25 22:54 ` [PATCH bpf-next 1/2] selftests/bpf: extract insert_test from parse_test_list Stephen Veiss
2023-04-26  3:42   ` Yonghong Song
2023-04-25 22:54 ` [PATCH bpf-next 2/2] selftests/bpf: test_progs can read test lists from file Stephen Veiss
2023-04-26  4:25   ` Yonghong Song
2023-04-26 16:10     ` Stephen Veiss
2023-04-26 16:24       ` Yonghong Song

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.