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

Arkadiusz Hiler arkadiusz.hiler at intel.com
Mon Jun 17 10:54:39 UTC 2019


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.

Cc: Petri Latvala <petri.latvala at intel.com>
Signed-off-by: Arkadiusz Hiler <arkadiusz.hiler at intel.com>
---
 lib/igt_core.c           | 179 ++++++++++++++++++++++++--
 lib/igt_core.h           | 137 ++++++++++++++++++--
 lib/tests/igt_describe.c | 266 +++++++++++++++++++++++++++++++++++++++
 lib/tests/meson.build    |   1 +
 4 files changed, 562 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..8b1c7b2f 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[4096];
+
+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';
+}
+
+#define __INDENT "  "
+static void __igt_print_description(const char *subtest_name, const char *file, const int line)
+{
+	struct description_node *desc;
+	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);
+}
+#undef __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:
+ *
+ * |[<!-- language="C" -->
+ * #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:
+ *
+ * |[<!-- language="C" -->
+ * #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..a9f857bb
--- /dev/null
+++ b/lib/tests/igt_describe.c
@@ -0,0 +1,266 @@
+/*
+ * 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))) {
+		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)) {}
+
+		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(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',
-- 
2.21.0



More information about the igt-dev mailing list