[PATCH v3 1/2] metadata: add tests grouping support

Andrea Cervesato <[email protected]>
Newsgroups gmane.linux.ltp
Message-ID <[email protected]>
From: Andrea Cervesato <andrea.cervesato-IBi9RG/[email protected]>

Add groups field to metaparse JSON output, so we can filter out tests
in kirk. Groups are derived from:

1. Source file path - the two nearest parent directories (immediate
   parent first), skipping 'kernel' as too generic. For example:
   - testcases/kernel/syscalls/clone/clone01.c -> clone, syscalls
   - testcases/kernel/kvm/kvm_pagefault01.c -> kvm
   - testcases/cve/cve-2017-16939.c -> cve

2. @groups tags in the doc comment block, e.g.:

   /*\
    * Test description.
    *
    * @groups stress syscalls
    * @groups memory
    */

Add test case for @groups tag parsing.

Signed-off-by: Andrea Cervesato <andrea.cervesato-IBi9RG/[email protected]>
---
 metadata/metaparse.c                  | 163 +++++++++++++++++++++++++++-------
 metadata/tests/array_size01.c.json    |   2 +
 metadata/tests/array_size02.c.json    |   2 +
 metadata/tests/array_size03.c.json    |   2 +
 metadata/tests/array_size04.c.json    |   2 +
 metadata/tests/empty_struct.c.json    |   2 +
 metadata/tests/expand_flags.c.json    |   2 +
 metadata/tests/groups.c               |  11 +++
 metadata/tests/groups.c.json          |  12 +++
 metadata/tests/include.c.json         |   2 +
 metadata/tests/macro.c.json           |   2 +
 metadata/tests/macro_str.c.json       |   2 +
 metadata/tests/multiline_macro.c.json |   2 +
 metadata/tests/tags.c.json            |   2 +
 14 files changed, 177 insertions(+), 31 deletions(-)

diff --git a/metadata/metaparse.c b/metadata/metaparse.c
index 561cbb9d2d54689988c9aa49d591628696bcf847..f83acc5f057719ca1a48e24db7dec22b9f04e789 100644
--- a/metadata/metaparse.c
+++ b/metadata/metaparse.c
@@ -17,6 +17,7 @@
 #include "data_storage.h"
 
 #define INCLUDE_PATH_MAX 5
+#define GROUPS_TAG "@groups"
 
 static int verbose;
 static char *cmdline_includepath[INCLUDE_PATH_MAX];
@@ -34,7 +35,7 @@ static void remove_to_newline(FILE *f)
 	} while (c != '\n');
 }
 
-static const char *eat_asterisk_space(const char *c)
+static char *eat_asterisk_space(char *c)
 {
 	unsigned int i = 0;
 
@@ -50,7 +51,51 @@ static const char *eat_asterisk_space(const char *c)
 	return c;
 }
 
-static void multiline_comment(FILE *f, struct data_node *doc)
+/*
+ * Add a group to the groups array, skipping 'kernel' as it's too generic.
+ * Returns 0 if no group was added, 1 otherwise.
+ */
+static int add_group(struct data_node *groups, const char *name)
+{
+	if (name && strcmp(name, "kernel")) {
+		data_node_array_add(groups, data_node_string(name));
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Parse a '@groups foo bar baz' doc comment line, adding each
+ * whitespace-separated name to the groups array. Returns 1 if the line
+ * was a @groups tag (and should be consumed), 0 otherwise.
+ */
+static int parse_groups(struct data_node *groups, char *line)
+{
+	char *s;
+	char *name;
+
+	if (!groups)
+		return 0;
+
+	s = eat_asterisk_space(line);
+	if (strncmp(s, GROUPS_TAG, sizeof(GROUPS_TAG) - 1))
+		return 0;
+
+	s += sizeof(GROUPS_TAG) - 1;
+	if (*s && *s != ' ' && *s != '\t') {
+		WARN("Empty @group");
+		return 1;
+	}
+
+	for (name = strtok(s, " \t"); name; name = strtok(NULL, " \t"))
+		add_group(groups, name);
+
+	return 1;
+}
+
+static void multiline_comment(FILE *f, struct data_node *doc,
+			      struct data_node *groups)
 {
 	int c;
 	int state = 0;
@@ -62,12 +107,14 @@ static void multiline_comment(FILE *f, struct data_node *doc)
 
 		if (doc) {
 			if (c == '\n') {
-				struct data_node *line;
+				char *str;
 				buf[bufp] = 0;
-				line = data_node_string(eat_asterisk_space(buf));
-				if (data_node_array_add(doc, line))
-					WARN("doc string comment truncated");
+				str = eat_asterisk_space(buf);
 				bufp = 0;
+				if (parse_groups(groups, str))
+					continue;
+				if (data_node_array_add(doc, data_node_string(str)))
+					WARN("doc string comment truncated");
 				continue;
 			}
 
@@ -100,7 +147,8 @@ static void multiline_comment(FILE *f, struct data_node *doc)
 
 static const char doc_prefix[] = "\\\n";
 
-static void maybe_doc_comment(FILE *f, struct data_node *doc)
+static void maybe_doc_comment(FILE *f, struct data_node *doc,
+			      struct data_node *groups)
 {
 	int c, i;
 
@@ -113,14 +161,15 @@ static void maybe_doc_comment(FILE *f, struct data_node *doc)
 		if (c == '*')
 			ungetc(c, f);
 
-		multiline_comment(f, NULL);
+		multiline_comment(f, NULL, NULL);
 		return;
 	}
 
-	multiline_comment(f, doc);
+	multiline_comment(f, doc, groups);
 }
 
-static void maybe_comment(FILE *f, struct data_node *doc)
+static void maybe_comment(FILE *f, struct data_node *doc,
+			  struct data_node *groups)
 {
 	int c = getc(f);
 
@@ -129,7 +178,7 @@ static void maybe_comment(FILE *f, struct data_node *doc)
 		remove_to_newline(f);
 	break;
 	case '*':
-		maybe_doc_comment(f, doc);
+		maybe_doc_comment(f, doc, groups);
 	break;
 	default:
 		ungetc(c, f);
@@ -137,7 +186,8 @@ static void maybe_comment(FILE *f, struct data_node *doc)
 	}
 }
 
-static char *next_token2(FILE *f, char *buf, size_t buf_len, struct data_node *doc)
+static char *next_token2(FILE *f, char *buf, size_t buf_len,
+			 struct data_node *doc, struct data_node *groups)
 {
 	size_t i = 0;
 	int c;
@@ -194,7 +244,7 @@ static char *next_token2(FILE *f, char *buf, size_t buf_len, struct data_node *d
 			buf[i++] = c;
 		break;
 		case '/':
-			maybe_comment(f, doc);
+			maybe_comment(f, doc, groups);
 		break;
 		case '"':
 			in_str = 1;
@@ -216,11 +266,11 @@ exit:
 	return buf;
 }
 
-static char *next_token(FILE *f, struct data_node *doc)
+static char *next_token(FILE *f, struct data_node *doc, struct data_node *groups)
 {
 	static char buf[4096];
 
-	return next_token2(f, buf, sizeof(buf), doc);
+	return next_token2(f, buf, sizeof(buf), doc, groups);
 }
 
 static FILE *open_file(const char *dir, const char *fname)
@@ -383,7 +433,7 @@ static int array_is_hash(FILE *f)
 	int in_id = 1;
 	char *token;
 
-	while ((token = next_token(f, NULL))) {
+	while ((token = next_token(f, NULL, NULL))) {
 
 		if (!strcmp(token, "}")) {
 			if (in_id && !comma_last)
@@ -402,7 +452,7 @@ static int array_is_hash(FILE *f)
 			int level = 1;
 
 			for (;;) {
-				token = next_token(f, NULL);
+				token = next_token(f, NULL, NULL);
 
 				if (!token)
 					goto ret;
@@ -453,7 +503,7 @@ static int parse_array(FILE *f, const char *arr_id, struct data_node **ret)
 		*ret = data_node_array();
 
 	for (;;) {
-		if (!(token = next_token(f, NULL)))
+		if (!(token = next_token(f, NULL, NULL)))
 			return 1;
 
 		if (!strcmp(token, "{")) {
@@ -529,14 +579,14 @@ static int parse_get_array_len(FILE *f)
 	const char *token;
 	int cnt = 0, depth = 0, prev_comma = 0;
 
-	if (!(token = next_token(f, NULL)))
+	if (!(token = next_token(f, NULL, NULL)))
 		return 0;
 
 	if (strcmp(token, "{"))
 		return 0;
 
 	for (;;) {
-		if (!(token = next_token(f, NULL)))
+		if (!(token = next_token(f, NULL, NULL)))
 			return 0;
 
 		if (!strcmp(token, "{"))
@@ -565,7 +615,7 @@ static void look_for_array_size(FILE *f, const char *arr_id, struct data_node **
 	int prev_buf = 1;
 
 	for (;;) {
-		if (!(token = next_token2(f, buf[cur_buf], sizeof(buf[cur_buf]), NULL)))
+		if (!(token = next_token2(f, buf[cur_buf], sizeof(buf[cur_buf]), NULL, NULL)))
 			break;
 
 		if (!strcmp(token, "=") && !strcmp(buf[prev_buf], arr_id)) {
@@ -595,13 +645,13 @@ static int parse_array_size(FILE *f, struct data_node **res)
 
 	*res = NULL;
 
-	if (!(token = next_token(f, NULL)))
+	if (!(token = next_token(f, NULL, NULL)))
 		return 1;
 
 	if (strcmp(token, "("))
 		return 1;
 
-	if (!(token = next_token(f, NULL)))
+	if (!(token = next_token(f, NULL, NULL)))
 		return 1;
 
 	arr_id = strdup(token);
@@ -621,7 +671,7 @@ static int parse_array_size(FILE *f, struct data_node **res)
 		rewind(f);
 
 		for (;;) {
-			if (!(token = next_token(f, NULL)))
+			if (!(token = next_token(f, NULL, NULL)))
 				break;
 
 			if (token[0] == '#') {
@@ -654,7 +704,8 @@ static int parse_array_size(FILE *f, struct data_node **res)
 	return 0;
 }
 
-static int parse_test_struct(FILE *f, struct data_node *doc, struct data_node *node)
+static int parse_test_struct(FILE *f, struct data_node *doc,
+			     struct data_node *groups, struct data_node *node)
 {
 	char *token;
 	char *id = NULL;
@@ -662,7 +713,7 @@ static int parse_test_struct(FILE *f, struct data_node *doc, struct data_node *n
 	struct data_node *ret;
 
 	for (;;) {
-		if (!(token = next_token(f, doc)))
+		if (!(token = next_token(f, doc, groups)))
 			return 1;
 
 		if (!strcmp(token, "}"))
@@ -842,7 +893,7 @@ static void parse_include_macros(FILE *f, int level)
 	if (!inc)
 		return;
 
-	while ((token = next_token(inc, NULL))) {
+	while ((token = next_token(inc, NULL, NULL))) {
 		if (token[0] == '#') {
 			hash = 1;
 			continue;
@@ -907,6 +958,49 @@ static void load_internal_macros(void)
 		fprintf(stderr, "END PREDEFINED MACROS\n");
 }
 
+/*
+ * Add groups derived from the source file path.
+ *
+ * Groups are the two nearest parent directories (immediate parent
+ * first), skipping 'kernel' as it's too generic:
+ *
+ *   testcases/kernel/syscalls/clone/clone01.c  -> clone, syscalls
+ *   testcases/kernel/kvm/kvm_pagefault01.c     -> kvm
+ *   testcases/cve/cve-2017-16939.c             -> cve
+ */
+static void add_path_groups(struct data_node *groups, const char *fname)
+{
+	char *buf;
+	char *dirs[8];
+	int ndirs = 0;
+	char *p;
+
+	if (strncmp(fname, "testcases/", 10))
+		return;
+
+	buf = strdup(fname + 10);
+	if (!buf) {
+		fprintf(stderr, "Allocation failed!\n");
+		exit(1);
+	}
+
+	p = strtok(buf, "/");
+	while (p && ndirs < 8) {
+		dirs[ndirs++] = p;
+		p = strtok(NULL, "/");
+	}
+
+	/* Last element is the filename, skip it */
+	ndirs--;
+
+	if (ndirs >= 1)
+		add_group(groups, dirs[ndirs - 1]);
+	if (ndirs >= 2)
+		add_group(groups, dirs[ndirs - 2]);
+
+	free(buf);
+}
+
 static struct data_node *parse_file(const char *fname)
 {
 	int state = 0, found = 0;
@@ -923,15 +1017,18 @@ static struct data_node *parse_file(const char *fname)
 
 	struct data_node *res = data_node_hash();
 	struct data_node *doc = data_node_array();
+	struct data_node *groups = data_node_array();
+
+	add_path_groups(groups, fname);
 
 	load_internal_macros();
 
-	while ((token = next_token(f, doc))) {
+	while ((token = next_token(f, doc, groups))) {
 		if (state < 6 && !strcmp(tokens[state], token)) {
 			state++;
 		} else {
 			if (token[0] == '#') {
-				token = next_token(f, doc);
+				token = next_token(f, doc, groups);
 				if (token) {
 					if (!strcmp(token, "define"))
 						parse_macro(f);
@@ -948,7 +1045,7 @@ static struct data_node *parse_file(const char *fname)
 			continue;
 
 		found = 1;
-		parse_test_struct(f, doc, res);
+		parse_test_struct(f, doc, groups, res);
 	}
 
 	if (data_node_array_len(doc)) {
@@ -958,6 +1055,9 @@ static struct data_node *parse_file(const char *fname)
 		data_node_free(doc);
 	}
 
+	/* inside the testcases/ folder we will always have at least one group */
+	data_node_hash_add(res, "groups", groups);
+
 	fclose(f);
 
 	if (!found) {
@@ -985,7 +1085,7 @@ static void parse_must_files(void)
 			if (!f)
 				continue;
 
-			while ((token = next_token(f, NULL))) {
+			while ((token = next_token(f, NULL, NULL))) {
 				if (!strcmp(token, "define"))
 					parse_macro(f);
 			}
@@ -1238,6 +1338,7 @@ int main(int argc, char *argv[])
 	}
 
 	data_node_hash_add(res, "fname", data_node_string(argv[optind]));
+
 	printf("  \"%s\": ", strip_name(argv[optind]));
 	data_to_json(res, stdout, 2);
 	data_node_free(res);
diff --git a/metadata/tests/array_size01.c.json b/metadata/tests/array_size01.c.json
index ec364be1207673c7c2efe8a3b6284dc70d0d5e68..98ad9f6b0ba483b19a7b7253606e0ccf92060f29 100644
--- a/metadata/tests/array_size01.c.json
+++ b/metadata/tests/array_size01.c.json
@@ -1,4 +1,6 @@
   "array_size01": {
    "test_variants": 1,
+   "groups": [
+   ],
    "fname": "array_size01.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/array_size02.c.json b/metadata/tests/array_size02.c.json
index 1226869525dfc5cc298f2916150e491f9a1f0db6..10e08c6af8a2d57e463431c3b350bb0d66739554 100644
--- a/metadata/tests/array_size02.c.json
+++ b/metadata/tests/array_size02.c.json
@@ -1,4 +1,6 @@
   "array_size02": {
    "test_variants": 3,
+   "groups": [
+   ],
    "fname": "array_size02.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/array_size03.c.json b/metadata/tests/array_size03.c.json
index bb690c9f5abc332c2c55d2af7aaeec9e315bcaa8..44727933187cf465aaf91e43cc47d67cbcdae5a0 100644
--- a/metadata/tests/array_size03.c.json
+++ b/metadata/tests/array_size03.c.json
@@ -1,4 +1,6 @@
   "array_size03": {
    "test_variants": 2,
+   "groups": [
+   ],
    "fname": "array_size03.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/array_size04.c.json b/metadata/tests/array_size04.c.json
index 6b8d41792035bbb04e8e69b2ae0f0e2d89af3073..13a987e1c0852d8520f0e37f38b467a8652469de 100644
--- a/metadata/tests/array_size04.c.json
+++ b/metadata/tests/array_size04.c.json
@@ -1,4 +1,6 @@
   "array_size04": {
    "test_variants": 3,
+   "groups": [
+   ],
    "fname": "array_size04.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/empty_struct.c.json b/metadata/tests/empty_struct.c.json
index 9f49f53320040441c46f7ba314ec20912e0d94d7..3fbd9a38a664acf9ec893bf29ab6d0ab1a387ef8 100644
--- a/metadata/tests/empty_struct.c.json
+++ b/metadata/tests/empty_struct.c.json
@@ -1,3 +1,5 @@
   "empty_struct": {
+   "groups": [
+   ],
    "fname": "empty_struct.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/expand_flags.c.json b/metadata/tests/expand_flags.c.json
index 079972e4a7cf65a3717c179f7ddeb30c49964820..3941ae7f6ccb1d3c98176f75d1f10e6a99c2d18d 100644
--- a/metadata/tests/expand_flags.c.json
+++ b/metadata/tests/expand_flags.c.json
@@ -1,4 +1,6 @@
   "expand_flags": {
+   "groups": [
+   ],
    "all_filesystems": true,
    "needs_device": true,
    "needs_tmpdir": true,
diff --git a/metadata/tests/groups.c b/metadata/tests/groups.c
new file mode 100644
index 0000000000000000000000000000000000000000..40032a7ef60e398c50690995fc68025575d7f684
--- /dev/null
+++ b/metadata/tests/groups.c
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*\
+ * Test for @groups tag parsing.
+ *
+ * @groups stress regression
+ * @groups smoke
+ */
+
+static struct tst_test test = {
+};
diff --git a/metadata/tests/groups.c.json b/metadata/tests/groups.c.json
new file mode 100644
index 0000000000000000000000000000000000000000..2ca8b5d21d980cdffae4bdaeea20b659a52bad8a
--- /dev/null
+++ b/metadata/tests/groups.c.json
@@ -0,0 +1,12 @@
+  "groups": {
+   "doc": [
+    "Test for @groups tag parsing.",
+    ""
+   ],
+   "groups": [
+    "stress",
+    "regression",
+    "smoke"
+   ],
+   "fname": "groups.c"
+  }
\ No newline at end of file
diff --git a/metadata/tests/include.c.json b/metadata/tests/include.c.json
index b7c636e82e1ec2d90e680cc8b79891f147848cea..df0e5814bdac5c12a0c62b3a02b14718179449a1 100644
--- a/metadata/tests/include.c.json
+++ b/metadata/tests/include.c.json
@@ -1,4 +1,6 @@
   "include": {
+   "groups": [
+   ],
    "test_variants": 10,
    "fname": "include.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/macro.c.json b/metadata/tests/macro.c.json
index c3f53ae432de6a4db851b02fcb0d384530d0c8a6..85907d3aba42330c0b7b5da5d7a86470097ae5d9 100644
--- a/metadata/tests/macro.c.json
+++ b/metadata/tests/macro.c.json
@@ -1,4 +1,6 @@
   "macro": {
+   "groups": [
+   ],
    "test_variants": 10,
    "fname": "macro.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/macro_str.c.json b/metadata/tests/macro_str.c.json
index b162283166dfa13c9023c018206c0e7617d8c702..23a5dbd0775e303cfdc6e996daa13f53d4982aa6 100644
--- a/metadata/tests/macro_str.c.json
+++ b/metadata/tests/macro_str.c.json
@@ -1,4 +1,6 @@
   "macro_str": {
    "syscall": "syscall(\"foo\")",
+   "groups": [
+   ],
    "fname": "macro_str.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/multiline_macro.c.json b/metadata/tests/multiline_macro.c.json
index 63451624256d9845cd4f1552f03b23b033c9a55b..4ae49bc0df1f89c24e3fc70cac3aa2c798b2cc88 100644
--- a/metadata/tests/multiline_macro.c.json
+++ b/metadata/tests/multiline_macro.c.json
@@ -1,4 +1,6 @@
   "multiline_macro": {
+   "groups": [
+   ],
    "test_variants": 10,
    "fname": "multiline_macro.c"
   }
\ No newline at end of file
diff --git a/metadata/tests/tags.c.json b/metadata/tests/tags.c.json
index d45c9f447f1337c9bed86edb7fd71a134db5d25e..4d176f5448640f87c9ea189a300e167b0030e97b 100644
--- a/metadata/tests/tags.c.json
+++ b/metadata/tests/tags.c.json
@@ -9,5 +9,7 @@
      "tag-value-2"
     ]
    ],
+   "groups": [
+   ],
    "fname": "tags.c"
   }
\ No newline at end of file

-- 
2.51.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.