Re: [igt-dev] [PATCH 1/5 v2 i-g-t] lib/igt_core: Add support for subtest descriptions

Ser, Simon
2019-07-04T13:02:23Z

On Thu, 2019-07-04 at 14:55 +0300, Arkadiusz Hiler wrote:
> This patch adds igt_description() which attaches a description to the
> following igt_subtest or igt_subtest_group block.
>
> Descriptions are accessible via './test --describe[=pattern]'
>
> Subtest description is its own igt_describe as well as igt_describes
> of all the parenting igt_subtest_groups, starting from the outermost
> scope.
>
> Examples of code and produced outputs are included in
> lib/test/igt_describe.c and as a documentation comment on igt_describe()
> macro.
>
> v2: address Simon's review
>
> Cc: Simon Ser <simon.ser@intel.com>
> Cc: Petri Latvala <petri.latvala@intel.com>
> Signed-off-by: Arkadiusz Hiler <arkadiusz.hiler@intel.com>
> Acked-by: Petri Latvala <petri.latvala@intel.com>

LGTM

Reviewed-by: Simon Ser <simon.ser@intel.com>

> ---
> delta with v1: https://hiler.eu/p/ab062f76.txt
>
> lib/igt_core.c | 179 +++++++++++++++++++++++--
> lib/igt_core.h | 137 +++++++++++++++++--
> lib/tests/igt_describe.c | 280 +++++++++++++++++++++++++++++++++++++++
> lib/tests/meson.build | 1 +
> 4 files changed, 576 insertions(+), 21 deletions(-)
> create mode 100644 lib/tests/igt_describe.c
>
> diff --git a/lib/igt_core.c b/lib/igt_core.c
> index 6b9f0425..425848a2 100644
> --- a/lib/igt_core.c
> +++ b/lib/igt_core.c
> @@ -70,6 +70,7 @@
> #include "igt_sysfs.h"
> #include "igt_sysrq.h"
> #include "igt_rc.h"
> +#include "igt_list.h"
>
> #define UNW_LOCAL_ONLY
> #include <libunwind.h>
> @@ -259,6 +260,7 @@ const char *igt_interactive_debug;
>
> /* subtests helpers */
> static bool list_subtests = false;
> +static bool describe_subtests = false;
> static char *run_single_subtest = NULL;
> static bool run_single_subtest_found = false;
> static const char *in_subtest = NULL;
> @@ -271,6 +273,16 @@ static enum {
> CONT = 0, SKIP, FAIL
> } skip_subtests_henceforth = CONT;
>
> +static char __current_description[512];
> +
> +struct description_node {
> + char desc[sizeof(__current_description)];
> + struct igt_list link;
> +};
> +
> +static struct igt_list subgroup_descriptions;
> +
> +
> bool __igt_plain_output = false;
>
> /* fork support state */
> @@ -285,6 +297,7 @@ enum {
> * conflict with core ones
> */
> OPT_LIST_SUBTESTS = 500,
> + OPT_DESCRIBE_SUBTESTS,
> OPT_RUN_SUBTEST,
> OPT_DESCRIPTION,
> OPT_DEBUG,
> @@ -528,10 +541,59 @@ static void common_exit_handler(int sig)
> assert(sig != 0 || igt_exit_called);
> }
>
> +static void print_line_wrapping(const char *indent, const char *text)
> +{
> + char *copy, *curr, *next_space;
> + int current_line_length = 0;
> + bool done = false;
> +
> + const int total_line_length = 80;
> + const int line_length = total_line_length - strlen(indent);
> +
> + copy = malloc(strlen(text) + 1);
> + memcpy(copy, text, strlen(text) + 1);
> +
> + curr = copy;
> +
> + printf("%s", indent);
> +
> + while (!done) {
> + next_space = strchr(curr, ' ');
> +
> + if (!next_space) { /* no more spaces, print everything that is left */
> + done = true;
> + next_space = strchr(curr, '\0');
> + }
> +
> + *next_space = '\0';
> +
> + if ((next_space - curr) + current_line_length > line_length && curr != copy) {
> + printf("\n%s", indent);
> + current_line_length = 0;
> + }
> +
> + if (current_line_length == 0)
> + printf("%s", curr); /* first word in a line, don't space out */
> + else
> + printf(" %s", curr);
> +
> + current_line_length += next_space - curr;
> + curr = next_space + 1;
> + }
> +
> + printf("\n");
> +
> + free(copy);
> +}
> +
> +
> static void print_test_description(void)
> {
> - if (&__igt_test_description)
> - printf("%s\n", __igt_test_description);
> + if (&__igt_test_description) {
> + print_line_wrapping("", __igt_test_description);
> + if (describe_subtests)
> + printf("\n");
> + }
> }
>
> static void print_version(void)
> @@ -558,6 +620,7 @@ static void print_usage(const char *help_str, bool output_on_stderr)
> " --debug[=log-domain]\n"
> " --interactive-debug[=domain]\n"
> " --help-description\n"
> + " --describe\n"
> " --help|-h\n");
> if (help_str)
> fprintf(f, "%s\n", help_str);
> @@ -671,6 +734,7 @@ static int common_init(int *argc, char **argv,
> int c, option_index = 0, i, x;
> static struct option long_options[] = {
> {"list-subtests", no_argument, NULL, OPT_LIST_SUBTESTS},
> + {"describe", optional_argument, NULL, OPT_DESCRIBE_SUBTESTS},
> {"run-subtest", required_argument, NULL, OPT_RUN_SUBTEST},
> {"help-description", no_argument, NULL, OPT_DESCRIPTION},
> {"debug", optional_argument, NULL, OPT_DEBUG},
> @@ -687,6 +751,7 @@ static int common_init(int *argc, char **argv,
> int ret = 0;
>
> common_init_env();
> + igt_list_init(&subgroup_descriptions);
>
> command_str = argv[0];
> if (strrchr(command_str, '/'))
> @@ -777,6 +842,13 @@ static int common_init(int *argc, char **argv,
> if (!run_single_subtest)
> list_subtests = true;
> break;
> + case OPT_DESCRIBE_SUBTESTS:
> + if (optarg)
> + run_single_subtest = strdup(optarg);
> + list_subtests = true;
> + describe_subtests = true;
> + print_test_description();
> + break;
> case OPT_RUN_SUBTEST:
> assert(optarg);
> if (!list_subtests)
> @@ -934,12 +1006,41 @@ void igt_simple_init_parse_opts(int *argc, char **argv,
> extra_opt_handler, handler_data);
> }
>
> +static void _clear_current_description(void) {
> + __current_description[0] = '\0';
> +}
> +
> +static void __igt_print_description(const char *subtest_name, const char *file, int line)
> +{
> + struct description_node *desc;
> + const char indent[] = " ";
> + bool has_doc = false;
> +
> +
> + printf("SUB %s %s:%d:\n", subtest_name, file, line);
> +
> + igt_list_for_each(desc, &subgroup_descriptions, link) {
> + print_line_wrapping(indent, desc->desc);
> + printf("\n");
> + has_doc = true;
> + }
> +
> + if (__current_description[0] != '\0') {
> + print_line_wrapping(indent, __current_description);
> + printf("\n");
> + has_doc = true;
> + }
> +
> + if (!has_doc)
> + printf("%sNO DOCUMENTATION!\n\n", indent);
> +}
> +
> /*
> * Note: Testcases which use these helpers MUST NOT output anything to stdout
> * outside of places protected by igt_run_subtest checks - the piglit
> * runner adds every line to the subtest list.
> */
> -bool __igt_run_subtest(const char *subtest_name)
> +bool __igt_run_subtest(const char *subtest_name, const char *file, const int line)
> {
> int i;
>
> @@ -954,18 +1055,25 @@ bool __igt_run_subtest(const char *subtest_name)
> igt_exit();
> }
>
> - if (list_subtests) {
> - printf("%s\n", subtest_name);
> - return false;
> - }
> -
> if (run_single_subtest) {
> - if (uwildmat(subtest_name, run_single_subtest) == 0)
> + if (uwildmat(subtest_name, run_single_subtest) == 0) {
> + _clear_current_description();
> return false;
> - else
> + } else {
> run_single_subtest_found = true;
> + }
> }
>
> + if (describe_subtests) {
> + __igt_print_description(subtest_name, file, line);
> + _clear_current_description();
> + return false;
> + } else if (list_subtests) {
> + printf("%s\n", subtest_name);
> + return false;
> + }
> +
> +
> if (skip_subtests_henceforth) {
> printf("%sSubtest %s: %s%s\n",
> (!__igt_plain_output) ? "\x1b[1m" : "", subtest_name,
> @@ -1014,15 +1122,32 @@ bool igt_only_list_subtests(void)
> return list_subtests;
> }
>
> -void __igt_subtest_group_save(int *save)
> +
> +
> +void __igt_subtest_group_save(int *save, int *desc)
> {
> assert(test_with_subtests);
>
> + if (__current_description[0] != '\0') {
> + struct description_node *new = calloc(1, sizeof(*new));
> + memcpy(new->desc, __current_description, sizeof(__current_description));
> + igt_list_add_tail(&new->link, &subgroup_descriptions);
> + _clear_current_description();
> + *desc = true;
> + }
> +
> *save = skip_subtests_henceforth;
> }
>
> -void __igt_subtest_group_restore(int save)
> +void __igt_subtest_group_restore(int save, int desc)
> {
> + if (desc) {
> + struct description_node *last =
> + igt_list_last_entry(&subgroup_descriptions, last, link);
> + igt_list_del(&last->link);
> + free(last);
> + }
> +
> skip_subtests_henceforth = save;
> }
>
> @@ -1229,6 +1354,34 @@ bool igt_can_fail(void)
> return !test_with_subtests || in_fixture || in_subtest;
> }
>
> +/**
> + * igt_describe_f:
> + * @fmt: format string containing description
> + * @...: argument used by the format string
> + *
> + * Attach a description to the following #igt_subtest or #igt_subtest_group
> + * block.
> + *
> + * Check #igt_describe for more details.
> + *
> + */
> +void igt_describe_f(const char *fmt, ...)
> +{
> + int ret;
> + va_list args;
> +
> + if (!describe_subtests)
> + return;
> +
> + va_start(args, fmt);
> +
> + ret = vsnprintf(__current_description, sizeof(__current_description), fmt, args);
> +
> + va_end(args);
> +
> + assert(ret < sizeof(__current_description));
> +}
> +
> static bool running_under_gdb(void)
> {
> char pathname[30], buf[1024];
> @@ -1540,7 +1693,7 @@ void igt_exit(void)
> g_key_file_free(igt_key_file);
>
> if (run_single_subtest && !run_single_subtest_found) {
> - igt_warn("Unknown subtest: %s\n", run_single_subtest);
> + igt_critical("Unknown subtest: %s\n", run_single_subtest);
> exit(IGT_EXIT_INVALID);
> }
>
> diff --git a/lib/igt_core.h b/lib/igt_core.h
> index 88a95ec2..1b37f142 100644
> --- a/lib/igt_core.h
> +++ b/lib/igt_core.h
> @@ -174,7 +174,7 @@ int igt_subtest_init_parse_opts(int *argc, char **argv,
> #define igt_subtest_init(argc, argv) \
> igt_subtest_init_parse_opts(&argc, argv, NULL, NULL, NULL, NULL, NULL);
>
> -bool __igt_run_subtest(const char *subtest_name);
> +bool __igt_run_subtest(const char *subtest_name, const char *file, const int line);
> #define __igt_tokencat2(x, y) x ## y
>
> /**
> @@ -198,14 +198,14 @@ bool __igt_run_subtest(const char *subtest_name);
> *
> * This is a simpler version of igt_subtest_f()
> */
> -#define igt_subtest(name) for (; __igt_run_subtest((name)) && \
> +#define igt_subtest(name) for (; __igt_run_subtest((name), __FILE__, __LINE__) && \
> (sigsetjmp(igt_subtest_jmpbuf, 1) == 0); \
> igt_success())
> #define __igt_subtest_f(tmp, format...) \
> for (char tmp [256]; \
> snprintf( tmp , sizeof( tmp ), \
> format), \
> - __igt_run_subtest( tmp ) && \
> + __igt_run_subtest(tmp, __FILE__, __LINE__) && \
> (sigsetjmp(igt_subtest_jmpbuf, 1) == 0); \
> igt_success())
>
> @@ -227,8 +227,8 @@ bool __igt_run_subtest(const char *subtest_name);
> const char *igt_subtest_name(void);
> bool igt_only_list_subtests(void);
>
> -void __igt_subtest_group_save(int *);
> -void __igt_subtest_group_restore(int);
> +void __igt_subtest_group_save(int *, int *);
> +void __igt_subtest_group_restore(int, int);
> /**
> * igt_subtest_group:
> *
> @@ -245,11 +245,14 @@ void __igt_subtest_group_restore(int);
> * group will fail or skip. Subtest groups can be arbitrarily nested.
> */
> #define igt_subtest_group for (int igt_tokencat(__tmpint,__LINE__) = 0, \
> - igt_tokencat(__save,__LINE__) = 0; \
> + igt_tokencat(__save,__LINE__) = 0, \
> + igt_tokencat(__desc,__LINE__) = 0; \
> igt_tokencat(__tmpint,__LINE__) < 1 && \
> - (__igt_subtest_group_save(& igt_tokencat(__save,__LINE__) ), true); \
> + (__igt_subtest_group_save(& igt_tokencat(__save,__LINE__), \
> + & igt_tokencat(__desc,__LINE__) ), true); \
> igt_tokencat(__tmpint,__LINE__) ++, \
> - __igt_subtest_group_restore(igt_tokencat(__save,__LINE__) ))
> + __igt_subtest_group_restore(igt_tokencat(__save,__LINE__), \
> + igt_tokencat(__desc,__LINE__)))
>
> /**
> * igt_main_args:
> @@ -385,6 +388,124 @@ static inline void igt_ignore_warn(bool value)
> {
> }
>
> +__attribute__((format(printf, 1, 2)))
> +void igt_describe_f(const char *fmt, ...);
> +
> +/**
> + * igt_describe:
> + * @dsc: string containing description
> + *
> + * Attach a description to the following #igt_subtest or #igt_subtest_group
> + * block.
> + *
> + * The description should complement the test/subtest name and provide more
> + * context on what is being tested. It should explain the idea of the test and
> + * do not mention implementation details, so that it never goes out of date.
> + *
> + * DO:
> + * * focus on the userspace's perspective
> + * * try to capture the reason for the test's existence
> + * * be brief
> + *
> + * DON'T:
> + * * try to translate the code into English
> + * * explain all the checks the test does
> + * * delve on the implementation
> + *
> + * Good examples:
> + * * "make sure that legacy cursor updates do not stall atomic commits"
> + * * "check that atomic updates of many planes are indeed atomic and take
> + * effect immediately after the commit"
> + * * "make sure that the meta-data exposed by the kernel to the userspace
> + * is correct and matches the used EDID"
> + *
> + * Bad examples:
> + * * "spawn 10 threads, each pinning cpu core with a busy loop..."
> + * * "randomly generate holes in a primary plane then try to cover each hole
> + * with a plane and make sure that CRC matches, do 25 gazillion rounds of
> + * that..."
> + *
> + *
> + * Resulting #igt_subtest documentation is a concatenation of its own
> + * description and all the parenting #igt_subtest_group descriptions, starting
> + * from the outermost one. Example:
> + *
> + * |[
> + * #include "igt.h"
> + *
> + * IGT_TEST_DESCRIPTION("Global description of the whole binary");
> + * igt_main
> + * {
> + * igt_describe("Desc of the subgroup with A and B");
> + * igt_subtest_group {
> + * igt_describe("Desc of the subtest A");
> + * igt_subtest("subtest-a") {
> + * ...
> + * }
> + *
> + * igt_describe("Desc of the subtest B");
> + * igt_subtest("subtest-b") {
> + * ...
> + * }
> + * }
> + *
> + * igt_describe("Desc of the subtest C");
> + * igt_subtest("subtest-c") {
> + * ...
> + * }
> + * }
> + * ]|
> + *
> + * It's will accessible via --describe command line switch:
> + *
> + * |[
> + * $ test --describe
> + * Global description of the whole binary
> + *
> + * SUB subtest-a test.c:5:
> + * Desc of the subgroup with A and B
> + *
> + * Desc of the subtest A
> + *
> + * SUB subtest-b test.c:10:
> + * Desc of the subgroup with A and B
> + *
> + * Desc of the subtest B
> + *
> + * SUB subtest-c test.c:15:
> + * Desc of the subtest C
> + * ]|
> + *
> + * Every single #igt_subtest does not have to be preceded with a #igt_describe
> + * as long as it has good-enough explanation provided on the #igt_subtest_group
> + * level.
> + *
> + * Example:
> + *
> + * |[
> + * #include "igt.h"
> + *
> + * igt_main
> + * {
> + * igt_describe("check xyz with different tilings");
> + * igt_subtest_group {
> + * // no need for extra description, group is enough and tiling is
> + * // obvious from the test name
> + * igt_subtest("foo-tiling-x") {
> + * ...
> + * }
> + *
> + * igt_subtest("foo-tiling-y") {
> + * ...
> + * }
> + * }
> + * }
> + * ]|
> + *
> + */
> +#define igt_describe(dsc) \
> + igt_describe_f("%s", dsc)
> +
> /**
> * igt_assert:
> * @expr: condition to test
> diff --git a/lib/tests/igt_describe.c b/lib/tests/igt_describe.c
> new file mode 100644
> index 00000000..a4457fa9
> --- /dev/null
> +++ b/lib/tests/igt_describe.c
> @@ -0,0 +1,280 @@
> +/*
> + * Copyright © 2019 Intel Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> + * IN THE SOFTWARE.
> + */
> +
> +#include <sys/wait.h>
> +#include <string.h>
> +#include <stdlib.h>
> +
> +#include "drmtest.h"
> +#include "igt_tests_common.h"
> +
> +IGT_TEST_DESCRIPTION("the top level description");
> +static void fake_main(int argc, char **argv) {
> + igt_subtest_init(argc, argv);
> +
> + igt_describe("Basic A");
> + igt_subtest("A")
> + ;
> +
> + igt_fixture
> + printf("should not be executed!\n");
> +
> + igt_describe("Group with B, C & D");
> + igt_subtest_group {
> + igt_describe("Basic B");
> + igt_subtest("B")
> + ;
> +
> + if (!igt_only_list_subtests())
> + printf("should not be executed!\n");
> +
> + igt_describe("Group with C & D");
> + igt_subtest_group {
> + igt_describe("Basic C");
> + igt_subtest("C")
> + printf("should not be executed!\n");
> +
> + // NO DOC
> + igt_subtest("D")
> + ;
> + }
> + }
> +
> + // NO DOC
> + igt_subtest_group {
> + // NO DOC
> + igt_subtest("E")
> + ;
> + }
> +
> + // NO DOC
> + igt_subtest("F")
> + ;
> +
> + igt_describe("this description should be so long that it wraps itself nicely in the terminal "
> + "this description should be so long that it wraps itself nicely in the terminal "
> + "this description should be so long that it wraps itself nicely in the terminal "
> + "this description should be so long that it wraps itself nicely in the terminal "
> + "this description should be so long that it wraps itself nicely in the terminal "
> + "this description should be so long that it wraps itself nicely in the terminal");
> + igt_subtest("G")
> + ;
> +
> + igt_describe("verylongwordthatshoudlbeprintedeventhoughitspastthewrppinglimit"
> + "verylongwordthatshoudlbeprintedeventhoughitspastthewrappinglimit "
> + "verylongwordthatshoudlbeprintedeventhoughitspastthewrappinglimit"
> + "verylongwordthatshoudlbeprintedeventhoughitspastthewrappinglimit");
> + igt_subtest("F")
> + ;
> +
> + igt_exit();
> +}
> +
> +static const char DESCRIBE_ALL_OUTPUT[] = \
> + "the top level description\n"
> + "\n"
> + "SUB A ../lib/tests/igt_describe.c:36:\n"
> + " Basic A\n"
> + "\n"
> + "SUB B ../lib/tests/igt_describe.c:45:\n"
> + " Group with B, C & D\n"
> + "\n"
> + " Basic B\n"
> + "\n"
> + "SUB C ../lib/tests/igt_describe.c:54:\n"
> + " Group with B, C & D\n"
> + "\n"
> + " Group with C & D\n"
> + "\n"
> + " Basic C\n"
> + "\n"
> + "SUB D ../lib/tests/igt_describe.c:58:\n"
> + " Group with B, C & D\n"
> + "\n"
> + " Group with C & D\n"
> + "\n"
> + "SUB E ../lib/tests/igt_describe.c:66:\n"
> + " NO DOCUMENTATION!\n"
> + "\n"
> + "SUB F ../lib/tests/igt_describe.c:71:\n"
> + " NO DOCUMENTATION!\n"
> + "\n"
> + "SUB G ../lib/tests/igt_describe.c:80:\n"
> + " this description should be so long that it wraps itself nicely in the terminal this\n"
> + " description should be so long that it wraps itself nicely in the terminal this description\n"
> + " should be so long that it wraps itself nicely in the terminal this description should be so\n"
> + " long that it wraps itself nicely in the terminal this description should be so long that it\n"
> + " wraps itself nicely in the terminal this description should be so long that it wraps itself\n"
> + " nicely in the terminal\n"
> + "\n"
> + "SUB F ../lib/tests/igt_describe.c:87:\n"
> + " verylongwordthatshoudlbeprintedeventhoughitspastthewrppinglimitverylongwordthatshoudlbeprintedeventhoughitspastthewrappinglimit\n"
> + " verylongwordthatshoudlbeprintedeventhoughitspastthewrappinglimitverylongwordthatshoudlbeprintedeventhoughitspastthewrappinglimit\n\n";
> +
> +static const char JUST_C_OUTPUT[] = \
> + "the top level description\n"
> + "\n"
> + "SUB C ../lib/tests/igt_describe.c:54:\n"
> + " Group with B, C & D\n"
> + "\n"
> + " Group with C & D\n"
> + "\n"
> + " Basic C\n"
> + "\n";
> +
> +static void assert_pipe_empty(int fd)
> +{
> + char buf[5];
> + internal_assert(0 == read(fd, buf, sizeof(buf)));
> +}
> +
> +static void read_whole_pipe(int fd, char *buf, size_t buflen)
> +{
> + ssize_t readlen;
> + off_t offset;
> +
> + offset = 0;
> + while ((readlen = read(fd, buf+offset, buflen-offset))) {
> + if (readlen == -1) {
> + if (errno == EINTR) {
> + continue;
> + } else {
> + printf("read failed with %s\n", strerror(errno));
> + exit(1);
> + }
> + }
> + internal_assert(readlen != -1);
> + offset += readlen;
> + }
> +}
> +
> +static pid_t do_fork(int argc, char **argv, int *out, int *err)
> +{
> + int outfd[2], errfd[2];
> + pid_t pid;
> +
> + internal_assert(pipe(outfd) != -1);
> + internal_assert(pipe(errfd) != -1);
> +
> + pid = fork();
> + internal_assert(pid != -1);
> +
> + if (pid == 0) {
> + while (dup2(outfd[1], STDOUT_FILENO) == -1 && errno == EINTR) {}
> + while (dup2(errfd[1], STDERR_FILENO) == -1 && errno == EINTR) {}
> +
> + close(outfd[0]);
> + close(outfd[1]);
> + close(errfd[0]);
> + close(errfd[1]);
> +
> + fake_main(argc, argv);
> +
> + exit(-1);
> + } else {
> + /* close the writing ends */
> + close(outfd[1]);
> + close(errfd[1]);
> +
> + *out = outfd[0];
> + *err = errfd[0];
> +
> + return pid;
> + }
> +}
> +
> +static int _wait(pid_t pid, int *status) {
> + int ret;
> +
> + do {
> + ret = waitpid(pid, status, 0);
> + } while (ret == -1 && errno == EINTR);
> +
> + return ret;
> +}
> +
> +int main(int argc, char **argv)
> +{
> + char prog[] = "igt_describe";
> + int status;
> + int outfd, errfd;
> +
> + /* describe all subtest */ {
> + static char out[4096];
> + char arg[] = "--describe";
> + char *fake_argv[] = {prog, arg};
> + int fake_argc = ARRAY_SIZE(fake_argv);
> +
> + pid_t pid = do_fork(fake_argc, fake_argv, &outfd, &errfd);
> +
> + read_whole_pipe(outfd, out, sizeof(out));
> + assert_pipe_empty(errfd);
> +
> + internal_assert(_wait(pid, &status) != -1);
> + internal_assert(WIFEXITED(status));
> + internal_assert(WEXITSTATUS(status) == IGT_EXIT_SUCCESS);
> + internal_assert(0 == strcmp(DESCRIBE_ALL_OUTPUT, out));
> +
> + close(outfd);
> + close(errfd);
> + }
> +
> + /* describe C using a pattern */ {
> + static char out[4096];
> + char arg[] = "--describe=C";
> + char *fake_argv[] = {prog, arg};
> + int fake_argc = ARRAY_SIZE(fake_argv);
> +
> + pid_t pid = do_fork(fake_argc, fake_argv, &outfd, &errfd);
> +
> + read_whole_pipe(outfd, out, sizeof(out));
> + assert_pipe_empty(errfd);
> +
> + internal_assert(_wait(pid, &status) != -1);
> + internal_assert(WEXITSTATUS(status) == IGT_EXIT_SUCCESS);
> + internal_assert(0 == strcmp(JUST_C_OUTPUT, out));
> +
> + close(outfd);
> + close(errfd);
> + }
> +
> + /* fail describing with a bad pattern */ {
> + static char err[4096];
> + char arg[] = "--describe=Z";
> + char *fake_argv[] = {prog, arg};
> + int fake_argc = ARRAY_SIZE(fake_argv);
> +
> + pid_t pid = do_fork(fake_argc, fake_argv, &outfd, &errfd);
> +
> + read_whole_pipe(errfd, err, sizeof(err));
> +
> + internal_assert(_wait(pid, &status) != -1);
> + internal_assert(WEXITSTATUS(status) == IGT_EXIT_INVALID);
> + internal_assert(strstr(err, "Unknown subtest: Z"));
> +
> + close(outfd);
> + close(errfd);
> + }
> +
> + return 0;
> +}
> diff --git a/lib/tests/meson.build b/lib/tests/meson.build
> index b930ee6e..4d907e34 100644
> --- a/lib/tests/meson.build
> +++ b/lib/tests/meson.build
> @@ -3,6 +3,7 @@ lib_tests = [
> 'igt_can_fail',
> 'igt_can_fail_simple',
> 'igt_conflicting_args',
> + 'igt_describe',
> 'igt_edid',
> 'igt_exit_handler',
> 'igt_fork',