[igt-dev] [PATCH i-g-t v2 13/17] lib/ktap: Reimplement KTAP parser

Mauro Carvalho Chehab mauro.chehab at linux.intel.com
Fri Sep 15 11:45:25 UTC 2023


On Fri,  8 Sep 2023 14:32:47 +0200
Janusz Krzysztofik <janusz.krzysztofik at linux.intel.com> wrote:

> Current implementation of KTAP parser suffers from several issues:
> - in most cases, kernel messages that are not part of KTAP output but
>   happen to appear in between break the parser,
> - results from parametrized test cases, not preceded with a "1..N" test
>   plan, break the parser,
> - skips are not supported, reported as success,
> - IGT results from all 3 kunit test nesting levels, i.e., from
>   parametrized subtests (should those were fixed to work correctly), test
>   cases and test suites, are reported individually as if all those items
>   were executed sequentially, all at the same level of nesting, which can
>   be confusing to igt_runner,
> - the parser is not only parsing the data, but also handles data input
>   from a /dev/kmsg like source, which is integrated into it, making it
>   hard if not impossible to feed KTAP data from different sources,
>   including mock sources,
> - since the parser has been designed for running it in a separate thread,
>   it's not possible to use igt_skip() nor igt_fail() and friends
>   immediately when a result is available, only pass it to the main thread
>   over a buffer.  As a consequence, it is virtually impossible to
>   synchronize IGT output with KTAP output.
> 
> Fixing the existing parser occurred more complicated than re-implementing
> it from scratch.  This patch provides a new implementation.
> 
> Only results from kunit test cases are reported as results of IGT dynamic
> sub-subtests.  Results from individual parametrized subtests have been
> considered problematic since many of them provide multi-word descriptions
> in place of single-word subtest names.  If a parametrized test case fails
> then full KTAP output from its subtests, potentially mixed with
> accompanying kernel messages, is available in dmesg for analysis so users
> can still find out which individual subtests succeeded and which failed.
> 
> Results from test suites level are also omitted in faith that IGT can
> handle aggregation of results from individual kunit test cases reported as
> IGT dynamic sub-subtests and report those aggregated results correctly as
> results from an IGT dynamic subtest.  That 1:1 mapping of kunit test
> suites to IGT dynamic subtests now works perfectly for modules that
> provide only one test suite, which is the case for all kunit test modules
> now existing under drivers/gpu/drm, and the most common case among all
> kunit test modules in the whole kernel tree.
> 
> New igt_ktap functions can be called directly from igt_kunit subtest body,
> but for this patch, the old igt_ktap_parser() function that runs in a
> separate thread has been preserved, only modified to use the new
> implementation and translate results from those new functions to legacy
> format.  Unlike the former implementation, translation of kunit names to
> IGT names is handled outside the parser itself, though for now it is still
> performed inside the legacy igt_ktap_parser() function.
> 
> For better readability of the patch, no longer used functions have been
> left untouched, only tagged with __maybe_unused to shut up compiler
> warnings / errors.  Kunit library functions will be modified to use the
> new igt_ktap interface, and those old ktap functions removed by follow-
> up patches.
> 
> A test with example subtests that feed igt_ktap_parse() function with some
> example data and verifies correctness of their parsing is also provided.
> 
> v2: Fix incorrect and missing includes in the test source file,
>   - add license and copyright clauses to the test source file.
> 
> Signed-off-by: Janusz Krzysztofik <janusz.krzysztofik at linux.intel.com>

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

> ---
>  lib/igt_ktap.c              | 422 ++++++++++++++++++++++++++++++++----
>  lib/igt_ktap.h              |  15 ++
>  lib/tests/igt_ktap_parser.c | 246 +++++++++++++++++++++
>  lib/tests/meson.build       |   1 +
>  4 files changed, 645 insertions(+), 39 deletions(-)
>  create mode 100644 lib/tests/igt_ktap_parser.c
> 
> diff --git a/lib/igt_ktap.c b/lib/igt_ktap.c
> index 5e9967f980..d46a2433e5 100644
> --- a/lib/igt_ktap.c
> +++ b/lib/igt_ktap.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: MIT
>  /*
>   * Copyright © 2023 Isabella Basso do Amaral <isabbasso at riseup.net>
> + * Copyright © 2023 Intel Corporation
>   */
>  
>  #include <ctype.h>
> @@ -8,12 +9,310 @@
>  #include <libkmod.h>
>  #include <pthread.h>
>  #include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
>  
>  #include "igt_aux.h"
>  #include "igt_core.h"
>  #include "igt_ktap.h"
>  #include "igt_list.h"
>  
> +enum ktap_phase {
> +	KTAP_START,
> +	SUITE_COUNT,
> +	SUITE_START,
> +	SUITE_NAME,
> +	CASE_COUNT,
> +	CASE_NAME,
> +	SUB_RESULT,
> +	CASE_RESULT,
> +	SUITE_RESULT,
> +};
> +
> +struct igt_ktap_results {
> +	enum ktap_phase expect;
> +	unsigned int suite_count;
> +	unsigned int suite_last;
> +	char *suite_name;
> +	unsigned int case_count;
> +	unsigned int case_last;
> +	char *case_name;
> +	unsigned int sub_last;
> +	struct igt_list_head *results;
> +};
> +
> +/**
> + * igt_ktap_parse:
> + *
> + * This function parses a line of text for KTAP report data
> + * and passes results back to IGT kunit layer.
> + */
> +int igt_ktap_parse(const char *buf, struct igt_ktap_results *ktap)
> +{
> +	char *suite_name = NULL, *case_name = NULL, *msg = NULL;
> +	struct igt_ktap_result *result;
> +	int code = IGT_EXIT_INVALID;
> +	unsigned int n, len;
> +	char s[2];
> +
> +	/* KTAP report header */
> +	if (igt_debug_on(sscanf(buf, "KTAP%*[ ]version%*[ ]%u %n",
> +				&n, &len) == 1 && len == strlen(buf))) {
> +		if (igt_debug_on(ktap->expect != KTAP_START))
> +			return -EPROTO;
> +
> +		ktap->suite_count = 0;
> +		ktap->expect = SUITE_COUNT;
> +
> +	/* malformed TAP test plan? */
> +	} else if (len = 0,
> +		   igt_debug_on(sscanf(buf, " 1..%1[ ]", s) == 1)) {
> +		return -EINPROGRESS;
> +
> +	/* valid test plan of a KTAP report */
> +	} else if (igt_debug_on(sscanf(buf, "1..%u %n", &n, &len) == 1 &&
> +				len == strlen(buf))) {
> +		if (igt_debug_on(ktap->expect != SUITE_COUNT))
> +			return -EPROTO;
> +
> +		if (!n)
> +			return 0;
> +
> +		ktap->suite_count = n;
> +		ktap->suite_last = 0;
> +		ktap->suite_name = NULL;
> +		ktap->expect = SUITE_START;
> +
> +	/* KTAP test suite header */
> +	} else if (len = 0,
> +		   igt_debug_on(sscanf(buf,
> +				       "%*1[ ]%*1[ ]%*1[ ]%*1[ ]KTAP%*[ ]version%*[ ]%u %n",
> +				       &n, &len) == 1 && len == strlen(buf))) {
> +		/*
> +		 * TODO: drop the following workaround as soon as
> +		 * kernel side issue of missing lines with top level
> +		 * KTAP version and test suite plan is fixed.
> +		 */
> +		if (ktap->expect == KTAP_START) {
> +			ktap->suite_count = 1;
> +			ktap->suite_last = 0;
> +			ktap->suite_name = NULL;
> +			ktap->expect = SUITE_START;
> +		}
> +
> +		if (igt_debug_on(ktap->expect != SUITE_START))
> +			return -EPROTO;
> +
> +		ktap->expect = SUITE_NAME;
> +
> +	/* KTAP test suite name */
> +	} else if (len = 0,
> +		   igt_debug_on(sscanf(buf,
> +				       "%*1[ ]%*1[ ]%*1[ ]%*1[ ]#%*[ ]Subtest:%*[ ]%ms %n",
> +				       &suite_name, &len) == 1 && len == strlen(buf))) {
> +		if (igt_debug_on(ktap->expect != SUITE_NAME))
> +			return -EPROTO;
> +
> +		ktap->suite_name = suite_name;
> +		suite_name = NULL;
> +		ktap->case_count = 0;
> +		ktap->expect = CASE_COUNT;
> +
> +	/* valid test plan of a KTAP test suite */
> +	} else if (len = 0, free(suite_name), suite_name = NULL,
> +		   igt_debug_on(sscanf(buf,
> +				       "%*1[ ]%*1[ ]%*1[ ]%*1[ ]1..%u %n",
> +				       &n, &len) == 1 && len == strlen(buf))) {
> +		if (igt_debug_on(ktap->expect != CASE_COUNT))
> +			return -EPROTO;
> +
> +		if (n) {
> +			ktap->case_count = n;
> +			ktap->case_last = 0;
> +			ktap->case_name = NULL;
> +			ktap->expect = CASE_RESULT;
> +		} else {
> +			ktap->expect = SUITE_RESULT;
> +		}
> +
> +	/* KTAP parametrized test case header */
> +	} else if (len = 0,
> +		   igt_debug_on(sscanf(buf,
> +				       "%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]KTAP%*[ ]version%*[ ]%u %n",
> +				       &n, &len) == 1 && len == strlen(buf))) {
> +		if (igt_debug_on(ktap->expect != CASE_RESULT))
> +			return -EPROTO;
> +
> +		ktap->sub_last = 0;
> +		ktap->expect = CASE_NAME;
> +
> +	/* KTAP parametrized test case name */
> +	} else if (len = 0,
> +		   igt_debug_on(sscanf(buf,
> +				       "%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]#%*[ ]Subtest:%*[ ]%ms %n",
> +				       &case_name, &len) == 1 && len == strlen(buf))) {
> +		if (igt_debug_on(ktap->expect != CASE_NAME))
> +			return -EPROTO;
> +
> +		n = ktap->case_last + 1;
> +		ktap->expect = SUB_RESULT;
> +
> +	/* KTAP parametrized subtest result */
> +	} else if (len = 0, free(case_name), case_name = NULL,
> +		   igt_debug_on(sscanf(buf,
> +				       "%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]ok%*[ ]%u%*[ ]%*[^#\n]%1[#\n]",
> +				       &n, s) == 2) ||
> +		   igt_debug_on(sscanf(buf,
> +				       "%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]%*1[ ]not%*1[ ]ok%*[ ]%u%*[ ]%*[^#\n]%1[#\n]",
> +				       &n, s) == 2)) {
> +		/* at lease one result of a parametrised subtest expected */
> +		if (igt_debug_on(ktap->expect == SUB_RESULT &&
> +				 ktap->sub_last == 0))
> +			ktap->expect = CASE_RESULT;
> +
> +		if (igt_debug_on(ktap->expect != CASE_RESULT) ||
> +		    igt_debug_on(n != ++ktap->sub_last))
> +			return -EPROTO;
> +
> +	/* KTAP test case skip result */
> +	} else if ((igt_debug_on(sscanf(buf,
> +					"%*1[ ]%*1[ ]%*1[ ]%*1[ ]ok%*[ ]%u%*[ ]%ms%*[ ]#%*[ ]SKIP %n",
> +					&n, &case_name, &len) == 2 &&
> +				 len == strlen(buf))) ||
> +		   (len = 0, free(case_name), case_name = NULL,
> +		    igt_debug_on(sscanf(buf,
> +					"%*1[ ]%*1[ ]%*1[ ]%*1[ ]ok%*[ ]%u%*[ ]%ms%*[ ]#%*[ ]SKIP%*[ ]%m[^\n]",
> +					&n, &case_name, &msg) == 3))) {
> +		code = IGT_EXIT_SKIP;
> +
> +	/* KTAP test case pass result */
> +	} else if ((free(case_name), case_name = NULL, free(msg), msg = NULL,
> +		    igt_debug_on(sscanf(buf,
> +					"%*1[ ]%*1[ ]%*1[ ]%*1[ ]ok%*[ ]%u%*[ ]%ms %n",
> +					&n, &case_name, &len) == 2 &&
> +				 len == strlen(buf))) ||
> +		   (len = 0, free(case_name), case_name = NULL,
> +		    igt_debug_on(sscanf(buf,
> +					"%*1[ ]%*1[ ]%*1[ ]%*1[ ]ok%*[ ]%u%*[ ]%ms%*[ ]#%*[ ]%m[^\n]",
> +					&n, &case_name, &msg) == 3))) {
> +		code = IGT_EXIT_SUCCESS;
> +
> +	/* KTAP test case fail result */
> +	} else if ((free(case_name), case_name = NULL, free(msg), msg = NULL,
> +		    igt_debug_on(sscanf(buf,
> +					"%*1[ ]%*1[ ]%*1[ ]%*1[ ]not%*1[ ]ok%*[ ]%u%*[ ]%ms %n",
> +					&n, &case_name, &len) == 2 &&
> +				 len == strlen(buf))) ||
> +		   (len = 0, free(case_name), case_name = NULL,
> +		    igt_debug_on(sscanf(buf,
> +					"%*1[ ]%*1[ ]%*1[ ]%*1[ ]not%*1[ ]ok%*[ ]%u%*[ ]%ms%*[ ]#%*[ ]%m[^\n]",
> +					&n, &case_name, &msg) == 3))) {
> +		code = IGT_EXIT_FAILURE;
> +
> +	/* KTAP test suite result */
> +	} else if ((free(case_name), free(msg),
> +		    igt_debug_on(sscanf(buf, "ok%*[ ]%u%*[ ]%ms %n",
> +					&n, &suite_name, &len) == 2 &&
> +				 len == strlen(buf))) ||
> +		   (len = 0, free(suite_name), suite_name = NULL,
> +		    igt_debug_on(sscanf(buf, "ok%*[ ]%u%*[ ]%ms%*[ ]%1[#]",
> +					&n, &suite_name, s) == 3)) ||
> +		   (free(suite_name), suite_name = NULL,
> +		    igt_debug_on(sscanf(buf,
> +					"not%*[ ]ok%*[ ]%u%*[ ]%ms %n",
> +					&n, &suite_name, &len) == 2 &&
> +				 len == strlen(buf))) ||
> +		   (len = 0, free(suite_name), suite_name = NULL,
> +		    igt_debug_on(sscanf(buf,
> +					"not%*[ ]ok%*[ ]%u%*[ ]%ms%*[ ]%1[#]",
> +					&n, &suite_name, s) == 3))) {
> +		if (igt_debug_on(ktap->expect != SUITE_RESULT) ||
> +		    igt_debug_on(strcmp(suite_name, ktap->suite_name)) ||
> +		    igt_debug_on(n != ++ktap->suite_last) ||
> +		    igt_debug_on(n > ktap->suite_count)) {
> +			free(suite_name);
> +			return -EPROTO;
> +		}
> +		free(suite_name);
> +
> +		/* last test suite? */
> +		if (igt_debug_on(n == ktap->suite_count))
> +			return 0;
> +
> +		ktap->suite_name = NULL;
> +		ktap->expect = SUITE_START;
> +
> +	} else {
> +		return -EINPROGRESS;
> +	}
> +
> +	/* neither a test case name nor result */
> +	if (ktap->expect != SUB_RESULT && code == IGT_EXIT_INVALID)
> +		return -EINPROGRESS;
> +
> +	if (igt_debug_on(ktap->expect == SUB_RESULT &&
> +			 code != IGT_EXIT_INVALID) ||
> +	    igt_debug_on(code != IGT_EXIT_INVALID &&
> +			 ktap->expect != CASE_RESULT) ||
> +	    igt_debug_on(!ktap->suite_name) || igt_debug_on(!case_name) ||
> +	    igt_debug_on(ktap->expect == CASE_RESULT && ktap->case_name &&
> +			 strcmp(case_name, ktap->case_name)) ||
> +	    igt_debug_on(n > ktap->case_count) ||
> +	    igt_debug_on(n != (ktap->expect == SUB_RESULT ?
> +			       ktap->case_last + 1: ++ktap->case_last))) {
> +		free(case_name);
> +		free(msg);
> +		return -EPROTO;
> +	}
> +
> +	if (ktap->expect == SUB_RESULT) {
> +		/* KTAP parametrized test case name */
> +		ktap->case_name = case_name;
> +
> +	} else {
> +		/* KTAP test case result */
> +		ktap->case_name = NULL;
> +
> +		/* last test case in a suite */
> +		if (n == ktap->case_count)
> +			ktap->expect = SUITE_RESULT;
> +	}
> +
> +	if (igt_debug_on((result = calloc(1, sizeof(*result)), !result))) {
> +		free(case_name);
> +		free(msg);
> +		return -ENOMEM;
> +	}
> +
> +	result->suite_name = ktap->suite_name;
> +	result->case_name = case_name;
> +	result->code = code;
> +	result->msg = msg;
> +	igt_list_add_tail(&result->link, ktap->results);
> +
> +	return -EINPROGRESS;
> +}
> +
> +struct igt_ktap_results *igt_ktap_alloc(struct igt_list_head *results)
> +{
> +	struct igt_ktap_results *ktap = calloc(1, sizeof(*ktap));
> +
> +	if (!ktap)
> +		return NULL;
> +
> +	ktap->expect = KTAP_START;
> +	ktap->results = results;
> +
> +	return ktap;
> +}
> +
> +void igt_ktap_free(struct igt_ktap_results *ktap)
> +{
> +	free(ktap);
> +}
> +
>  #define DELIMITER "-"
>  
>  struct ktap_parser_args {
> @@ -332,6 +631,7 @@ static int parse_kmsg_for_tap(int fd, char *record, char *test_name)
>   * 0 if succeded
>   * -1 if error occurred
>   */
> +__maybe_unused
>  static int parse_tap_level(int fd, char *base_test_name, int test_count, bool *failed_tests,
>  			   bool *found_tests, bool is_builtin)
>  {
> @@ -445,62 +745,106 @@ static int parse_tap_level(int fd, char *base_test_name, int test_count, bool *f
>   */
>  void *igt_ktap_parser(void *unused)
>  {
> +	char record[BUF_LEN + 1], *buf, *suite_name = NULL, *case_name = NULL;
> +	struct igt_ktap_results *ktap = NULL;
>  	int fd = ktap_args.fd;
> -	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;
> +	IGT_LIST_HEAD(list);
> +	int err;
>  
> -	failed_tests = false;
> -	found_tests = false;
> +	ktap = igt_ktap_alloc(&list);
> +	if (igt_debug_on(!ktap))
> +		goto igt_ktap_parser_end;
>  
> -igt_ktap_parser_start:
> -	test_name[0] = '\0';
> -	test_name[BUF_LEN] = '\0';
> +	while (err = read(fd, record, BUF_LEN), err > 0) {
> +		struct igt_ktap_result *r, *rn;
>  
> -	if (read(fd, record, BUF_LEN) < 0) {
> -		if (errno == EPIPE)
> -			igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
> -		else if (errno != EINTR)
> -			igt_warn("error reading kmsg (%m)\n");
> +		/* skip kmsg continuation lines */
> +		if (igt_debug_on(*record == ' '))
> +			continue;
>  
> -		goto igt_ktap_parser_end;
> -	}
> +		/* NULL-terminate the record */
> +		record[err] = '\0';
>  
> -	test_count = find_next_tap_subtest(fd, record, test_name, is_builtin);
> +		/* detect start of log message, continue if not found */
> +		buf = strchrnul(record, ';');
> +		if (igt_debug_on(*buf == '\0'))
> +			continue;
> +		buf++;
>  
> -	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;
> +		err = igt_ktap_parse(buf, ktap);
>  
> -		if (parse_tap_level(fd, test_name, test_count, &failed_tests, &found_tests,
> -				    is_builtin) == -1)
> +		/* parsing error */
> +		if (err && err != -EINPROGRESS)
>  			goto igt_ktap_parser_end;
>  
> -		break;
> +		igt_list_for_each_entry_safe(r, rn, &list, link) {
> +			struct ktap_test_results_element *result = NULL;
> +			int code = r->code;
> +
> +			if (code != IGT_EXIT_INVALID)
> +				result = calloc(1, sizeof(*result));
> +
> +			if (result) {
> +				snprintf(result->test_name, sizeof(result->test_name),
> +					 "%s-%s", r->suite_name, r->case_name);
> +
> +				if (code == IGT_EXIT_SUCCESS)
> +					result->passed = true;
> +			}
> +
> +			igt_list_del(&r->link);
> +			if (r->suite_name != suite_name) {
> +				free(suite_name);
> +				suite_name = r->suite_name;
> +			}
> +			if (r->case_name != case_name) {
> +				free(case_name);
> +				case_name = r->case_name;
> +			}
> +			free(r->msg);
> +			free(r);
> +
> +			/*
> +			 * no extra result record expected on start
> +			 * of parametrized test case -- skip it
> +			 */
> +			if (code == IGT_EXIT_INVALID)
> +				continue;
> +
> +			if (!result) {
> +				err = -ENOMEM;
> +				goto igt_ktap_parser_end;
> +			}
> +
> +			pthread_mutex_lock(&results.mutex);
> +			igt_list_add_tail(&result->link, &results.list);
> +			pthread_mutex_unlock(&results.mutex);
> +		}
> +
> +		/* end of KTAP report */
> +		if (!err)
> +			goto igt_ktap_parser_end;
>  	}
>  
> -	/* Parse topmost level */
> -	test_name[0] = '\0';
> -	parse_tap_level(fd, test_name, test_count, &failed_tests, &found_tests, is_builtin);
> +	if (err < 0) {
> +		if (errno == EPIPE)
> +			igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
> +		else if (errno != EINTR)
> +			igt_warn("error reading kmsg (%m)\n");
> +	}
>  
>  igt_ktap_parser_end:
> -	results.still_running = false;
> +	free(suite_name);
> +	free(case_name);
>  
> -	if (found_tests && !failed_tests)
> +	if (!err)
>  		ktap_args.ret = IGT_EXIT_SUCCESS;
>  
> +	results.still_running = false;
> +
> +	if (ktap)
> +		igt_ktap_free(ktap);
> +
>  	return NULL;
>  }
>  
> diff --git a/lib/igt_ktap.h b/lib/igt_ktap.h
> index b4d7a6dbc7..6f8da3eab6 100644
> --- a/lib/igt_ktap.h
> +++ b/lib/igt_ktap.h
> @@ -1,5 +1,6 @@
>  /*
>   * Copyright © 2022 Isabella Basso do Amaral <isabbasso at riseup.net>
> + * Copyright © 2023 Intel Corporation
>   *
>   * Permission is hereby granted, free of charge, to any person obtaining a
>   * copy of this software and associated documentation files (the "Software"),
> @@ -30,6 +31,20 @@
>  
>  #include "igt_list.h"
>  
> +struct igt_ktap_result {
> +	struct igt_list_head link;
> +	char *suite_name;
> +	char *case_name;
> +	char *msg;
> +	int code;
> +};
> +
> +struct igt_ktap_results;
> +
> +struct igt_ktap_results *igt_ktap_alloc(struct igt_list_head *results);
> +int igt_ktap_parse(const char *buf, struct igt_ktap_results *ktap);
> +void igt_ktap_free(struct igt_ktap_results *ktap);
> +
>  void *igt_ktap_parser(void *unused);
>  
>  typedef struct ktap_test_results_element {
> diff --git a/lib/tests/igt_ktap_parser.c b/lib/tests/igt_ktap_parser.c
> new file mode 100644
> index 0000000000..6357bdf6a5
> --- /dev/null
> +++ b/lib/tests/igt_ktap_parser.c
> @@ -0,0 +1,246 @@
> +// SPDX-License-Identifier: MIT
> +/*
> +* Copyright © 2023 Intel Corporation
> +*/
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include "igt_core.h"
> +#include "igt_ktap.h"
> +#include "igt_list.h"
> +
> +static void ktap_list(void)
> +{
> +	struct igt_ktap_result *result, *rn;
> +	struct igt_ktap_results *ktap;
> +	int suite = 1, test = 1;
> +	IGT_LIST_HEAD(results);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +
> +	igt_assert_eq(igt_ktap_parse("KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("1..3\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    # Subtest: test_suite_1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    1..3\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 1 test_case_1 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 2 test_case_2 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 3 test_case_3 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("ok 1 test_suite_1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    # Subtest: test_suite_2\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    1..1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 1 test_case_1 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("ok 2 test_suite_2\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    # Subtest: test_suite_3\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    1..4\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 1 test_case_1 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 2 test_case_2 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 3 test_case_3 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 4 test_case_4 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("ok 3 test_suite_3\n", ktap), 0);
> +
> +	igt_ktap_free(ktap);
> +
> +	igt_assert_eq(igt_list_length(&results), 8);
> +
> +	igt_list_for_each_entry_safe(result, rn, &results, link) {
> +		char *case_name, *suite_name;
> +
> +		igt_list_del(&result->link);
> +
> +		igt_assert_lt(0, asprintf(&case_name, "test_case_%u", test));
> +		igt_assert_lt(0, asprintf(&suite_name, "test_suite_%u", suite));
> +
> +		igt_assert(result->case_name);
> +		igt_assert_eq(strcmp(result->case_name, case_name), 0);
> +		free(result->case_name);
> +		free(case_name);
> +
> +		igt_assert(result->suite_name);
> +		igt_assert_eq(strcmp(result->suite_name, suite_name), 0);
> +		free(suite_name);
> +
> +		igt_assert(!result->msg);
> +		igt_assert_eq(result->code, IGT_EXIT_SKIP);
> +
> +		if ((suite == 1 && test < 3) || (suite == 3 && test < 4)) {
> +			test++;
> +		} else {
> +			free(result->suite_name);
> +			suite++;
> +			test = 1;
> +		}
> +
> +		free(result);
> +	}
> +}
> +
> +static void ktap_results(void)
> +{
> +	struct igt_ktap_result *result;
> +	struct igt_ktap_results *ktap;
> +	char *suite_name, *case_name;
> +	IGT_LIST_HEAD(results);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +
> +	igt_assert_eq(igt_ktap_parse("KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("1..1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    # Subtest: test_suite\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    1..1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        # Subtest: test_case\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        ok 1 parameter 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        ok 2 parameter 2 # a comment\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        ok 3 parameter 3 # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        ok 4 parameter 4 # SKIP with a message\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        not ok 5 parameter 5\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        not ok 6 parameter 6 # failure message\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    ok 1 test_case\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("not ok 1 test_suite\n", ktap), 0);
> +
> +	igt_ktap_free(ktap);
> +
> +	igt_assert_eq(igt_list_length(&results), 2);
> +
> +	result = igt_list_first_entry(&results, result, link);
> +	igt_list_del(&result->link);
> +	igt_assert_eq(strcmp(result->suite_name, "test_suite"), 0);
> +	igt_assert_eq(strcmp(result->case_name, "test_case"), 0);
> +	igt_assert_eq(result->code, IGT_EXIT_INVALID);
> +	igt_assert(!result->msg);
> +	free(result->msg);
> +	suite_name = result->suite_name;
> +	case_name = result->case_name;
> +	free(result);
> +
> +	result = igt_list_first_entry(&results, result, link);
> +	igt_list_del(&result->link);
> +	igt_assert_eq(strcmp(result->suite_name, suite_name), 0);
> +	igt_assert_eq(strcmp(result->case_name, case_name), 0);
> +	igt_assert_neq(result->code, IGT_EXIT_INVALID);
> +	free(result->msg);
> +	free(suite_name);
> +	free(case_name);
> +	free(result);
> +}
> +
> +static void ktap_success(void)
> +{
> +	struct igt_ktap_result *result;
> +	struct igt_ktap_results *ktap;
> +	IGT_LIST_HEAD(results);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +
> +	igt_assert_eq(igt_ktap_parse("KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("1..1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    # Subtest: test_suite\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("    1..1\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_ktap_parse("        KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_assert(igt_list_empty(&results));
> +
> +	igt_assert_eq(igt_ktap_parse("        # Subtest: test_case\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_list_length(&results), 1);
> +
> +	igt_assert_eq(igt_ktap_parse("        ok 1 parameter # SKIP\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_list_length(&results), 1);
> +
> +	igt_assert_eq(igt_ktap_parse("    ok 1 test_case\n", ktap), -EINPROGRESS);
> +	igt_assert_eq(igt_list_length(&results), 2);
> +
> +	igt_assert_eq(igt_ktap_parse("not ok 1 test_suite\n", ktap), 0);
> +	igt_assert_eq(igt_list_length(&results), 2);
> +
> +	igt_ktap_free(ktap);
> +
> +	result = igt_list_last_entry(&results, result, link);
> +	igt_list_del(&result->link);
> +	igt_assert_eq(result->code, IGT_EXIT_SUCCESS);
> +	free(result->msg);
> +	free(result);
> +
> +	result = igt_list_last_entry(&results, result, link);
> +	igt_list_del(&result->link);
> +	free(result->suite_name);
> +	free(result->case_name);
> +	free(result->msg);
> +	free(result);
> +}
> +
> +static void ktap_top_version(void)
> +{
> +	struct igt_ktap_results *ktap;
> +	IGT_LIST_HEAD(results);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	igt_assert_eq(igt_ktap_parse("1..1\n", ktap), -EPROTO);
> +	igt_ktap_free(ktap);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	/* TODO: change to -EPROTO as soon as related workaround is dropped */
> +	igt_assert_eq(igt_ktap_parse("    KTAP version 1\n", ktap), -EINPROGRESS);
> +	igt_ktap_free(ktap);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	igt_assert_eq(igt_ktap_parse("    # Subtest: test_suite\n", ktap), -EPROTO);
> +	igt_ktap_free(ktap);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	igt_assert_eq(igt_ktap_parse("    1..1\n", ktap), -EPROTO);
> +	igt_ktap_free(ktap);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	igt_assert_eq(igt_ktap_parse("        KTAP version 1\n", ktap), -EPROTO);
> +	igt_ktap_free(ktap);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	igt_assert_eq(igt_ktap_parse("        # Subtest: test_case\n", ktap), -EPROTO);
> +	igt_ktap_free(ktap);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	igt_assert_eq(igt_ktap_parse("        ok 1 parameter 1\n", ktap), -EPROTO);
> +	igt_ktap_free(ktap);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	igt_assert_eq(igt_ktap_parse("    ok 1 test_case\n", ktap), -EPROTO);
> +	igt_ktap_free(ktap);
> +
> +	ktap = igt_ktap_alloc(&results);
> +	igt_require(ktap);
> +	igt_assert_eq(igt_ktap_parse("ok 1 test_suite\n", ktap), -EPROTO);
> +	igt_ktap_free(ktap);
> +}
> +
> +igt_main
> +{
> +	igt_subtest("list")
> +		ktap_list();
> +
> +	igt_subtest("results")
> +		ktap_results();
> +
> +	igt_subtest("success")
> +		ktap_success();
> +
> +	igt_subtest("top-ktap-version")
> +		ktap_top_version();
> +}
> diff --git a/lib/tests/meson.build b/lib/tests/meson.build
> index 7a52a7876e..fa3d81de6c 100644
> --- a/lib/tests/meson.build
> +++ b/lib/tests/meson.build
> @@ -10,6 +10,7 @@ lib_tests = [
>  	'igt_exit_handler',
>  	'igt_fork',
>  	'igt_fork_helper',
> +        'igt_ktap_parser',
>  	'igt_list_only',
>  	'igt_invalid_subtest_name',
>  	'igt_nesting',


More information about the igt-dev mailing list