[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