[igt-dev] [PATCH i-g-t 05/10] Change logic of ktap parser to run on a thread

Dominik Karol Piatkowski dominik.karol.piatkowski at intel.com
Wed Jun 14 10:58:06 UTC 2023


The ktap parser should be listening and parsing messages as the tests
are executed, and not after the end of module load.

v1 -> v2:
- fix coding style
- remove usleep
- add error check logic
- follow the structure of igt_kselftests more closely

v2 -> v3:
- fixed sublevel issues by rewriting tap parser flow

v3 -> v4:
- fixed delimiter

- squashed lib/igt_kmod: fix nesting igt_fixture in igt_subtest
Fix the following issue:
	$ ./build/tests/drm_buddy
	Starting subtest: all-tests
	nesting igt_fixture in igt_subtest is invalid
	please refer to lib/igt_core documentation

- squashed lib/igt_kmod: place KUnit tests on a subtest
There's a hidden bug at KUnit implementation: as it doesn't
place tests inside a subtest, trying to use it with igt_main
causes a crash:

	$ ./build/tests/drm_mm --list
	skipping is allowed only in fixtures, subtests or igt_simple_main
	please refer to lib/igt_core documentation
	drm_mm: ../lib/igt_core.c:437: internal_assert: Assertion `0' failed.
	Received signal SIGABRT.
	Stack trace:
	 #0 [fatal_sig_handler+0x17b]
	 #1 [__sigaction+0x50]
	 #2 [__pthread_kill_implementation+0x114]
	 #3 [gsignal+0x1e]
	 #4 [abort+0xdf]
	 #5 [__assert_fail_base.cold+0xe]
	 #6 [__assert_fail+0x47]
	 #7 [internal_assert+0xe5]
	 #8 [igt_skip+0x169]
	 #9 [__igt_skip_check+0x1bb]
	 #10 [igt_ktest_begin+0xa6]
	 #11 [igt_kunit+0x70]
	 #12 [main+0x2a]
	 #13 [__libc_start_call_main+0x7a]
	 #14 [__libc_start_main+0x8b]
	 #15 [_start+0x25]

Fix it by using igt_subtests() before actually implememnting
KUnit logic.

After the patch, it should now report subtests:

	$ ./build/tests/drm_mm --list
	all-tests

Signed-off-by: Mauro Carvalho Chehab <mchehab at kernel.org>

Signed-off-by: Dominik Karol Piątkowski <dominik.karol.piatkowski at intel.com>
Acked-by: Mauro Carvalho Chehab <mchehab at kernel.org>
Cc: Janusz Krzysztofik <janusz.krzysztofik at linux.intel.com>
Cc: Mauro Carvalho Chehab <mauro.chehab at linux.intel.com>
---
 lib/igt_kmod.c |  51 +++++-
 lib/igt_ktap.c | 431 ++++++++++++++++++++++++++++++++++++++++---------
 lib/igt_ktap.h |  21 ++-
 3 files changed, 421 insertions(+), 82 deletions(-)

diff --git a/lib/igt_kmod.c b/lib/igt_kmod.c
index 21e801bde..73478f75e 100644
--- a/lib/igt_kmod.c
+++ b/lib/igt_kmod.c
@@ -754,14 +754,15 @@ void igt_kselftest_get_tests(struct kmod_module *kmod,
  *
  * Returns: IGT default codes
  */
-int igt_kunit(const char *module_name, const char *opts)
+static int __igt_kunit(const char *module_name, const char *opts)
 {
 	struct igt_ktest tst;
 	struct kmod_module *kunit_kmod;
-	char record[BUF_LEN + 1];
 	FILE *f;
 	bool is_builtin;
 	int ret;
+	struct ktap_test_results *results;
+	struct ktap_test_results_element *temp;
 
 	ret = IGT_EXIT_INVALID;
 
@@ -804,25 +805,63 @@ int igt_kunit(const char *module_name, const char *opts)
 
 	is_builtin = kmod_module_get_initstate(kunit_kmod) == KMOD_MODULE_BUILTIN;
 
+	results = ktap_parser_start(f, is_builtin);
+
 	if (igt_kmod_load(module_name, opts) != 0) {
 		igt_warn("Unable to load %s module\n", module_name);
+		ret = ktap_parser_stop();
 		igt_fail(IGT_EXIT_FAILURE);
 	}
 
-	ret = igt_ktap_parser(f, record, is_builtin);
-	if (ret != 0)
-		ret = IGT_EXIT_ABORT;
+	while (READ_ONCE(results->still_running) || READ_ONCE(results->head) != NULL)
+	{
+		if (READ_ONCE(results->head) != NULL) {
+			pthread_mutex_lock(&results->mutex);
+
+			igt_dynamic(results->head->test_name) {
+				if (READ_ONCE(results->head->passed))
+					igt_success();
+				else
+					igt_fail(IGT_EXIT_FAILURE);
+			}
+
+			temp = results->head;
+			results->head = results->head->next;
+			free(temp);
+
+			pthread_mutex_unlock(&results->mutex);
+		}
+	}
+
 unload:
 	igt_ktest_end(&tst);
 
 	igt_ktest_fini(&tst);
 
+	ret = ktap_parser_stop();
+
+	if (ret != 0)
+		ret = IGT_EXIT_ABORT;
+
 	if (ret == 0)
 		igt_success();
-
 	return ret;
 }
 
+int igt_kunit(const char *module_name, const char *opts)
+{
+	/*
+	 * We need to use igt_subtest here, as otherwise it may crash with:
+	 *  skipping is allowed only in fixtures, subtests or igt_simple_main
+	 * if used on igt_main. This is also needed in order to provide
+	 * proper namespace for dynamic subtests, with is required for CI
+	 * and for documentation.
+	 */
+	igt_subtest_with_dynamic("all-tests")
+		return __igt_kunit(module_name, opts);
+	return 0;
+}
+
 static int open_parameters(const char *module_name)
 {
 	char path[256];
diff --git a/lib/igt_ktap.c b/lib/igt_ktap.c
index 117598faa..ecdcb8d83 100644
--- a/lib/igt_ktap.c
+++ b/lib/igt_ktap.c
@@ -5,11 +5,25 @@
 
 #include <ctype.h>
 #include <limits.h>
+#include <libkmod.h>
+#include <pthread.h>
+#include <errno.h>
 
 #include "igt_aux.h"
 #include "igt_core.h"
 #include "igt_ktap.h"
 
+#define DELIMITER "-"
+
+struct ktap_parser_args {
+	FILE *fp;
+	bool is_builtin;
+	volatile bool is_running;
+	int ret;
+} ktap_args;
+
+static struct ktap_test_results results;
+
 static int log_to_end(enum igt_log_level level, FILE *f,
 		      char *record, const char *format, ...) __attribute__((format(printf, 4, 5)));
 
@@ -30,6 +44,14 @@ static int log_to_end(enum igt_log_level level, FILE *f,
 {
 	va_list args;
 	const char *lend;
+	int f_fd = fileno(f);
+
+	/* Cutoff after newline character, in order to not display garbage */
+	char *cutoff = strchr(record, '\n');
+	if (cutoff) {
+		if (cutoff - record < BUF_LEN)
+			cutoff[1] = '\0';
+	}
 
 	va_start(args, format);
 	igt_vlog(IGT_LOG_DOMAIN, level, format, args);
@@ -38,10 +60,29 @@ static int log_to_end(enum igt_log_level level, FILE *f,
 	lend = strchrnul(record, '\n');
 	while (*lend == '\0') {
 		igt_log(IGT_LOG_DOMAIN, level, "%s", record);
-		if (fgets(record, BUF_LEN, f) == NULL) {
+
+		while (read(f_fd, record, BUF_LEN) < 0) {
+			if (!READ_ONCE(ktap_args.is_running)) {
+				igt_warn("ktap parser stopped\n");
+				return -2;
+			}
+
+			if (errno == EINTR)
+				continue;
+
+			if (errno == EPIPE) {
+				igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
+				return -2;
+			}
+
+			if (errno == EAGAIN)
+				/* No records available */
+				continue;
+
 			igt_warn("kmsg truncated: unknown error (%m)\n");
 			return -2;
 		}
+
 		lend = strchrnul(record, '\n');
 	}
 	return 0;
@@ -65,7 +106,7 @@ static long lookup_value(const char *haystack, const char *needle)
 	if (needle_rptr == NULL)
 		return -1;
 
-	/* skip search string and whitespaces after it */
+	/* Skip search string and whitespaces after it */
 	needle_rptr += strlen(needle);
 
 	num = strtol(needle_rptr, &needle_end, 10);
@@ -79,6 +120,41 @@ static long lookup_value(const char *haystack, const char *needle)
 	return num > 0 ? num : 0;
 }
 
+/**
+ * tap_version_present:
+ * @record: buffer with tap data
+ * @print_info: whether tap version should be printed or not
+ *
+ * Returns:
+ * 0 if not found
+ * 1 if found
+ */
+static int tap_version_present(char* record, bool print_info)
+{
+	/*
+	 * "(K)TAP version XX" should be the first line on all (sub)tests as per
+	 * https://kernel.org/doc/html/latest/dev-tools/ktap.html#version-lines
+	 *
+	 * but actually isn't, as it currently depends on the KUnit module
+	 * being built-in, so we can't rely on it every time
+	 */
+	const char *version_rptr = strcasestr(record, "TAP version ");
+	char *cutoff;
+
+	if (version_rptr == NULL)
+		return 0;
+
+	/* Cutoff after newline character, in order to not display garbage */
+	cutoff = strchr(version_rptr, '\n');
+	if (cutoff)
+		cutoff[0] = '\0';
+
+	if (print_info)
+		igt_info("%s\n", version_rptr);
+
+	return 1;
+}
+
 /**
  * find_next_tap_subtest:
  * @fp: FILE pointer
@@ -91,11 +167,12 @@ static long lookup_value(const char *haystack, const char *needle)
  * -2 if there are problems while reading the file.
  * any other value corresponds to the amount of cases of the next (sub)test
  */
-static int find_next_tap_subtest(FILE *fp, char *record, bool is_builtin)
+static int find_next_tap_subtest(FILE *fp, char *record, char *test_name, bool is_builtin)
 {
-	const char *test_lookup_str, *subtest_lookup_str, *name_rptr, *version_rptr;
-	char test_name[BUF_LEN + 1];
+	const char *test_lookup_str, *subtest_lookup_str, *name_rptr;
 	long test_count;
+	int fp_fd = fileno(fp);
+	char *cutoff;
 
 	test_name[0] = '\0';
 	test_name[BUF_LEN] = '\0';
@@ -103,21 +180,28 @@ static int find_next_tap_subtest(FILE *fp, char *record, bool is_builtin)
 	test_lookup_str = " subtest: ";
 	subtest_lookup_str = " test: ";
 
-	/*
-	 * "(K)TAP version XX" should be the first line on all (sub)tests as per
-	 * https://kernel.org/doc/html/latest/dev-tools/ktap.html#version-lines
-	 *
-	 * but actually isn't, as it currently depends on the KUnit module
-	 * being built-in, so we can't rely on it every time
-	 */
+	if (!tap_version_present(record, true))
+		return -1;
+
 	if (is_builtin) {
-		version_rptr = strcasestr(record, "TAP version ");
-		if (version_rptr == NULL)
-			return -1;
+		while (read(fp_fd, record, BUF_LEN) < 0) {
+			if (!READ_ONCE(ktap_args.is_running)) {
+				igt_warn("ktap parser stopped\n");
+				return -2;
+			}
+
+			if (errno == EINTR)
+				continue;
 
-		igt_info("%s", version_rptr);
+			if (errno == EPIPE) {
+				igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
+				return -2;
+			}
+
+			if (errno == EAGAIN)
+				/* No records available */
+				continue;
 
-		if (fgets(record, BUF_LEN, fp) == NULL) {
 			igt_warn("kmsg truncated: unknown error (%m)\n");
 			return -2;
 		}
@@ -134,22 +218,45 @@ static int find_next_tap_subtest(FILE *fp, char *record, bool is_builtin)
 
 	if (name_rptr == NULL) {
 		if (!is_builtin)
-			/* we've probably found nothing */
+			/* We've probably found nothing */
 			return -1;
 		igt_info("Missing test name\n");
 	} else {
 		strncpy(test_name, name_rptr, BUF_LEN);
-		if (fgets(record, BUF_LEN, fp) == NULL) {
+		/* Cutoff after newline character, in order to not display garbage */
+		cutoff = strchr(test_name, '\n');
+		if (cutoff)
+			cutoff[0] = '\0';
+
+		while (read(fp_fd, record, BUF_LEN) < 0) {
+			if (!READ_ONCE(ktap_args.is_running)) {
+				igt_warn("ktap parser stopped\n");
+				return -2;
+			}
+
+			if (errno == EINTR)
+				continue;
+
+			if (errno == EPIPE) {
+				igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
+				return -2;
+			}
+
+			if (errno == EAGAIN)
+				/* No records available */
+				continue;
+
 			igt_warn("kmsg truncated: unknown error (%m)\n");
 			return -2;
 		}
-		/* now we can be sure we found tests */
+
+		/* Now we can be sure we found tests */
 		if (!is_builtin)
 			igt_info("KUnit is not built-in, skipping version check...\n");
 	}
 
 	/*
-	 * total test count will almost always appear as 0..N at the beginning
+	 * Total test count will almost always appear as 0..N at the beginning
 	 * of a run, so we use it to reliably identify a new run
 	 */
 	test_count = lookup_value(record, "..");
@@ -159,7 +266,7 @@ static int find_next_tap_subtest(FILE *fp, char *record, bool is_builtin)
 		if (test_name[0] == '\0')
 			return 0;
 		if (log_to_end(IGT_LOG_INFO, fp, record,
-				"Running some tests in: %s",
+				"Running some tests in: %s\n",
 				test_name) < 0)
 			return -2;
 		return 0;
@@ -169,7 +276,7 @@ static int find_next_tap_subtest(FILE *fp, char *record, bool is_builtin)
 	}
 
 	if (log_to_end(IGT_LOG_INFO, fp, record,
-			"Executing %ld tests in: %s",
+			"Executing %ld tests in: %s\n",
 			test_count, test_name) < 0)
 		return -2;
 
@@ -177,7 +284,7 @@ static int find_next_tap_subtest(FILE *fp, char *record, bool is_builtin)
 }
 
 /**
- * find_next_tap_test:
+ * parse_kmsg_for_tap:
  * @fp: FILE pointer
  * @record: buffer used to read fp
  * @test_name: buffer to store the test name
@@ -225,7 +332,7 @@ static int parse_kmsg_for_tap(FILE *fp, char *record, char *test_name)
 
 	comment_start = strchrnul(lstart, '#');
 
-	/* check if we're still in a subtest */
+	/* Check if we're still in a subtest */
 	if (*comment_start != '\0') {
 		comment_start++;
 		value_parse_start = comment_start;
@@ -254,81 +361,255 @@ static int parse_kmsg_for_tap(FILE *fp, char *record, char *test_name)
 }
 
 /**
- * igt_ktap_parser:
+ * parse_tap_level:
  * @fp: FILE pointer
- * @record: buffer used to read fp
+ * @base_test_name: test_name from upper recursion level
+ * @test_count: test_count of this level
+ * @failed_tests: top level failed_tests pointer
+ * @found_tests: top level found_tests pointer
  * @is_builtin: whether the KUnit module is built-in or not
  *
- * This function parses the output of a ktap script and prints the test results,
- * as well as any other output to stdout.
- *
- * Returns: IGT default codes
+ * Returns:
+ * 0 if succeded
+ * -1 if error occurred
  */
-int igt_ktap_parser(FILE *fp, char *record, bool is_builtin)
+static int parse_tap_level(FILE *fp, char *base_test_name, int test_count, bool *failed_tests,
+			   bool *found_tests, bool is_builtin)
 {
+	int fp_fd = fileno(fp);
+	char record[BUF_LEN + 1];
+	struct ktap_test_results_element *r, *temp;
+	int internal_test_count;
 	char test_name[BUF_LEN + 1];
-	bool failed_tests, found_tests;
-	int sublevel = 0;
+	char base_test_name_for_next_level[BUF_LEN + 1];
 
-	test_name[0] = '\0';
-	test_name[BUF_LEN] = '\0';
+	for (int i = 0; i < test_count; i++) {
+		while (read(fp_fd, record, BUF_LEN) < 0) {
+			if (!READ_ONCE(ktap_args.is_running)) {
+				igt_warn("ktap parser stopped\n");
+				return -1;
+			}
 
-	failed_tests = false;
-	found_tests = false;
+			if (errno == EINTR)
+				continue;
 
-	while (sublevel >= 0) {
-		if (fgets(record, BUF_LEN, fp) == NULL) {
-			if (!found_tests)
-				igt_warn("kmsg truncated: unknown error (%m)\n");
-			break;
+			if (errno == EAGAIN)
+				/* No records available */
+				continue;
+
+			if (errno == EPIPE) {
+				igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
+				return -1;
+			}
+
+			igt_warn("kmsg truncated: unknown error (%m)\n");
+			return -1;
 		}
 
-		switch (find_next_tap_subtest(fp, record, is_builtin)) {
-		case -2:
-			/* no more data to read */
-			return IGT_EXIT_FAILURE;
-		case -1:
-			/* no test found, so we keep parsing */
-			break;
-		case 0:
-			/*
-			 * tests found, but they're missing info, so we might
-			 * have read into test output
-			 */
-			found_tests = true;
-			sublevel++;
-			break;
-		default:
-			if (fgets(record, BUF_LEN, fp) == NULL) {
-				igt_warn("kmsg truncated: unknown error (%m)\n");
-				return -2;
+		/* Sublevel found */
+		if (tap_version_present(record, false))
+		{
+			internal_test_count = find_next_tap_subtest(fp, record, test_name,
+								    is_builtin);
+			switch (internal_test_count) {
+			case -2:
+				/* No more data to read */
+				return -1;
+			case -1:
+				/* No test found */
+				return -1;
+			case 0:
+				/* Tests found, but they're missing info */
+				*found_tests = true;
+				return -1;
+			default:
+				*found_tests = true;
+
+				memcpy(base_test_name_for_next_level, base_test_name, BUF_LEN);
+				if (strlen(base_test_name_for_next_level) < BUF_LEN - 1 &&
+				    base_test_name_for_next_level[0])
+					strncat(base_test_name_for_next_level, DELIMITER,
+						BUF_LEN - strlen(base_test_name_for_next_level));
+				memcpy(base_test_name_for_next_level + strlen(base_test_name_for_next_level),
+				       test_name, BUF_LEN - strlen(base_test_name_for_next_level));
+
+				if (parse_tap_level(fp, base_test_name_for_next_level,
+						    internal_test_count, failed_tests, found_tests,
+						    is_builtin) == -1)
+					return -1;
+				break;
 			}
-			found_tests = true;
-			sublevel++;
-			break;
 		}
 
 		switch (parse_kmsg_for_tap(fp, record, test_name)) {
 		case -2:
-			return IGT_EXIT_FAILURE;
+			return -1;
 		case -1:
-			sublevel--;
-			failed_tests = true;
-			igt_subtest(test_name)
-				igt_fail(IGT_EXIT_FAILURE);
+			*failed_tests = true;
+
+			r = malloc(sizeof(*r));
+
+			memcpy(r->test_name, base_test_name, BUF_LEN);
+			if (strlen(r->test_name) < BUF_LEN - 1)
+				if (r->test_name[0])
+					strncat(r->test_name, DELIMITER,
+						BUF_LEN - strlen(r->test_name));
+			memcpy(r->test_name + strlen(r->test_name), test_name,
+			       BUF_LEN - strlen(r->test_name));
+			r->test_name[BUF_LEN] = '\0';
+
+			r->passed = false;
+			r->next = NULL;
+
+			pthread_mutex_lock(&results.mutex);
+			if (results.head == NULL) {
+				results.head = r;
+			} else {
+				temp = results.head;
+				while (temp->next != NULL)
+					temp = temp->next;
+				temp->next = r;
+			}
+			pthread_mutex_unlock(&results.mutex);
+
 			test_name[0] = '\0';
 			break;
-		case 0: /* fallthrough */
-			igt_subtest(test_name)
-				igt_success();
+		case 0:
+			r = malloc(sizeof(*r));
+
+			memcpy(r->test_name, base_test_name, BUF_LEN);
+			if (strlen(r->test_name) < BUF_LEN - 1)
+				if (r->test_name[0])
+					strncat(r->test_name, DELIMITER,
+						BUF_LEN - strlen(r->test_name));
+			memcpy(r->test_name + strlen(r->test_name), test_name,
+			       BUF_LEN - strlen(r->test_name));
+			r->test_name[BUF_LEN] = '\0';
+
+			r->passed = true;
+			r->next = NULL;
+
+			pthread_mutex_lock(&results.mutex);
+			if (results.head == NULL) {
+				results.head = r;
+			} else {
+				temp = results.head;
+				while (temp->next != NULL)
+					temp = temp->next;
+				temp->next = r;
+			}
+			pthread_mutex_unlock(&results.mutex);
+
 			test_name[0] = '\0';
+			break;
 		default:
 			break;
 		}
 	}
+	return 0;
+}
+
+/**
+ * igt_ktap_parser:
+ *
+ * This function parses the output of a ktap script and passes it to main thread.
+ */
+void *igt_ktap_parser(void *unused)
+{
+	FILE *fp = ktap_args.fp;
+	int fp_fd = fileno(fp);
+	char record[BUF_LEN + 1];
+	bool is_builtin = ktap_args.is_builtin;
+	char test_name[BUF_LEN + 1];
+	bool failed_tests, found_tests;
+	int test_count;
+
+	failed_tests = false;
+	found_tests = false;
+
+	if (!READ_ONCE(ktap_args.is_running))
+		goto igt_ktap_parser_end;
+
+igt_ktap_parser_start:
+	test_name[0] = '\0';
+	test_name[BUF_LEN] = '\0';
+
+	while (read(fp_fd, record, BUF_LEN) < 0) {
+		if (!READ_ONCE(ktap_args.is_running)) {
+			igt_warn("ktap parser stopped\n");
+			goto igt_ktap_parser_end;
+		}
+
+		if (errno == EAGAIN)
+			/* No records available */
+			continue;
+
+		if (errno == EINTR)
+			continue;
+
+		if (errno == EPIPE) {
+			igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
+			goto igt_ktap_parser_end;
+		}
+	}
+
+	test_count = find_next_tap_subtest(fp, record, test_name, is_builtin);
+
+	switch (test_count) {
+	case -2:
+		/* Problems while reading the file */
+		goto igt_ktap_parser_end;
+	case -1:
+		/* No test found */
+		goto igt_ktap_parser_start;
+	case 0:
+		/* Tests found, but they're missing info */
+		found_tests = true;
+		goto igt_ktap_parser_end;
+	default:
+		found_tests = true;
+
+		if (parse_tap_level(fp, test_name, test_count, &failed_tests, &found_tests,
+				    is_builtin) == -1)
+			goto igt_ktap_parser_end;
+
+		break;
+	}
+
+	/* Parse topmost level */
+	test_name[0] = '\0';
+	parse_tap_level(fp, test_name, test_count, &failed_tests, &found_tests, is_builtin);
+
+igt_ktap_parser_end:
+	results.still_running = false;
 
 	if (failed_tests || !found_tests)
-		return IGT_EXIT_FAILURE;
+		ktap_args.ret = IGT_EXIT_FAILURE;
+	else
+		ktap_args.ret = IGT_EXIT_SUCCESS;
+
+	return NULL;
+}
+
+static pthread_t ktap_parser_thread;
+
+struct ktap_test_results *ktap_parser_start(FILE *fp, bool is_builtin)
+{
+	results.head = NULL;
+	pthread_mutex_init(&results.mutex, NULL);
+	results.still_running = true;
 
-	return IGT_EXIT_SUCCESS;
+	ktap_args.fp = fp;
+	ktap_args.is_builtin = is_builtin;
+	ktap_args.is_running = true;
+	pthread_create(&ktap_parser_thread, NULL, igt_ktap_parser, NULL);
+
+	return &results;
+}
+
+int ktap_parser_stop(void)
+{
+	ktap_args.is_running = false;
+	pthread_join(ktap_parser_thread, NULL);
+	return ktap_args.ret;
 }
diff --git a/lib/igt_ktap.h b/lib/igt_ktap.h
index b2f69df2d..34fe09572 100644
--- a/lib/igt_ktap.h
+++ b/lib/igt_ktap.h
@@ -26,6 +26,25 @@
 
 #define BUF_LEN 4096
 
-int igt_ktap_parser(FILE *fp, char *record, bool is_builtin);
+#include <pthread.h>
+
+void *igt_ktap_parser(void *unused);
+
+typedef struct ktap_test_results_element {
+	char test_name[BUF_LEN + 1];
+	bool passed;
+	struct ktap_test_results_element *next;
+} ktap_test_results_element;
+
+struct ktap_test_results {
+	ktap_test_results_element *head;
+	pthread_mutex_t mutex;
+	bool still_running;
+};
+
+
+
+struct ktap_test_results *ktap_parser_start(FILE *fp, bool is_builtin);
+int ktap_parser_stop(void);
 
 #endif /* IGT_KTAP_H */
-- 
2.34.1



More information about the igt-dev mailing list