Igt-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH i-g-t 0/3] Add support for hook script
@ 2024-05-09 15:24 Gustavo Sousa
  2024-05-09 15:24 ` [PATCH i-g-t 1/3] igt_hook: Add feature Gustavo Sousa
                   ` (7 more replies)
  0 siblings, 8 replies; 22+ messages in thread
From: Gustavo Sousa @ 2024-05-09 15:24 UTC (permalink / raw)
  To: igt-dev

For development purposes, sometimes it is useful to have a way of
running custom scripts at certain points of test executions. A
real-world example I bumped into recently is to collect information from
sysfs before and after running each entry of a testlist.

While it is possible for the user to handcraft a script that calls each
test with the correct actions before and after execution, we can provide
a better experience by adding built-in support for running hooks during
test execution.

This series adds support for running a hook script during test
execution. The feature is exposed to users via option --hook, which made
available for regular test binaries as well as for igt_runner.

Gustavo Sousa (3):
  igt_hook: Add feature
  runner: Make it easier to extend argv
  runner: Add option --hook

 .../igt-gpu-tools/igt-gpu-tools-docs.xml      |   1 +
 lib/igt_core.c                                | 116 +++-
 lib/igt_hook.c                                | 499 ++++++++++++++++++
 lib/igt_hook.h                                |  86 +++
 lib/meson.build                               |   1 +
 lib/tests/igt_hook.c                          | 187 +++++++
 lib/tests/igt_hook_integration.c              | 297 +++++++++++
 lib/tests/meson.build                         |   2 +
 runner/executor.c                             |  52 +-
 runner/runner_tests.c                         |   5 +
 runner/settings.c                             |  25 +-
 runner/settings.h                             |   1 +
 12 files changed, 1248 insertions(+), 24 deletions(-)
 create mode 100644 lib/igt_hook.c
 create mode 100644 lib/igt_hook.h
 create mode 100644 lib/tests/igt_hook.c
 create mode 100644 lib/tests/igt_hook_integration.c

-- 
2.45.0


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
@ 2024-05-09 15:24 ` Gustavo Sousa
  2024-05-13 17:10   ` Kamil Konieczny
                     ` (2 more replies)
  2024-05-09 15:24 ` [PATCH i-g-t 2/3] runner: Make it easier to extend argv Gustavo Sousa
                   ` (6 subsequent siblings)
  7 siblings, 3 replies; 22+ messages in thread
From: Gustavo Sousa @ 2024-05-09 15:24 UTC (permalink / raw)
  To: igt-dev

For development purposes, sometimes it is useful to have a way of
running custom scripts at certain points of test executions. A
real-world example I bumped into recently is to collect information from
sysfs before and after running each entry of a testlist.

While it is possible for the user to handcraft a script that calls each
test with the correct actions before and after execution, we can provide
a better experience by adding built-in support for running hooks during
test execution.

That would be even better when adding the same kind of support for
igt_runner (which is done in an upcoming change), since the user can
also nicely resume with igt_resume with the hook already setup in case a
crash happens during execution of the test list.

As such provide implement support for hooks, integrate it into
igt_core and expose the functionality via --hook CLI option on test
executables.

Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
---
 .../igt-gpu-tools/igt-gpu-tools-docs.xml      |   1 +
 lib/igt_core.c                                | 116 +++-
 lib/igt_hook.c                                | 499 ++++++++++++++++++
 lib/igt_hook.h                                |  86 +++
 lib/meson.build                               |   1 +
 lib/tests/igt_hook.c                          | 187 +++++++
 lib/tests/igt_hook_integration.c              | 297 +++++++++++
 lib/tests/meson.build                         |   2 +
 8 files changed, 1180 insertions(+), 9 deletions(-)
 create mode 100644 lib/igt_hook.c
 create mode 100644 lib/igt_hook.h
 create mode 100644 lib/tests/igt_hook.c
 create mode 100644 lib/tests/igt_hook_integration.c

diff --git a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
index 9085eb924e85..11458c68124b 100644
--- a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
+++ b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
@@ -32,6 +32,7 @@
     <xi:include href="xml/igt_fb.xml"/>
     <xi:include href="xml/igt_frame.xml"/>
     <xi:include href="xml/igt_gt.xml"/>
+    <xi:include href="xml/igt_hook.xml"/>
     <xi:include href="xml/igt_io.xml"/>
     <xi:include href="xml/igt_kmod.xml"/>
     <xi:include href="xml/igt_kms.xml"/>
diff --git a/lib/igt_core.c b/lib/igt_core.c
index 3ff3e0392316..291d891cf884 100644
--- a/lib/igt_core.c
+++ b/lib/igt_core.c
@@ -70,6 +70,7 @@
 
 #include "igt_core.h"
 #include "igt_aux.h"
+#include "igt_hook.h"
 #include "igt_sysfs.h"
 #include "igt_sysrq.h"
 #include "igt_rc.h"
@@ -241,6 +242,9 @@
  * - '*,!basic*' match any subtest not starting basic
  * - 'basic*,!basic-render*' match any subtest starting basic but not starting basic-render
  *
+ * It is possible to run a shell script at certain points of test execution with
+ * "--hook". See the usage description with "--help-hook" for details.
+ *
  * # Configuration
  *
  * Some of IGT's behavior can be configured through a configuration file.
@@ -273,6 +277,8 @@ static unsigned int exit_handler_count;
 const char *igt_interactive_debug;
 bool igt_skip_crc_compare;
 
+static struct igt_hook *igt_hook = NULL;
+
 /* subtests helpers */
 static bool show_testlist = false;
 static bool list_subtests = false;
@@ -338,6 +344,8 @@ enum {
 	OPT_INTERACTIVE_DEBUG,
 	OPT_SKIP_CRC,
 	OPT_TRACE_OOPS,
+	OPT_HOOK,
+	OPT_HELP_HOOK,
 	OPT_DEVICE,
 	OPT_VERSION,
 	OPT_HELP = 'h'
@@ -810,6 +818,8 @@ static void common_exit_handler(int sig)
 		bind_fbcon(true);
 	}
 
+	igt_hook_free(igt_hook);
+
 	/* When not killed by a signal check that igt_exit() has been properly
 	 * called. */
 	assert(sig != 0 || igt_exit_called || igt_is_aborting);
@@ -907,6 +917,8 @@ static void print_usage(const char *help_str, bool output_on_stderr)
 		   "  --interactive-debug[=domain]\n"
 		   "  --skip-crc-compare\n"
 		   "  --trace-on-oops\n"
+		   "  --hook [<events>:]<cmd>\n"
+		   "  --help-hook\n"
 		   "  --help-description\n"
 		   "  --describe\n"
 		   "  --device filters\n"
@@ -1090,6 +1102,8 @@ static int common_init(int *argc, char **argv,
 		{"interactive-debug", optional_argument, NULL, OPT_INTERACTIVE_DEBUG},
 		{"skip-crc-compare",  no_argument,       NULL, OPT_SKIP_CRC},
 		{"trace-on-oops",     no_argument,       NULL, OPT_TRACE_OOPS},
+		{"hook",              required_argument, NULL, OPT_HOOK},
+		{"help-hook",         no_argument,       NULL, OPT_HELP_HOOK},
 		{"device",            required_argument, NULL, OPT_DEVICE},
 		{"version",           no_argument,       NULL, OPT_VERSION},
 		{"help",              no_argument,       NULL, OPT_HELP},
@@ -1225,6 +1239,24 @@ static int common_init(int *argc, char **argv,
 		case OPT_TRACE_OOPS:
 			show_ftrace = true;
 			break;
+		case OPT_HOOK:
+			assert(optarg);
+			if (igt_hook) {
+				igt_warn("Overriding previous hook descriptor\n");
+				igt_hook_free(igt_hook);
+			}
+			igt_hook = igt_hook_init(optarg, &ret);
+			if (!igt_hook) {
+				igt_critical("Failed to initialize hook data: %s\n",
+					     igt_hook_error_str(ret));
+				ret = ret > 0 ? -2 : -3;
+				goto out;
+			}
+			break;
+		case OPT_HELP_HOOK:
+			igt_hook_print_help(stdout, "--hook");
+			ret = -1;
+			goto out;
 		case OPT_DEVICE:
 			assert(optarg);
 			/* if set by env IGT_DEVICE we need to free it */
@@ -1274,9 +1306,24 @@ out:
 			exit(IGT_EXIT_INVALID);
 	}
 
-	if (ret < 0)
-		/* exit with no error for -h/--help */
-		exit(ret == -1 ? 0 : IGT_EXIT_INVALID);
+	if (ret < 0) {
+		free(igt_hook);
+		igt_hook = NULL;
+
+		switch (ret) {
+		case -1: /* exit with no error for -h/--help */
+			exit(0);
+			break;
+		case -2:
+			exit(IGT_EXIT_INVALID);
+			break;
+		case -3:
+			exit(IGT_EXIT_ABORT);
+			break;
+		default:
+			assert(0);
+		}
+	}
 
 	if (!igt_only_list_subtests()) {
 		bind_fbcon(false);
@@ -1284,6 +1331,15 @@ out:
 		print_version();
 		igt_srandom();
 
+		if (igt_hook) {
+			struct igt_hook_evt hook_evt = {
+				.evt_type = IGT_HOOK_PRE_TEST,
+				.target_name = command_str,
+			};
+
+			igt_hook_push_evt(igt_hook, &hook_evt);
+		}
+
 		sync();
 		oom_adjust_for_doom();
 		ftrace_dump_on_oops(show_ftrace);
@@ -1487,6 +1543,16 @@ bool __igt_run_subtest(const char *subtest_name, const char *file, const int lin
 	igt_thread_clear_fail_state();
 
 	igt_gettime(&subtest_time);
+
+	if (igt_hook) {
+		struct igt_hook_evt hook_evt = {
+			.evt_type = IGT_HOOK_PRE_SUBTEST,
+			.target_name = subtest_name,
+		};
+
+		igt_hook_push_evt(igt_hook, &hook_evt);
+	}
+
 	return (in_subtest = subtest_name);
 }
 
@@ -1517,6 +1583,16 @@ bool __igt_run_dynamic_subtest(const char *dynamic_subtest_name)
 	_igt_dynamic_tests_executed++;
 
 	igt_gettime(&dynamic_subtest_time);
+
+	if (igt_hook) {
+		struct igt_hook_evt hook_evt = {
+			.evt_type = IGT_HOOK_PRE_DYN_SUBTEST,
+			.target_name = dynamic_subtest_name,
+		};
+
+		igt_hook_push_evt(igt_hook, &hook_evt);
+	}
+
 	return (in_dynamic_subtest = dynamic_subtest_name);
 }
 
@@ -1602,6 +1678,17 @@ __noreturn static void exit_subtest(const char *result)
 	struct timespec *thentime = in_dynamic_subtest ? &dynamic_subtest_time : &subtest_time;
 	jmp_buf *jmptarget = in_dynamic_subtest ? &igt_dynamic_jmpbuf : &igt_subtest_jmpbuf;
 
+	if (igt_hook) {
+		struct igt_hook_evt hook_evt = {
+			.evt_type = (in_dynamic_subtest
+					? IGT_HOOK_POST_DYN_SUBTEST
+					: IGT_HOOK_POST_SUBTEST),
+			.result = result,
+		};
+
+		igt_hook_push_evt(igt_hook, &hook_evt);
+	}
+
 	if (!igt_thread_is_main()) {
 		igt_thread_fail();
 		pthread_exit(NULL);
@@ -2274,6 +2361,7 @@ void __igt_abort(const char *domain, const char *file, const int line,
 void igt_exit(void)
 {
 	int tmp;
+	const char *result;
 
 	if (!test_with_subtests)
 		igt_thread_assert_no_failures();
@@ -2318,12 +2406,7 @@ void igt_exit(void)
 
 	assert(waitpid(-1, &tmp, WNOHANG) == -1 && errno == ECHILD);
 
-	if (!test_with_subtests) {
-		struct timespec now;
-		const char *result;
-
-		igt_gettime(&now);
-
+	if (!test_with_subtests || igt_hook) {
 		switch (igt_exitcode) {
 			case IGT_EXIT_SUCCESS:
 				result = "SUCCESS";
@@ -2334,6 +2417,12 @@ void igt_exit(void)
 			default:
 				result = "FAIL";
 		}
+	}
+
+	if (!test_with_subtests) {
+		struct timespec now;
+
+		igt_gettime(&now);
 
 		if (test_multi_fork_child) /* parent will do the yelling */
 			_log_line_fprintf(stdout, "dyn_child pid:%d (%.3fs) ends with err=%d\n",
@@ -2344,6 +2433,15 @@ void igt_exit(void)
 					  result, igt_time_elapsed(&subtest_time, &now));
 	}
 
+	if (igt_hook) {
+		struct igt_hook_evt hook_evt = {
+			.evt_type = IGT_HOOK_POST_TEST,
+			.result = result,
+		};
+
+		igt_hook_push_evt(igt_hook, &hook_evt);
+	}
+
 	exit(igt_exitcode);
 }
 
diff --git a/lib/igt_hook.c b/lib/igt_hook.c
new file mode 100644
index 000000000000..8a39e19e3e5f
--- /dev/null
+++ b/lib/igt_hook.c
@@ -0,0 +1,499 @@
+/*
+ * Copyright © 2024 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 <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "igt_hook.h"
+
+/**
+ * SECTION:igt_hook
+ * @short_description: Support for running a hook script on test execution
+ * @title: Hook support
+ *
+ * IGT provides support for running a hook script when executing tests. This
+ * support is provided to users via CLI option `--hook` available in test
+ * binaries. Users should use `--help-hook` for detailed usaged description of
+ * the feature.
+ *
+ * The sole user of the exposed API is `igt_core`, which calls @igt_hook_init()
+ * when initializing a test case, then calls @igt_hook_push_evt() for each event
+ * that occurs during that test's execution and finally calls @igt_hook_free()
+ * to clean up at the end.
+ */
+
+#define TEST_NAME_INITIAL_SIZE 16
+
+typedef uint16_t evt_mask_t;
+
+struct igt_hook {
+	evt_mask_t evt_mask;
+	char *cmd;
+	char *test_name;
+	size_t test_name_size;
+	char *subtest_name;
+	size_t subtest_name_size;
+	char *dyn_subtest_name;
+	size_t dyn_subtest_name_size;
+	char *test_fullname;
+};
+
+enum igt_hook_error {
+	IGT_HOOK_EVT_EMPTY_NAME = 1,
+	IGT_HOOK_EVT_NO_MATCH,
+};
+
+static_assert(IGT_HOOK_NUM_EVENTS <= sizeof(evt_mask_t) * CHAR_BIT,
+	      "Number of event types does not fit event type mask");
+
+static const char *igt_hook_evt_type_to_name(enum igt_hook_evt_type evt_type)
+{
+	switch (evt_type) {
+	case IGT_HOOK_PRE_TEST:
+		return "pre-test";
+	case IGT_HOOK_PRE_SUBTEST:
+		return "pre-subtest";
+	case IGT_HOOK_PRE_DYN_SUBTEST:
+		return "pre-dyn-subtest";
+	case IGT_HOOK_POST_DYN_SUBTEST:
+		return "post-dyn-subtest";
+	case IGT_HOOK_POST_SUBTEST:
+		return "post-subtest";
+	case IGT_HOOK_POST_TEST:
+		return "post-test";
+	case IGT_HOOK_NUM_EVENTS:
+		break;
+	/* No "default:" case, to force a warning from -Wswitch in case we miss
+	 * any new event type. */
+	}
+	return "?";
+}
+
+static int igt_hook_parse_hook_str(const char *hook_str, evt_mask_t *evt_mask, const char **cmd)
+{
+	const char *s;
+
+	if (!strchr(hook_str, ':')) {
+		*evt_mask = ~0;
+		*cmd = hook_str;
+		return 0;
+	}
+
+	s = hook_str;
+	*evt_mask = 0;
+
+	while (1) {
+		const char *evt_name;
+		bool has_match;
+		bool is_star;
+		enum igt_hook_evt_type evt_type;
+
+		evt_name = s;
+
+		while (*s != ':' && *s != ',')
+			s++;
+
+		if (evt_name == s)
+			return IGT_HOOK_EVT_EMPTY_NAME;
+
+		has_match = false;
+		is_star = *evt_name == '*' && evt_name + 1 == s;
+
+		for (evt_type = IGT_HOOK_PRE_TEST; evt_type < IGT_HOOK_NUM_EVENTS; evt_type++) {
+			if (!is_star) {
+				const char *this_event_name = igt_hook_evt_type_to_name(evt_type);
+				size_t len = s - evt_name;
+
+				if (len != strlen(this_event_name))
+					continue;
+
+				if (strncmp(evt_name, this_event_name, len))
+					continue;
+			}
+
+			*evt_mask |= 1 << evt_type;
+			has_match = true;
+
+			if (!is_star)
+				break;
+		}
+
+		if (!has_match)
+			return IGT_HOOK_EVT_NO_MATCH;
+
+		if (*s++ == ':')
+			break;
+	}
+
+	*cmd = s;
+
+	return 0;
+}
+
+static size_t igt_hook_calc_test_fullname_size(struct igt_hook *igt_hook) {
+	/* The maximum size of test_fullname will be the maximum length of
+	 * "igt@<test_name>@<subtest_name>@<dyn_subtest_name>" plus 1 for the
+	 * null byte. */
+	return (igt_hook->test_name_size +
+		igt_hook->subtest_name_size +
+		igt_hook->dyn_subtest_name_size) + 4;
+}
+
+static void igt_hook_update_test_fullname(struct igt_hook *igt_hook)
+{
+	int i;
+	char *s;
+	const char *values[3] = {
+		igt_hook->test_name,
+		igt_hook->subtest_name,
+		igt_hook->dyn_subtest_name,
+	};
+
+	if (igt_hook->test_name[0] == '\0') {
+		igt_hook->test_fullname[0] = '\0';
+		return;
+	}
+
+	s = stpcpy(igt_hook->test_fullname, "igt");
+	for (i = 0; i < 3 && values[i][0] != '\0'; i++) {
+		*s++ = '@';
+		s = stpcpy(s, values[i]);
+	}
+}
+
+/**
+ * igt_hook_init:
+ * @hook_str: Hook descriptor string.
+ * @error: Pointer to error number.
+ *
+ * Allocate and initialize an #igt_hook structure.
+ *
+ * This function parses the hook descriptor @hook_str and initializes the struct
+ * to be returned.
+ *
+ * The hook descriptor comes from the argument to `--hook` of the test
+ * executable being run.
+ *
+ * If not #NULL, @error is used to store a non-zero error number if an error
+ * happens. A human-readable string for that error number can be obtained with
+ * @igt_hook_error_str().
+ *
+ * Returns: The pointer to the #igt_hook structure on success or #NULL on error.
+ */
+struct igt_hook *igt_hook_init(const char *hook_str, int *error)
+{
+	int err;
+	evt_mask_t evt_mask;
+	const char *cmd;
+	struct igt_hook *igt_hook = NULL;
+
+
+	err = igt_hook_parse_hook_str(hook_str, &evt_mask, &cmd);
+	if (err)
+		goto out;
+
+	igt_hook = calloc(1, sizeof(*igt_hook));
+	igt_hook->evt_mask = evt_mask;
+
+	igt_hook->cmd = strdup(cmd);
+	if (!igt_hook->cmd) {
+		err = -errno;
+		goto out;
+	}
+
+	igt_hook->test_name = malloc(TEST_NAME_INITIAL_SIZE);
+	igt_hook->test_name_size = TEST_NAME_INITIAL_SIZE;
+	igt_hook->subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
+	igt_hook->subtest_name_size = TEST_NAME_INITIAL_SIZE;
+	igt_hook->dyn_subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
+	igt_hook->dyn_subtest_name_size = TEST_NAME_INITIAL_SIZE;
+	igt_hook->test_fullname = malloc(igt_hook_calc_test_fullname_size(igt_hook));
+
+	igt_hook->test_name[0] = '\0';
+	igt_hook->subtest_name[0] = '\0';
+	igt_hook->dyn_subtest_name[0] = '\0';
+	igt_hook->test_fullname[0] = '\0';
+
+out:
+	if (err) {
+		if (error)
+			*error = err;
+
+		igt_hook_free(igt_hook);
+
+		return NULL;
+	}
+
+	return igt_hook;
+}
+
+/**
+ * igt_hook_free:
+ * @igt_hook: The igt_hook struct.
+ *
+ * De-initialize an igt_hook struct returned by @igt_hook_init().
+ *
+ * This is a no-op if @igt_hook is #NULL.
+ */
+void igt_hook_free(struct igt_hook *igt_hook)
+{
+	if (!igt_hook)
+		return;
+
+	free(igt_hook->cmd);
+	free(igt_hook->test_name);
+	free(igt_hook->subtest_name);
+	free(igt_hook->dyn_subtest_name);
+	free(igt_hook);
+}
+
+static void igt_hook_update_test_name_pre_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
+{
+	char **name_ptr;
+	size_t *size_ptr;
+	size_t len;
+
+	switch (evt->evt_type) {
+	case IGT_HOOK_PRE_TEST:
+		name_ptr = &igt_hook->test_name;
+		size_ptr = &igt_hook->test_name_size;
+		break;
+	case IGT_HOOK_PRE_SUBTEST:
+		name_ptr = &igt_hook->subtest_name;
+		size_ptr = &igt_hook->subtest_name_size;
+		break;
+	case IGT_HOOK_PRE_DYN_SUBTEST:
+		name_ptr = &igt_hook->dyn_subtest_name;
+		size_ptr = &igt_hook->dyn_subtest_name_size;
+		break;
+	default:
+		return;
+	}
+
+	len = strlen(evt->target_name);
+	if (len + 1 > *size_ptr) {
+		size_t fullname_size;
+
+		*size_ptr *= 2;
+		*name_ptr = realloc(*name_ptr, *size_ptr);
+
+		fullname_size = igt_hook_calc_test_fullname_size(igt_hook);
+		igt_hook->test_fullname = realloc(igt_hook->test_fullname, fullname_size);
+	}
+
+	strcpy(*name_ptr, evt->target_name);
+	igt_hook_update_test_fullname(igt_hook);
+}
+
+static void igt_hook_update_test_name_post_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
+{
+	switch (evt->evt_type) {
+	case IGT_HOOK_POST_TEST:
+		igt_hook->test_name[0] = '\0';
+		break;
+	case IGT_HOOK_POST_SUBTEST:
+		igt_hook->subtest_name[0] = '\0';
+		break;
+	case IGT_HOOK_POST_DYN_SUBTEST:
+		igt_hook->dyn_subtest_name[0] = '\0';
+		break;
+	default:
+		return;
+	}
+
+	igt_hook_update_test_fullname(igt_hook);
+}
+
+static void igt_hook_update_env_vars(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
+{
+	setenv("IGT_HOOK_EVENT", igt_hook_evt_type_to_name(evt->evt_type), 1);
+	setenv("IGT_HOOK_TEST_FULLNAME", igt_hook->test_fullname, 1);
+	setenv("IGT_HOOK_TEST", igt_hook->test_name, 1);
+	setenv("IGT_HOOK_SUBTEST", igt_hook->subtest_name, 1);
+	setenv("IGT_HOOK_DYN_SUBTEST", igt_hook->dyn_subtest_name, 1);
+	setenv("IGT_HOOK_RESULT", evt->result ?: "", 1);
+}
+
+/**
+ * igt_hook_push_evt:
+ * @igt_hook: The igt_hook structure.
+ * @evt: The event to be pushed.
+ *
+ * Push a new igt_hook event.
+ *
+ * This function must be used to register a new igt_hook event. Calling it will
+ * cause execution of the hook script if the event type matches the filters
+ * provided during initialization of @igt_hook.
+ */
+void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
+{
+	typeof(igt_hook->evt_mask) evt_bit = (1 << evt->evt_type);
+
+	igt_hook_update_test_name_pre_call(igt_hook, evt);
+
+	if ((evt_bit & igt_hook->evt_mask)) {
+		igt_hook_update_env_vars(igt_hook, evt);
+		system(igt_hook->cmd);
+	}
+
+	igt_hook_update_test_name_post_call(igt_hook, evt);
+}
+
+/**
+ * igt_hook_error_str:
+ * @error: Non-zero error number.
+ *
+ * Return a human-readable string containing a description of an error number
+ * generated by one of the `igt_hook_*` functions.
+ *
+ * The string will be the result of strerror() for errors from the C standard
+ * library or a custom description specific to igt_hook.
+ */
+const char *igt_hook_error_str(int error)
+{
+	if (!error)
+		return "No error";
+
+	if (error > 0) {
+		enum igt_hook_error hook_error = error;
+
+		switch (hook_error) {
+		case IGT_HOOK_EVT_EMPTY_NAME:
+			return "Empty name in event descriptor";
+		case IGT_HOOK_EVT_NO_MATCH:
+			return "Event name in event descriptor does not match any event type";
+		default:
+			return "Unknown error";
+		}
+	} else {
+		return strerror(-error);
+	}
+}
+
+/**
+ * igt_hook_print_help:
+ * @f: File pointer where to write the output.
+ * @option_name: Name of the CLI option that accepts the hook descriptor.
+ *
+ * Print a detailed user help text on hook usage.
+ */
+void igt_hook_print_help(FILE *f, const char *option_name)
+{
+	fprintf(f, "\
+The option %1$s receives as argument a \"hook descriptor\" and allows the\n\
+execution of a shell command at different points during execution of tests. Each\n\
+such a point is called a \"hook event\".\n\
+\n\
+Examples:\n\
+\n\
+  # Prints hook-specic env vars for every event.\n\
+  %1$s 'printenv | grep ^IGT_HOOK_'\n\
+\n\
+  # Equivalent to the above. Useful if command contains ':'.\n\
+  %1$s '*:printenv | grep ^IGT_HOOK_'\n\
+\n\
+  # Adds a line to out.txt containing the result of each test case.\n\
+  %1$s 'post-test:echo $IGT_HOOK_TEST_FULLNAME $IGT_HOOK_RESULT >> out.txt'\n\
+\n\
+The accepted format for a hook descriptor is `[<events>:]<cmd>`, where:\n\
+\n\
+  - <events> is a comma-separated list of event descriptors, which defines the\n\
+    set of events be tracked. If omitted, all events are tracked.\n\
+\n\
+  - <cmd> is a shell command to be executed on the occurrence each tracked\n\
+    event. If the command contains ':', then passing <events> is required,\n\
+    otherwise part of the command would be treated as an event descriptor.\n\
+\n\
+", option_name);
+
+	fprintf(f, "\
+An \"event descriptor\" is either the name of an event or the string '*'. The\n\
+latter matches all event names. The list of possible event names is provided\n\
+below:\n\
+\n\
+");
+
+	for (enum igt_hook_evt_type et = 0; et < IGT_HOOK_NUM_EVENTS; et++) {
+		const char *desc;
+
+		switch (et) {
+		case IGT_HOOK_PRE_TEST:
+			desc = "Occurs before a test case starts.";
+			break;
+		case IGT_HOOK_PRE_SUBTEST:
+			desc = "Occurs before the execution of a subtest.";
+			break;
+		case IGT_HOOK_PRE_DYN_SUBTEST:
+			desc = "Occurs before the execution of a dynamic subtest.";
+			break;
+		case IGT_HOOK_POST_DYN_SUBTEST:
+			desc = "Occurs after the execution of a dynamic subtest.";
+			break;
+		case IGT_HOOK_POST_SUBTEST:
+			desc = "Occurs after the execution of a subtest.";
+			break;
+		case IGT_HOOK_POST_TEST:
+			desc = "Occurs after a test case has finished.";
+			break;
+		default:
+			desc = "MISSING DESCRIPTION";
+		}
+
+		fprintf(f, "  %s\n  %s\n\n", igt_hook_evt_type_to_name(et), desc);
+	}
+
+	fprintf(f, "\
+For each event matched by <events>, <cmd> is executed as a shell command. The\n\
+exit status of the command is ignored. The following environment variables are\n\
+available to the command:\n\
+\n\
+  IGT_HOOK_EVENT\n\
+  Name of the current event.\n\
+\n\
+  IGT_HOOK_TEST_FULLNAME\n\
+  Full name of the test in the format `igt@<test>[@<subtest>[@<dyn_subtest>]]`.\n\
+\n\
+  IGT_HOOK_TEST		   \n\
+  Name of the current test.\n\
+\n\
+  IGT_HOOK_SUBTEST\n\
+  Name of the current subtest. Will be the empty string if not running a\n\
+  subtest.\n\
+\n\
+  IGT_HOOK_DYN_SUBTEST\n\
+  Name of the current dynamic subtest. Will be the empty string if not running a\n\
+  dynamic subtest.\n\
+\n\
+  IGT_HOOK_RESULT\n\
+  String representing the result of the test/subtest/dynamic subtest. Possible\n\
+  values are: SUCCESS, SKIP or FAIL. This is only applicable on \"post-*\"\n\
+  events and will be the empty string for other types of events.\n\
+\n\
+");
+}
diff --git a/lib/igt_hook.h b/lib/igt_hook.h
new file mode 100644
index 000000000000..6fef7254a317
--- /dev/null
+++ b/lib/igt_hook.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright © 2024 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.
+ */
+#ifndef IGT_HOOK_H
+#define IGT_HOOK_H
+
+#include <stdio.h>
+
+/**
+ * igt_hook:
+ *
+ * Opaque struct to hold data related to hook support.
+ */
+struct igt_hook;
+
+/**
+ * igt_hook_evt_type:
+ * @IGT_HOOK_PRE_TEST: Occurs before a test case (executable) starts the
+ * test code.
+ * @IGT_HOOK_PRE_SUBTEST: Occurs before the execution of a subtest.
+ * @IGT_HOOK_PRE_DYN_SUBTEST: Occurs before the execution of a dynamic subtest.
+ * @IGT_HOOK_POST_DYN_SUBTEST: Occurs after the execution of a dynamic subtest.
+ * @IGT_HOOK_POST_SUBTEST: Occurs after the execution of a subtest..
+ * @IGT_HOOK_POST_TEST: Occurs after a test case (executable) is finished with
+ * the test code.
+ * @IGT_HOOK_NUM_EVENTS: This is not really an event and represents the number
+ * of possible events tracked by igt_hook.
+ *
+ * Events tracked by igt_hook. Those events occur at specific points during the
+ * execution of a test.
+ */
+enum igt_hook_evt_type {
+	IGT_HOOK_PRE_TEST,
+	IGT_HOOK_PRE_SUBTEST,
+	IGT_HOOK_PRE_DYN_SUBTEST,
+	IGT_HOOK_POST_DYN_SUBTEST,
+	IGT_HOOK_POST_SUBTEST,
+	IGT_HOOK_POST_TEST,
+	IGT_HOOK_NUM_EVENTS /* This must always be the last one. */
+};
+
+/**
+ * igt_hook_evt:
+ * @evt_type: Type of event.
+ * @target_name: A string pointing to the name of the test, subtest or dynamic
+ * subtest, depending on @evt_type.
+ * @result: A string containing the result of the test, subtest or dynamic
+ * subtest. This is only applicable for the `IGT_HOOK_POST_\*' event types;
+ * other types must initialize this to #NULL.
+ *
+ * An event tracked by igt_hook, which is done with @igt_hook_push_evt(). This must
+ * be zero initialized and fields relevant to the event type must be set before
+ * passing its reference to @igt_hook_push_evt().
+ */
+struct igt_hook_evt {
+	enum igt_hook_evt_type evt_type;
+	const char *target_name;
+	const char *result;
+};
+
+struct igt_hook *igt_hook_init(const char *hook_str, int *error);
+void igt_hook_free(struct igt_hook *igt_hook);
+void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt);
+const char *igt_hook_error_str(int error);
+void igt_hook_print_help(FILE *f, const char *option_name);
+
+#endif /* IGT_HOOK_H */
diff --git a/lib/meson.build b/lib/meson.build
index e2f740c116f8..10b8066647f2 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -109,6 +109,7 @@ lib_sources = [
 	'veboxcopy_gen12.c',
 	'igt_msm.c',
 	'igt_dsc.c',
+	'igt_hook.c',
 	'xe/xe_gt.c',
 	'xe/xe_ioctl.c',
 	'xe/xe_mmio.c',
diff --git a/lib/tests/igt_hook.c b/lib/tests/igt_hook.c
new file mode 100644
index 000000000000..bd7e570f9607
--- /dev/null
+++ b/lib/tests/igt_hook.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright © 2024 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 <stdbool.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "igt_core.h"
+#include "igt_hook.h"
+
+static const char *env_var_names[] = {
+	"IGT_HOOK_EVENT",
+	"IGT_HOOK_TEST_FULLNAME",
+	"IGT_HOOK_TEST",
+	"IGT_HOOK_SUBTEST",
+	"IGT_HOOK_DYN_SUBTEST",
+	"IGT_HOOK_RESULT",
+};
+
+#define num_env_vars (sizeof(env_var_names) / sizeof(env_var_names[0]))
+
+static int env_var_name_lookup(char *line)
+{
+	int i;
+	char *c;
+
+	c = strchr(line, '=');
+	if (c)
+		*c = '\0';
+
+	for (i = 0; i < num_env_vars; i++)
+		if (!strcmp(line, env_var_names[i]))
+			goto out;
+
+	i = -1;
+out:
+	if (c)
+		*c = '=';
+
+	return i;
+}
+
+static void test_null_error_pointer(void)
+{
+	struct igt_hook *igt_hook;
+
+	/* Ensure passing NULL error pointer does not cause issues. */
+	igt_hook = igt_hook_init("invalid:echo hello", NULL);
+	igt_assert(igt_hook == NULL);
+}
+
+static void test_invalid_hook_descriptors(void)
+{
+	struct {
+		const char *name;
+		const char *hook_desc;
+	} invalid_cases[] = {
+		{"invalid-event-name", "invalid-event:echo hello"},
+		{"invalid-empty-event-name", ":echo hello"},
+		{"invalid-colon-in-cmd", "echo hello:world"},
+		{},
+	};
+
+	for (int i = 0; invalid_cases[i].name; i++) {
+		igt_subtest(invalid_cases[i].name) {
+			int err = 0;
+			struct igt_hook *igt_hook;
+
+			igt_hook = igt_hook_init(invalid_cases[i].hook_desc, &err);
+			igt_assert(igt_hook == NULL);
+			igt_assert(err != 0);
+		}
+	}
+}
+
+static void test_print_help(void)
+{
+	char *buf;
+	size_t len;
+	FILE *f;
+	const char expected_initial_text[] = "The option --hook receives as argument a \"hook descriptor\"";
+
+	f = open_memstream(&buf, &len);
+	igt_assert(f);
+
+	igt_hook_print_help(f, "--hook");
+	fclose(f);
+
+	igt_assert(!strncmp(buf, expected_initial_text, sizeof(expected_initial_text) - 1));
+
+	/* This is an extra check to catch a case where an event type is added
+	 * without a proper description. */
+	igt_assert(!strstr(buf, "MISSING DESCRIPTION"));
+
+	free(buf);
+}
+
+static void test_all_env_vars(void)
+{
+	struct igt_hook_evt evt = {
+		.evt_type = IGT_HOOK_PRE_SUBTEST,
+		.target_name = "foo",
+	};
+	bool env_vars_checklist[num_env_vars] = {};
+	struct igt_hook *igt_hook;
+	char *hook_str;
+	FILE *f;
+	int pipefd[2];
+	int ret;
+	int i;
+	char *line;
+	size_t line_size;
+
+	ret = pipe(pipefd);
+	igt_assert(ret == 0);
+
+	/* Use grep to filter only env var set by us. This should ensure that
+	 * writing to the pipe will not block due to capacity, since we only
+	 * read from the pipe after the shell command is done. */
+	ret = asprintf(&hook_str, "printenv -0 | grep -z ^IGT_HOOK >&%d", pipefd[1]);
+	igt_assert(ret > 0);
+
+	igt_hook = igt_hook_init(hook_str, NULL);
+	igt_assert(igt_hook);
+
+	igt_hook_push_evt(igt_hook, &evt);
+
+	close(pipefd[1]);
+	f = fdopen(pipefd[0], "r");
+	igt_assert(f);
+
+	line = NULL;
+	line_size = 0;
+
+	while (getdelim(&line, &line_size, '\0', f) != -1) {
+		ret = env_var_name_lookup(line);
+		igt_assert_f(ret >= 0, "Unexpected env var %s\n", line);
+		env_vars_checklist[ret] = true;
+	}
+
+	for (i = 0; i < num_env_vars; i++)
+		igt_assert_f(env_vars_checklist[i], "Missing env var %s\n", env_var_names[i]);
+
+	fclose(f);
+	igt_hook_free(igt_hook);
+	free(hook_str);
+	free(line);
+}
+
+igt_main
+{
+	igt_subtest("null-error-pointer")
+		test_null_error_pointer();
+
+	test_invalid_hook_descriptors();
+
+	igt_subtest("help-description")
+		test_print_help();
+
+	igt_subtest_group {
+		igt_fixture {
+			igt_require_f(system(NULL), "Shell seems not to be available\n");
+		}
+
+		igt_subtest("all-env-vars")
+			test_all_env_vars();
+	}
+}
diff --git a/lib/tests/igt_hook_integration.c b/lib/tests/igt_hook_integration.c
new file mode 100644
index 000000000000..56632b13ae81
--- /dev/null
+++ b/lib/tests/igt_hook_integration.c
@@ -0,0 +1,297 @@
+/*
+ * Copyright © 2024 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 <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "igt_core.h"
+
+#include "igt_tests_common.h"
+
+char prog[] = "igt_hook_integration";
+char hook_opt[] = "--hook";
+char hook_str[128];
+char *fake_argv[] = {prog, hook_opt, hook_str};
+int fake_argc = sizeof(fake_argv) / sizeof(fake_argv[0]);
+
+#define ENV_ARRAY(evt_name, fullname_suffix, subtest, dyn_subtest, result) \
+{ \
+	"IGT_HOOK_EVENT=" evt_name, \
+	"IGT_HOOK_TEST_FULLNAME=igt@igt_hook_integration" fullname_suffix, \
+	"IGT_HOOK_TEST=igt_hook_integration", \
+	"IGT_HOOK_SUBTEST=" subtest, \
+	"IGT_HOOK_DYN_SUBTEST=" dyn_subtest, \
+	"IGT_HOOK_RESULT=" result, \
+}
+
+#define TEST_ENV(evt_name, result) \
+	ENV_ARRAY(evt_name, "", "", "", result)
+
+#define SUBTEST_ENV(evt_name, subtest, result) \
+	ENV_ARRAY(evt_name, "@" subtest, subtest, "", result)
+
+#define DYN_SUBTEST_ENV(evt_name, subtest, dyn_subtest, result) \
+	ENV_ARRAY(evt_name, "@" subtest "@" dyn_subtest, subtest, dyn_subtest, result)
+
+const char *pre_test_env[] = TEST_ENV("pre-test", "");
+const char *pre_subtest_a_env[] = SUBTEST_ENV("pre-subtest", "a", "");
+const char *pre_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "success", "");
+const char *post_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "success", "SUCCESS");
+const char *pre_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "failed", "");
+const char *post_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "failed", "FAIL");
+const char *pre_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "skipped", "");
+const char *post_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "skipped", "SKIP");
+const char *post_subtest_a_env[] = SUBTEST_ENV("post-subtest", "a", "FAIL");
+const char *pre_subtest_b_env[] = SUBTEST_ENV("pre-subtest", "b", "");
+const char *post_subtest_b_env[] = SUBTEST_ENV("post-subtest", "b", "SUCCESS");
+const char *post_test_env[] = TEST_ENV("post-test", "FAIL");
+
+#define num_env_vars (sizeof(pre_test_env) / sizeof(pre_test_env[0]))
+
+__noreturn static void fake_main(void)
+{
+	igt_subtest_init(fake_argc, fake_argv);
+
+	igt_subtest_with_dynamic("a") {
+		igt_dynamic("success") {
+			igt_info("...@a@success\n");
+		}
+
+		igt_dynamic("failed") {
+			igt_assert_f(false, "Fail on purpose\n");
+			igt_info("...@a@failed\n");
+		}
+
+		igt_dynamic("skipped") {
+			igt_require_f(false, "Skip on purpose\n");
+			igt_info("...@a@skipped\n");
+		}
+	}
+
+	igt_subtest("b") {
+		igt_info("...@b\n");
+	}
+
+	igt_exit();
+}
+
+static void test_invalid_hook_str(void)
+{
+	int status;
+	pid_t pid;
+	static char err[4096];
+	int errfd;
+
+	sprintf(hook_str, "invalid-event:echo hello");
+
+	pid = do_fork_bg_with_pipes(fake_main, NULL, &errfd);
+
+	read_whole_pipe(errfd, err, sizeof(err));
+
+	internal_assert(safe_wait(pid, &status) != -1);
+	internal_assert_wexited(status, IGT_EXIT_INVALID);
+
+	internal_assert(strstr(err, "Failed to initialize hook data:"));
+
+	close(errfd);
+}
+
+static bool match_env(FILE *hook_out_stream, const char **expected_env)
+{
+	int i;
+	char hook_env_buf[4096];
+	size_t buf_len = 0;
+	char *line = NULL;
+	size_t line_size;
+	bool env_checklist[num_env_vars] = {};
+	bool has_unexpected = false;
+	bool has_missing = false;
+
+	/* Store env from hook so we can show it in case of errors */
+	while (getdelim(&line, &line_size, '\0', hook_out_stream) != -1) {
+		internal_assert(buf_len + strlen(line) + 1 <= sizeof(hook_env_buf));
+		strcpy(hook_env_buf + buf_len, line);
+		buf_len += strlen(line) + 1;
+
+		if (!strcmp(line, "---"))
+			break;
+	}
+
+	if (!expected_env && !buf_len) {
+		/* We have consumed everything and we are done now. */
+		return false;
+	}
+
+
+	if (!expected_env) {
+		printf("Detected unexpected hook execution\n");
+		has_unexpected = true;
+		goto out;
+	}
+
+	if (!buf_len) {
+		printf("Expected more hook execution, but none found\n");
+		has_missing = true;
+		goto out;
+	}
+
+
+	line = hook_env_buf;
+	while (strcmp(line, "---")) {
+		for (i = 0; i < num_env_vars; i++) {
+			if (!strcmp(line, expected_env[i])) {
+				env_checklist[i] = true;
+				break;
+			}
+		}
+
+		if (i == num_env_vars) {
+			printf("Unexpected envline from hook: %s\n", line);
+			has_unexpected = true;
+		}
+
+		line += strlen(line) + 1;
+	}
+
+	for (i = 0; i < num_env_vars; i++) {
+		if (!env_checklist[i]) {
+			has_missing = true;
+			printf("Missing expected envline: %s\n", expected_env[i]);
+		}
+	}
+
+out:
+	if (has_unexpected || has_missing) {
+		if (expected_env) {
+			printf("Expected environment:\n");
+			for (i = 0; i < num_env_vars; i++)
+				printf("  %s\n", expected_env[i]);
+		}
+
+		if (buf_len) {
+			printf("Environment from hook:\n");
+			line = hook_env_buf;
+			while (strcmp(line, "---")) {
+				printf("  %s\n", line);
+				line += strlen(line) + 1;
+			}
+		} else {
+			printf("No hook execution found\n");
+		}
+	}
+
+	internal_assert(!has_unexpected);
+	internal_assert(!has_missing);
+
+	/* Ready to consume next hook output. */
+	return true;
+}
+
+static void run_tests_and_match_env(const char *evt_descriptors, const char **expected_envs[])
+{
+	int i;
+	int ret;
+	int pipefd[2];
+	pid_t pid;
+	FILE *f;
+
+	ret = pipe(pipefd);
+	internal_assert(ret == 0);
+
+	/* Use grep to filter only env var set by us. This should ensure that
+	 * writing to the pipe will not block due to capacity, since we only
+	 * read from the pipe after the shell command is done. */
+	sprintf(hook_str,
+		"%1$s:printenv -0 | grep -z ^IGT_HOOK >&%2$d; echo -en ---\\\\x00 >&%2$d",
+		evt_descriptors,
+		pipefd[1]);
+
+	pid = do_fork_bg_with_pipes(fake_main, NULL, NULL);
+	internal_assert(safe_wait(pid, &ret) != -1);
+	internal_assert_wexited(ret, IGT_EXIT_FAILURE);
+
+	close(pipefd[1]);
+	f = fdopen(pipefd[0], "r");
+	internal_assert(f);
+
+	while (match_env(f, expected_envs[i]))
+		i++;
+
+	fclose(f);
+
+}
+
+int main(int argc, char **argv)
+{
+	{
+		printf("Check invalid hook string\n");
+		test_invalid_hook_str();
+	}
+
+	{
+		const char **expected_envs[] = {
+			pre_test_env,
+			pre_subtest_a_env,
+			pre_dyn_subtest_a_success_env,
+			post_dyn_subtest_a_success_env,
+			pre_dyn_subtest_a_failed_env,
+			post_dyn_subtest_a_failed_env,
+			pre_dyn_subtest_a_skipped_env,
+			post_dyn_subtest_a_skipped_env,
+			post_subtest_a_env,
+			pre_subtest_b_env,
+			post_subtest_b_env,
+			post_test_env,
+			NULL,
+		};
+
+		printf("Check full event tracking\n");
+		run_tests_and_match_env("*", expected_envs);
+	}
+
+	{
+		const char **expected_envs[] = {
+			pre_dyn_subtest_a_success_env,
+			pre_dyn_subtest_a_failed_env,
+			pre_dyn_subtest_a_skipped_env,
+			NULL,
+		};
+
+		printf("Check single event type tracking\n");
+		run_tests_and_match_env("pre-dyn-subtest", expected_envs);
+	}
+
+	{
+		const char **expected_envs[] = {
+			pre_subtest_a_env,
+			post_dyn_subtest_a_success_env,
+			post_dyn_subtest_a_failed_env,
+			post_dyn_subtest_a_skipped_env,
+			pre_subtest_b_env,
+			NULL,
+		};
+
+		printf("Check multiple event types tracking\n");
+		run_tests_and_match_env("post-dyn-subtest,pre-subtest", expected_envs);
+	}
+}
diff --git a/lib/tests/meson.build b/lib/tests/meson.build
index fa3d81de6cef..df8092638eca 100644
--- a/lib/tests/meson.build
+++ b/lib/tests/meson.build
@@ -10,6 +10,8 @@ lib_tests = [
 	'igt_exit_handler',
 	'igt_fork',
 	'igt_fork_helper',
+	'igt_hook',
+	'igt_hook_integration',
         'igt_ktap_parser',
 	'igt_list_only',
 	'igt_invalid_subtest_name',
-- 
2.45.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH i-g-t 2/3] runner: Make it easier to extend argv
  2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
  2024-05-09 15:24 ` [PATCH i-g-t 1/3] igt_hook: Add feature Gustavo Sousa
@ 2024-05-09 15:24 ` Gustavo Sousa
  2024-05-09 15:24 ` [PATCH i-g-t 3/3] runner: Add option --hook Gustavo Sousa
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 22+ messages in thread
From: Gustavo Sousa @ 2024-05-09 15:24 UTC (permalink / raw)
  To: igt-dev

In an upcoming change, we will be adding the option to forward the
--hook option to the test cases, which will require updating
execute_test_process() to add the option when asked by the user.

The current implementation makes that task not quite straightforward:
filling of argv is already dependent on stuff like entry->subtest_count
and dynbegin; if we want to keep on using constant indices, we would
need several conditional branches for adding arguments for --hook.

One way of simplifying this is to treat argv as a stack and have a
"head" index variable which is incremented after we are done setting the
current value. However, we would lose the benefit of static array bounds
analysis from the compiler, which we do have with the current code.

Let's instead define a static mapping in argv_refs of the possible
arguments that execute_test_process() could define and then generate
argv at the end with only those values that were set. With that, we keep
benefits of static array bounds analysis and make it easier to extend
argv, which is just a matter of adding more stuff into argv_refs and
setting them when necessary.

Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
---
 runner/executor.c | 44 ++++++++++++++++++++++++++++++--------------
 1 file changed, 30 insertions(+), 14 deletions(-)

diff --git a/runner/executor.c b/runner/executor.c
index 4b374d2235b2..e9b037ebcaf9 100644
--- a/runner/executor.c
+++ b/runner/executor.c
@@ -1506,7 +1506,18 @@ execute_test_process(int outfd, int errfd, int socketfd,
 		     struct settings *settings,
 		     struct job_list_entry *entry)
 {
-	char *argv[6] = {};
+	char *arg0;
+	char *arg_run_subtest[2] = {};
+	char *arg_dyn_subtest[2] = {};
+	char **argv_refs[] = {
+		&arg0,
+		&arg_run_subtest[0],
+		&arg_run_subtest[1],
+		&arg_dyn_subtest[0],
+		&arg_dyn_subtest[1],
+		NULL,
+	};
+	char *argv[sizeof(argv_refs) / sizeof(argv_refs[0])] = {};
 	size_t rootlen;
 
 	dup2(outfd, STDOUT_FILENO);
@@ -1515,30 +1526,30 @@ execute_test_process(int outfd, int errfd, int socketfd,
 	setpgid(0, 0);
 
 	rootlen = strlen(settings->test_root);
-	argv[0] = malloc(rootlen + strlen(entry->binary) + 2);
-	strcpy(argv[0], settings->test_root);
-	argv[0][rootlen] = '/';
-	strcpy(argv[0] + rootlen + 1, entry->binary);
+	arg0 = malloc(rootlen + strlen(entry->binary) + 2);
+	strcpy(arg0, settings->test_root);
+	arg0[rootlen] = '/';
+	strcpy(arg0 + rootlen + 1, entry->binary);
 
 	if (entry->subtest_count) {
 		size_t argsize;
 		const char *dynbegin;
 		size_t i;
 
-		argv[1] = strdup("--run-subtest");
+		arg_run_subtest[0] = strdup("--run-subtest");
 
 		if ((dynbegin = strchr(entry->subtests[0], '@')) != NULL)
 			argsize = dynbegin - entry->subtests[0];
 		else
 			argsize = strlen(entry->subtests[0]);
 
-		argv[2] = malloc(argsize + 1);
-		memcpy(argv[2], entry->subtests[0], argsize);
-		argv[2][argsize] = '\0';
+		arg_run_subtest[1] = malloc(argsize + 1);
+		memcpy(arg_run_subtest[1], entry->subtests[0], argsize);
+		arg_run_subtest[1][argsize] = '\0';
 
 		if (dynbegin) {
-			argv[3] = strdup("--dynamic-subtest");
-			argv[4] = strdup(dynbegin + 1);
+			arg_dyn_subtest[0] = strdup("--dynamic-subtest");
+			arg_dyn_subtest[1] = strdup(dynbegin + 1);
 		}
 
 		for (i = 1; i < entry->subtest_count; i++) {
@@ -1547,13 +1558,18 @@ execute_test_process(int outfd, int errfd, int socketfd,
 
 			assert(dynbegin == NULL);
 
-			argv[2] = realloc(argv[2], argsize + sublen + 2);
-			argv[2][argsize] = ',';
-			strcpy(argv[2] + argsize + 1, sub);
+			arg_run_subtest[1] = realloc(arg_run_subtest[1], argsize + sublen + 2);
+			arg_run_subtest[1][argsize] = ',';
+			strcpy(arg_run_subtest[1] + argsize + 1, sub);
 			argsize += sublen + 1;
 		}
 	}
 
+	/* Build argv with only stuff that is set. */
+	for (size_t i = 0, j = 0; argv_refs[i]; i++)
+		if (*argv_refs[i])
+			argv[j++] = *argv_refs[i];
+
 	if (socketfd >= 0) {
 		struct runnerpacket *packet;
 
-- 
2.45.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH i-g-t 3/3] runner: Add option --hook
  2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
  2024-05-09 15:24 ` [PATCH i-g-t 1/3] igt_hook: Add feature Gustavo Sousa
  2024-05-09 15:24 ` [PATCH i-g-t 2/3] runner: Make it easier to extend argv Gustavo Sousa
@ 2024-05-09 15:24 ` Gustavo Sousa
  2024-05-09 16:13 ` ✗ GitLab.Pipeline: warning for Add support for hook script Patchwork
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 22+ messages in thread
From: Gustavo Sousa @ 2024-05-09 15:24 UTC (permalink / raw)
  To: igt-dev

Now that we have support for setting a hook script for test cases, let's
also add the option --hook to igt_runner, which forwards it to test
executables.

Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
---
 runner/executor.c     |  8 ++++++++
 runner/runner_tests.c |  5 +++++
 runner/settings.c     | 25 ++++++++++++++++++++++++-
 runner/settings.h     |  1 +
 4 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/runner/executor.c b/runner/executor.c
index e9b037ebcaf9..92dee6e32f6f 100644
--- a/runner/executor.c
+++ b/runner/executor.c
@@ -1509,12 +1509,15 @@ execute_test_process(int outfd, int errfd, int socketfd,
 	char *arg0;
 	char *arg_run_subtest[2] = {};
 	char *arg_dyn_subtest[2] = {};
+	char *arg_hook[2] = {};
 	char **argv_refs[] = {
 		&arg0,
 		&arg_run_subtest[0],
 		&arg_run_subtest[1],
 		&arg_dyn_subtest[0],
 		&arg_dyn_subtest[1],
+		&arg_hook[0],
+		&arg_hook[1],
 		NULL,
 	};
 	char *argv[sizeof(argv_refs) / sizeof(argv_refs[0])] = {};
@@ -1565,6 +1568,11 @@ execute_test_process(int outfd, int errfd, int socketfd,
 		}
 	}
 
+	if (settings->hook_str) {
+		arg_hook[0] = strdup("--hook");
+		arg_hook[1] = strdup(settings->hook_str);
+	}
+
 	/* Build argv with only stuff that is set. */
 	for (size_t i = 0, j = 0; argv_refs[i]; i++)
 		if (*argv_refs[i])
diff --git a/runner/runner_tests.c b/runner/runner_tests.c
index 0aa7dd6626b7..7470cc24052f 100644
--- a/runner/runner_tests.c
+++ b/runner/runner_tests.c
@@ -202,6 +202,7 @@ static void assert_settings_equal(struct settings *one, struct settings *two)
 	igt_assert_eq(one->piglit_style_dmesg, two->piglit_style_dmesg);
 	igt_assert_eq(one->dmesg_warn_level, two->dmesg_warn_level);
 	igt_assert_eq(one->prune_mode, two->prune_mode);
+	igt_assert_eqstr(one->hook_str, two->hook_str);
 }
 
 static void assert_job_list_equal(struct job_list *one, struct job_list *two)
@@ -302,6 +303,7 @@ igt_main
 		igt_assert_eq(settings->overall_timeout, 0);
 		igt_assert(!settings->use_watchdog);
 		igt_assert_eq(settings->prune_mode, 0);
+		igt_assert(!settings->hook_str);
 		igt_assert(strstr(settings->test_root, "test-root-dir") != NULL);
 		igt_assert(strstr(settings->results_path, "path-to-results") != NULL);
 
@@ -464,6 +466,7 @@ igt_main
 				       "--collect-code-cov",
 				       "--coverage-per-test",
 				       "--collect-script", "/usr/bin/true",
+				       "--hook", "echo hello",
 				       "--prune-mode=keep-subtests",
 				       "test-root-dir",
 				       "path-to-results",
@@ -511,6 +514,7 @@ igt_main
 		igt_assert_eq(settings->per_test_timeout, 72);
 		igt_assert_eq(settings->overall_timeout, 360);
 		igt_assert(settings->use_watchdog);
+		igt_assert_eqstr(settings->hook_str, "echo hello");
 		igt_assert_eq(settings->prune_mode, PRUNE_KEEP_SUBTESTS);
 		igt_assert(strstr(settings->test_root, "test-root-dir") != NULL);
 		igt_assert(strstr(settings->results_path, "path-to-results") != NULL);
@@ -961,6 +965,7 @@ igt_main
 					       "--use-watchdog",
 					       "--piglit-style-dmesg",
 					       "--prune-mode=keep-all",
+					       "--hook", "echo hello",
 					       testdatadir,
 					       dirname,
 			};
diff --git a/runner/settings.c b/runner/settings.c
index 42d8137f18e9..e554a5c70776 100644
--- a/runner/settings.c
+++ b/runner/settings.c
@@ -1,3 +1,4 @@
+#include "igt_hook.h"
 #include "settings.h"
 #include "version.h"
 
@@ -28,6 +29,8 @@ enum {
 	OPT_CODE_COV_SCRIPT,
 	OPT_ENABLE_CODE_COVERAGE,
 	OPT_COV_RESULTS_PER_TEST,
+	OPT_HOOK,
+	OPT_HELP_HOOK,
 	OPT_VERSION,
 	OPT_PRUNE_MODE,
 	OPT_HELP = 'h',
@@ -297,6 +300,10 @@ static const char *usage_str =
 	"                        Requires --collect-script FILENAME\n"
 	"  --collect-script FILENAME\n"
 	"                        Use FILENAME as script to collect code coverage data.\n"
+	"  --hook HOOK_STR\n"
+	"                        Forward HOOK_STR to the --hook option of each test.\n"
+	"  --help-hook\n"
+	"                        Show detailed usage information for --hook.\n"
 	"\n"
 	"  [test_root]           Directory that contains the IGT tests. The environment\n"
 	"                        variable IGT_TEST_ROOT will be used if set, overriding\n"
@@ -654,6 +661,8 @@ bool parse_options(int argc, char **argv,
 		{"collect-code-cov", no_argument, NULL, OPT_ENABLE_CODE_COVERAGE},
 		{"coverage-per-test", no_argument, NULL, OPT_COV_RESULTS_PER_TEST},
 		{"collect-script", required_argument, NULL, OPT_CODE_COV_SCRIPT},
+		{"hook", required_argument, NULL, OPT_HOOK},
+		{"help-hook", no_argument, NULL, OPT_HELP_HOOK},
 		{"multiple-mode", no_argument, NULL, OPT_MULTIPLE},
 		{"inactivity-timeout", required_argument, NULL, OPT_TIMEOUT},
 		{"per-test-timeout", required_argument, NULL, OPT_PER_TEST_TIMEOUT},
@@ -741,7 +750,19 @@ bool parse_options(int argc, char **argv,
 		case OPT_CODE_COV_SCRIPT:
 			settings->code_coverage_script = bin_path(optarg);
 			break;
-
+		case OPT_HOOK:
+			/* FIXME: In order to allow line breaks, we should
+			 * change the format of settings serialization. Maybe
+			 * use JSON instead of our own format? */
+			if (strchr(optarg, '\n')) {
+				fprintf(stderr, "Newlines in --hook are currently unsupported.\n");
+				goto error;
+			}
+			settings->hook_str = optarg;
+			break;
+		case OPT_HELP_HOOK:
+			igt_hook_print_help(stdout, "--hook");
+			goto error;
 		case OPT_MULTIPLE:
 			settings->multiple_mode = true;
 			break;
@@ -1040,6 +1061,7 @@ bool serialize_settings(struct settings *settings)
 	SERIALIZE_LINE(f, settings, enable_code_coverage, "%d");
 	SERIALIZE_LINE(f, settings, cov_results_per_test, "%d");
 	SERIALIZE_LINE(f, settings, code_coverage_script, "%s");
+	SERIALIZE_LINE(f, settings, hook_str, "%s");
 
 	if (settings->sync) {
 		fflush(f);
@@ -1103,6 +1125,7 @@ bool read_settings_from_file(struct settings *settings, FILE *f)
 		PARSE_LINE(settings, name, val, enable_code_coverage, numval);
 		PARSE_LINE(settings, name, val, cov_results_per_test, numval);
 		PARSE_LINE(settings, name, val, code_coverage_script, val ? strdup(val) : NULL);
+		PARSE_LINE(settings, name, val, hook_str, val ? strdup(val) : NULL);
 
 		printf("Warning: Unknown field in settings file: %s = %s\n",
 		       name, val);
diff --git a/runner/settings.h b/runner/settings.h
index 819c346027ed..d3afb56de039 100644
--- a/runner/settings.h
+++ b/runner/settings.h
@@ -72,6 +72,7 @@ struct settings {
 	char *code_coverage_script;
 	bool enable_code_coverage;
 	bool cov_results_per_test;
+	char *hook_str;
 };
 
 /**
-- 
2.45.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* ✗ GitLab.Pipeline: warning for Add support for hook script
  2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
                   ` (2 preceding siblings ...)
  2024-05-09 15:24 ` [PATCH i-g-t 3/3] runner: Add option --hook Gustavo Sousa
@ 2024-05-09 16:13 ` Patchwork
  2024-05-09 17:04   ` Gustavo Sousa
  2024-05-09 16:24 ` ✓ CI.xeBAT: success " Patchwork
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 22+ messages in thread
From: Patchwork @ 2024-05-09 16:13 UTC (permalink / raw)
  To: Gustavo Sousa; +Cc: igt-dev

== Series Details ==

Series: Add support for hook script
URL   : https://patchwork.freedesktop.org/series/133391/
State : warning

== Summary ==

Pipeline status: FAILED.

see https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/pipelines/1173738 for the overview.

test:ninja-test-minimal has failed (https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606):
  26/30 lib i915_perf_data_alignment            OK       0.42 s 
  27/30 lib bad_subtest_type                    EXPECTEDFAIL 0.37 s 
  28/30 lib igt_no_subtest                      EXPECTEDFAIL 0.42 s 
  29/30 lib igt_simple_test_subtests            EXPECTEDFAIL 0.27 s 
  30/30 lib igt_timeout                         EXPECTEDFAIL 1.32 s 
  
  Ok:                   25
  Expected Fail:         4
  Fail:                  1
  Unexpected Pass:       0
  Skipped:               0
  Timeout:               0
  
  Full log written to /builds/gfx-ci/igt-ci-tags/build/meson-logs/testlog.txt
  section_end:1715270916:step_script
  section_start:1715270916:cleanup_file_variables
  Cleaning up project directory and file based variables
  section_end:1715270917:cleanup_file_variables
  ERROR: Job failed: exit code 1

== Logs ==

For more details see: https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/pipelines/1173738

^ permalink raw reply	[flat|nested] 22+ messages in thread

* ✓ CI.xeBAT: success for Add support for hook script
  2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
                   ` (3 preceding siblings ...)
  2024-05-09 16:13 ` ✗ GitLab.Pipeline: warning for Add support for hook script Patchwork
@ 2024-05-09 16:24 ` Patchwork
  2024-05-09 16:33 ` ✓ Fi.CI.BAT: " Patchwork
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 22+ messages in thread
From: Patchwork @ 2024-05-09 16:24 UTC (permalink / raw)
  To: Gustavo Sousa; +Cc: igt-dev

[-- Attachment #1: Type: text/plain, Size: 846 bytes --]

== Series Details ==

Series: Add support for hook script
URL   : https://patchwork.freedesktop.org/series/133391/
State : success

== Summary ==

CI Bug Log - changes from XEIGT_7846_BAT -> XEIGTPW_11120_BAT
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  

Participating hosts (5 -> 5)
------------------------------

  No changes in participating hosts


Changes
-------

  No changes found


Build changes
-------------

  * IGT: IGT_7846 -> IGTPW_11120

  IGTPW_11120: 11120
  IGT_7846: 4a5fd4e7cb2798636f6464e2bd61399f3242b322 @ https://gitlab.freedesktop.org/drm/igt-gpu-tools.git
  xe-1263-92f877dd46245e4a44b6d24b5e303b8c03c40c75: 92f877dd46245e4a44b6d24b5e303b8c03c40c75

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/index.html

[-- Attachment #2: Type: text/html, Size: 1391 bytes --]

^ permalink raw reply	[flat|nested] 22+ messages in thread

* ✓ Fi.CI.BAT: success for Add support for hook script
  2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
                   ` (4 preceding siblings ...)
  2024-05-09 16:24 ` ✓ CI.xeBAT: success " Patchwork
@ 2024-05-09 16:33 ` Patchwork
  2024-05-09 21:12 ` ✗ CI.xeFULL: failure " Patchwork
  2024-05-10  5:30 ` ✗ Fi.CI.IGT: " Patchwork
  7 siblings, 0 replies; 22+ messages in thread
From: Patchwork @ 2024-05-09 16:33 UTC (permalink / raw)
  To: Gustavo Sousa; +Cc: igt-dev

[-- Attachment #1: Type: text/plain, Size: 11092 bytes --]

== Series Details ==

Series: Add support for hook script
URL   : https://patchwork.freedesktop.org/series/133391/
State : success

== Summary ==

CI Bug Log - changes from IGT_7846 -> IGTPW_11120
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  External URL: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/index.html

Participating hosts (41 -> 41)
------------------------------

  Additional (3): bat-kbl-2 fi-kbl-8809g fi-elk-e7500 
  Missing    (3): fi-glk-j4005 fi-snb-2520m fi-bsw-n3050 

Known issues
------------

  Here are the changes found in IGTPW_11120 that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@debugfs_test@basic-hwmon:
    - bat-arls-3:         NOTRUN -> [SKIP][1] ([i915#9318])
   [1]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@debugfs_test@basic-hwmon.html

  * igt@fbdev@info:
    - bat-kbl-2:          NOTRUN -> [SKIP][2] ([i915#1849])
   [2]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-kbl-2/igt@fbdev@info.html

  * igt@gem_huc_copy@huc-copy:
    - fi-kbl-8809g:       NOTRUN -> [SKIP][3] ([i915#2190])
   [3]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/fi-kbl-8809g/igt@gem_huc_copy@huc-copy.html

  * igt@gem_lmem_swapping@basic:
    - fi-kbl-8809g:       NOTRUN -> [SKIP][4] ([i915#4613]) +3 other tests skip
   [4]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/fi-kbl-8809g/igt@gem_lmem_swapping@basic.html

  * igt@gem_lmem_swapping@parallel-random-engines:
    - bat-kbl-2:          NOTRUN -> [SKIP][5] +39 other tests skip
   [5]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-kbl-2/igt@gem_lmem_swapping@parallel-random-engines.html
    - bat-arls-3:         NOTRUN -> [SKIP][6] ([i915#10213]) +3 other tests skip
   [6]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@gem_lmem_swapping@parallel-random-engines.html

  * igt@gem_mmap@basic:
    - bat-arls-3:         NOTRUN -> [SKIP][7] ([i915#4083])
   [7]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@gem_mmap@basic.html

  * igt@gem_render_tiled_blits@basic:
    - bat-arls-3:         NOTRUN -> [SKIP][8] ([i915#10197] / [i915#10211] / [i915#4079])
   [8]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@gem_render_tiled_blits@basic.html

  * igt@gem_tiled_blits@basic:
    - bat-arls-3:         NOTRUN -> [SKIP][9] ([i915#10196] / [i915#4077]) +2 other tests skip
   [9]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@gem_tiled_blits@basic.html

  * igt@gem_tiled_pread_basic:
    - bat-arls-3:         NOTRUN -> [SKIP][10] ([i915#10206] / [i915#4079])
   [10]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@gem_tiled_pread_basic.html

  * igt@i915_pm_rps@basic-api:
    - bat-arls-3:         NOTRUN -> [SKIP][11] ([i915#10209])
   [11]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@i915_pm_rps@basic-api.html

  * igt@i915_selftest@live@execlists:
    - fi-bsw-nick:        [PASS][12] -> [ABORT][13] ([i915#10800])
   [12]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/fi-bsw-nick/igt@i915_selftest@live@execlists.html
   [13]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/fi-bsw-nick/igt@i915_selftest@live@execlists.html

  * igt@kms_addfb_basic@addfb25-x-tiled-legacy:
    - bat-arls-3:         NOTRUN -> [SKIP][14] ([i915#10200]) +9 other tests skip
   [14]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@kms_addfb_basic@addfb25-x-tiled-legacy.html

  * igt@kms_cursor_legacy@basic-busy-flip-before-cursor-atomic:
    - bat-arls-3:         NOTRUN -> [SKIP][15] ([i915#10202]) +1 other test skip
   [15]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@kms_cursor_legacy@basic-busy-flip-before-cursor-atomic.html

  * igt@kms_dsc@dsc-basic:
    - bat-arls-3:         NOTRUN -> [SKIP][16] ([i915#9886])
   [16]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@kms_dsc@dsc-basic.html

  * igt@kms_force_connector_basic@force-load-detect:
    - fi-kbl-8809g:       NOTRUN -> [SKIP][17] +30 other tests skip
   [17]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/fi-kbl-8809g/igt@kms_force_connector_basic@force-load-detect.html
    - bat-arls-3:         NOTRUN -> [SKIP][18] ([i915#10207])
   [18]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@kms_force_connector_basic@force-load-detect.html

  * igt@kms_pm_backlight@basic-brightness:
    - bat-arls-3:         NOTRUN -> [SKIP][19] ([i915#9812])
   [19]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@kms_pm_backlight@basic-brightness.html

  * igt@kms_pm_rpm@basic-pci-d3-state:
    - fi-elk-e7500:       NOTRUN -> [SKIP][20] +24 other tests skip
   [20]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/fi-elk-e7500/igt@kms_pm_rpm@basic-pci-d3-state.html

  * igt@kms_psr@psr-primary-mmap-gtt:
    - bat-arls-3:         NOTRUN -> [SKIP][21] ([i915#9732]) +3 other tests skip
   [21]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@kms_psr@psr-primary-mmap-gtt.html

  * igt@kms_setmode@basic-clone-single-crtc:
    - bat-arls-3:         NOTRUN -> [SKIP][22] ([i915#10208] / [i915#8809])
   [22]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@kms_setmode@basic-clone-single-crtc.html

  * igt@prime_vgem@basic-fence-mmap:
    - bat-arls-3:         NOTRUN -> [SKIP][23] ([i915#10196] / [i915#3708] / [i915#4077]) +1 other test skip
   [23]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@prime_vgem@basic-fence-mmap.html

  * igt@prime_vgem@basic-fence-read:
    - bat-arls-3:         NOTRUN -> [SKIP][24] ([i915#10212] / [i915#3708])
   [24]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@prime_vgem@basic-fence-read.html

  * igt@prime_vgem@basic-read:
    - bat-arls-3:         NOTRUN -> [SKIP][25] ([i915#10214] / [i915#3708])
   [25]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@prime_vgem@basic-read.html

  * igt@prime_vgem@basic-write:
    - bat-arls-3:         NOTRUN -> [SKIP][26] ([i915#10216] / [i915#3708])
   [26]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@prime_vgem@basic-write.html

  
#### Possible fixes ####

  * igt@i915_module_load@load:
    - bat-arls-3:         [ABORT][27] ([i915#11041]) -> [PASS][28]
   [27]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/bat-arls-3/igt@i915_module_load@load.html
   [28]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-arls-3/igt@i915_module_load@load.html

  * igt@kms_flip@basic-flip-vs-wf_vblank@c-dp6:
    - {bat-mtlp-9}:       [DMESG-WARN][29] -> [PASS][30]
   [29]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/bat-mtlp-9/igt@kms_flip@basic-flip-vs-wf_vblank@c-dp6.html
   [30]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-mtlp-9/igt@kms_flip@basic-flip-vs-wf_vblank@c-dp6.html

  * igt@kms_flip@basic-flip-vs-wf_vblank@d-dp7:
    - {bat-mtlp-9}:       [FAIL][31] ([i915#6121]) -> [PASS][32] +4 other tests pass
   [31]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/bat-mtlp-9/igt@kms_flip@basic-flip-vs-wf_vblank@d-dp7.html
   [32]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-mtlp-9/igt@kms_flip@basic-flip-vs-wf_vblank@d-dp7.html

  * igt@kms_pipe_crc_basic@hang-read-crc@pipe-c-dp-6:
    - {bat-mtlp-9}:       [DMESG-FAIL][33] -> [PASS][34]
   [33]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/bat-mtlp-9/igt@kms_pipe_crc_basic@hang-read-crc@pipe-c-dp-6.html
   [34]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-mtlp-9/igt@kms_pipe_crc_basic@hang-read-crc@pipe-c-dp-6.html

  * igt@kms_pipe_crc_basic@hang-read-crc@pipe-d-dp-6:
    - {bat-mtlp-9}:       [FAIL][35] ([i915#10979]) -> [PASS][36]
   [35]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/bat-mtlp-9/igt@kms_pipe_crc_basic@hang-read-crc@pipe-d-dp-6.html
   [36]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/bat-mtlp-9/igt@kms_pipe_crc_basic@hang-read-crc@pipe-d-dp-6.html

  
  {name}: This element is suppressed. This means it is ignored when computing
          the status of the difference (SUCCESS, WARNING, or FAILURE).

  [i915#10196]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10196
  [i915#10197]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10197
  [i915#10200]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10200
  [i915#10202]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10202
  [i915#10206]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10206
  [i915#10207]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10207
  [i915#10208]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10208
  [i915#10209]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10209
  [i915#10211]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10211
  [i915#10212]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10212
  [i915#10213]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10213
  [i915#10214]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10214
  [i915#10216]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10216
  [i915#10800]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10800
  [i915#10900]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10900
  [i915#10979]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10979
  [i915#11041]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/11041
  [i915#1849]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/1849
  [i915#2190]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2190
  [i915#3708]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3708
  [i915#4077]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4077
  [i915#4079]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4079
  [i915#4083]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4083
  [i915#4613]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4613
  [i915#6121]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6121
  [i915#8585]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8585
  [i915#8809]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8809
  [i915#9318]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9318
  [i915#9732]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9732
  [i915#9812]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9812
  [i915#9886]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9886


Build changes
-------------

  * CI: CI-20190529 -> None
  * IGT: IGT_7846 -> IGTPW_11120

  CI-20190529: 20190529
  CI_DRM_14737: 92f877dd46245e4a44b6d24b5e303b8c03c40c75 @ git://anongit.freedesktop.org/gfx-ci/linux
  IGTPW_11120: 11120
  IGT_7846: 4a5fd4e7cb2798636f6464e2bd61399f3242b322 @ https://gitlab.freedesktop.org/drm/igt-gpu-tools.git

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/index.html

[-- Attachment #2: Type: text/html, Size: 12697 bytes --]

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: ✗ GitLab.Pipeline: warning for Add support for hook script
  2024-05-09 16:13 ` ✗ GitLab.Pipeline: warning for Add support for hook script Patchwork
@ 2024-05-09 17:04   ` Gustavo Sousa
  2024-05-20 19:44     ` Lucas De Marchi
  0 siblings, 1 reply; 22+ messages in thread
From: Gustavo Sousa @ 2024-05-09 17:04 UTC (permalink / raw)
  To: Patchwork, igt-dev; +Cc: igt-dev

Quoting Patchwork (2024-05-09 13:13:43-03:00)
>== Series Details ==
>
>Series: Add support for hook script
>URL   : https://patchwork.freedesktop.org/series/133391/
>State : warning
>
>== Summary ==
>
>Pipeline status: FAILED.
>
>see https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/pipelines/1173738 for the overview.
>
>test:ninja-test-minimal has failed (https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606):
>  26/30 lib i915_perf_data_alignment            OK       0.42 s 
>  27/30 lib bad_subtest_type                    EXPECTEDFAIL 0.37 s 
>  28/30 lib igt_no_subtest                      EXPECTEDFAIL 0.42 s 
>  29/30 lib igt_simple_test_subtests            EXPECTEDFAIL 0.27 s 
>  30/30 lib igt_timeout                         EXPECTEDFAIL 1.32 s 
>  
>  Ok:                   25
>  Expected Fail:         4
>  Fail:                  1
>  Unexpected Pass:       0
>  Skipped:               0
>  Timeout:               0
>  
>  Full log written to /builds/gfx-ci/igt-ci-tags/build/meson-logs/testlog.txt
>  section_end:1715270916:step_script
>  section_start:1715270916:cleanup_file_variables
>  Cleaning up project directory and file based variables
>  section_end:1715270917:cleanup_file_variables
>  ERROR: Job failed: exit code 1
>
>== Logs ==
>
>For more details see: https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/pipelines/1173738

Looking at the output on the page linked above, I see that the test I
introduced (igt_hook_integration) is failing but I passes locally for
me.

Is there any way I get more details on the failure to investigate? The
job output says:

    "Full log written to
    /builds/gfx-ci/igt-ci-tags/build/meson-logs/testlog.txt"

, but I'm not sure if that is available somewhere for me to check.

--
Gustavo

^ permalink raw reply	[flat|nested] 22+ messages in thread

* ✗ CI.xeFULL: failure for Add support for hook script
  2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
                   ` (5 preceding siblings ...)
  2024-05-09 16:33 ` ✓ Fi.CI.BAT: " Patchwork
@ 2024-05-09 21:12 ` Patchwork
  2024-05-10  5:30 ` ✗ Fi.CI.IGT: " Patchwork
  7 siblings, 0 replies; 22+ messages in thread
From: Patchwork @ 2024-05-09 21:12 UTC (permalink / raw)
  To: Gustavo Sousa; +Cc: igt-dev

[-- Attachment #1: Type: text/plain, Size: 40125 bytes --]

== Series Details ==

Series: Add support for hook script
URL   : https://patchwork.freedesktop.org/series/133391/
State : failure

== Summary ==

CI Bug Log - changes from XEIGT_7846_full -> XEIGTPW_11120_full
====================================================

Summary
-------

  **FAILURE**

  Serious unknown changes coming with XEIGTPW_11120_full absolutely need to be
  verified manually.
  
  If you think the reported changes have nothing to do with the changes
  introduced in XEIGTPW_11120_full, please notify your bug team (I915-ci-infra@lists.freedesktop.org) to allow them
  to document this new failure mode, which will reduce false positives in CI.

  

Participating hosts (3 -> 1)
------------------------------

  ERROR: It appears as if the changes made in XEIGTPW_11120_full prevented too many machines from booting.

  Missing    (2): shard-adlp shard-lnl 

Possible new issues
-------------------

  Here are the unknown changes that may have been introduced in XEIGTPW_11120_full:

### IGT changes ###

#### Possible regressions ####

  * igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24@pipe-b-dp-4:
    - shard-dg2-set2:     [PASS][1] -> [ABORT][2]
   [1]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24@pipe-b-dp-4.html
   [2]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-434/igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24@pipe-b-dp-4.html

  * igt@xe_exec_basic@multigpu-many-execqueues-many-vm-bindexecqueue-userptr-invalidate-race:
    - shard-dg2-set2:     [PASS][3] -> [CRASH][4]
   [3]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-463/igt@xe_exec_basic@multigpu-many-execqueues-many-vm-bindexecqueue-userptr-invalidate-race.html
   [4]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-466/igt@xe_exec_basic@multigpu-many-execqueues-many-vm-bindexecqueue-userptr-invalidate-race.html

  
Known issues
------------

  Here are the changes found in XEIGTPW_11120_full that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@kms_addfb_basic@basic-x-tiled-legacy:
    - shard-dg2-set2:     NOTRUN -> [SKIP][5] ([Intel XE#1201] / [i915#6077])
   [5]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_addfb_basic@basic-x-tiled-legacy.html

  * igt@kms_atomic_transition@plane-toggle-modeset-transition@pipe-a-hdmi-a-6:
    - shard-dg2-set2:     [PASS][6] -> [FAIL][7] ([Intel XE#1388]) +1 other test fail
   [6]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_atomic_transition@plane-toggle-modeset-transition@pipe-a-hdmi-a-6.html
   [7]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-466/igt@kms_atomic_transition@plane-toggle-modeset-transition@pipe-a-hdmi-a-6.html

  * igt@kms_big_fb@linear-8bpp-rotate-180:
    - shard-dg2-set2:     NOTRUN -> [SKIP][8] ([Intel XE#1201] / [Intel XE#829]) +1 other test skip
   [8]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_big_fb@linear-8bpp-rotate-180.html

  * igt@kms_big_fb@x-tiled-max-hw-stride-32bpp-rotate-0-async-flip:
    - shard-dg2-set2:     [PASS][9] -> [SKIP][10] ([Intel XE#1201] / [Intel XE#829]) +1 other test skip
   [9]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-463/igt@kms_big_fb@x-tiled-max-hw-stride-32bpp-rotate-0-async-flip.html
   [10]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_big_fb@x-tiled-max-hw-stride-32bpp-rotate-0-async-flip.html

  * igt@kms_big_fb@yf-tiled-64bpp-rotate-0:
    - shard-dg2-set2:     NOTRUN -> [SKIP][11] ([Intel XE#1124] / [Intel XE#1201])
   [11]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-436/igt@kms_big_fb@yf-tiled-64bpp-rotate-0.html

  * igt@kms_bw@linear-tiling-4-displays-2160x1440p:
    - shard-dg2-set2:     NOTRUN -> [SKIP][12] ([Intel XE#1201] / [Intel XE#367]) +1 other test skip
   [12]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-436/igt@kms_bw@linear-tiling-4-displays-2160x1440p.html

  * igt@kms_ccs@bad-pixel-format-4-tiled-mtl-rc-ccs-cc@pipe-a-dp-4:
    - shard-dg2-set2:     NOTRUN -> [SKIP][13] ([Intel XE#1201] / [Intel XE#787]) +58 other tests skip
   [13]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-435/igt@kms_ccs@bad-pixel-format-4-tiled-mtl-rc-ccs-cc@pipe-a-dp-4.html

  * igt@kms_ccs@crc-primary-basic-4-tiled-mtl-rc-ccs-cc@pipe-d-hdmi-a-7:
    - shard-dg2-set2:     NOTRUN -> [SKIP][14] ([Intel XE#1201] / [Intel XE#455] / [Intel XE#787]) +18 other tests skip
   [14]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_ccs@crc-primary-basic-4-tiled-mtl-rc-ccs-cc@pipe-d-hdmi-a-7.html

  * igt@kms_ccs@crc-primary-rotation-180-4-tiled-dg2-mc-ccs@pipe-a-hdmi-a-6:
    - shard-dg2-set2:     NOTRUN -> [FAIL][15] ([Intel XE#650]) +11 other tests fail
   [15]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-466/igt@kms_ccs@crc-primary-rotation-180-4-tiled-dg2-mc-ccs@pipe-a-hdmi-a-6.html

  * igt@kms_ccs@crc-sprite-planes-basic-4-tiled-dg2-rc-ccs-cc@pipe-a-hdmi-a-7:
    - shard-dg2-set2:     NOTRUN -> [FAIL][16] ([Intel XE#616]) +3 other tests fail
   [16]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_ccs@crc-sprite-planes-basic-4-tiled-dg2-rc-ccs-cc@pipe-a-hdmi-a-7.html

  * igt@kms_ccs@random-ccs-data-4-tiled-xe2-ccs:
    - shard-dg2-set2:     NOTRUN -> [SKIP][17] ([Intel XE#1201] / [Intel XE#1252])
   [17]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@kms_ccs@random-ccs-data-4-tiled-xe2-ccs.html

  * igt@kms_content_protection@atomic@pipe-a-dp-4:
    - shard-dg2-set2:     NOTRUN -> [FAIL][18] ([Intel XE#1178])
   [18]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_content_protection@atomic@pipe-a-dp-4.html

  * igt@kms_cursor_crc@cursor-rapid-movement-512x170:
    - shard-dg2-set2:     NOTRUN -> [SKIP][19] ([Intel XE#1201] / [Intel XE#308])
   [19]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-434/igt@kms_cursor_crc@cursor-rapid-movement-512x170.html

  * igt@kms_cursor_crc@cursor-sliding-128x128:
    - shard-dg2-set2:     [PASS][20] -> [SKIP][21] ([Intel XE#1201] / [Intel XE#1226]) +9 other tests skip
   [20]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_cursor_crc@cursor-sliding-128x128.html
   [21]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_cursor_crc@cursor-sliding-128x128.html

  * igt@kms_cursor_edge_walk@64x64-right-edge:
    - shard-dg2-set2:     [PASS][22] -> [FAIL][23] ([Intel XE#581])
   [22]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@kms_cursor_edge_walk@64x64-right-edge.html
   [23]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_cursor_edge_walk@64x64-right-edge.html

  * igt@kms_cursor_legacy@2x-cursor-vs-flip-legacy:
    - shard-dg2-set2:     [PASS][24] -> [DMESG-WARN][25] ([Intel XE#1214] / [Intel XE#282]) +8 other tests dmesg-warn
   [24]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@kms_cursor_legacy@2x-cursor-vs-flip-legacy.html
   [25]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_cursor_legacy@2x-cursor-vs-flip-legacy.html

  * igt@kms_cursor_legacy@forked-move@pipe-b:
    - shard-dg2-set2:     NOTRUN -> [DMESG-WARN][26] ([Intel XE#1214] / [Intel XE#282]) +3 other tests dmesg-warn
   [26]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_cursor_legacy@forked-move@pipe-b.html

  * igt@kms_flip@basic-plain-flip:
    - shard-dg2-set2:     NOTRUN -> [SKIP][27] ([Intel XE#1201] / [Intel XE#1226]) +1 other test skip
   [27]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_flip@basic-plain-flip.html

  * igt@kms_flip_scaled_crc@flip-64bpp-linear-to-32bpp-linear-downscaling:
    - shard-dg2-set2:     [PASS][28] -> [SKIP][29] ([Intel XE#1201] / [Intel XE#1226] / [Intel XE#1235])
   [28]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_flip_scaled_crc@flip-64bpp-linear-to-32bpp-linear-downscaling.html
   [29]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_flip_scaled_crc@flip-64bpp-linear-to-32bpp-linear-downscaling.html

  * igt@kms_frontbuffer_tracking@fbcdrrs-1p-primscrn-shrfb-msflip-blt:
    - shard-dg2-set2:     NOTRUN -> [SKIP][30] ([Intel XE#1201] / [Intel XE#651]) +3 other tests skip
   [30]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-435/igt@kms_frontbuffer_tracking@fbcdrrs-1p-primscrn-shrfb-msflip-blt.html

  * igt@kms_frontbuffer_tracking@psr-2p-primscrn-shrfb-msflip-blt:
    - shard-dg2-set2:     NOTRUN -> [SKIP][31] ([Intel XE#1201] / [Intel XE#653]) +2 other tests skip
   [31]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-436/igt@kms_frontbuffer_tracking@psr-2p-primscrn-shrfb-msflip-blt.html

  * igt@kms_getfb@getfb-handle-zero:
    - shard-dg2-set2:     [PASS][32] -> [SKIP][33] ([Intel XE#1201] / [Intel XE#687])
   [32]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-463/igt@kms_getfb@getfb-handle-zero.html
   [33]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_getfb@getfb-handle-zero.html

  * igt@kms_hdr@static-toggle:
    - shard-dg2-set2:     [PASS][34] -> [SKIP][35] ([Intel XE#1201] / [Intel XE#1226] / [Intel XE#1234]) +1 other test skip
   [34]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_hdr@static-toggle.html
   [35]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_hdr@static-toggle.html

  * igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24:
    - shard-dg2-set2:     [PASS][36] -> [ABORT][37] ([Intel XE#1553])
   [36]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24.html
   [37]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-434/igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24.html

  * igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24@pipe-c-hdmi-a-6:
    - shard-dg2-set2:     [PASS][38] -> [DMESG-WARN][39] ([Intel XE#1214] / [Intel XE#1553])
   [38]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24@pipe-c-hdmi-a-6.html
   [39]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-434/igt@kms_pipe_crc_basic@compare-crc-sanitycheck-xr24@pipe-c-hdmi-a-6.html

  * igt@kms_plane_lowres@tiling-y:
    - shard-dg2-set2:     NOTRUN -> [SKIP][40] ([Intel XE#1201] / [Intel XE#455]) +4 other tests skip
   [40]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_plane_lowres@tiling-y.html

  * igt@kms_plane_scaling@planes-downscale-factor-0-5-upscale-factor-0-25@pipe-a-hdmi-a-6:
    - shard-dg2-set2:     [PASS][41] -> [INCOMPLETE][42] ([Intel XE#1195]) +14 other tests incomplete
   [41]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-434/igt@kms_plane_scaling@planes-downscale-factor-0-5-upscale-factor-0-25@pipe-a-hdmi-a-6.html
   [42]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-466/igt@kms_plane_scaling@planes-downscale-factor-0-5-upscale-factor-0-25@pipe-a-hdmi-a-6.html

  * igt@kms_prop_blob@basic:
    - shard-dg2-set2:     [PASS][43] -> [SKIP][44] ([Intel XE#1201] / [Intel XE#780])
   [43]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_prop_blob@basic.html
   [44]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_prop_blob@basic.html

  * igt@kms_psr2_sf@overlay-plane-update-sf-dmg-area:
    - shard-dg2-set2:     NOTRUN -> [SKIP][45] ([Intel XE#1201] / [Intel XE#929])
   [45]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-434/igt@kms_psr2_sf@overlay-plane-update-sf-dmg-area.html

  * igt@kms_universal_plane@cursor-fb-leak:
    - shard-dg2-set2:     [PASS][46] -> [FAIL][47] ([Intel XE#771] / [Intel XE#899])
   [46]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_universal_plane@cursor-fb-leak.html
   [47]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@kms_universal_plane@cursor-fb-leak.html

  * igt@kms_universal_plane@cursor-fb-leak@pipe-c-hdmi-a-6:
    - shard-dg2-set2:     [PASS][48] -> [FAIL][49] ([Intel XE#899])
   [48]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_universal_plane@cursor-fb-leak@pipe-c-hdmi-a-6.html
   [49]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@kms_universal_plane@cursor-fb-leak@pipe-c-hdmi-a-6.html

  * igt@xe_evict@evict-beng-threads-large:
    - shard-dg2-set2:     [PASS][50] -> [TIMEOUT][51] ([Intel XE#1473])
   [50]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-464/igt@xe_evict@evict-beng-threads-large.html
   [51]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@xe_evict@evict-beng-threads-large.html

  * igt@xe_exec_fault_mode@once-bindexecqueue-userptr-invalidate-imm:
    - shard-dg2-set2:     NOTRUN -> [SKIP][52] ([Intel XE#1201] / [Intel XE#288])
   [52]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@xe_exec_fault_mode@once-bindexecqueue-userptr-invalidate-imm.html

  * igt@xe_live_ktest@xe_migrate:
    - shard-dg2-set2:     [PASS][53] -> [SKIP][54] ([Intel XE#1192] / [Intel XE#1201])
   [53]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@xe_live_ktest@xe_migrate.html
   [54]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@xe_live_ktest@xe_migrate.html

  * igt@xe_module_load@many-reload:
    - shard-dg2-set2:     [PASS][55] -> [INCOMPLETE][56] ([Intel XE#1044] / [Intel XE#1195])
   [55]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-433/igt@xe_module_load@many-reload.html
   [56]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@xe_module_load@many-reload.html

  * igt@xe_pm@s3-basic-exec:
    - shard-dg2-set2:     [PASS][57] -> [DMESG-WARN][58] ([Intel XE#1162] / [Intel XE#1214]) +7 other tests dmesg-warn
   [57]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-464/igt@xe_pm@s3-basic-exec.html
   [58]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-436/igt@xe_pm@s3-basic-exec.html

  * igt@xe_pm@s3-d3cold-basic-exec:
    - shard-dg2-set2:     NOTRUN -> [SKIP][59] ([Intel XE#1201] / [Intel XE#366])
   [59]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@xe_pm@s3-d3cold-basic-exec.html

  * igt@xe_query@multigpu-query-cs-cycles:
    - shard-dg2-set2:     NOTRUN -> [SKIP][60] ([Intel XE#1201] / [Intel XE#944])
   [60]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@xe_query@multigpu-query-cs-cycles.html

  
#### Possible fixes ####

  * igt@kms_cursor_legacy@cursor-vs-flip-toggle:
    - shard-dg2-set2:     [DMESG-WARN][61] ([Intel XE#1214] / [Intel XE#282]) -> [PASS][62] +4 other tests pass
   [61]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_cursor_legacy@cursor-vs-flip-toggle.html
   [62]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-434/igt@kms_cursor_legacy@cursor-vs-flip-toggle.html

  * igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-cur-indfb-draw-mmap-wc:
    - shard-dg2-set2:     [SKIP][63] ([Intel XE#1201]) -> [PASS][64] +3 other tests pass
   [63]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-cur-indfb-draw-mmap-wc.html
   [64]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-436/igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-cur-indfb-draw-mmap-wc.html

  * igt@kms_pm_rpm@i2c:
    - shard-dg2-set2:     [FAIL][65] ([Intel XE#1787]) -> [PASS][66]
   [65]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-464/igt@kms_pm_rpm@i2c.html
   [66]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_pm_rpm@i2c.html

  * igt@kms_universal_plane@disable-primary-vs-flip:
    - shard-dg2-set2:     [SKIP][67] ([Intel XE#1201] / [Intel XE#829]) -> [PASS][68]
   [67]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_universal_plane@disable-primary-vs-flip.html
   [68]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_universal_plane@disable-primary-vs-flip.html

  * igt@xe_evict@evict-beng-mixed-many-threads-small:
    - shard-dg2-set2:     [TIMEOUT][69] ([Intel XE#1473] / [Intel XE#402]) -> [PASS][70]
   [69]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@xe_evict@evict-beng-mixed-many-threads-small.html
   [70]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-436/igt@xe_evict@evict-beng-mixed-many-threads-small.html

  * igt@xe_evict@evict-beng-mixed-threads-large:
    - shard-dg2-set2:     [DMESG-FAIL][71] ([Intel XE#482]) -> [PASS][72]
   [71]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@xe_evict@evict-beng-mixed-threads-large.html
   [72]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-434/igt@xe_evict@evict-beng-mixed-threads-large.html

  * igt@xe_evict@evict-cm-threads-large:
    - shard-dg2-set2:     [INCOMPLETE][73] ([Intel XE#1195] / [Intel XE#1473] / [Intel XE#392]) -> [PASS][74]
   [73]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@xe_evict@evict-cm-threads-large.html
   [74]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@xe_evict@evict-cm-threads-large.html

  * igt@xe_gt_freq@freq_low_max:
    - shard-dg2-set2:     [FAIL][75] ([Intel XE#1045] / [Intel XE#1204]) -> [PASS][76]
   [75]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@xe_gt_freq@freq_low_max.html
   [76]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@xe_gt_freq@freq_low_max.html

  * igt@xe_module_load@reload:
    - shard-dg2-set2:     [DMESG-WARN][77] ([Intel XE#1162] / [Intel XE#1214]) -> [PASS][78] +4 other tests pass
   [77]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-464/igt@xe_module_load@reload.html
   [78]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-466/igt@xe_module_load@reload.html

  * igt@xe_pm@d3-mmap-system:
    - shard-dg2-set2:     [FAIL][79] ([Intel XE#1221]) -> [PASS][80]
   [79]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@xe_pm@d3-mmap-system.html
   [80]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@xe_pm@d3-mmap-system.html

  
#### Warnings ####

  * igt@kms_big_fb@4-tiled-64bpp-rotate-270:
    - shard-dg2-set2:     [SKIP][81] ([Intel XE#1201] / [Intel XE#316]) -> [SKIP][82] ([Intel XE#1201] / [Intel XE#829])
   [81]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_big_fb@4-tiled-64bpp-rotate-270.html
   [82]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_big_fb@4-tiled-64bpp-rotate-270.html

  * igt@kms_big_fb@yf-tiled-64bpp-rotate-90:
    - shard-dg2-set2:     [SKIP][83] ([Intel XE#1124] / [Intel XE#1201]) -> [SKIP][84] ([Intel XE#1201] / [Intel XE#829]) +2 other tests skip
   [83]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_big_fb@yf-tiled-64bpp-rotate-90.html
   [84]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_big_fb@yf-tiled-64bpp-rotate-90.html

  * igt@kms_bw@linear-tiling-3-displays-1920x1080p:
    - shard-dg2-set2:     [SKIP][85] ([Intel XE#1201]) -> [SKIP][86] ([Intel XE#1201] / [Intel XE#367])
   [85]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_bw@linear-tiling-3-displays-1920x1080p.html
   [86]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_bw@linear-tiling-3-displays-1920x1080p.html

  * igt@kms_ccs@crc-primary-basic-4-tiled-xe2-ccs:
    - shard-dg2-set2:     [SKIP][87] ([Intel XE#1201] / [Intel XE#1252]) -> [SKIP][88] ([Intel XE#1201] / [Intel XE#829])
   [87]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-433/igt@kms_ccs@crc-primary-basic-4-tiled-xe2-ccs.html
   [88]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_ccs@crc-primary-basic-4-tiled-xe2-ccs.html

  * igt@kms_ccs@crc-primary-rotation-180-4-tiled-dg2-mc-ccs:
    - shard-dg2-set2:     [SKIP][89] ([Intel XE#1201] / [Intel XE#829]) -> [FAIL][90] ([Intel XE#650])
   [89]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_ccs@crc-primary-rotation-180-4-tiled-dg2-mc-ccs.html
   [90]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-466/igt@kms_ccs@crc-primary-rotation-180-4-tiled-dg2-mc-ccs.html

  * igt@kms_ccs@crc-sprite-planes-basic-4-tiled-mtl-rc-ccs-cc:
    - shard-dg2-set2:     [SKIP][91] ([Intel XE#1201] / [Intel XE#455] / [Intel XE#787]) -> [SKIP][92] ([Intel XE#1201] / [Intel XE#829]) +2 other tests skip
   [91]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-434/igt@kms_ccs@crc-sprite-planes-basic-4-tiled-mtl-rc-ccs-cc.html
   [92]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_ccs@crc-sprite-planes-basic-4-tiled-mtl-rc-ccs-cc.html

  * igt@kms_chamelium_color@ctm-green-to-red:
    - shard-dg2-set2:     [SKIP][93] ([Intel XE#1201] / [Intel XE#306]) -> [SKIP][94] ([Intel XE#1201] / [Intel XE#1226])
   [93]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@kms_chamelium_color@ctm-green-to-red.html
   [94]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_chamelium_color@ctm-green-to-red.html

  * igt@kms_chamelium_frames@hdmi-cmp-planes-random:
    - shard-dg2-set2:     [SKIP][95] ([Intel XE#1201] / [Intel XE#373]) -> [SKIP][96] ([Intel XE#1201] / [Intel XE#1226]) +1 other test skip
   [95]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_chamelium_frames@hdmi-cmp-planes-random.html
   [96]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_chamelium_frames@hdmi-cmp-planes-random.html

  * igt@kms_chamelium_hpd@dp-hpd-after-suspend:
    - shard-dg2-set2:     [SKIP][97] ([Intel XE#1201]) -> [SKIP][98] ([Intel XE#1201] / [Intel XE#373])
   [97]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_chamelium_hpd@dp-hpd-after-suspend.html
   [98]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_chamelium_hpd@dp-hpd-after-suspend.html

  * igt@kms_content_protection@atomic:
    - shard-dg2-set2:     [SKIP][99] ([Intel XE#1201] / [Intel XE#455]) -> [FAIL][100] ([Intel XE#1178])
   [99]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-464/igt@kms_content_protection@atomic.html
   [100]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_content_protection@atomic.html

  * igt@kms_content_protection@dp-mst-type-0:
    - shard-dg2-set2:     [SKIP][101] ([Intel XE#1201]) -> [SKIP][102] ([Intel XE#1201] / [Intel XE#307])
   [101]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_content_protection@dp-mst-type-0.html
   [102]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-436/igt@kms_content_protection@dp-mst-type-0.html

  * igt@kms_content_protection@lic-type-0:
    - shard-dg2-set2:     [FAIL][103] ([Intel XE#1204]) -> [SKIP][104] ([Intel XE#1201] / [Intel XE#455])
   [103]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_content_protection@lic-type-0.html
   [104]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_content_protection@lic-type-0.html

  * igt@kms_cursor_crc@cursor-suspend@pipe-d-hdmi-a-6:
    - shard-dg2-set2:     [DMESG-FAIL][105] ([Intel XE#1162]) -> [FAIL][106] ([Intel XE#616]) +1 other test fail
   [105]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@kms_cursor_crc@cursor-suspend@pipe-d-hdmi-a-6.html
   [106]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@kms_cursor_crc@cursor-suspend@pipe-d-hdmi-a-6.html

  * igt@kms_feature_discovery@chamelium:
    - shard-dg2-set2:     [SKIP][107] ([Intel XE#1201] / [Intel XE#701]) -> [SKIP][108] ([Intel XE#1201] / [Intel XE#1226])
   [107]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-464/igt@kms_feature_discovery@chamelium.html
   [108]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_feature_discovery@chamelium.html

  * igt@kms_flip@2x-flip-vs-suspend:
    - shard-dg2-set2:     [DMESG-WARN][109] ([Intel XE#1162] / [Intel XE#1214]) -> [SKIP][110] ([Intel XE#1201] / [Intel XE#1226])
   [109]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_flip@2x-flip-vs-suspend.html
   [110]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_flip@2x-flip-vs-suspend.html

  * igt@kms_flip@flip-vs-suspend:
    - shard-dg2-set2:     [DMESG-WARN][111] ([Intel XE#1162] / [Intel XE#1214]) -> [INCOMPLETE][112] ([Intel XE#1195])
   [111]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-433/igt@kms_flip@flip-vs-suspend.html
   [112]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-434/igt@kms_flip@flip-vs-suspend.html

  * igt@kms_flip_scaled_crc@flip-64bpp-yftile-to-16bpp-yftile-upscaling@pipe-a-valid-mode:
    - shard-dg2-set2:     [SKIP][113] ([Intel XE#1201] / [Intel XE#455]) -> [INCOMPLETE][114] ([Intel XE#1195]) +3 other tests incomplete
   [113]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_flip_scaled_crc@flip-64bpp-yftile-to-16bpp-yftile-upscaling@pipe-a-valid-mode.html
   [114]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_flip_scaled_crc@flip-64bpp-yftile-to-16bpp-yftile-upscaling@pipe-a-valid-mode.html

  * igt@kms_frontbuffer_tracking@drrs-2p-scndscrn-indfb-pgflip-blt:
    - shard-dg2-set2:     [SKIP][115] ([Intel XE#1201]) -> [SKIP][116] ([Intel XE#1201] / [Intel XE#651]) +1 other test skip
   [115]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_frontbuffer_tracking@drrs-2p-scndscrn-indfb-pgflip-blt.html
   [116]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-466/igt@kms_frontbuffer_tracking@drrs-2p-scndscrn-indfb-pgflip-blt.html

  * igt@kms_frontbuffer_tracking@fbcdrrs-2p-scndscrn-spr-indfb-draw-blt:
    - shard-dg2-set2:     [SKIP][117] ([Intel XE#1201] / [Intel XE#651]) -> [SKIP][118] ([Intel XE#1201] / [Intel XE#1226]) +4 other tests skip
   [117]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_frontbuffer_tracking@fbcdrrs-2p-scndscrn-spr-indfb-draw-blt.html
   [118]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_frontbuffer_tracking@fbcdrrs-2p-scndscrn-spr-indfb-draw-blt.html

  * igt@kms_frontbuffer_tracking@fbcpsr-2p-scndscrn-pri-indfb-draw-blt:
    - shard-dg2-set2:     [SKIP][119] ([Intel XE#1201] / [Intel XE#653]) -> [SKIP][120] ([Intel XE#1201] / [Intel XE#1226] / [Intel XE#1234])
   [119]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@kms_frontbuffer_tracking@fbcpsr-2p-scndscrn-pri-indfb-draw-blt.html
   [120]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_frontbuffer_tracking@fbcpsr-2p-scndscrn-pri-indfb-draw-blt.html

  * igt@kms_frontbuffer_tracking@psr-1p-primscrn-spr-indfb-onoff:
    - shard-dg2-set2:     [SKIP][121] ([Intel XE#1201] / [Intel XE#653]) -> [SKIP][122] ([Intel XE#1201] / [Intel XE#1226]) +2 other tests skip
   [121]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_frontbuffer_tracking@psr-1p-primscrn-spr-indfb-onoff.html
   [122]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_frontbuffer_tracking@psr-1p-primscrn-spr-indfb-onoff.html

  * igt@kms_frontbuffer_tracking@psr-2p-scndscrn-spr-indfb-draw-blt:
    - shard-dg2-set2:     [SKIP][123] ([Intel XE#1201]) -> [SKIP][124] ([Intel XE#1201] / [Intel XE#653]) +1 other test skip
   [123]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_frontbuffer_tracking@psr-2p-scndscrn-spr-indfb-draw-blt.html
   [124]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-466/igt@kms_frontbuffer_tracking@psr-2p-scndscrn-spr-indfb-draw-blt.html

  * igt@kms_plane_scaling@plane-downscale-factor-0-25-with-modifiers:
    - shard-dg2-set2:     [SKIP][125] ([Intel XE#1201] / [Intel XE#455] / [Intel XE#498]) -> [INCOMPLETE][126] ([Intel XE#1195])
   [125]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-463/igt@kms_plane_scaling@plane-downscale-factor-0-25-with-modifiers.html
   [126]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@kms_plane_scaling@plane-downscale-factor-0-25-with-modifiers.html

  * igt@kms_plane_scaling@plane-downscale-factor-0-25-with-modifiers@pipe-a-hdmi-a-6:
    - shard-dg2-set2:     [SKIP][127] ([Intel XE#1201] / [Intel XE#498]) -> [INCOMPLETE][128] ([Intel XE#1195])
   [127]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-463/igt@kms_plane_scaling@plane-downscale-factor-0-25-with-modifiers@pipe-a-hdmi-a-6.html
   [128]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@kms_plane_scaling@plane-downscale-factor-0-25-with-modifiers@pipe-a-hdmi-a-6.html

  * igt@kms_plane_scaling@plane-downscale-factor-0-5-with-pixel-format:
    - shard-dg2-set2:     [TIMEOUT][129] ([Intel XE#380] / [Intel XE#904] / [Intel XE#909]) -> [INCOMPLETE][130] ([Intel XE#1195] / [Intel XE#904] / [Intel XE#909])
   [129]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-433/igt@kms_plane_scaling@plane-downscale-factor-0-5-with-pixel-format.html
   [130]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_plane_scaling@plane-downscale-factor-0-5-with-pixel-format.html

  * igt@kms_plane_scaling@plane-downscale-factor-0-5-with-pixel-format@pipe-a-hdmi-a-6:
    - shard-dg2-set2:     [TIMEOUT][131] ([Intel XE#904] / [Intel XE#909]) -> [INCOMPLETE][132] ([Intel XE#1195] / [Intel XE#904] / [Intel XE#909])
   [131]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-433/igt@kms_plane_scaling@plane-downscale-factor-0-5-with-pixel-format@pipe-a-hdmi-a-6.html
   [132]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_plane_scaling@plane-downscale-factor-0-5-with-pixel-format@pipe-a-hdmi-a-6.html

  * igt@kms_plane_scaling@plane-downscale-factor-0-75-with-pixel-format:
    - shard-dg2-set2:     [INCOMPLETE][133] ([Intel XE#1195] / [Intel XE#904] / [Intel XE#909]) -> [INCOMPLETE][134] ([Intel XE#1195]) +1 other test incomplete
   [133]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-435/igt@kms_plane_scaling@plane-downscale-factor-0-75-with-pixel-format.html
   [134]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-435/igt@kms_plane_scaling@plane-downscale-factor-0-75-with-pixel-format.html

  * igt@kms_plane_scaling@planes-downscale-factor-0-25-unity-scaling:
    - shard-dg2-set2:     [SKIP][135] ([Intel XE#1201] / [Intel XE#305] / [Intel XE#455]) -> [INCOMPLETE][136] ([Intel XE#1195]) +1 other test incomplete
   [135]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_plane_scaling@planes-downscale-factor-0-25-unity-scaling.html
   [136]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_plane_scaling@planes-downscale-factor-0-25-unity-scaling.html

  * igt@kms_plane_scaling@planes-downscale-factor-0-25-upscale-20x20:
    - shard-dg2-set2:     [SKIP][137] ([Intel XE#1201] / [Intel XE#305] / [Intel XE#455]) -> [SKIP][138] ([Intel XE#1201] / [Intel XE#1226])
   [137]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-433/igt@kms_plane_scaling@planes-downscale-factor-0-25-upscale-20x20.html
   [138]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_plane_scaling@planes-downscale-factor-0-25-upscale-20x20.html

  * igt@kms_plane_scaling@planes-unity-scaling-downscale-factor-0-25@pipe-a-hdmi-a-6:
    - shard-dg2-set2:     [SKIP][139] ([Intel XE#1201] / [Intel XE#305]) -> [INCOMPLETE][140] ([Intel XE#1195]) +1 other test incomplete
   [139]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-466/igt@kms_plane_scaling@planes-unity-scaling-downscale-factor-0-25@pipe-a-hdmi-a-6.html
   [140]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-464/igt@kms_plane_scaling@planes-unity-scaling-downscale-factor-0-25@pipe-a-hdmi-a-6.html

  * igt@kms_pm_dc@dc3co-vpb-simulation:
    - shard-dg2-set2:     [SKIP][141] ([Intel XE#1122] / [Intel XE#1201]) -> [SKIP][142] ([Intel XE#1201] / [Intel XE#1226])
   [141]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_pm_dc@dc3co-vpb-simulation.html
   [142]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_pm_dc@dc3co-vpb-simulation.html

  * igt@kms_psr@fbc-psr2-cursor-plane-move:
    - shard-dg2-set2:     [SKIP][143] ([Intel XE#1201]) -> [SKIP][144] ([Intel XE#1201] / [Intel XE#929]) +2 other tests skip
   [143]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_psr@fbc-psr2-cursor-plane-move.html
   [144]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-435/igt@kms_psr@fbc-psr2-cursor-plane-move.html

  * igt@kms_psr@fbc-psr2-cursor-plane-onoff:
    - shard-dg2-set2:     [SKIP][145] ([Intel XE#1201] / [Intel XE#929]) -> [SKIP][146] ([Intel XE#1201] / [Intel XE#1226]) +3 other tests skip
   [145]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_psr@fbc-psr2-cursor-plane-onoff.html
   [146]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_psr@fbc-psr2-cursor-plane-onoff.html

  * igt@kms_vrr@flip-basic:
    - shard-dg2-set2:     [SKIP][147] ([Intel XE#1201]) -> [SKIP][148] ([Intel XE#1201] / [Intel XE#455])
   [147]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_vrr@flip-basic.html
   [148]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@kms_vrr@flip-basic.html

  * igt@kms_vrr@flipline:
    - shard-dg2-set2:     [SKIP][149] ([Intel XE#1201] / [Intel XE#455]) -> [SKIP][150] ([Intel XE#1201] / [Intel XE#1226]) +1 other test skip
   [149]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@kms_vrr@flipline.html
   [150]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_vrr@flipline.html

  * igt@kms_writeback@writeback-fb-id:
    - shard-dg2-set2:     [SKIP][151] ([Intel XE#1201] / [Intel XE#756]) -> [SKIP][152] ([Intel XE#1201] / [Intel XE#1226])
   [151]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-434/igt@kms_writeback@writeback-fb-id.html
   [152]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-433/igt@kms_writeback@writeback-fb-id.html

  * igt@xe_evict@evict-large-multi-vm-cm:
    - shard-dg2-set2:     [FAIL][153] ([Intel XE#1600]) -> [FAIL][154] ([Intel XE#1041])
   [153]: https://intel-gfx-ci.01.org/tree/intel-xe/IGT_7846/shard-dg2-436/igt@xe_evict@evict-large-multi-vm-cm.html
   [154]: https://intel-gfx-ci.01.org/tree/intel-xe/IGTPW_11120/shard-dg2-463/igt@xe_evict@evict-large-multi-vm-cm.html

  
  {name}: This element is suppressed. This means it is ignored when computing
          the status of the difference (SUCCESS, WARNING, or FAILURE).

  [Intel XE#1041]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1041
  [Intel XE#1044]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1044
  [Intel XE#1045]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1045
  [Intel XE#1122]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1122
  [Intel XE#1124]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1124
  [Intel XE#1162]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1162
  [Intel XE#1178]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1178
  [Intel XE#1192]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1192
  [Intel XE#1195]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1195
  [Intel XE#1201]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1201
  [Intel XE#1204]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1204
  [Intel XE#1214]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1214
  [Intel XE#1221]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1221
  [Intel XE#1226]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1226
  [Intel XE#1234]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1234
  [Intel XE#1235]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1235
  [Intel XE#1252]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1252
  [Intel XE#1388]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1388
  [Intel XE#1473]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1473
  [Intel XE#1553]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1553
  [Intel XE#1600]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1600
  [Intel XE#1787]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1787
  [Intel XE#1818]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1818
  [Intel XE#282]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/282
  [Intel XE#288]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/288
  [Intel XE#305]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/305
  [Intel XE#306]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/306
  [Intel XE#307]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/307
  [Intel XE#308]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/308
  [Intel XE#316]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/316
  [Intel XE#366]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/366
  [Intel XE#367]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/367
  [Intel XE#373]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/373
  [Intel XE#380]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/380
  [Intel XE#392]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/392
  [Intel XE#402]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/402
  [Intel XE#455]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/455
  [Intel XE#482]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/482
  [Intel XE#498]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/498
  [Intel XE#581]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/581
  [Intel XE#616]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/616
  [Intel XE#650]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/650
  [Intel XE#651]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/651
  [Intel XE#653]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/653
  [Intel XE#687]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/687
  [Intel XE#701]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/701
  [Intel XE#756]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/756
  [Intel XE#771]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/771
  [Intel XE#780]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/780
  [Intel XE#787]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/787
  [Intel XE#829]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/829
  [Intel XE#899]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/899
  [Intel XE#904]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/904
  [Intel XE#909]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/909
  [Intel XE#929]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/929
  [Intel XE#944]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/944
  [i915#6077]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6077


Build changes
-------------

  * IGT: IGT_7846 -> IGTPW_11120

  IGTPW_11120: 11120
  IGT_7846: 4a5fd4e7cb2798636f6464e2bd61399f3242b322 @ https://gitlab.freedesktop.org/drm/igt-gpu-tools.git
  xe-1263-92f877dd46245e4a44b6d24b5e303b8c03c40c75: 92f877dd46245e4a44b6d24b5e303b8c03c40c75

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-133391v1/index.html

[-- Attachment #2: Type: text/html, Size: 54130 bytes --]

^ permalink raw reply	[flat|nested] 22+ messages in thread

* ✗ Fi.CI.IGT: failure for Add support for hook script
  2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
                   ` (6 preceding siblings ...)
  2024-05-09 21:12 ` ✗ CI.xeFULL: failure " Patchwork
@ 2024-05-10  5:30 ` Patchwork
  7 siblings, 0 replies; 22+ messages in thread
From: Patchwork @ 2024-05-10  5:30 UTC (permalink / raw)
  To: Gustavo Sousa; +Cc: igt-dev

[-- Attachment #1: Type: text/plain, Size: 96762 bytes --]

== Series Details ==

Series: Add support for hook script
URL   : https://patchwork.freedesktop.org/series/133391/
State : failure

== Summary ==

CI Bug Log - changes from IGT_7846_full -> IGTPW_11120_full
====================================================

Summary
-------

  **FAILURE**

  Serious unknown changes coming with IGTPW_11120_full absolutely need to be
  verified manually.
  
  If you think the reported changes have nothing to do with the changes
  introduced in IGTPW_11120_full, please notify your bug team (&#x27;I915-ci-infra@lists.freedesktop.org&#x27;) to allow them
  to document this new failure mode, which will reduce false positives in CI.

  External URL: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/index.html

Participating hosts (9 -> 9)
------------------------------

  No changes in participating hosts

Possible new issues
-------------------

  Here are the unknown changes that may have been introduced in IGTPW_11120_full:

### IGT changes ###

#### Possible regressions ####

  * igt@kms_fb_coherency@memset-crc@mmap-gtt:
    - shard-rkl:          [PASS][1] -> [CRASH][2]
   [1]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-rkl-4/igt@kms_fb_coherency@memset-crc@mmap-gtt.html
   [2]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_fb_coherency@memset-crc@mmap-gtt.html
    - shard-tglu:         [PASS][3] -> [CRASH][4]
   [3]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-tglu-2/igt@kms_fb_coherency@memset-crc@mmap-gtt.html
   [4]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-10/igt@kms_fb_coherency@memset-crc@mmap-gtt.html

  * igt@kms_fb_coherency@memset-crc@mmap-offset-fixed:
    - shard-dg1:          [PASS][5] -> [CRASH][6]
   [5]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-16/igt@kms_fb_coherency@memset-crc@mmap-offset-fixed.html
   [6]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_fb_coherency@memset-crc@mmap-offset-fixed.html
    - shard-dg2:          [PASS][7] -> [CRASH][8]
   [7]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-1/igt@kms_fb_coherency@memset-crc@mmap-offset-fixed.html
   [8]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@kms_fb_coherency@memset-crc@mmap-offset-fixed.html

  * igt@kms_fb_coherency@memset-crc@mmap-offset-wc:
    - shard-mtlp:         [PASS][9] -> [CRASH][10]
   [9]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-mtlp-1/igt@kms_fb_coherency@memset-crc@mmap-offset-wc.html
   [10]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@kms_fb_coherency@memset-crc@mmap-offset-wc.html

  * igt@kms_flip@wf_vblank-ts-check-interruptible@d-hdmi-a4:
    - shard-dg1:          [PASS][11] -> [FAIL][12]
   [11]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-15/igt@kms_flip@wf_vblank-ts-check-interruptible@d-hdmi-a4.html
   [12]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_flip@wf_vblank-ts-check-interruptible@d-hdmi-a4.html

  * igt@syncobj_timeline@etime-multi-wait-all-available-unsubmitted-signaled:
    - shard-dg2:          [PASS][13] -> [INCOMPLETE][14]
   [13]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-8/igt@syncobj_timeline@etime-multi-wait-all-available-unsubmitted-signaled.html
   [14]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@syncobj_timeline@etime-multi-wait-all-available-unsubmitted-signaled.html
    - shard-dg1:          NOTRUN -> [INCOMPLETE][15]
   [15]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@syncobj_timeline@etime-multi-wait-all-available-unsubmitted-signaled.html

  
Known issues
------------

  Here are the changes found in IGTPW_11120_full that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@api_intel_bb@blit-reloc-purge-cache:
    - shard-mtlp:         NOTRUN -> [SKIP][16] ([i915#8411])
   [16]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@api_intel_bb@blit-reloc-purge-cache.html
    - shard-rkl:          NOTRUN -> [SKIP][17] ([i915#8411])
   [17]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@api_intel_bb@blit-reloc-purge-cache.html

  * igt@api_intel_bb@crc32:
    - shard-dg1:          NOTRUN -> [SKIP][18] ([i915#6230])
   [18]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@api_intel_bb@crc32.html

  * igt@api_intel_bb@object-reloc-keep-cache:
    - shard-dg1:          NOTRUN -> [SKIP][19] ([i915#8411])
   [19]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@api_intel_bb@object-reloc-keep-cache.html

  * igt@drm_fdinfo@busy@rcs0:
    - shard-dg2:          NOTRUN -> [SKIP][20] ([i915#8414]) +22 other tests skip
   [20]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@drm_fdinfo@busy@rcs0.html

  * igt@drm_fdinfo@most-busy-check-all@bcs0:
    - shard-dg1:          NOTRUN -> [SKIP][21] ([i915#8414]) +10 other tests skip
   [21]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@drm_fdinfo@most-busy-check-all@bcs0.html

  * igt@drm_fdinfo@most-busy-idle-check-all@ccs0:
    - shard-mtlp:         NOTRUN -> [SKIP][22] ([i915#8414]) +5 other tests skip
   [22]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@drm_fdinfo@most-busy-idle-check-all@ccs0.html

  * igt@gem_busy@semaphore:
    - shard-dg2:          NOTRUN -> [SKIP][23] ([i915#3936])
   [23]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@gem_busy@semaphore.html

  * igt@gem_ccs@block-copy-compressed:
    - shard-rkl:          NOTRUN -> [SKIP][24] ([i915#3555] / [i915#9323])
   [24]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-2/igt@gem_ccs@block-copy-compressed.html

  * igt@gem_close_race@multigpu-basic-process:
    - shard-dg2:          NOTRUN -> [SKIP][25] ([i915#7697])
   [25]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@gem_close_race@multigpu-basic-process.html
    - shard-rkl:          NOTRUN -> [SKIP][26] ([i915#7697])
   [26]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@gem_close_race@multigpu-basic-process.html
    - shard-dg1:          NOTRUN -> [SKIP][27] ([i915#7697])
   [27]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@gem_close_race@multigpu-basic-process.html

  * igt@gem_create@create-ext-cpu-access-sanity-check:
    - shard-rkl:          NOTRUN -> [SKIP][28] ([i915#6335])
   [28]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@gem_create@create-ext-cpu-access-sanity-check.html

  * igt@gem_create@create-ext-set-pat:
    - shard-dg2:          NOTRUN -> [SKIP][29] ([i915#8562])
   [29]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@gem_create@create-ext-set-pat.html
    - shard-rkl:          NOTRUN -> [SKIP][30] ([i915#8562])
   [30]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-1/igt@gem_create@create-ext-set-pat.html

  * igt@gem_ctx_persistence@engines-hang:
    - shard-snb:          NOTRUN -> [SKIP][31] ([i915#1099])
   [31]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb7/igt@gem_ctx_persistence@engines-hang.html

  * igt@gem_ctx_persistence@hang:
    - shard-mtlp:         NOTRUN -> [SKIP][32] ([i915#8555])
   [32]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-5/igt@gem_ctx_persistence@hang.html

  * igt@gem_ctx_sseu@invalid-sseu:
    - shard-rkl:          NOTRUN -> [SKIP][33] ([i915#280])
   [33]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@gem_ctx_sseu@invalid-sseu.html
    - shard-tglu:         NOTRUN -> [SKIP][34] ([i915#280])
   [34]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-4/igt@gem_ctx_sseu@invalid-sseu.html
    - shard-mtlp:         NOTRUN -> [SKIP][35] ([i915#280])
   [35]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@gem_ctx_sseu@invalid-sseu.html

  * igt@gem_eio@kms:
    - shard-dg2:          NOTRUN -> [INCOMPLETE][36] ([i915#10513] / [i915#1982])
   [36]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@gem_eio@kms.html

  * igt@gem_exec_balancer@noheartbeat:
    - shard-dg2:          NOTRUN -> [SKIP][37] ([i915#8555])
   [37]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@gem_exec_balancer@noheartbeat.html

  * igt@gem_exec_balancer@parallel:
    - shard-rkl:          NOTRUN -> [SKIP][38] ([i915#4525]) +1 other test skip
   [38]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@gem_exec_balancer@parallel.html

  * igt@gem_exec_capture@capture-invisible@lmem0:
    - shard-dg2:          NOTRUN -> [SKIP][39] ([i915#6334]) +1 other test skip
   [39]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-3/igt@gem_exec_capture@capture-invisible@lmem0.html

  * igt@gem_exec_capture@capture-invisible@smem0:
    - shard-rkl:          NOTRUN -> [SKIP][40] ([i915#6334])
   [40]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@gem_exec_capture@capture-invisible@smem0.html

  * igt@gem_exec_capture@capture@vecs0-lmem0:
    - shard-dg1:          NOTRUN -> [FAIL][41] ([i915#10386]) +1 other test fail
   [41]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@gem_exec_capture@capture@vecs0-lmem0.html

  * igt@gem_exec_capture@many-4k-zero:
    - shard-tglu:         NOTRUN -> [FAIL][42] ([i915#9606])
   [42]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-6/igt@gem_exec_capture@many-4k-zero.html
    - shard-glk:          NOTRUN -> [FAIL][43] ([i915#9606])
   [43]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-glk5/igt@gem_exec_capture@many-4k-zero.html
    - shard-mtlp:         NOTRUN -> [FAIL][44] ([i915#9606])
   [44]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-2/igt@gem_exec_capture@many-4k-zero.html
    - shard-rkl:          NOTRUN -> [FAIL][45] ([i915#9606])
   [45]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@gem_exec_capture@many-4k-zero.html

  * igt@gem_exec_fair@basic-none@vcs0:
    - shard-rkl:          [PASS][46] -> [FAIL][47] ([i915#2842]) +2 other tests fail
   [46]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-rkl-3/igt@gem_exec_fair@basic-none@vcs0.html
   [47]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-6/igt@gem_exec_fair@basic-none@vcs0.html

  * igt@gem_exec_fair@basic-pace-solo@rcs0:
    - shard-rkl:          NOTRUN -> [FAIL][48] ([i915#2842])
   [48]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@gem_exec_fair@basic-pace-solo@rcs0.html

  * igt@gem_exec_fair@basic-sync:
    - shard-mtlp:         NOTRUN -> [SKIP][49] ([i915#4473] / [i915#4771])
   [49]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-4/igt@gem_exec_fair@basic-sync.html

  * igt@gem_exec_fair@basic-throttle@rcs0:
    - shard-tglu:         [PASS][50] -> [FAIL][51] ([i915#2842])
   [50]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-tglu-9/igt@gem_exec_fair@basic-throttle@rcs0.html
   [51]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-9/igt@gem_exec_fair@basic-throttle@rcs0.html

  * igt@gem_exec_flush@basic-uc-pro-default:
    - shard-dg2:          NOTRUN -> [SKIP][52] ([i915#3539] / [i915#4852]) +1 other test skip
   [52]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@gem_exec_flush@basic-uc-pro-default.html

  * igt@gem_exec_flush@basic-uc-prw-default:
    - shard-dg2:          NOTRUN -> [SKIP][53] ([i915#3539])
   [53]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-1/igt@gem_exec_flush@basic-uc-prw-default.html

  * igt@gem_exec_flush@basic-uc-rw-default:
    - shard-dg1:          NOTRUN -> [SKIP][54] ([i915#3539] / [i915#4852]) +3 other tests skip
   [54]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@gem_exec_flush@basic-uc-rw-default.html

  * igt@gem_exec_reloc@basic-gtt-read-noreloc:
    - shard-rkl:          NOTRUN -> [SKIP][55] ([i915#3281]) +8 other tests skip
   [55]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@gem_exec_reloc@basic-gtt-read-noreloc.html
    - shard-dg1:          NOTRUN -> [SKIP][56] ([i915#3281]) +4 other tests skip
   [56]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@gem_exec_reloc@basic-gtt-read-noreloc.html

  * igt@gem_exec_reloc@basic-gtt-wc-active:
    - shard-mtlp:         NOTRUN -> [SKIP][57] ([i915#3281]) +6 other tests skip
   [57]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@gem_exec_reloc@basic-gtt-wc-active.html

  * igt@gem_exec_reloc@basic-write-read-active:
    - shard-dg2:          NOTRUN -> [SKIP][58] ([i915#3281]) +4 other tests skip
   [58]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-3/igt@gem_exec_reloc@basic-write-read-active.html

  * igt@gem_exec_schedule@preempt-queue-contexts:
    - shard-dg1:          NOTRUN -> [SKIP][59] ([i915#4812]) +1 other test skip
   [59]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@gem_exec_schedule@preempt-queue-contexts.html

  * igt@gem_exec_schedule@reorder-wide:
    - shard-dg2:          NOTRUN -> [SKIP][60] ([i915#4537] / [i915#4812]) +1 other test skip
   [60]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@gem_exec_schedule@reorder-wide.html

  * igt@gem_fenced_exec_thrash@no-spare-fences:
    - shard-dg1:          NOTRUN -> [SKIP][61] ([i915#4860]) +2 other tests skip
   [61]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@gem_fenced_exec_thrash@no-spare-fences.html

  * igt@gem_fenced_exec_thrash@no-spare-fences-interruptible:
    - shard-mtlp:         NOTRUN -> [SKIP][62] ([i915#4860])
   [62]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@gem_fenced_exec_thrash@no-spare-fences-interruptible.html

  * igt@gem_lmem_swapping@heavy-random@lmem0:
    - shard-dg2:          [PASS][63] -> [FAIL][64] ([i915#10378])
   [63]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-5/igt@gem_lmem_swapping@heavy-random@lmem0.html
   [64]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@gem_lmem_swapping@heavy-random@lmem0.html

  * igt@gem_lmem_swapping@heavy-verify-random-ccs@lmem0:
    - shard-dg1:          NOTRUN -> [SKIP][65] ([i915#4565]) +1 other test skip
   [65]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@gem_lmem_swapping@heavy-verify-random-ccs@lmem0.html

  * igt@gem_lmem_swapping@massive:
    - shard-rkl:          NOTRUN -> [SKIP][66] ([i915#4613]) +2 other tests skip
   [66]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@gem_lmem_swapping@massive.html

  * igt@gem_lmem_swapping@random-engines:
    - shard-mtlp:         NOTRUN -> [SKIP][67] ([i915#4613]) +1 other test skip
   [67]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@gem_lmem_swapping@random-engines.html

  * igt@gem_lmem_swapping@smem-oom@lmem0:
    - shard-dg2:          [PASS][68] -> [TIMEOUT][69] ([i915#5493])
   [68]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-5/igt@gem_lmem_swapping@smem-oom@lmem0.html
   [69]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-3/igt@gem_lmem_swapping@smem-oom@lmem0.html
    - shard-dg1:          [PASS][70] -> [TIMEOUT][71] ([i915#5493])
   [70]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-18/igt@gem_lmem_swapping@smem-oom@lmem0.html
   [71]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@gem_lmem_swapping@smem-oom@lmem0.html

  * igt@gem_lmem_swapping@verify-ccs:
    - shard-glk:          NOTRUN -> [SKIP][72] ([i915#4613]) +2 other tests skip
   [72]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-glk1/igt@gem_lmem_swapping@verify-ccs.html

  * igt@gem_media_fill@media-fill:
    - shard-mtlp:         NOTRUN -> [SKIP][73] ([i915#8289])
   [73]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-6/igt@gem_media_fill@media-fill.html

  * igt@gem_mmap@bad-object:
    - shard-dg1:          NOTRUN -> [SKIP][74] ([i915#4083]) +6 other tests skip
   [74]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@gem_mmap@bad-object.html

  * igt@gem_mmap_gtt@big-copy:
    - shard-dg2:          NOTRUN -> [SKIP][75] ([i915#4077]) +9 other tests skip
   [75]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@gem_mmap_gtt@big-copy.html

  * igt@gem_mmap_gtt@coherency:
    - shard-dg1:          NOTRUN -> [SKIP][76] ([i915#4077]) +13 other tests skip
   [76]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@gem_mmap_gtt@coherency.html

  * igt@gem_mmap_gtt@cpuset-medium-copy:
    - shard-mtlp:         NOTRUN -> [SKIP][77] ([i915#4077]) +6 other tests skip
   [77]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-4/igt@gem_mmap_gtt@cpuset-medium-copy.html

  * igt@gem_partial_pwrite_pread@reads-display:
    - shard-mtlp:         NOTRUN -> [SKIP][78] ([i915#3282]) +1 other test skip
   [78]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-5/igt@gem_partial_pwrite_pread@reads-display.html

  * igt@gem_partial_pwrite_pread@reads-uncached:
    - shard-dg2:          NOTRUN -> [SKIP][79] ([i915#3282]) +1 other test skip
   [79]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-6/igt@gem_partial_pwrite_pread@reads-uncached.html

  * igt@gem_pxp@display-protected-crc:
    - shard-rkl:          NOTRUN -> [SKIP][80] ([i915#4270]) +3 other tests skip
   [80]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-6/igt@gem_pxp@display-protected-crc.html
    - shard-tglu:         NOTRUN -> [SKIP][81] ([i915#4270])
   [81]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-7/igt@gem_pxp@display-protected-crc.html

  * igt@gem_pxp@regular-baseline-src-copy-readible:
    - shard-dg2:          NOTRUN -> [SKIP][82] ([i915#4270]) +1 other test skip
   [82]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@gem_pxp@regular-baseline-src-copy-readible.html

  * igt@gem_pxp@verify-pxp-stale-ctx-execution:
    - shard-dg1:          NOTRUN -> [SKIP][83] ([i915#4270]) +2 other tests skip
   [83]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@gem_pxp@verify-pxp-stale-ctx-execution.html
    - shard-mtlp:         NOTRUN -> [SKIP][84] ([i915#4270]) +1 other test skip
   [84]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@gem_pxp@verify-pxp-stale-ctx-execution.html

  * igt@gem_render_copy@y-tiled-ccs-to-y-tiled-mc-ccs:
    - shard-mtlp:         NOTRUN -> [SKIP][85] ([i915#8428]) +2 other tests skip
   [85]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-5/igt@gem_render_copy@y-tiled-ccs-to-y-tiled-mc-ccs.html

  * igt@gem_render_copy@y-tiled-ccs-to-yf-tiled:
    - shard-dg2:          NOTRUN -> [SKIP][86] ([i915#5190] / [i915#8428]) +3 other tests skip
   [86]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@gem_render_copy@y-tiled-ccs-to-yf-tiled.html

  * igt@gem_set_tiling_vs_pwrite:
    - shard-rkl:          NOTRUN -> [SKIP][87] ([i915#3282]) +4 other tests skip
   [87]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@gem_set_tiling_vs_pwrite.html
    - shard-dg2:          NOTRUN -> [SKIP][88] ([i915#4079])
   [88]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@gem_set_tiling_vs_pwrite.html

  * igt@gem_softpin@evict-snoop:
    - shard-dg1:          NOTRUN -> [SKIP][89] ([i915#4885])
   [89]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@gem_softpin@evict-snoop.html

  * igt@gem_tiled_pread_pwrite:
    - shard-dg1:          NOTRUN -> [SKIP][90] ([i915#4079])
   [90]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@gem_tiled_pread_pwrite.html
    - shard-mtlp:         NOTRUN -> [SKIP][91] ([i915#4079])
   [91]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-5/igt@gem_tiled_pread_pwrite.html

  * igt@gem_userptr_blits@access-control:
    - shard-dg1:          NOTRUN -> [SKIP][92] ([i915#3297])
   [92]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@gem_userptr_blits@access-control.html

  * igt@gem_userptr_blits@dmabuf-sync:
    - shard-tglu:         NOTRUN -> [SKIP][93] ([i915#3323])
   [93]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-6/igt@gem_userptr_blits@dmabuf-sync.html
    - shard-mtlp:         NOTRUN -> [SKIP][94] ([i915#3297])
   [94]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@gem_userptr_blits@dmabuf-sync.html

  * igt@gem_userptr_blits@invalid-mmap-offset-unsync:
    - shard-dg2:          NOTRUN -> [SKIP][95] ([i915#3297])
   [95]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@gem_userptr_blits@invalid-mmap-offset-unsync.html

  * igt@gem_userptr_blits@map-fixed-invalidate-overlap-busy:
    - shard-dg2:          NOTRUN -> [SKIP][96] ([i915#3297] / [i915#4880])
   [96]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@gem_userptr_blits@map-fixed-invalidate-overlap-busy.html

  * igt@gen9_exec_parse@allowed-single:
    - shard-mtlp:         NOTRUN -> [SKIP][97] ([i915#2856])
   [97]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@gen9_exec_parse@allowed-single.html

  * igt@gen9_exec_parse@bb-start-out:
    - shard-dg1:          NOTRUN -> [SKIP][98] ([i915#2527]) +2 other tests skip
   [98]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@gen9_exec_parse@bb-start-out.html

  * igt@gen9_exec_parse@unaligned-access:
    - shard-dg2:          NOTRUN -> [SKIP][99] ([i915#2856]) +2 other tests skip
   [99]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@gen9_exec_parse@unaligned-access.html
    - shard-rkl:          NOTRUN -> [SKIP][100] ([i915#2527]) +3 other tests skip
   [100]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-5/igt@gen9_exec_parse@unaligned-access.html

  * igt@i915_fb_tiling:
    - shard-dg1:          NOTRUN -> [SKIP][101] ([i915#4881])
   [101]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-13/igt@i915_fb_tiling.html

  * igt@i915_module_load@load:
    - shard-glk:          NOTRUN -> [SKIP][102] ([i915#6227])
   [102]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-glk6/igt@i915_module_load@load.html

  * igt@i915_module_load@reload-with-fault-injection:
    - shard-dg1:          [PASS][103] -> [INCOMPLETE][104] ([i915#1982] / [i915#9820] / [i915#9849])
   [103]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-18/igt@i915_module_load@reload-with-fault-injection.html
   [104]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@i915_module_load@reload-with-fault-injection.html

  * igt@i915_module_load@resize-bar:
    - shard-rkl:          NOTRUN -> [SKIP][105] ([i915#6412])
   [105]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@i915_module_load@resize-bar.html
    - shard-dg1:          NOTRUN -> [SKIP][106] ([i915#7178])
   [106]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@i915_module_load@resize-bar.html

  * igt@i915_pm_freq_mult@media-freq@gt0:
    - shard-dg1:          NOTRUN -> [SKIP][107] ([i915#6590])
   [107]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@i915_pm_freq_mult@media-freq@gt0.html

  * igt@i915_pm_rps@min-max-config-idle:
    - shard-dg2:          NOTRUN -> [SKIP][108] ([i915#6621])
   [108]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@i915_pm_rps@min-max-config-idle.html

  * igt@i915_pm_rps@thresholds-idle-park@gt0:
    - shard-dg2:          NOTRUN -> [SKIP][109] ([i915#8925]) +1 other test skip
   [109]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@i915_pm_rps@thresholds-idle-park@gt0.html

  * igt@i915_pm_rps@thresholds@gt0:
    - shard-dg1:          NOTRUN -> [SKIP][110] ([i915#8925])
   [110]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@i915_pm_rps@thresholds@gt0.html

  * igt@i915_pm_sseu@full-enable:
    - shard-rkl:          NOTRUN -> [SKIP][111] ([i915#4387])
   [111]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-6/igt@i915_pm_sseu@full-enable.html

  * igt@i915_query@hwconfig_table:
    - shard-dg1:          NOTRUN -> [SKIP][112] ([i915#6245])
   [112]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@i915_query@hwconfig_table.html

  * igt@i915_query@query-topology-coherent-slice-mask:
    - shard-dg2:          NOTRUN -> [SKIP][113] ([i915#6188])
   [113]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@i915_query@query-topology-coherent-slice-mask.html

  * igt@i915_selftest@mock@memory_region:
    - shard-dg2:          NOTRUN -> [DMESG-WARN][114] ([i915#9311])
   [114]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@i915_selftest@mock@memory_region.html

  * igt@i915_suspend@basic-s3-without-i915:
    - shard-tglu:         NOTRUN -> [INCOMPLETE][115] ([i915#7443])
   [115]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-5/igt@i915_suspend@basic-s3-without-i915.html
    - shard-mtlp:         NOTRUN -> [SKIP][116] ([i915#6645])
   [116]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@i915_suspend@basic-s3-without-i915.html

  * igt@kms_addfb_basic@addfb25-x-tiled-mismatch-legacy:
    - shard-dg1:          NOTRUN -> [SKIP][117] ([i915#4212])
   [117]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_addfb_basic@addfb25-x-tiled-mismatch-legacy.html

  * igt@kms_addfb_basic@addfb25-y-tiled-small-legacy:
    - shard-mtlp:         NOTRUN -> [SKIP][118] ([i915#5190])
   [118]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@kms_addfb_basic@addfb25-y-tiled-small-legacy.html

  * igt@kms_addfb_basic@basic-y-tiled-legacy:
    - shard-dg1:          NOTRUN -> [SKIP][119] ([i915#4215])
   [119]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_addfb_basic@basic-y-tiled-legacy.html

  * igt@kms_addfb_basic@bo-too-small-due-to-tiling:
    - shard-mtlp:         NOTRUN -> [SKIP][120] ([i915#4212])
   [120]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-6/igt@kms_addfb_basic@bo-too-small-due-to-tiling.html

  * igt@kms_addfb_basic@clobberred-modifier:
    - shard-dg2:          NOTRUN -> [SKIP][121] ([i915#4212])
   [121]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@kms_addfb_basic@clobberred-modifier.html

  * igt@kms_async_flips@invalid-async-flip:
    - shard-dg2:          NOTRUN -> [SKIP][122] ([i915#6228])
   [122]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-6/igt@kms_async_flips@invalid-async-flip.html

  * igt@kms_atomic@plane-primary-overlay-mutable-zpos:
    - shard-dg1:          NOTRUN -> [SKIP][123] ([i915#9531])
   [123]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@kms_atomic@plane-primary-overlay-mutable-zpos.html

  * igt@kms_big_fb@4-tiled-8bpp-rotate-180:
    - shard-dg1:          NOTRUN -> [SKIP][124] ([i915#4538] / [i915#5286]) +4 other tests skip
   [124]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_big_fb@4-tiled-8bpp-rotate-180.html

  * igt@kms_big_fb@4-tiled-addfb:
    - shard-rkl:          NOTRUN -> [SKIP][125] ([i915#5286]) +4 other tests skip
   [125]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_big_fb@4-tiled-addfb.html

  * igt@kms_big_fb@4-tiled-max-hw-stride-64bpp-rotate-0-async-flip:
    - shard-tglu:         NOTRUN -> [SKIP][126] ([i915#5286])
   [126]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-9/igt@kms_big_fb@4-tiled-max-hw-stride-64bpp-rotate-0-async-flip.html

  * igt@kms_big_fb@4-tiled-max-hw-stride-64bpp-rotate-0-hflip:
    - shard-mtlp:         [PASS][127] -> [DMESG-FAIL][128] ([i915#2017])
   [127]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-mtlp-2/igt@kms_big_fb@4-tiled-max-hw-stride-64bpp-rotate-0-hflip.html
   [128]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-6/igt@kms_big_fb@4-tiled-max-hw-stride-64bpp-rotate-0-hflip.html

  * igt@kms_big_fb@linear-32bpp-rotate-90:
    - shard-rkl:          NOTRUN -> [SKIP][129] ([i915#3638]) +3 other tests skip
   [129]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_big_fb@linear-32bpp-rotate-90.html

  * igt@kms_big_fb@y-tiled-64bpp-rotate-90:
    - shard-dg1:          NOTRUN -> [SKIP][130] ([i915#3638]) +5 other tests skip
   [130]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_big_fb@y-tiled-64bpp-rotate-90.html

  * igt@kms_big_fb@y-tiled-addfb-size-offset-overflow:
    - shard-dg2:          NOTRUN -> [SKIP][131] ([i915#5190])
   [131]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@kms_big_fb@y-tiled-addfb-size-offset-overflow.html

  * igt@kms_big_fb@y-tiled-max-hw-stride-32bpp-rotate-0-hflip:
    - shard-mtlp:         NOTRUN -> [SKIP][132] +9 other tests skip
   [132]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@kms_big_fb@y-tiled-max-hw-stride-32bpp-rotate-0-hflip.html

  * igt@kms_big_fb@y-tiled-max-hw-stride-64bpp-rotate-0-hflip-async-flip:
    - shard-dg2:          NOTRUN -> [SKIP][133] ([i915#4538] / [i915#5190]) +11 other tests skip
   [133]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_big_fb@y-tiled-max-hw-stride-64bpp-rotate-0-hflip-async-flip.html

  * igt@kms_big_fb@yf-tiled-addfb:
    - shard-mtlp:         NOTRUN -> [SKIP][134] ([i915#6187]) +1 other test skip
   [134]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-1/igt@kms_big_fb@yf-tiled-addfb.html

  * igt@kms_big_fb@yf-tiled-max-hw-stride-32bpp-rotate-0:
    - shard-dg1:          NOTRUN -> [SKIP][135] ([i915#4538]) +3 other tests skip
   [135]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_big_fb@yf-tiled-max-hw-stride-32bpp-rotate-0.html

  * igt@kms_big_joiner@basic-force-joiner:
    - shard-dg2:          NOTRUN -> [SKIP][136] ([i915#10656])
   [136]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@kms_big_joiner@basic-force-joiner.html

  * igt@kms_big_joiner@invalid-modeset-force-joiner:
    - shard-dg1:          NOTRUN -> [SKIP][137] ([i915#10656])
   [137]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_big_joiner@invalid-modeset-force-joiner.html

  * igt@kms_ccs@bad-pixel-format-y-tiled-ccs@pipe-d-hdmi-a-1:
    - shard-dg2:          NOTRUN -> [SKIP][138] ([i915#10307] / [i915#10434] / [i915#6095]) +3 other tests skip
   [138]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@kms_ccs@bad-pixel-format-y-tiled-ccs@pipe-d-hdmi-a-1.html

  * igt@kms_ccs@bad-rotation-90-4-tiled-mtl-rc-ccs@pipe-b-hdmi-a-2:
    - shard-rkl:          NOTRUN -> [SKIP][139] ([i915#6095]) +71 other tests skip
   [139]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@kms_ccs@bad-rotation-90-4-tiled-mtl-rc-ccs@pipe-b-hdmi-a-2.html

  * igt@kms_ccs@bad-rotation-90-4-tiled-xe2-ccs:
    - shard-dg1:          NOTRUN -> [SKIP][140] ([i915#10278])
   [140]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@kms_ccs@bad-rotation-90-4-tiled-xe2-ccs.html

  * igt@kms_ccs@ccs-on-another-bo-y-tiled-ccs@pipe-b-hdmi-a-1:
    - shard-dg2:          NOTRUN -> [SKIP][141] ([i915#10307] / [i915#6095]) +155 other tests skip
   [141]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@kms_ccs@ccs-on-another-bo-y-tiled-ccs@pipe-b-hdmi-a-1.html

  * igt@kms_ccs@crc-primary-basic-yf-tiled-ccs@pipe-c-hdmi-a-1:
    - shard-tglu:         NOTRUN -> [SKIP][142] ([i915#6095]) +15 other tests skip
   [142]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-3/igt@kms_ccs@crc-primary-basic-yf-tiled-ccs@pipe-c-hdmi-a-1.html

  * igt@kms_ccs@crc-primary-rotation-180-4-tiled-dg2-mc-ccs@pipe-c-edp-1:
    - shard-mtlp:         NOTRUN -> [SKIP][143] ([i915#6095]) +31 other tests skip
   [143]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-1/igt@kms_ccs@crc-primary-rotation-180-4-tiled-dg2-mc-ccs@pipe-c-edp-1.html

  * igt@kms_ccs@crc-primary-rotation-180-y-tiled-gen12-mc-ccs@pipe-c-hdmi-a-4:
    - shard-dg1:          NOTRUN -> [SKIP][144] ([i915#6095]) +83 other tests skip
   [144]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_ccs@crc-primary-rotation-180-y-tiled-gen12-mc-ccs@pipe-c-hdmi-a-4.html

  * igt@kms_ccs@crc-sprite-planes-basic-4-tiled-xe2-ccs:
    - shard-rkl:          NOTRUN -> [SKIP][145] ([i915#10278])
   [145]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_ccs@crc-sprite-planes-basic-4-tiled-xe2-ccs.html

  * igt@kms_cdclk@mode-transition-all-outputs:
    - shard-tglu:         NOTRUN -> [SKIP][146] ([i915#3742])
   [146]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-8/igt@kms_cdclk@mode-transition-all-outputs.html
    - shard-mtlp:         NOTRUN -> [SKIP][147] ([i915#7213] / [i915#9010])
   [147]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-1/igt@kms_cdclk@mode-transition-all-outputs.html
    - shard-rkl:          NOTRUN -> [SKIP][148] ([i915#3742])
   [148]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_cdclk@mode-transition-all-outputs.html

  * igt@kms_cdclk@mode-transition@pipe-d-hdmi-a-1:
    - shard-dg2:          NOTRUN -> [SKIP][149] ([i915#7213]) +3 other tests skip
   [149]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@kms_cdclk@mode-transition@pipe-d-hdmi-a-1.html

  * igt@kms_chamelium_edid@hdmi-edid-change-during-suspend:
    - shard-rkl:          NOTRUN -> [SKIP][150] ([i915#7828]) +8 other tests skip
   [150]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@kms_chamelium_edid@hdmi-edid-change-during-suspend.html

  * igt@kms_chamelium_frames@dp-crc-single:
    - shard-tglu:         NOTRUN -> [SKIP][151] ([i915#7828]) +1 other test skip
   [151]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-3/igt@kms_chamelium_frames@dp-crc-single.html

  * igt@kms_chamelium_frames@hdmi-crc-multiple:
    - shard-dg2:          NOTRUN -> [SKIP][152] ([i915#7828]) +7 other tests skip
   [152]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@kms_chamelium_frames@hdmi-crc-multiple.html

  * igt@kms_chamelium_hpd@hdmi-hpd-with-enabled-mode:
    - shard-mtlp:         NOTRUN -> [SKIP][153] ([i915#7828]) +3 other tests skip
   [153]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@kms_chamelium_hpd@hdmi-hpd-with-enabled-mode.html

  * igt@kms_chamelium_hpd@vga-hpd-for-each-pipe:
    - shard-dg1:          NOTRUN -> [SKIP][154] ([i915#7828]) +7 other tests skip
   [154]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_chamelium_hpd@vga-hpd-for-each-pipe.html

  * igt@kms_color@deep-color:
    - shard-tglu:         NOTRUN -> [SKIP][155] ([i915#3555] / [i915#9979])
   [155]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-5/igt@kms_color@deep-color.html

  * igt@kms_content_protection@content-type-change:
    - shard-tglu:         NOTRUN -> [SKIP][156] ([i915#6944] / [i915#9424])
   [156]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-5/igt@kms_content_protection@content-type-change.html
    - shard-mtlp:         NOTRUN -> [SKIP][157] ([i915#6944] / [i915#9424])
   [157]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-5/igt@kms_content_protection@content-type-change.html

  * igt@kms_content_protection@dp-mst-lic-type-0:
    - shard-mtlp:         NOTRUN -> [SKIP][158] ([i915#3299])
   [158]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-2/igt@kms_content_protection@dp-mst-lic-type-0.html
    - shard-dg1:          NOTRUN -> [SKIP][159] ([i915#3299])
   [159]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-13/igt@kms_content_protection@dp-mst-lic-type-0.html

  * igt@kms_content_protection@dp-mst-type-0:
    - shard-rkl:          NOTRUN -> [SKIP][160] ([i915#3116])
   [160]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-5/igt@kms_content_protection@dp-mst-type-0.html
    - shard-dg2:          NOTRUN -> [SKIP][161] ([i915#3299])
   [161]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@kms_content_protection@dp-mst-type-0.html

  * igt@kms_content_protection@legacy:
    - shard-dg1:          NOTRUN -> [SKIP][162] ([i915#7116] / [i915#9424])
   [162]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_content_protection@legacy.html

  * igt@kms_content_protection@uevent:
    - shard-dg2:          NOTRUN -> [SKIP][163] ([i915#7118] / [i915#9424]) +1 other test skip
   [163]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@kms_content_protection@uevent.html

  * igt@kms_cursor_crc@cursor-onscreen-32x32:
    - shard-rkl:          NOTRUN -> [SKIP][164] ([i915#3555]) +8 other tests skip
   [164]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-5/igt@kms_cursor_crc@cursor-onscreen-32x32.html

  * igt@kms_cursor_crc@cursor-onscreen-max-size:
    - shard-dg2:          NOTRUN -> [SKIP][165] ([i915#3555]) +3 other tests skip
   [165]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@kms_cursor_crc@cursor-onscreen-max-size.html

  * igt@kms_cursor_crc@cursor-random-32x10:
    - shard-tglu:         NOTRUN -> [SKIP][166] ([i915#3555]) +1 other test skip
   [166]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-8/igt@kms_cursor_crc@cursor-random-32x10.html

  * igt@kms_cursor_crc@cursor-random-32x32:
    - shard-dg1:          NOTRUN -> [SKIP][167] ([i915#3555]) +7 other tests skip
   [167]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_cursor_crc@cursor-random-32x32.html

  * igt@kms_cursor_crc@cursor-random-max-size:
    - shard-mtlp:         NOTRUN -> [SKIP][168] ([i915#3555] / [i915#8814]) +2 other tests skip
   [168]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-1/igt@kms_cursor_crc@cursor-random-max-size.html

  * igt@kms_cursor_crc@cursor-rapid-movement-512x512:
    - shard-dg2:          NOTRUN -> [SKIP][169] ([i915#3359])
   [169]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@kms_cursor_crc@cursor-rapid-movement-512x512.html

  * igt@kms_cursor_crc@cursor-sliding-256x85:
    - shard-mtlp:         NOTRUN -> [SKIP][170] ([i915#8814])
   [170]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@kms_cursor_crc@cursor-sliding-256x85.html

  * igt@kms_cursor_crc@cursor-sliding-512x512:
    - shard-rkl:          NOTRUN -> [SKIP][171] ([i915#3359])
   [171]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@kms_cursor_crc@cursor-sliding-512x512.html
    - shard-dg1:          NOTRUN -> [SKIP][172] ([i915#3359]) +1 other test skip
   [172]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@kms_cursor_crc@cursor-sliding-512x512.html

  * igt@kms_cursor_legacy@2x-cursor-vs-flip-legacy:
    - shard-mtlp:         NOTRUN -> [SKIP][173] ([i915#9809]) +1 other test skip
   [173]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@kms_cursor_legacy@2x-cursor-vs-flip-legacy.html

  * igt@kms_cursor_legacy@flip-vs-cursor-atomic-transitions-varying-size:
    - shard-glk:          [PASS][174] -> [FAIL][175] ([i915#2346])
   [174]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-glk8/igt@kms_cursor_legacy@flip-vs-cursor-atomic-transitions-varying-size.html
   [175]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-glk6/igt@kms_cursor_legacy@flip-vs-cursor-atomic-transitions-varying-size.html

  * igt@kms_cursor_legacy@modeset-atomic-cursor-hotspot:
    - shard-dg2:          NOTRUN -> [SKIP][176] ([i915#9067])
   [176]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@kms_cursor_legacy@modeset-atomic-cursor-hotspot.html

  * igt@kms_cursor_legacy@short-busy-flip-before-cursor-atomic-transitions:
    - shard-dg1:          NOTRUN -> [SKIP][177] ([i915#4103] / [i915#4213])
   [177]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-13/igt@kms_cursor_legacy@short-busy-flip-before-cursor-atomic-transitions.html

  * igt@kms_cursor_legacy@short-busy-flip-before-cursor-toggle:
    - shard-tglu:         NOTRUN -> [SKIP][178] ([i915#4103])
   [178]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-5/igt@kms_cursor_legacy@short-busy-flip-before-cursor-toggle.html
    - shard-mtlp:         NOTRUN -> [SKIP][179] ([i915#4213]) +1 other test skip
   [179]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-4/igt@kms_cursor_legacy@short-busy-flip-before-cursor-toggle.html

  * igt@kms_dirtyfb@fbc-dirtyfb-ioctl@a-hdmi-a-1:
    - shard-rkl:          NOTRUN -> [SKIP][180] ([i915#9723])
   [180]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-2/igt@kms_dirtyfb@fbc-dirtyfb-ioctl@a-hdmi-a-1.html

  * igt@kms_dp_aux_dev:
    - shard-dg2:          NOTRUN -> [SKIP][181] ([i915#1257])
   [181]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_dp_aux_dev.html

  * igt@kms_draw_crc@draw-method-mmap-gtt:
    - shard-dg1:          NOTRUN -> [SKIP][182] ([i915#8812])
   [182]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@kms_draw_crc@draw-method-mmap-gtt.html

  * igt@kms_dsc@dsc-fractional-bpp-with-bpc:
    - shard-rkl:          NOTRUN -> [SKIP][183] ([i915#3840])
   [183]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_dsc@dsc-fractional-bpp-with-bpc.html
    - shard-dg1:          NOTRUN -> [SKIP][184] ([i915#3840])
   [184]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_dsc@dsc-fractional-bpp-with-bpc.html
    - shard-mtlp:         NOTRUN -> [SKIP][185] ([i915#3840])
   [185]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-4/igt@kms_dsc@dsc-fractional-bpp-with-bpc.html

  * igt@kms_dsc@dsc-with-bpc:
    - shard-rkl:          NOTRUN -> [SKIP][186] ([i915#3555] / [i915#3840])
   [186]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-6/igt@kms_dsc@dsc-with-bpc.html

  * igt@kms_dsc@dsc-with-output-formats:
    - shard-dg2:          NOTRUN -> [SKIP][187] ([i915#3555] / [i915#3840]) +1 other test skip
   [187]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_dsc@dsc-with-output-formats.html

  * igt@kms_fbcon_fbt@psr:
    - shard-dg1:          NOTRUN -> [SKIP][188] ([i915#3469])
   [188]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@kms_fbcon_fbt@psr.html

  * igt@kms_feature_discovery@chamelium:
    - shard-dg1:          NOTRUN -> [SKIP][189] ([i915#4854])
   [189]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@kms_feature_discovery@chamelium.html

  * igt@kms_feature_discovery@display-4x:
    - shard-rkl:          NOTRUN -> [SKIP][190] ([i915#1839])
   [190]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-2/igt@kms_feature_discovery@display-4x.html
    - shard-tglu:         NOTRUN -> [SKIP][191] ([i915#1839])
   [191]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-3/igt@kms_feature_discovery@display-4x.html
    - shard-mtlp:         NOTRUN -> [SKIP][192] ([i915#1839])
   [192]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@kms_feature_discovery@display-4x.html

  * igt@kms_feature_discovery@psr1:
    - shard-rkl:          NOTRUN -> [SKIP][193] ([i915#658])
   [193]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-5/igt@kms_feature_discovery@psr1.html

  * igt@kms_flip@2x-flip-vs-fences-interruptible:
    - shard-dg1:          NOTRUN -> [SKIP][194] ([i915#8381])
   [194]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_flip@2x-flip-vs-fences-interruptible.html

  * igt@kms_flip@2x-modeset-vs-vblank-race:
    - shard-tglu:         NOTRUN -> [SKIP][195] ([i915#3637]) +3 other tests skip
   [195]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-4/igt@kms_flip@2x-modeset-vs-vblank-race.html
    - shard-mtlp:         NOTRUN -> [SKIP][196] ([i915#3637]) +4 other tests skip
   [196]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@kms_flip@2x-modeset-vs-vblank-race.html

  * igt@kms_flip@2x-plain-flip-ts-check@ab-vga1-hdmi-a1:
    - shard-snb:          [PASS][197] -> [FAIL][198] ([i915#2122])
   [197]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-snb5/igt@kms_flip@2x-plain-flip-ts-check@ab-vga1-hdmi-a1.html
   [198]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb7/igt@kms_flip@2x-plain-flip-ts-check@ab-vga1-hdmi-a1.html

  * igt@kms_flip@2x-single-buffer-flip-vs-dpms-off-vs-modeset:
    - shard-dg1:          NOTRUN -> [SKIP][199] ([i915#9934]) +9 other tests skip
   [199]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_flip@2x-single-buffer-flip-vs-dpms-off-vs-modeset.html

  * igt@kms_flip@flip-vs-fences:
    - shard-dg2:          NOTRUN -> [SKIP][200] ([i915#8381])
   [200]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@kms_flip@flip-vs-fences.html

  * igt@kms_flip@plain-flip-fb-recreate@a-hdmi-a1:
    - shard-rkl:          [PASS][201] -> [FAIL][202] ([i915#2122]) +1 other test fail
   [201]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-rkl-5/igt@kms_flip@plain-flip-fb-recreate@a-hdmi-a1.html
   [202]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-5/igt@kms_flip@plain-flip-fb-recreate@a-hdmi-a1.html

  * igt@kms_flip_scaled_crc@flip-32bpp-ytile-to-32bpp-ytileccs-upscaling@pipe-a-default-mode:
    - shard-mtlp:         NOTRUN -> [SKIP][203] ([i915#2672]) +1 other test skip
   [203]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@kms_flip_scaled_crc@flip-32bpp-ytile-to-32bpp-ytileccs-upscaling@pipe-a-default-mode.html

  * igt@kms_flip_scaled_crc@flip-32bpp-ytile-to-32bpp-ytileccs-upscaling@pipe-a-valid-mode:
    - shard-rkl:          NOTRUN -> [SKIP][204] ([i915#2672]) +1 other test skip
   [204]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_flip_scaled_crc@flip-32bpp-ytile-to-32bpp-ytileccs-upscaling@pipe-a-valid-mode.html
    - shard-dg1:          NOTRUN -> [SKIP][205] ([i915#2587] / [i915#2672]) +1 other test skip
   [205]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_flip_scaled_crc@flip-32bpp-ytile-to-32bpp-ytileccs-upscaling@pipe-a-valid-mode.html

  * igt@kms_flip_scaled_crc@flip-32bpp-ytile-to-32bpp-ytilegen12rcccs-downscaling@pipe-a-default-mode:
    - shard-mtlp:         NOTRUN -> [SKIP][206] ([i915#2672] / [i915#3555])
   [206]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@kms_flip_scaled_crc@flip-32bpp-ytile-to-32bpp-ytilegen12rcccs-downscaling@pipe-a-default-mode.html

  * igt@kms_flip_scaled_crc@flip-64bpp-xtile-to-16bpp-xtile-downscaling@pipe-a-default-mode:
    - shard-mtlp:         NOTRUN -> [SKIP][207] ([i915#3555] / [i915#8810]) +1 other test skip
   [207]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@kms_flip_scaled_crc@flip-64bpp-xtile-to-16bpp-xtile-downscaling@pipe-a-default-mode.html

  * igt@kms_flip_scaled_crc@flip-64bpp-ytile-to-32bpp-ytile-upscaling@pipe-a-valid-mode:
    - shard-dg2:          NOTRUN -> [SKIP][208] ([i915#2672]) +3 other tests skip
   [208]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@kms_flip_scaled_crc@flip-64bpp-ytile-to-32bpp-ytile-upscaling@pipe-a-valid-mode.html

  * igt@kms_force_connector_basic@prune-stale-modes:
    - shard-dg2:          NOTRUN -> [SKIP][209] ([i915#5274])
   [209]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@kms_force_connector_basic@prune-stale-modes.html

  * igt@kms_frontbuffer_tracking@fbc-2p-primscrn-pri-indfb-draw-mmap-wc:
    - shard-rkl:          NOTRUN -> [SKIP][210] ([i915#1825]) +38 other tests skip
   [210]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_frontbuffer_tracking@fbc-2p-primscrn-pri-indfb-draw-mmap-wc.html

  * igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-cur-indfb-draw-render:
    - shard-dg1:          NOTRUN -> [SKIP][211] +42 other tests skip
   [211]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-cur-indfb-draw-render.html

  * igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-indfb-plflip-blt:
    - shard-dg2:          NOTRUN -> [SKIP][212] ([i915#5354]) +29 other tests skip
   [212]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-indfb-plflip-blt.html

  * igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-pri-indfb-draw-render:
    - shard-mtlp:         NOTRUN -> [SKIP][213] ([i915#1825]) +19 other tests skip
   [213]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@kms_frontbuffer_tracking@fbc-2p-scndscrn-pri-indfb-draw-render.html

  * igt@kms_frontbuffer_tracking@fbc-rgb565-draw-blt:
    - shard-dg2:          [PASS][214] -> [FAIL][215] ([i915#6880])
   [214]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-6/igt@kms_frontbuffer_tracking@fbc-rgb565-draw-blt.html
   [215]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-6/igt@kms_frontbuffer_tracking@fbc-rgb565-draw-blt.html

  * igt@kms_frontbuffer_tracking@fbc-tiling-4:
    - shard-rkl:          NOTRUN -> [SKIP][216] ([i915#5439])
   [216]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-2/igt@kms_frontbuffer_tracking@fbc-tiling-4.html

  * igt@kms_frontbuffer_tracking@fbc-tiling-y:
    - shard-mtlp:         NOTRUN -> [SKIP][217] ([i915#10055])
   [217]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@kms_frontbuffer_tracking@fbc-tiling-y.html

  * igt@kms_frontbuffer_tracking@fbcpsr-1p-primscrn-shrfb-plflip-blt:
    - shard-dg2:          NOTRUN -> [SKIP][218] ([i915#3458]) +12 other tests skip
   [218]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_frontbuffer_tracking@fbcpsr-1p-primscrn-shrfb-plflip-blt.html

  * igt@kms_frontbuffer_tracking@fbcpsr-2p-primscrn-shrfb-msflip-blt:
    - shard-tglu:         NOTRUN -> [SKIP][219] +26 other tests skip
   [219]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-8/igt@kms_frontbuffer_tracking@fbcpsr-2p-primscrn-shrfb-msflip-blt.html

  * igt@kms_frontbuffer_tracking@fbcpsr-2p-shrfb-fliptrack-mmap-gtt:
    - shard-dg2:          NOTRUN -> [SKIP][220] ([i915#8708]) +14 other tests skip
   [220]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@kms_frontbuffer_tracking@fbcpsr-2p-shrfb-fliptrack-mmap-gtt.html

  * igt@kms_frontbuffer_tracking@fbcpsr-rgb101010-draw-blt:
    - shard-snb:          NOTRUN -> [SKIP][221] +17 other tests skip
   [221]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb6/igt@kms_frontbuffer_tracking@fbcpsr-rgb101010-draw-blt.html

  * igt@kms_frontbuffer_tracking@fbcpsr-rgb565-draw-mmap-wc:
    - shard-dg1:          NOTRUN -> [SKIP][222] ([i915#8708]) +18 other tests skip
   [222]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@kms_frontbuffer_tracking@fbcpsr-rgb565-draw-mmap-wc.html

  * igt@kms_frontbuffer_tracking@psr-1p-offscren-pri-shrfb-draw-blt:
    - shard-dg1:          NOTRUN -> [SKIP][223] ([i915#3458]) +23 other tests skip
   [223]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_frontbuffer_tracking@psr-1p-offscren-pri-shrfb-draw-blt.html

  * igt@kms_frontbuffer_tracking@psr-2p-primscrn-spr-indfb-draw-mmap-gtt:
    - shard-mtlp:         NOTRUN -> [SKIP][224] ([i915#8708]) +2 other tests skip
   [224]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@kms_frontbuffer_tracking@psr-2p-primscrn-spr-indfb-draw-mmap-gtt.html

  * igt@kms_frontbuffer_tracking@psr-modesetfrombusy:
    - shard-rkl:          NOTRUN -> [SKIP][225] ([i915#3023]) +24 other tests skip
   [225]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@kms_frontbuffer_tracking@psr-modesetfrombusy.html

  * igt@kms_getfb@getfb-reject-ccs:
    - shard-dg2:          NOTRUN -> [SKIP][226] ([i915#6118])
   [226]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@kms_getfb@getfb-reject-ccs.html

  * igt@kms_hdr@invalid-hdr:
    - shard-dg2:          NOTRUN -> [SKIP][227] ([i915#3555] / [i915#8228])
   [227]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-1/igt@kms_hdr@invalid-hdr.html

  * igt@kms_hdr@invalid-metadata-sizes:
    - shard-mtlp:         NOTRUN -> [SKIP][228] ([i915#3555] / [i915#8228])
   [228]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@kms_hdr@invalid-metadata-sizes.html
    - shard-rkl:          NOTRUN -> [SKIP][229] ([i915#3555] / [i915#8228]) +2 other tests skip
   [229]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_hdr@invalid-metadata-sizes.html

  * igt@kms_hdr@static-swap:
    - shard-dg1:          NOTRUN -> [SKIP][230] ([i915#3555] / [i915#8228]) +2 other tests skip
   [230]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@kms_hdr@static-swap.html

  * igt@kms_multipipe_modeset@basic-max-pipe-crc-check:
    - shard-dg1:          NOTRUN -> [SKIP][231] ([i915#1839])
   [231]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@kms_multipipe_modeset@basic-max-pipe-crc-check.html

  * igt@kms_pipe_b_c_ivb@disable-pipe-b-enable-pipe-c:
    - shard-dg2:          NOTRUN -> [SKIP][232] +22 other tests skip
   [232]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_pipe_b_c_ivb@disable-pipe-b-enable-pipe-c.html

  * igt@kms_plane_multiple@tiling-y:
    - shard-dg2:          NOTRUN -> [SKIP][233] ([i915#8806])
   [233]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@kms_plane_multiple@tiling-y.html

  * igt@kms_plane_scaling@intel-max-src-size:
    - shard-dg2:          NOTRUN -> [SKIP][234] ([i915#6953] / [i915#9423])
   [234]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-3/igt@kms_plane_scaling@intel-max-src-size.html

  * igt@kms_plane_scaling@intel-max-src-size@pipe-a-hdmi-a-2:
    - shard-rkl:          NOTRUN -> [FAIL][235] ([i915#8292])
   [235]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-1/igt@kms_plane_scaling@intel-max-src-size@pipe-a-hdmi-a-2.html

  * igt@kms_plane_scaling@plane-downscale-factor-0-25-with-pixel-format@pipe-c-hdmi-a-2:
    - shard-dg2:          NOTRUN -> [SKIP][236] ([i915#9423]) +7 other tests skip
   [236]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-3/igt@kms_plane_scaling@plane-downscale-factor-0-25-with-pixel-format@pipe-c-hdmi-a-2.html

  * igt@kms_plane_scaling@plane-scaler-unity-scaling-with-rotation@pipe-a-hdmi-a-1:
    - shard-tglu:         NOTRUN -> [SKIP][237] ([i915#9423]) +3 other tests skip
   [237]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-9/igt@kms_plane_scaling@plane-scaler-unity-scaling-with-rotation@pipe-a-hdmi-a-1.html

  * igt@kms_plane_scaling@plane-scaler-unity-scaling-with-rotation@pipe-b-hdmi-a-1:
    - shard-rkl:          NOTRUN -> [SKIP][238] ([i915#9423]) +5 other tests skip
   [238]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-5/igt@kms_plane_scaling@plane-scaler-unity-scaling-with-rotation@pipe-b-hdmi-a-1.html

  * igt@kms_plane_scaling@plane-scaler-with-clipping-clamping-rotation@pipe-c-hdmi-a-4:
    - shard-dg1:          NOTRUN -> [SKIP][239] ([i915#5176] / [i915#9423]) +3 other tests skip
   [239]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_plane_scaling@plane-scaler-with-clipping-clamping-rotation@pipe-c-hdmi-a-4.html

  * igt@kms_plane_scaling@plane-upscale-20x20-with-rotation@pipe-a-hdmi-a-4:
    - shard-dg1:          NOTRUN -> [SKIP][240] ([i915#9423]) +3 other tests skip
   [240]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@kms_plane_scaling@plane-upscale-20x20-with-rotation@pipe-a-hdmi-a-4.html

  * igt@kms_plane_scaling@planes-downscale-factor-0-25-upscale-factor-0-25@pipe-b-hdmi-a-1:
    - shard-rkl:          NOTRUN -> [SKIP][241] ([i915#5235]) +3 other tests skip
   [241]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_plane_scaling@planes-downscale-factor-0-25-upscale-factor-0-25@pipe-b-hdmi-a-1.html

  * igt@kms_plane_scaling@planes-downscale-factor-0-25@pipe-c-hdmi-a-4:
    - shard-dg1:          NOTRUN -> [SKIP][242] ([i915#5235]) +7 other tests skip
   [242]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_plane_scaling@planes-downscale-factor-0-25@pipe-c-hdmi-a-4.html

  * igt@kms_plane_scaling@planes-downscale-factor-0-25@pipe-d-hdmi-a-2:
    - shard-dg2:          NOTRUN -> [SKIP][243] ([i915#5235] / [i915#9423] / [i915#9728]) +3 other tests skip
   [243]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_plane_scaling@planes-downscale-factor-0-25@pipe-d-hdmi-a-2.html

  * igt@kms_plane_scaling@planes-downscale-factor-0-75@pipe-a-edp-1:
    - shard-mtlp:         NOTRUN -> [SKIP][244] ([i915#5235]) +5 other tests skip
   [244]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-5/igt@kms_plane_scaling@planes-downscale-factor-0-75@pipe-a-edp-1.html

  * igt@kms_plane_scaling@planes-downscale-factor-0-75@pipe-d-edp-1:
    - shard-mtlp:         NOTRUN -> [SKIP][245] ([i915#3555] / [i915#5235]) +1 other test skip
   [245]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-5/igt@kms_plane_scaling@planes-downscale-factor-0-75@pipe-d-edp-1.html

  * igt@kms_plane_scaling@planes-unity-scaling-downscale-factor-0-25@pipe-b-dp-4:
    - shard-dg2:          NOTRUN -> [SKIP][246] ([i915#5235] / [i915#9423]) +15 other tests skip
   [246]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@kms_plane_scaling@planes-unity-scaling-downscale-factor-0-25@pipe-b-dp-4.html

  * igt@kms_pm_backlight@fade-with-suspend:
    - shard-rkl:          NOTRUN -> [SKIP][247] ([i915#5354])
   [247]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-6/igt@kms_pm_backlight@fade-with-suspend.html

  * igt@kms_pm_dc@dc6-psr:
    - shard-dg1:          NOTRUN -> [SKIP][248] ([i915#9685])
   [248]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_pm_dc@dc6-psr.html

  * igt@kms_pm_lpsp@kms-lpsp:
    - shard-dg2:          NOTRUN -> [SKIP][249] ([i915#9340])
   [249]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_pm_lpsp@kms-lpsp.html

  * igt@kms_pm_rpm@dpms-lpsp:
    - shard-rkl:          [PASS][250] -> [SKIP][251] ([i915#9519])
   [250]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-rkl-5/igt@kms_pm_rpm@dpms-lpsp.html
   [251]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-1/igt@kms_pm_rpm@dpms-lpsp.html

  * igt@kms_pm_rpm@dpms-mode-unset-lpsp:
    - shard-dg2:          NOTRUN -> [SKIP][252] ([i915#9519])
   [252]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@kms_pm_rpm@dpms-mode-unset-lpsp.html
    - shard-rkl:          NOTRUN -> [SKIP][253] ([i915#9519])
   [253]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-1/igt@kms_pm_rpm@dpms-mode-unset-lpsp.html

  * igt@kms_pm_rpm@modeset-non-lpsp-stress:
    - shard-dg2:          [PASS][254] -> [SKIP][255] ([i915#9519]) +2 other tests skip
   [254]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-2/igt@kms_pm_rpm@modeset-non-lpsp-stress.html
   [255]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@kms_pm_rpm@modeset-non-lpsp-stress.html

  * igt@kms_prime@basic-modeset-hybrid:
    - shard-dg1:          NOTRUN -> [SKIP][256] ([i915#6524]) +1 other test skip
   [256]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_prime@basic-modeset-hybrid.html

  * igt@kms_psr2_sf@fbc-overlay-plane-move-continuous-sf@psr2-pipe-a-edp-1:
    - shard-mtlp:         NOTRUN -> [SKIP][257] ([i915#9808]) +3 other tests skip
   [257]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-2/igt@kms_psr2_sf@fbc-overlay-plane-move-continuous-sf@psr2-pipe-a-edp-1.html

  * igt@kms_psr2_su@page_flip-nv12:
    - shard-dg2:          NOTRUN -> [SKIP][258] ([i915#9683])
   [258]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@kms_psr2_su@page_flip-nv12.html
    - shard-rkl:          NOTRUN -> [SKIP][259] ([i915#9683])
   [259]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@kms_psr2_su@page_flip-nv12.html

  * igt@kms_psr@fbc-pr-sprite-render:
    - shard-tglu:         NOTRUN -> [SKIP][260] ([i915#9732]) +5 other tests skip
   [260]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-5/igt@kms_psr@fbc-pr-sprite-render.html

  * igt@kms_psr@fbc-pr-suspend:
    - shard-dg2:          NOTRUN -> [SKIP][261] ([i915#1072] / [i915#9673] / [i915#9732])
   [261]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@kms_psr@fbc-pr-suspend.html

  * igt@kms_psr@fbc-psr-cursor-blt@edp-1:
    - shard-mtlp:         NOTRUN -> [SKIP][262] ([i915#9688]) +4 other tests skip
   [262]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-4/igt@kms_psr@fbc-psr-cursor-blt@edp-1.html

  * igt@kms_psr@fbc-psr-primary-blt:
    - shard-dg2:          NOTRUN -> [SKIP][263] ([i915#1072] / [i915#9732]) +15 other tests skip
   [263]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-6/igt@kms_psr@fbc-psr-primary-blt.html

  * igt@kms_psr@fbc-psr2-cursor-mmap-gtt:
    - shard-glk:          NOTRUN -> [SKIP][264] +333 other tests skip
   [264]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-glk5/igt@kms_psr@fbc-psr2-cursor-mmap-gtt.html

  * igt@kms_psr@fbc-psr2-sprite-render:
    - shard-rkl:          NOTRUN -> [SKIP][265] ([i915#1072] / [i915#9732]) +19 other tests skip
   [265]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@kms_psr@fbc-psr2-sprite-render.html

  * igt@kms_psr@pr-cursor-mmap-gtt:
    - shard-dg1:          NOTRUN -> [SKIP][266] ([i915#1072] / [i915#9732]) +21 other tests skip
   [266]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@kms_psr@pr-cursor-mmap-gtt.html

  * igt@kms_psr@psr2-primary-mmap-gtt@edp-1:
    - shard-mtlp:         NOTRUN -> [SKIP][267] ([i915#4077] / [i915#9688]) +1 other test skip
   [267]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-6/igt@kms_psr@psr2-primary-mmap-gtt@edp-1.html

  * igt@kms_psr_stress_test@flip-primary-invalidate-overlay:
    - shard-rkl:          NOTRUN -> [SKIP][268] ([i915#9685]) +1 other test skip
   [268]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-6/igt@kms_psr_stress_test@flip-primary-invalidate-overlay.html

  * igt@kms_psr_stress_test@invalidate-primary-flip-overlay:
    - shard-dg2:          NOTRUN -> [SKIP][269] ([i915#9685]) +1 other test skip
   [269]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@kms_psr_stress_test@invalidate-primary-flip-overlay.html

  * igt@kms_rotation_crc@primary-yf-tiled-reflect-x-90:
    - shard-dg1:          NOTRUN -> [SKIP][270] ([i915#5289]) +2 other tests skip
   [270]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@kms_rotation_crc@primary-yf-tiled-reflect-x-90.html

  * igt@kms_rotation_crc@sprite-rotation-270:
    - shard-mtlp:         NOTRUN -> [SKIP][271] ([i915#4235]) +1 other test skip
   [271]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-6/igt@kms_rotation_crc@sprite-rotation-270.html

  * igt@kms_rotation_crc@sprite-rotation-90:
    - shard-dg2:          NOTRUN -> [SKIP][272] ([i915#4235]) +3 other tests skip
   [272]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@kms_rotation_crc@sprite-rotation-90.html

  * igt@kms_setmode@basic@pipe-a-hdmi-a-1:
    - shard-snb:          [PASS][273] -> [FAIL][274] ([i915#5465])
   [273]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-snb7/igt@kms_setmode@basic@pipe-a-hdmi-a-1.html
   [274]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb6/igt@kms_setmode@basic@pipe-a-hdmi-a-1.html

  * igt@kms_sysfs_edid_timing:
    - shard-dg1:          NOTRUN -> [FAIL][275] ([IGT#2] / [i915#6493])
   [275]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@kms_sysfs_edid_timing.html

  * igt@kms_universal_plane@cursor-fb-leak@pipe-b-hdmi-a-4:
    - shard-dg1:          [PASS][276] -> [FAIL][277] ([i915#9196])
   [276]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-16/igt@kms_universal_plane@cursor-fb-leak@pipe-b-hdmi-a-4.html
   [277]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_universal_plane@cursor-fb-leak@pipe-b-hdmi-a-4.html

  * igt@kms_vrr@max-min:
    - shard-mtlp:         NOTRUN -> [SKIP][278] ([i915#8808] / [i915#9906]) +1 other test skip
   [278]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@kms_vrr@max-min.html

  * igt@kms_vrr@seamless-rr-switch-drrs:
    - shard-dg2:          NOTRUN -> [SKIP][279] ([i915#9906])
   [279]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@kms_vrr@seamless-rr-switch-drrs.html

  * igt@kms_vrr@seamless-rr-switch-vrr:
    - shard-rkl:          NOTRUN -> [SKIP][280] ([i915#9906]) +1 other test skip
   [280]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_vrr@seamless-rr-switch-vrr.html
    - shard-tglu:         NOTRUN -> [SKIP][281] ([i915#9906])
   [281]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-8/igt@kms_vrr@seamless-rr-switch-vrr.html

  * igt@kms_writeback@writeback-fb-id-xrgb2101010:
    - shard-dg1:          NOTRUN -> [SKIP][282] ([i915#2437] / [i915#9412])
   [282]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_writeback@writeback-fb-id-xrgb2101010.html
    - shard-glk:          NOTRUN -> [SKIP][283] ([i915#2437])
   [283]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-glk6/igt@kms_writeback@writeback-fb-id-xrgb2101010.html

  * igt@kms_writeback@writeback-invalid-parameters:
    - shard-dg2:          NOTRUN -> [SKIP][284] ([i915#2437])
   [284]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-2/igt@kms_writeback@writeback-invalid-parameters.html
    - shard-rkl:          NOTRUN -> [SKIP][285] ([i915#2437])
   [285]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@kms_writeback@writeback-invalid-parameters.html

  * igt@kms_writeback@writeback-pixel-formats:
    - shard-dg2:          NOTRUN -> [SKIP][286] ([i915#2437] / [i915#9412])
   [286]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@kms_writeback@writeback-pixel-formats.html

  * igt@perf@mi-rpc:
    - shard-dg1:          NOTRUN -> [SKIP][287] ([i915#2434])
   [287]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@perf@mi-rpc.html

  * igt@perf@unprivileged-single-ctx-counters:
    - shard-rkl:          NOTRUN -> [SKIP][288] ([i915#2433])
   [288]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@perf@unprivileged-single-ctx-counters.html

  * igt@perf_pmu@busy-double-start@vecs1:
    - shard-dg2:          [PASS][289] -> [FAIL][290] ([i915#4349]) +3 other tests fail
   [289]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-11/igt@perf_pmu@busy-double-start@vecs1.html
   [290]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@perf_pmu@busy-double-start@vecs1.html

  * igt@perf_pmu@cpu-hotplug:
    - shard-dg2:          NOTRUN -> [SKIP][291] ([i915#8850])
   [291]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@perf_pmu@cpu-hotplug.html
    - shard-rkl:          NOTRUN -> [SKIP][292] ([i915#8850])
   [292]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-3/igt@perf_pmu@cpu-hotplug.html

  * igt@prime_vgem@basic-fence-flip:
    - shard-dg2:          NOTRUN -> [SKIP][293] ([i915#3708])
   [293]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@prime_vgem@basic-fence-flip.html

  * igt@prime_vgem@basic-write:
    - shard-dg1:          NOTRUN -> [SKIP][294] ([i915#3708]) +1 other test skip
   [294]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@prime_vgem@basic-write.html

  * igt@prime_vgem@fence-read-hang:
    - shard-rkl:          NOTRUN -> [SKIP][295] ([i915#3708]) +1 other test skip
   [295]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-5/igt@prime_vgem@fence-read-hang.html

  * igt@prime_vgem@fence-write-hang:
    - shard-mtlp:         NOTRUN -> [SKIP][296] ([i915#3708])
   [296]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@prime_vgem@fence-write-hang.html

  * igt@sriov_basic@enable-vfs-autoprobe-off:
    - shard-dg2:          NOTRUN -> [SKIP][297] ([i915#9917])
   [297]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@sriov_basic@enable-vfs-autoprobe-off.html

  * igt@sriov_basic@enable-vfs-autoprobe-on:
    - shard-tglu:         NOTRUN -> [SKIP][298] ([i915#9917])
   [298]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-3/igt@sriov_basic@enable-vfs-autoprobe-on.html
    - shard-mtlp:         NOTRUN -> [SKIP][299] ([i915#9917])
   [299]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-1/igt@sriov_basic@enable-vfs-autoprobe-on.html

  * igt@syncobj_timeline@invalid-wait-zero-handles:
    - shard-dg2:          NOTRUN -> [FAIL][300] ([i915#9781])
   [300]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-8/igt@syncobj_timeline@invalid-wait-zero-handles.html
    - shard-dg1:          NOTRUN -> [FAIL][301] ([i915#9781])
   [301]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@syncobj_timeline@invalid-wait-zero-handles.html
    - shard-glk:          NOTRUN -> [FAIL][302] ([i915#9781])
   [302]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-glk8/igt@syncobj_timeline@invalid-wait-zero-handles.html

  * igt@tools_test@sysfs_l3_parity:
    - shard-rkl:          NOTRUN -> [SKIP][303] +43 other tests skip
   [303]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@tools_test@sysfs_l3_parity.html
    - shard-dg2:          NOTRUN -> [SKIP][304] ([i915#4818])
   [304]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@tools_test@sysfs_l3_parity.html

  * igt@v3d/v3d_perfmon@create-perfmon-invalid-counters:
    - shard-mtlp:         NOTRUN -> [SKIP][305] ([i915#2575]) +7 other tests skip
   [305]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-1/igt@v3d/v3d_perfmon@create-perfmon-invalid-counters.html

  * igt@v3d/v3d_perfmon@get-values-invalid-pad:
    - shard-dg1:          NOTRUN -> [SKIP][306] ([i915#2575]) +9 other tests skip
   [306]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-16/igt@v3d/v3d_perfmon@get-values-invalid-pad.html

  * igt@v3d/v3d_submit_cl@simple-flush-cache:
    - shard-dg2:          NOTRUN -> [SKIP][307] ([i915#2575]) +9 other tests skip
   [307]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@v3d/v3d_submit_cl@simple-flush-cache.html

  * igt@v3d/v3d_submit_csd@bad-perfmon:
    - shard-tglu:         NOTRUN -> [SKIP][308] ([i915#2575]) +5 other tests skip
   [308]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-9/igt@v3d/v3d_submit_csd@bad-perfmon.html

  * igt@vc4/vc4_purgeable_bo@mark-purgeable:
    - shard-rkl:          NOTRUN -> [SKIP][309] ([i915#7711]) +5 other tests skip
   [309]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-1/igt@vc4/vc4_purgeable_bo@mark-purgeable.html
    - shard-dg1:          NOTRUN -> [SKIP][310] ([i915#7711]) +9 other tests skip
   [310]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-17/igt@vc4/vc4_purgeable_bo@mark-purgeable.html

  * igt@vc4/vc4_tiling@get-bad-flags:
    - shard-mtlp:         NOTRUN -> [SKIP][311] ([i915#7711]) +3 other tests skip
   [311]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-3/igt@vc4/vc4_tiling@get-bad-flags.html

  * igt@vc4/vc4_tiling@get-bad-handle:
    - shard-dg2:          NOTRUN -> [SKIP][312] ([i915#7711]) +4 other tests skip
   [312]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@vc4/vc4_tiling@get-bad-handle.html

  
#### Possible fixes ####

  * igt@drm_fdinfo@idle@rcs0:
    - shard-rkl:          [FAIL][313] ([i915#7742]) -> [PASS][314] +1 other test pass
   [313]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-rkl-2/igt@drm_fdinfo@idle@rcs0.html
   [314]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-5/igt@drm_fdinfo@idle@rcs0.html

  * igt@gem_eio@reset-stress:
    - shard-dg1:          [FAIL][315] ([i915#5784]) -> [PASS][316]
   [315]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-16/igt@gem_eio@reset-stress.html
   [316]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-13/igt@gem_eio@reset-stress.html

  * igt@gem_exec_fair@basic-pace-share@rcs0:
    - shard-tglu:         [FAIL][317] ([i915#2842]) -> [PASS][318]
   [317]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-tglu-10/igt@gem_exec_fair@basic-pace-share@rcs0.html
   [318]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-5/igt@gem_exec_fair@basic-pace-share@rcs0.html

  * igt@gem_lmem_swapping@heavy-verify-multi@lmem0:
    - shard-dg2:          [FAIL][319] ([i915#10378]) -> [PASS][320]
   [319]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-7/igt@gem_lmem_swapping@heavy-verify-multi@lmem0.html
   [320]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@gem_lmem_swapping@heavy-verify-multi@lmem0.html

  * igt@gem_userptr_blits@stress-mm-invalidate-close-overlap:
    - shard-snb:          [ABORT][321] -> [PASS][322]
   [321]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-snb2/igt@gem_userptr_blits@stress-mm-invalidate-close-overlap.html
   [322]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb5/igt@gem_userptr_blits@stress-mm-invalidate-close-overlap.html

  * igt@i915_module_load@reload-with-fault-injection:
    - shard-rkl:          [ABORT][323] ([i915#9820]) -> [PASS][324]
   [323]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-rkl-6/igt@i915_module_load@reload-with-fault-injection.html
   [324]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-4/igt@i915_module_load@reload-with-fault-injection.html
    - shard-snb:          [INCOMPLETE][325] ([i915#9849]) -> [PASS][326]
   [325]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-snb4/igt@i915_module_load@reload-with-fault-injection.html
   [326]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb4/igt@i915_module_load@reload-with-fault-injection.html
    - shard-tglu:         [INCOMPLETE][327] ([i915#10047] / [i915#10887] / [i915#9820]) -> [PASS][328]
   [327]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-tglu-10/igt@i915_module_load@reload-with-fault-injection.html
   [328]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-10/igt@i915_module_load@reload-with-fault-injection.html

  * igt@kms_big_fb@x-tiled-64bpp-rotate-180:
    - shard-mtlp:         [FAIL][329] ([i915#5138]) -> [PASS][330]
   [329]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-mtlp-2/igt@kms_big_fb@x-tiled-64bpp-rotate-180.html
   [330]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-8/igt@kms_big_fb@x-tiled-64bpp-rotate-180.html

  * igt@kms_cursor_legacy@2x-long-flip-vs-cursor-legacy:
    - shard-snb:          [SKIP][331] -> [PASS][332] +2 other tests pass
   [331]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-snb4/igt@kms_cursor_legacy@2x-long-flip-vs-cursor-legacy.html
   [332]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb7/igt@kms_cursor_legacy@2x-long-flip-vs-cursor-legacy.html

  * igt@kms_fbcon_fbt@psr-suspend:
    - shard-mtlp:         [FAIL][333] ([i915#4767]) -> [PASS][334]
   [333]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-mtlp-5/igt@kms_fbcon_fbt@psr-suspend.html
   [334]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-4/igt@kms_fbcon_fbt@psr-suspend.html

  * igt@kms_flip@flip-vs-panning-vs-hang@a-edp1:
    - shard-mtlp:         [INCOMPLETE][335] ([i915#6113]) -> [PASS][336]
   [335]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-mtlp-8/igt@kms_flip@flip-vs-panning-vs-hang@a-edp1.html
   [336]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-5/igt@kms_flip@flip-vs-panning-vs-hang@a-edp1.html

  * igt@kms_flip@plain-flip-fb-recreate-interruptible@a-vga1:
    - shard-snb:          [FAIL][337] ([i915#2122]) -> [PASS][338]
   [337]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-snb7/igt@kms_flip@plain-flip-fb-recreate-interruptible@a-vga1.html
   [338]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb2/igt@kms_flip@plain-flip-fb-recreate-interruptible@a-vga1.html

  * igt@kms_pm_dc@dc9-dpms:
    - shard-tglu:         [SKIP][339] ([i915#4281]) -> [PASS][340]
   [339]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-tglu-2/igt@kms_pm_dc@dc9-dpms.html
   [340]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-4/igt@kms_pm_dc@dc9-dpms.html

  * igt@kms_pm_rpm@modeset-lpsp:
    - shard-dg2:          [SKIP][341] ([i915#9519]) -> [PASS][342]
   [341]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-7/igt@kms_pm_rpm@modeset-lpsp.html
   [342]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@kms_pm_rpm@modeset-lpsp.html

  * igt@kms_pm_rpm@modeset-non-lpsp-stress-no-wait:
    - shard-rkl:          [SKIP][343] ([i915#9519]) -> [PASS][344] +1 other test pass
   [343]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-rkl-5/igt@kms_pm_rpm@modeset-non-lpsp-stress-no-wait.html
   [344]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-6/igt@kms_pm_rpm@modeset-non-lpsp-stress-no-wait.html

  * igt@kms_universal_plane@cursor-fb-leak@pipe-b-hdmi-a-1:
    - shard-tglu:         [FAIL][345] ([i915#9196]) -> [PASS][346]
   [345]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-tglu-7/igt@kms_universal_plane@cursor-fb-leak@pipe-b-hdmi-a-1.html
   [346]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-tglu-10/igt@kms_universal_plane@cursor-fb-leak@pipe-b-hdmi-a-1.html

  * igt@kms_universal_plane@cursor-fb-leak@pipe-d-hdmi-a-4:
    - shard-dg1:          [FAIL][347] ([i915#9196]) -> [PASS][348]
   [347]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-16/igt@kms_universal_plane@cursor-fb-leak@pipe-d-hdmi-a-4.html
   [348]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-18/igt@kms_universal_plane@cursor-fb-leak@pipe-d-hdmi-a-4.html

  * igt@syncobj_timeline@single-wait-all-signaled:
    - shard-dg1:          [DMESG-WARN][349] ([i915#4423]) -> [PASS][350]
   [349]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-17/igt@syncobj_timeline@single-wait-all-signaled.html
   [350]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@syncobj_timeline@single-wait-all-signaled.html

  
#### Warnings ####

  * igt@gem_eio@kms:
    - shard-dg1:          [INCOMPLETE][351] ([i915#10513]) -> [FAIL][352] ([i915#5784])
   [351]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg1-18/igt@gem_eio@kms.html
   [352]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg1-15/igt@gem_eio@kms.html

  * igt@i915_module_load@reload-with-fault-injection:
    - shard-mtlp:         [ABORT][353] ([i915#10131] / [i915#9820]) -> [ABORT][354] ([i915#10131] / [i915#10887] / [i915#9820])
   [353]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-mtlp-2/igt@i915_module_load@reload-with-fault-injection.html
   [354]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-mtlp-7/igt@i915_module_load@reload-with-fault-injection.html

  * igt@kms_content_protection@uevent:
    - shard-snb:          [SKIP][355] -> [INCOMPLETE][356] ([i915#8816])
   [355]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-snb4/igt@kms_content_protection@uevent.html
   [356]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-snb7/igt@kms_content_protection@uevent.html

  * igt@kms_frontbuffer_tracking@fbcpsr-1p-offscren-pri-indfb-draw-mmap-cpu:
    - shard-dg2:          [SKIP][357] ([i915#3458]) -> [SKIP][358] ([i915#10433] / [i915#3458]) +3 other tests skip
   [357]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-8/igt@kms_frontbuffer_tracking@fbcpsr-1p-offscren-pri-indfb-draw-mmap-cpu.html
   [358]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-4/igt@kms_frontbuffer_tracking@fbcpsr-1p-offscren-pri-indfb-draw-mmap-cpu.html

  * igt@kms_frontbuffer_tracking@fbcpsr-rgb565-draw-render:
    - shard-dg2:          [SKIP][359] ([i915#10433] / [i915#3458]) -> [SKIP][360] ([i915#3458]) +4 other tests skip
   [359]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-4/igt@kms_frontbuffer_tracking@fbcpsr-rgb565-draw-render.html
   [360]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-10/igt@kms_frontbuffer_tracking@fbcpsr-rgb565-draw-render.html

  * igt@kms_pm_dc@dc6-dpms:
    - shard-rkl:          [FAIL][361] ([i915#9295]) -> [SKIP][362] ([i915#3361])
   [361]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-rkl-5/igt@kms_pm_dc@dc6-dpms.html
   [362]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-rkl-1/igt@kms_pm_dc@dc6-dpms.html

  * igt@kms_psr@fbc-pr-primary-mmap-gtt:
    - shard-dg2:          [SKIP][363] ([i915#1072] / [i915#9732]) -> [SKIP][364] ([i915#1072] / [i915#9673] / [i915#9732]) +9 other tests skip
   [363]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-4/igt@kms_psr@fbc-pr-primary-mmap-gtt.html
   [364]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-11/igt@kms_psr@fbc-pr-primary-mmap-gtt.html

  * igt@kms_psr@fbc-psr-primary-page-flip:
    - shard-dg2:          [SKIP][365] ([i915#1072] / [i915#9673] / [i915#9732]) -> [SKIP][366] ([i915#1072] / [i915#9732]) +11 other tests skip
   [365]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-11/igt@kms_psr@fbc-psr-primary-page-flip.html
   [366]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-1/igt@kms_psr@fbc-psr-primary-page-flip.html

  * igt@prime_mmap@test_aperture_limit@test_aperture_limit-smem:
    - shard-dg2:          [INCOMPLETE][367] ([i915#5493]) -> [CRASH][368] ([i915#9351])
   [367]: https://intel-gfx-ci.01.org/tree/drm-tip/IGT_7846/shard-dg2-5/igt@prime_mmap@test_aperture_limit@test_aperture_limit-smem.html
   [368]: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/shard-dg2-5/igt@prime_mmap@test_aperture_limit@test_aperture_limit-smem.html

  
  [IGT#2]: https://gitlab.freedesktop.org/drm/igt-gpu-tools/issues/2
  [i915#10047]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10047
  [i915#10055]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10055
  [i915#10131]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10131
  [i915#10278]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10278
  [i915#10307]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10307
  [i915#10378]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10378
  [i915#10386]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10386
  [i915#10433]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10433
  [i915#10434]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10434
  [i915#10513]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10513
  [i915#10656]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10656
  [i915#1072]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/1072
  [i915#10887]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/10887
  [i915#1099]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/1099
  [i915#1257]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/1257
  [i915#1825]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/1825
  [i915#1839]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/1839
  [i915#1982]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/1982
  [i915#2017]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2017
  [i915#2122]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2122
  [i915#2346]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2346
  [i915#2433]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2433
  [i915#2434]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2434
  [i915#2437]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2437
  [i915#2527]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2527
  [i915#2575]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2575
  [i915#2587]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2587
  [i915#2672]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2672
  [i915#280]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/280
  [i915#2842]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2842
  [i915#2856]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/2856
  [i915#3023]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3023
  [i915#3116]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3116
  [i915#3281]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3281
  [i915#3282]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3282
  [i915#3297]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3297
  [i915#3299]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3299
  [i915#3323]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3323
  [i915#3359]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3359
  [i915#3361]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3361
  [i915#3458]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3458
  [i915#3469]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3469
  [i915#3539]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3539
  [i915#3555]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3555
  [i915#3637]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3637
  [i915#3638]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3638
  [i915#3708]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3708
  [i915#3742]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3742
  [i915#3840]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3840
  [i915#3936]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/3936
  [i915#4077]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4077
  [i915#4079]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4079
  [i915#4083]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4083
  [i915#4103]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4103
  [i915#4212]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4212
  [i915#4213]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4213
  [i915#4215]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4215
  [i915#4235]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4235
  [i915#4270]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4270
  [i915#4281]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4281
  [i915#4349]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4349
  [i915#4387]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4387
  [i915#4423]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4423
  [i915#4473]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4473
  [i915#4525]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4525
  [i915#4537]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4537
  [i915#4538]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4538
  [i915#4565]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4565
  [i915#4613]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4613
  [i915#4767]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4767
  [i915#4771]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4771
  [i915#4812]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4812
  [i915#4818]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4818
  [i915#4852]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4852
  [i915#4854]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4854
  [i915#4860]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4860
  [i915#4880]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4880
  [i915#4881]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4881
  [i915#4885]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/4885
  [i915#5138]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5138
  [i915#5176]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5176
  [i915#5190]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5190
  [i915#5235]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5235
  [i915#5274]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5274
  [i915#5286]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5286
  [i915#5289]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5289
  [i915#5354]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5354
  [i915#5439]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5439
  [i915#5465]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5465
  [i915#5493]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5493
  [i915#5784]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/5784
  [i915#6095]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6095
  [i915#6113]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6113
  [i915#6118]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6118
  [i915#6187]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6187
  [i915#6188]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6188
  [i915#6227]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6227
  [i915#6228]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6228
  [i915#6230]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6230
  [i915#6245]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6245
  [i915#6334]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6334
  [i915#6335]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6335
  [i915#6412]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6412
  [i915#6493]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6493
  [i915#6524]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6524
  [i915#658]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/658
  [i915#6590]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6590
  [i915#6621]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6621
  [i915#6645]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6645
  [i915#6880]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6880
  [i915#6944]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6944
  [i915#6953]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/6953
  [i915#7116]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7116
  [i915#7118]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7118
  [i915#7178]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7178
  [i915#7213]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7213
  [i915#7443]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7443
  [i915#7697]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7697
  [i915#7711]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7711
  [i915#7742]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7742
  [i915#7828]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/7828
  [i915#8228]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8228
  [i915#8289]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8289
  [i915#8292]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8292
  [i915#8381]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8381
  [i915#8411]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8411
  [i915#8414]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8414
  [i915#8428]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8428
  [i915#8555]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8555
  [i915#8562]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8562
  [i915#8708]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8708
  [i915#8806]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8806
  [i915#8808]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8808
  [i915#8810]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8810
  [i915#8812]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8812
  [i915#8814]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8814
  [i915#8816]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8816
  [i915#8850]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8850
  [i915#8925]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/8925
  [i915#9010]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9010
  [i915#9067]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9067
  [i915#9196]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9196
  [i915#9295]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9295
  [i915#9311]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9311
  [i915#9323]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9323
  [i915#9340]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9340
  [i915#9351]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9351
  [i915#9412]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9412
  [i915#9423]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9423
  [i915#9424]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9424
  [i915#9519]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9519
  [i915#9531]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9531
  [i915#9606]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9606
  [i915#9673]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9673
  [i915#9683]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9683
  [i915#9685]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9685
  [i915#9688]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9688
  [i915#9723]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9723
  [i915#9728]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9728
  [i915#9732]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9732
  [i915#9781]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9781
  [i915#9808]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9808
  [i915#9809]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9809
  [i915#9820]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9820
  [i915#9849]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9849
  [i915#9906]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9906
  [i915#9917]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9917
  [i915#9934]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9934
  [i915#9979]: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/9979


Build changes
-------------

  * CI: CI-20190529 -> None
  * IGT: IGT_7846 -> IGTPW_11120

  CI-20190529: 20190529
  CI_DRM_14737: 92f877dd46245e4a44b6d24b5e303b8c03c40c75 @ git://anongit.freedesktop.org/gfx-ci/linux
  IGTPW_11120: 11120
  IGT_7846: 4a5fd4e7cb2798636f6464e2bd61399f3242b322 @ https://gitlab.freedesktop.org/drm/igt-gpu-tools.git

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/drm-tip/IGTPW_11120/index.html

[-- Attachment #2: Type: text/html, Size: 118822 bytes --]

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-09 15:24 ` [PATCH i-g-t 1/3] igt_hook: Add feature Gustavo Sousa
@ 2024-05-13 17:10   ` Kamil Konieczny
  2024-05-15 17:10   ` Kamil Konieczny
  2024-05-21 19:40   ` Lucas De Marchi
  2 siblings, 0 replies; 22+ messages in thread
From: Kamil Konieczny @ 2024-05-13 17:10 UTC (permalink / raw)
  To: igt-dev; +Cc: Gustavo Sousa

Hi Gustavo,
On 2024-05-09 at 12:24:29 -0300, Gustavo Sousa wrote:

could you prepend lib/ in subject? imho something like:

[PATCH i-g-t 1/3] lib/igt_hook: Add igt_hook feature

> For development purposes, sometimes it is useful to have a way of
> running custom scripts at certain points of test executions. A
> real-world example I bumped into recently is to collect information from
> sysfs before and after running each entry of a testlist.
> 
> While it is possible for the user to handcraft a script that calls each
> test with the correct actions before and after execution, we can provide
> a better experience by adding built-in support for running hooks during
> test execution.
> 
> That would be even better when adding the same kind of support for
> igt_runner (which is done in an upcoming change), since the user can
> also nicely resume with igt_resume with the hook already setup in case a
> crash happens during execution of the test list.
> 
> As such provide implement support for hooks, integrate it into
> igt_core and expose the functionality via --hook CLI option on test
> executables.
> 

I suggest to add Petri to Cc

> Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
> ---
>  .../igt-gpu-tools/igt-gpu-tools-docs.xml      |   1 +
>  lib/igt_core.c                                | 116 +++-
>  lib/igt_hook.c                                | 499 ++++++++++++++++++
>  lib/igt_hook.h                                |  86 +++
>  lib/meson.build                               |   1 +
>  lib/tests/igt_hook.c                          | 187 +++++++
>  lib/tests/igt_hook_integration.c              | 297 +++++++++++
>  lib/tests/meson.build                         |   2 +
>  8 files changed, 1180 insertions(+), 9 deletions(-)
>  create mode 100644 lib/igt_hook.c
>  create mode 100644 lib/igt_hook.h
>  create mode 100644 lib/tests/igt_hook.c
>  create mode 100644 lib/tests/igt_hook_integration.c
> 
> diff --git a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
> index 9085eb924e85..11458c68124b 100644
> --- a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
> +++ b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
> @@ -32,6 +32,7 @@
>      <xi:include href="xml/igt_fb.xml"/>
>      <xi:include href="xml/igt_frame.xml"/>
>      <xi:include href="xml/igt_gt.xml"/>
> +    <xi:include href="xml/igt_hook.xml"/>
>      <xi:include href="xml/igt_io.xml"/>
>      <xi:include href="xml/igt_kmod.xml"/>
>      <xi:include href="xml/igt_kms.xml"/>
> diff --git a/lib/igt_core.c b/lib/igt_core.c
> index 3ff3e0392316..291d891cf884 100644
> --- a/lib/igt_core.c
> +++ b/lib/igt_core.c
> @@ -70,6 +70,7 @@
>  
>  #include "igt_core.h"
>  #include "igt_aux.h"
> +#include "igt_hook.h"
>  #include "igt_sysfs.h"
>  #include "igt_sysrq.h"
>  #include "igt_rc.h"
> @@ -241,6 +242,9 @@
>   * - '*,!basic*' match any subtest not starting basic
>   * - 'basic*,!basic-render*' match any subtest starting basic but not starting basic-render
>   *
> + * It is possible to run a shell script at certain points of test execution with
> + * "--hook". See the usage description with "--help-hook" for details.
> + *
>   * # Configuration
>   *
>   * Some of IGT's behavior can be configured through a configuration file.
> @@ -273,6 +277,8 @@ static unsigned int exit_handler_count;
>  const char *igt_interactive_debug;
>  bool igt_skip_crc_compare;
>  
> +static struct igt_hook *igt_hook = NULL;
> +
>  /* subtests helpers */
>  static bool show_testlist = false;
>  static bool list_subtests = false;
> @@ -338,6 +344,8 @@ enum {
>  	OPT_INTERACTIVE_DEBUG,
>  	OPT_SKIP_CRC,
>  	OPT_TRACE_OOPS,
> +	OPT_HOOK,
> +	OPT_HELP_HOOK,
>  	OPT_DEVICE,
>  	OPT_VERSION,
>  	OPT_HELP = 'h'
> @@ -810,6 +818,8 @@ static void common_exit_handler(int sig)
>  		bind_fbcon(true);
>  	}
>  
> +	igt_hook_free(igt_hook);
> +
>  	/* When not killed by a signal check that igt_exit() has been properly
>  	 * called. */
>  	assert(sig != 0 || igt_exit_called || igt_is_aborting);
> @@ -907,6 +917,8 @@ static void print_usage(const char *help_str, bool output_on_stderr)
>  		   "  --interactive-debug[=domain]\n"
>  		   "  --skip-crc-compare\n"
>  		   "  --trace-on-oops\n"
> +		   "  --hook [<events>:]<cmd>\n"
> +		   "  --help-hook\n"
>  		   "  --help-description\n"
>  		   "  --describe\n"
>  		   "  --device filters\n"
> @@ -1090,6 +1102,8 @@ static int common_init(int *argc, char **argv,
>  		{"interactive-debug", optional_argument, NULL, OPT_INTERACTIVE_DEBUG},
>  		{"skip-crc-compare",  no_argument,       NULL, OPT_SKIP_CRC},
>  		{"trace-on-oops",     no_argument,       NULL, OPT_TRACE_OOPS},
> +		{"hook",              required_argument, NULL, OPT_HOOK},
> +		{"help-hook",         no_argument,       NULL, OPT_HELP_HOOK},
>  		{"device",            required_argument, NULL, OPT_DEVICE},
>  		{"version",           no_argument,       NULL, OPT_VERSION},
>  		{"help",              no_argument,       NULL, OPT_HELP},
> @@ -1225,6 +1239,24 @@ static int common_init(int *argc, char **argv,
>  		case OPT_TRACE_OOPS:
>  			show_ftrace = true;
>  			break;
> +		case OPT_HOOK:
> +			assert(optarg);
> +			if (igt_hook) {
> +				igt_warn("Overriding previous hook descriptor\n");
> +				igt_hook_free(igt_hook);
> +			}
> +			igt_hook = igt_hook_init(optarg, &ret);
> +			if (!igt_hook) {
> +				igt_critical("Failed to initialize hook data: %s\n",
> +					     igt_hook_error_str(ret));
> +				ret = ret > 0 ? -2 : -3;
> +				goto out;
> +			}
> +			break;
> +		case OPT_HELP_HOOK:
> +			igt_hook_print_help(stdout, "--hook");
> +			ret = -1;
> +			goto out;
>  		case OPT_DEVICE:
>  			assert(optarg);
>  			/* if set by env IGT_DEVICE we need to free it */
> @@ -1274,9 +1306,24 @@ out:
>  			exit(IGT_EXIT_INVALID);
>  	}
>  
> -	if (ret < 0)
> -		/* exit with no error for -h/--help */
> -		exit(ret == -1 ? 0 : IGT_EXIT_INVALID);
> +	if (ret < 0) {
> +		free(igt_hook);
> +		igt_hook = NULL;
> +
> +		switch (ret) {
> +		case -1: /* exit with no error for -h/--help */
> +			exit(0);
> +			break;
> +		case -2:
> +			exit(IGT_EXIT_INVALID);
> +			break;
> +		case -3:
> +			exit(IGT_EXIT_ABORT);
> +			break;
> +		default:
> +			assert(0);
> +		}
> +	}
>  
>  	if (!igt_only_list_subtests()) {
>  		bind_fbcon(false);
> @@ -1284,6 +1331,15 @@ out:
>  		print_version();
>  		igt_srandom();
>  
> +		if (igt_hook) {
> +			struct igt_hook_evt hook_evt = {
> +				.evt_type = IGT_HOOK_PRE_TEST,
> +				.target_name = command_str,
> +			};
> +
> +			igt_hook_push_evt(igt_hook, &hook_evt);
> +		}
> +
>  		sync();
>  		oom_adjust_for_doom();
>  		ftrace_dump_on_oops(show_ftrace);
> @@ -1487,6 +1543,16 @@ bool __igt_run_subtest(const char *subtest_name, const char *file, const int lin
>  	igt_thread_clear_fail_state();
>  
>  	igt_gettime(&subtest_time);
> +
> +	if (igt_hook) {
> +		struct igt_hook_evt hook_evt = {
> +			.evt_type = IGT_HOOK_PRE_SUBTEST,
> +			.target_name = subtest_name,
> +		};
> +
> +		igt_hook_push_evt(igt_hook, &hook_evt);
> +	}
> +
>  	return (in_subtest = subtest_name);
>  }
>  
> @@ -1517,6 +1583,16 @@ bool __igt_run_dynamic_subtest(const char *dynamic_subtest_name)
>  	_igt_dynamic_tests_executed++;
>  
>  	igt_gettime(&dynamic_subtest_time);
> +
> +	if (igt_hook) {
> +		struct igt_hook_evt hook_evt = {
> +			.evt_type = IGT_HOOK_PRE_DYN_SUBTEST,
> +			.target_name = dynamic_subtest_name,
> +		};
> +
> +		igt_hook_push_evt(igt_hook, &hook_evt);
> +	}
> +
>  	return (in_dynamic_subtest = dynamic_subtest_name);
>  }
>  
> @@ -1602,6 +1678,17 @@ __noreturn static void exit_subtest(const char *result)
>  	struct timespec *thentime = in_dynamic_subtest ? &dynamic_subtest_time : &subtest_time;
>  	jmp_buf *jmptarget = in_dynamic_subtest ? &igt_dynamic_jmpbuf : &igt_subtest_jmpbuf;
>  
> +	if (igt_hook) {
> +		struct igt_hook_evt hook_evt = {
> +			.evt_type = (in_dynamic_subtest
> +					? IGT_HOOK_POST_DYN_SUBTEST
> +					: IGT_HOOK_POST_SUBTEST),
> +			.result = result,
> +		};
> +
> +		igt_hook_push_evt(igt_hook, &hook_evt);
> +	}
> +
>  	if (!igt_thread_is_main()) {
>  		igt_thread_fail();
>  		pthread_exit(NULL);
> @@ -2274,6 +2361,7 @@ void __igt_abort(const char *domain, const char *file, const int line,
>  void igt_exit(void)
>  {
>  	int tmp;
> +	const char *result;
>  
>  	if (!test_with_subtests)
>  		igt_thread_assert_no_failures();
> @@ -2318,12 +2406,7 @@ void igt_exit(void)
>  
>  	assert(waitpid(-1, &tmp, WNOHANG) == -1 && errno == ECHILD);
>  
> -	if (!test_with_subtests) {
> -		struct timespec now;
> -		const char *result;
> -
> -		igt_gettime(&now);
> -
> +	if (!test_with_subtests || igt_hook) {
>  		switch (igt_exitcode) {
>  			case IGT_EXIT_SUCCESS:
>  				result = "SUCCESS";
> @@ -2334,6 +2417,12 @@ void igt_exit(void)
>  			default:
>  				result = "FAIL";
>  		}
> +	}
> +
> +	if (!test_with_subtests) {
> +		struct timespec now;
> +
> +		igt_gettime(&now);
>  
>  		if (test_multi_fork_child) /* parent will do the yelling */
>  			_log_line_fprintf(stdout, "dyn_child pid:%d (%.3fs) ends with err=%d\n",
> @@ -2344,6 +2433,15 @@ void igt_exit(void)
>  					  result, igt_time_elapsed(&subtest_time, &now));
>  	}
>  
> +	if (igt_hook) {
> +		struct igt_hook_evt hook_evt = {
> +			.evt_type = IGT_HOOK_POST_TEST,
> +			.result = result,
> +		};
> +
> +		igt_hook_push_evt(igt_hook, &hook_evt);
> +	}
> +
>  	exit(igt_exitcode);
>  }
>  
> diff --git a/lib/igt_hook.c b/lib/igt_hook.c
> new file mode 100644
> index 000000000000..8a39e19e3e5f
> --- /dev/null
> +++ b/lib/igt_hook.c
> @@ -0,0 +1,499 @@
> +/*
> + * Copyright © 2024 Intel Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a

Please replace this with SPDX, here and in other new files.
Also use checkpatch.pl from Linux kernel for checking.

> + * 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 <assert.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include "igt_hook.h"
> +
> +/**
> + * SECTION:igt_hook
> + * @short_description: Support for running a hook script on test execution
> + * @title: Hook support
> + *
> + * IGT provides support for running a hook script when executing tests. This
> + * support is provided to users via CLI option `--hook` available in test
> + * binaries. Users should use `--help-hook` for detailed usaged description of
> + * the feature.
> + *
> + * The sole user of the exposed API is `igt_core`, which calls @igt_hook_init()
> + * when initializing a test case, then calls @igt_hook_push_evt() for each event
> + * that occurs during that test's execution and finally calls @igt_hook_free()
> + * to clean up at the end.
> + */
> +
> +#define TEST_NAME_INITIAL_SIZE 16

Can it grow?

Regards,
Kamil

> +
> +typedef uint16_t evt_mask_t;
> +
> +struct igt_hook {
> +	evt_mask_t evt_mask;
> +	char *cmd;
> +	char *test_name;
> +	size_t test_name_size;
> +	char *subtest_name;
> +	size_t subtest_name_size;
> +	char *dyn_subtest_name;
> +	size_t dyn_subtest_name_size;
> +	char *test_fullname;
> +};
> +
> +enum igt_hook_error {
> +	IGT_HOOK_EVT_EMPTY_NAME = 1,
> +	IGT_HOOK_EVT_NO_MATCH,
> +};
> +
> +static_assert(IGT_HOOK_NUM_EVENTS <= sizeof(evt_mask_t) * CHAR_BIT,
> +	      "Number of event types does not fit event type mask");
> +
> +static const char *igt_hook_evt_type_to_name(enum igt_hook_evt_type evt_type)
> +{
> +	switch (evt_type) {
> +	case IGT_HOOK_PRE_TEST:
> +		return "pre-test";
> +	case IGT_HOOK_PRE_SUBTEST:
> +		return "pre-subtest";
> +	case IGT_HOOK_PRE_DYN_SUBTEST:
> +		return "pre-dyn-subtest";
> +	case IGT_HOOK_POST_DYN_SUBTEST:
> +		return "post-dyn-subtest";
> +	case IGT_HOOK_POST_SUBTEST:
> +		return "post-subtest";
> +	case IGT_HOOK_POST_TEST:
> +		return "post-test";
> +	case IGT_HOOK_NUM_EVENTS:
> +		break;
> +	/* No "default:" case, to force a warning from -Wswitch in case we miss
> +	 * any new event type. */
> +	}
> +	return "?";
> +}
> +
> +static int igt_hook_parse_hook_str(const char *hook_str, evt_mask_t *evt_mask, const char **cmd)
> +{
> +	const char *s;
> +
> +	if (!strchr(hook_str, ':')) {
> +		*evt_mask = ~0;
> +		*cmd = hook_str;
> +		return 0;
> +	}
> +
> +	s = hook_str;
> +	*evt_mask = 0;
> +
> +	while (1) {
> +		const char *evt_name;
> +		bool has_match;
> +		bool is_star;
> +		enum igt_hook_evt_type evt_type;
> +
> +		evt_name = s;
> +
> +		while (*s != ':' && *s != ',')
> +			s++;
> +
> +		if (evt_name == s)
> +			return IGT_HOOK_EVT_EMPTY_NAME;
> +
> +		has_match = false;
> +		is_star = *evt_name == '*' && evt_name + 1 == s;
> +
> +		for (evt_type = IGT_HOOK_PRE_TEST; evt_type < IGT_HOOK_NUM_EVENTS; evt_type++) {
> +			if (!is_star) {
> +				const char *this_event_name = igt_hook_evt_type_to_name(evt_type);
> +				size_t len = s - evt_name;
> +
> +				if (len != strlen(this_event_name))
> +					continue;
> +
> +				if (strncmp(evt_name, this_event_name, len))
> +					continue;
> +			}
> +
> +			*evt_mask |= 1 << evt_type;
> +			has_match = true;
> +
> +			if (!is_star)
> +				break;
> +		}
> +
> +		if (!has_match)
> +			return IGT_HOOK_EVT_NO_MATCH;
> +
> +		if (*s++ == ':')
> +			break;
> +	}
> +
> +	*cmd = s;
> +
> +	return 0;
> +}
> +
> +static size_t igt_hook_calc_test_fullname_size(struct igt_hook *igt_hook) {
> +	/* The maximum size of test_fullname will be the maximum length of
> +	 * "igt@<test_name>@<subtest_name>@<dyn_subtest_name>" plus 1 for the
> +	 * null byte. */
> +	return (igt_hook->test_name_size +
> +		igt_hook->subtest_name_size +
> +		igt_hook->dyn_subtest_name_size) + 4;
> +}
> +
> +static void igt_hook_update_test_fullname(struct igt_hook *igt_hook)
> +{
> +	int i;
> +	char *s;
> +	const char *values[3] = {
> +		igt_hook->test_name,
> +		igt_hook->subtest_name,
> +		igt_hook->dyn_subtest_name,
> +	};
> +
> +	if (igt_hook->test_name[0] == '\0') {
> +		igt_hook->test_fullname[0] = '\0';
> +		return;
> +	}
> +
> +	s = stpcpy(igt_hook->test_fullname, "igt");
> +	for (i = 0; i < 3 && values[i][0] != '\0'; i++) {
> +		*s++ = '@';
> +		s = stpcpy(s, values[i]);
> +	}
> +}
> +
> +/**
> + * igt_hook_init:
> + * @hook_str: Hook descriptor string.
> + * @error: Pointer to error number.
> + *
> + * Allocate and initialize an #igt_hook structure.
> + *
> + * This function parses the hook descriptor @hook_str and initializes the struct
> + * to be returned.
> + *
> + * The hook descriptor comes from the argument to `--hook` of the test
> + * executable being run.
> + *
> + * If not #NULL, @error is used to store a non-zero error number if an error
> + * happens. A human-readable string for that error number can be obtained with
> + * @igt_hook_error_str().
> + *
> + * Returns: The pointer to the #igt_hook structure on success or #NULL on error.
> + */
> +struct igt_hook *igt_hook_init(const char *hook_str, int *error)
> +{
> +	int err;
> +	evt_mask_t evt_mask;
> +	const char *cmd;
> +	struct igt_hook *igt_hook = NULL;
> +
> +
> +	err = igt_hook_parse_hook_str(hook_str, &evt_mask, &cmd);
> +	if (err)
> +		goto out;
> +
> +	igt_hook = calloc(1, sizeof(*igt_hook));
> +	igt_hook->evt_mask = evt_mask;
> +
> +	igt_hook->cmd = strdup(cmd);
> +	if (!igt_hook->cmd) {
> +		err = -errno;
> +		goto out;
> +	}
> +
> +	igt_hook->test_name = malloc(TEST_NAME_INITIAL_SIZE);
> +	igt_hook->test_name_size = TEST_NAME_INITIAL_SIZE;
> +	igt_hook->subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
> +	igt_hook->subtest_name_size = TEST_NAME_INITIAL_SIZE;
> +	igt_hook->dyn_subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
> +	igt_hook->dyn_subtest_name_size = TEST_NAME_INITIAL_SIZE;
> +	igt_hook->test_fullname = malloc(igt_hook_calc_test_fullname_size(igt_hook));
> +
> +	igt_hook->test_name[0] = '\0';
> +	igt_hook->subtest_name[0] = '\0';
> +	igt_hook->dyn_subtest_name[0] = '\0';
> +	igt_hook->test_fullname[0] = '\0';
> +
> +out:
> +	if (err) {
> +		if (error)
> +			*error = err;
> +
> +		igt_hook_free(igt_hook);
> +
> +		return NULL;
> +	}
> +
> +	return igt_hook;
> +}
> +
> +/**
> + * igt_hook_free:
> + * @igt_hook: The igt_hook struct.
> + *
> + * De-initialize an igt_hook struct returned by @igt_hook_init().
> + *
> + * This is a no-op if @igt_hook is #NULL.
> + */
> +void igt_hook_free(struct igt_hook *igt_hook)
> +{
> +	if (!igt_hook)
> +		return;
> +
> +	free(igt_hook->cmd);
> +	free(igt_hook->test_name);
> +	free(igt_hook->subtest_name);
> +	free(igt_hook->dyn_subtest_name);
> +	free(igt_hook);
> +}
> +
> +static void igt_hook_update_test_name_pre_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
> +{
> +	char **name_ptr;
> +	size_t *size_ptr;
> +	size_t len;
> +
> +	switch (evt->evt_type) {
> +	case IGT_HOOK_PRE_TEST:
> +		name_ptr = &igt_hook->test_name;
> +		size_ptr = &igt_hook->test_name_size;
> +		break;
> +	case IGT_HOOK_PRE_SUBTEST:
> +		name_ptr = &igt_hook->subtest_name;
> +		size_ptr = &igt_hook->subtest_name_size;
> +		break;
> +	case IGT_HOOK_PRE_DYN_SUBTEST:
> +		name_ptr = &igt_hook->dyn_subtest_name;
> +		size_ptr = &igt_hook->dyn_subtest_name_size;
> +		break;
> +	default:
> +		return;
> +	}
> +
> +	len = strlen(evt->target_name);
> +	if (len + 1 > *size_ptr) {
> +		size_t fullname_size;
> +
> +		*size_ptr *= 2;
> +		*name_ptr = realloc(*name_ptr, *size_ptr);
> +
> +		fullname_size = igt_hook_calc_test_fullname_size(igt_hook);
> +		igt_hook->test_fullname = realloc(igt_hook->test_fullname, fullname_size);
> +	}
> +
> +	strcpy(*name_ptr, evt->target_name);
> +	igt_hook_update_test_fullname(igt_hook);
> +}
> +
> +static void igt_hook_update_test_name_post_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
> +{
> +	switch (evt->evt_type) {
> +	case IGT_HOOK_POST_TEST:
> +		igt_hook->test_name[0] = '\0';
> +		break;
> +	case IGT_HOOK_POST_SUBTEST:
> +		igt_hook->subtest_name[0] = '\0';
> +		break;
> +	case IGT_HOOK_POST_DYN_SUBTEST:
> +		igt_hook->dyn_subtest_name[0] = '\0';
> +		break;
> +	default:
> +		return;
> +	}
> +
> +	igt_hook_update_test_fullname(igt_hook);
> +}
> +
> +static void igt_hook_update_env_vars(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
> +{
> +	setenv("IGT_HOOK_EVENT", igt_hook_evt_type_to_name(evt->evt_type), 1);
> +	setenv("IGT_HOOK_TEST_FULLNAME", igt_hook->test_fullname, 1);
> +	setenv("IGT_HOOK_TEST", igt_hook->test_name, 1);
> +	setenv("IGT_HOOK_SUBTEST", igt_hook->subtest_name, 1);
> +	setenv("IGT_HOOK_DYN_SUBTEST", igt_hook->dyn_subtest_name, 1);
> +	setenv("IGT_HOOK_RESULT", evt->result ?: "", 1);
> +}
> +
> +/**
> + * igt_hook_push_evt:
> + * @igt_hook: The igt_hook structure.
> + * @evt: The event to be pushed.
> + *
> + * Push a new igt_hook event.
> + *
> + * This function must be used to register a new igt_hook event. Calling it will
> + * cause execution of the hook script if the event type matches the filters
> + * provided during initialization of @igt_hook.
> + */
> +void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
> +{
> +	typeof(igt_hook->evt_mask) evt_bit = (1 << evt->evt_type);
> +
> +	igt_hook_update_test_name_pre_call(igt_hook, evt);
> +
> +	if ((evt_bit & igt_hook->evt_mask)) {
> +		igt_hook_update_env_vars(igt_hook, evt);
> +		system(igt_hook->cmd);
> +	}
> +
> +	igt_hook_update_test_name_post_call(igt_hook, evt);
> +}
> +
> +/**
> + * igt_hook_error_str:
> + * @error: Non-zero error number.
> + *
> + * Return a human-readable string containing a description of an error number
> + * generated by one of the `igt_hook_*` functions.
> + *
> + * The string will be the result of strerror() for errors from the C standard
> + * library or a custom description specific to igt_hook.
> + */
> +const char *igt_hook_error_str(int error)
> +{
> +	if (!error)
> +		return "No error";
> +
> +	if (error > 0) {
> +		enum igt_hook_error hook_error = error;
> +
> +		switch (hook_error) {
> +		case IGT_HOOK_EVT_EMPTY_NAME:
> +			return "Empty name in event descriptor";
> +		case IGT_HOOK_EVT_NO_MATCH:
> +			return "Event name in event descriptor does not match any event type";
> +		default:
> +			return "Unknown error";
> +		}
> +	} else {
> +		return strerror(-error);
> +	}
> +}
> +
> +/**
> + * igt_hook_print_help:
> + * @f: File pointer where to write the output.
> + * @option_name: Name of the CLI option that accepts the hook descriptor.
> + *
> + * Print a detailed user help text on hook usage.
> + */
> +void igt_hook_print_help(FILE *f, const char *option_name)
> +{
> +	fprintf(f, "\
> +The option %1$s receives as argument a \"hook descriptor\" and allows the\n\
> +execution of a shell command at different points during execution of tests. Each\n\
> +such a point is called a \"hook event\".\n\
> +\n\
> +Examples:\n\
> +\n\
> +  # Prints hook-specic env vars for every event.\n\
> +  %1$s 'printenv | grep ^IGT_HOOK_'\n\
> +\n\
> +  # Equivalent to the above. Useful if command contains ':'.\n\
> +  %1$s '*:printenv | grep ^IGT_HOOK_'\n\
> +\n\
> +  # Adds a line to out.txt containing the result of each test case.\n\
> +  %1$s 'post-test:echo $IGT_HOOK_TEST_FULLNAME $IGT_HOOK_RESULT >> out.txt'\n\
> +\n\
> +The accepted format for a hook descriptor is `[<events>:]<cmd>`, where:\n\
> +\n\
> +  - <events> is a comma-separated list of event descriptors, which defines the\n\
> +    set of events be tracked. If omitted, all events are tracked.\n\
> +\n\
> +  - <cmd> is a shell command to be executed on the occurrence each tracked\n\
> +    event. If the command contains ':', then passing <events> is required,\n\
> +    otherwise part of the command would be treated as an event descriptor.\n\
> +\n\
> +", option_name);
> +
> +	fprintf(f, "\
> +An \"event descriptor\" is either the name of an event or the string '*'. The\n\
> +latter matches all event names. The list of possible event names is provided\n\
> +below:\n\
> +\n\
> +");
> +
> +	for (enum igt_hook_evt_type et = 0; et < IGT_HOOK_NUM_EVENTS; et++) {
> +		const char *desc;
> +
> +		switch (et) {
> +		case IGT_HOOK_PRE_TEST:
> +			desc = "Occurs before a test case starts.";
> +			break;
> +		case IGT_HOOK_PRE_SUBTEST:
> +			desc = "Occurs before the execution of a subtest.";
> +			break;
> +		case IGT_HOOK_PRE_DYN_SUBTEST:
> +			desc = "Occurs before the execution of a dynamic subtest.";
> +			break;
> +		case IGT_HOOK_POST_DYN_SUBTEST:
> +			desc = "Occurs after the execution of a dynamic subtest.";
> +			break;
> +		case IGT_HOOK_POST_SUBTEST:
> +			desc = "Occurs after the execution of a subtest.";
> +			break;
> +		case IGT_HOOK_POST_TEST:
> +			desc = "Occurs after a test case has finished.";
> +			break;
> +		default:
> +			desc = "MISSING DESCRIPTION";
> +		}
> +
> +		fprintf(f, "  %s\n  %s\n\n", igt_hook_evt_type_to_name(et), desc);
> +	}
> +
> +	fprintf(f, "\
> +For each event matched by <events>, <cmd> is executed as a shell command. The\n\
> +exit status of the command is ignored. The following environment variables are\n\
> +available to the command:\n\
> +\n\
> +  IGT_HOOK_EVENT\n\
> +  Name of the current event.\n\
> +\n\
> +  IGT_HOOK_TEST_FULLNAME\n\
> +  Full name of the test in the format `igt@<test>[@<subtest>[@<dyn_subtest>]]`.\n\
> +\n\
> +  IGT_HOOK_TEST		   \n\
> +  Name of the current test.\n\
> +\n\
> +  IGT_HOOK_SUBTEST\n\
> +  Name of the current subtest. Will be the empty string if not running a\n\
> +  subtest.\n\
> +\n\
> +  IGT_HOOK_DYN_SUBTEST\n\
> +  Name of the current dynamic subtest. Will be the empty string if not running a\n\
> +  dynamic subtest.\n\
> +\n\
> +  IGT_HOOK_RESULT\n\
> +  String representing the result of the test/subtest/dynamic subtest. Possible\n\
> +  values are: SUCCESS, SKIP or FAIL. This is only applicable on \"post-*\"\n\
> +  events and will be the empty string for other types of events.\n\
> +\n\
> +");
> +}
> diff --git a/lib/igt_hook.h b/lib/igt_hook.h
> new file mode 100644
> index 000000000000..6fef7254a317
> --- /dev/null
> +++ b/lib/igt_hook.h
> @@ -0,0 +1,86 @@
> +/*
> + * Copyright © 2024 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.
> + */
> +#ifndef IGT_HOOK_H
> +#define IGT_HOOK_H
> +
> +#include <stdio.h>
> +
> +/**
> + * igt_hook:
> + *
> + * Opaque struct to hold data related to hook support.
> + */
> +struct igt_hook;
> +
> +/**
> + * igt_hook_evt_type:
> + * @IGT_HOOK_PRE_TEST: Occurs before a test case (executable) starts the
> + * test code.
> + * @IGT_HOOK_PRE_SUBTEST: Occurs before the execution of a subtest.
> + * @IGT_HOOK_PRE_DYN_SUBTEST: Occurs before the execution of a dynamic subtest.
> + * @IGT_HOOK_POST_DYN_SUBTEST: Occurs after the execution of a dynamic subtest.
> + * @IGT_HOOK_POST_SUBTEST: Occurs after the execution of a subtest..
> + * @IGT_HOOK_POST_TEST: Occurs after a test case (executable) is finished with
> + * the test code.
> + * @IGT_HOOK_NUM_EVENTS: This is not really an event and represents the number
> + * of possible events tracked by igt_hook.
> + *
> + * Events tracked by igt_hook. Those events occur at specific points during the
> + * execution of a test.
> + */
> +enum igt_hook_evt_type {
> +	IGT_HOOK_PRE_TEST,
> +	IGT_HOOK_PRE_SUBTEST,
> +	IGT_HOOK_PRE_DYN_SUBTEST,
> +	IGT_HOOK_POST_DYN_SUBTEST,
> +	IGT_HOOK_POST_SUBTEST,
> +	IGT_HOOK_POST_TEST,
> +	IGT_HOOK_NUM_EVENTS /* This must always be the last one. */
> +};
> +
> +/**
> + * igt_hook_evt:
> + * @evt_type: Type of event.
> + * @target_name: A string pointing to the name of the test, subtest or dynamic
> + * subtest, depending on @evt_type.
> + * @result: A string containing the result of the test, subtest or dynamic
> + * subtest. This is only applicable for the `IGT_HOOK_POST_\*' event types;
> + * other types must initialize this to #NULL.
> + *
> + * An event tracked by igt_hook, which is done with @igt_hook_push_evt(). This must
> + * be zero initialized and fields relevant to the event type must be set before
> + * passing its reference to @igt_hook_push_evt().
> + */
> +struct igt_hook_evt {
> +	enum igt_hook_evt_type evt_type;
> +	const char *target_name;
> +	const char *result;
> +};
> +
> +struct igt_hook *igt_hook_init(const char *hook_str, int *error);
> +void igt_hook_free(struct igt_hook *igt_hook);
> +void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt);
> +const char *igt_hook_error_str(int error);
> +void igt_hook_print_help(FILE *f, const char *option_name);
> +
> +#endif /* IGT_HOOK_H */
> diff --git a/lib/meson.build b/lib/meson.build
> index e2f740c116f8..10b8066647f2 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -109,6 +109,7 @@ lib_sources = [
>  	'veboxcopy_gen12.c',
>  	'igt_msm.c',
>  	'igt_dsc.c',
> +	'igt_hook.c',
>  	'xe/xe_gt.c',
>  	'xe/xe_ioctl.c',
>  	'xe/xe_mmio.c',
> diff --git a/lib/tests/igt_hook.c b/lib/tests/igt_hook.c
> new file mode 100644
> index 000000000000..bd7e570f9607
> --- /dev/null
> +++ b/lib/tests/igt_hook.c
> @@ -0,0 +1,187 @@
> +/*
> + * Copyright © 2024 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 <stdbool.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +
> +#include "igt_core.h"
> +#include "igt_hook.h"
> +
> +static const char *env_var_names[] = {
> +	"IGT_HOOK_EVENT",
> +	"IGT_HOOK_TEST_FULLNAME",
> +	"IGT_HOOK_TEST",
> +	"IGT_HOOK_SUBTEST",
> +	"IGT_HOOK_DYN_SUBTEST",
> +	"IGT_HOOK_RESULT",
> +};
> +
> +#define num_env_vars (sizeof(env_var_names) / sizeof(env_var_names[0]))
> +
> +static int env_var_name_lookup(char *line)
> +{
> +	int i;
> +	char *c;
> +
> +	c = strchr(line, '=');
> +	if (c)
> +		*c = '\0';
> +
> +	for (i = 0; i < num_env_vars; i++)
> +		if (!strcmp(line, env_var_names[i]))
> +			goto out;
> +
> +	i = -1;
> +out:
> +	if (c)
> +		*c = '=';
> +
> +	return i;
> +}
> +
> +static void test_null_error_pointer(void)
> +{
> +	struct igt_hook *igt_hook;
> +
> +	/* Ensure passing NULL error pointer does not cause issues. */
> +	igt_hook = igt_hook_init("invalid:echo hello", NULL);
> +	igt_assert(igt_hook == NULL);
> +}
> +
> +static void test_invalid_hook_descriptors(void)
> +{
> +	struct {
> +		const char *name;
> +		const char *hook_desc;
> +	} invalid_cases[] = {
> +		{"invalid-event-name", "invalid-event:echo hello"},
> +		{"invalid-empty-event-name", ":echo hello"},
> +		{"invalid-colon-in-cmd", "echo hello:world"},
> +		{},
> +	};
> +
> +	for (int i = 0; invalid_cases[i].name; i++) {
> +		igt_subtest(invalid_cases[i].name) {
> +			int err = 0;
> +			struct igt_hook *igt_hook;
> +
> +			igt_hook = igt_hook_init(invalid_cases[i].hook_desc, &err);
> +			igt_assert(igt_hook == NULL);
> +			igt_assert(err != 0);
> +		}
> +	}
> +}
> +
> +static void test_print_help(void)
> +{
> +	char *buf;
> +	size_t len;
> +	FILE *f;
> +	const char expected_initial_text[] = "The option --hook receives as argument a \"hook descriptor\"";
> +
> +	f = open_memstream(&buf, &len);
> +	igt_assert(f);
> +
> +	igt_hook_print_help(f, "--hook");
> +	fclose(f);
> +
> +	igt_assert(!strncmp(buf, expected_initial_text, sizeof(expected_initial_text) - 1));
> +
> +	/* This is an extra check to catch a case where an event type is added
> +	 * without a proper description. */
> +	igt_assert(!strstr(buf, "MISSING DESCRIPTION"));
> +
> +	free(buf);
> +}
> +
> +static void test_all_env_vars(void)
> +{
> +	struct igt_hook_evt evt = {
> +		.evt_type = IGT_HOOK_PRE_SUBTEST,
> +		.target_name = "foo",
> +	};
> +	bool env_vars_checklist[num_env_vars] = {};
> +	struct igt_hook *igt_hook;
> +	char *hook_str;
> +	FILE *f;
> +	int pipefd[2];
> +	int ret;
> +	int i;
> +	char *line;
> +	size_t line_size;
> +
> +	ret = pipe(pipefd);
> +	igt_assert(ret == 0);
> +
> +	/* Use grep to filter only env var set by us. This should ensure that
> +	 * writing to the pipe will not block due to capacity, since we only
> +	 * read from the pipe after the shell command is done. */
> +	ret = asprintf(&hook_str, "printenv -0 | grep -z ^IGT_HOOK >&%d", pipefd[1]);
> +	igt_assert(ret > 0);
> +
> +	igt_hook = igt_hook_init(hook_str, NULL);
> +	igt_assert(igt_hook);
> +
> +	igt_hook_push_evt(igt_hook, &evt);
> +
> +	close(pipefd[1]);
> +	f = fdopen(pipefd[0], "r");
> +	igt_assert(f);
> +
> +	line = NULL;
> +	line_size = 0;
> +
> +	while (getdelim(&line, &line_size, '\0', f) != -1) {
> +		ret = env_var_name_lookup(line);
> +		igt_assert_f(ret >= 0, "Unexpected env var %s\n", line);
> +		env_vars_checklist[ret] = true;
> +	}
> +
> +	for (i = 0; i < num_env_vars; i++)
> +		igt_assert_f(env_vars_checklist[i], "Missing env var %s\n", env_var_names[i]);
> +
> +	fclose(f);
> +	igt_hook_free(igt_hook);
> +	free(hook_str);
> +	free(line);
> +}
> +
> +igt_main
> +{
> +	igt_subtest("null-error-pointer")
> +		test_null_error_pointer();
> +
> +	test_invalid_hook_descriptors();
> +
> +	igt_subtest("help-description")
> +		test_print_help();
> +
> +	igt_subtest_group {
> +		igt_fixture {
> +			igt_require_f(system(NULL), "Shell seems not to be available\n");
> +		}
> +
> +		igt_subtest("all-env-vars")
> +			test_all_env_vars();
> +	}
> +}
> diff --git a/lib/tests/igt_hook_integration.c b/lib/tests/igt_hook_integration.c
> new file mode 100644
> index 000000000000..56632b13ae81
> --- /dev/null
> +++ b/lib/tests/igt_hook_integration.c
> @@ -0,0 +1,297 @@
> +/*
> + * Copyright © 2024 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 <stdbool.h>
> +#include <stdio.h>
> +#include <string.h>
> +
> +#include "igt_core.h"
> +
> +#include "igt_tests_common.h"
> +
> +char prog[] = "igt_hook_integration";
> +char hook_opt[] = "--hook";
> +char hook_str[128];
> +char *fake_argv[] = {prog, hook_opt, hook_str};
> +int fake_argc = sizeof(fake_argv) / sizeof(fake_argv[0]);
> +
> +#define ENV_ARRAY(evt_name, fullname_suffix, subtest, dyn_subtest, result) \
> +{ \
> +	"IGT_HOOK_EVENT=" evt_name, \
> +	"IGT_HOOK_TEST_FULLNAME=igt@igt_hook_integration" fullname_suffix, \
> +	"IGT_HOOK_TEST=igt_hook_integration", \
> +	"IGT_HOOK_SUBTEST=" subtest, \
> +	"IGT_HOOK_DYN_SUBTEST=" dyn_subtest, \
> +	"IGT_HOOK_RESULT=" result, \
> +}
> +
> +#define TEST_ENV(evt_name, result) \
> +	ENV_ARRAY(evt_name, "", "", "", result)
> +
> +#define SUBTEST_ENV(evt_name, subtest, result) \
> +	ENV_ARRAY(evt_name, "@" subtest, subtest, "", result)
> +
> +#define DYN_SUBTEST_ENV(evt_name, subtest, dyn_subtest, result) \
> +	ENV_ARRAY(evt_name, "@" subtest "@" dyn_subtest, subtest, dyn_subtest, result)
> +
> +const char *pre_test_env[] = TEST_ENV("pre-test", "");
> +const char *pre_subtest_a_env[] = SUBTEST_ENV("pre-subtest", "a", "");
> +const char *pre_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "success", "");
> +const char *post_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "success", "SUCCESS");
> +const char *pre_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "failed", "");
> +const char *post_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "failed", "FAIL");
> +const char *pre_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "skipped", "");
> +const char *post_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "skipped", "SKIP");
> +const char *post_subtest_a_env[] = SUBTEST_ENV("post-subtest", "a", "FAIL");
> +const char *pre_subtest_b_env[] = SUBTEST_ENV("pre-subtest", "b", "");
> +const char *post_subtest_b_env[] = SUBTEST_ENV("post-subtest", "b", "SUCCESS");
> +const char *post_test_env[] = TEST_ENV("post-test", "FAIL");
> +
> +#define num_env_vars (sizeof(pre_test_env) / sizeof(pre_test_env[0]))
> +
> +__noreturn static void fake_main(void)
> +{
> +	igt_subtest_init(fake_argc, fake_argv);
> +
> +	igt_subtest_with_dynamic("a") {
> +		igt_dynamic("success") {
> +			igt_info("...@a@success\n");
> +		}
> +
> +		igt_dynamic("failed") {
> +			igt_assert_f(false, "Fail on purpose\n");
> +			igt_info("...@a@failed\n");
> +		}
> +
> +		igt_dynamic("skipped") {
> +			igt_require_f(false, "Skip on purpose\n");
> +			igt_info("...@a@skipped\n");
> +		}
> +	}
> +
> +	igt_subtest("b") {
> +		igt_info("...@b\n");
> +	}
> +
> +	igt_exit();
> +}
> +
> +static void test_invalid_hook_str(void)
> +{
> +	int status;
> +	pid_t pid;
> +	static char err[4096];
> +	int errfd;
> +
> +	sprintf(hook_str, "invalid-event:echo hello");
> +
> +	pid = do_fork_bg_with_pipes(fake_main, NULL, &errfd);
> +
> +	read_whole_pipe(errfd, err, sizeof(err));
> +
> +	internal_assert(safe_wait(pid, &status) != -1);
> +	internal_assert_wexited(status, IGT_EXIT_INVALID);
> +
> +	internal_assert(strstr(err, "Failed to initialize hook data:"));
> +
> +	close(errfd);
> +}
> +
> +static bool match_env(FILE *hook_out_stream, const char **expected_env)
> +{
> +	int i;
> +	char hook_env_buf[4096];
> +	size_t buf_len = 0;
> +	char *line = NULL;
> +	size_t line_size;
> +	bool env_checklist[num_env_vars] = {};
> +	bool has_unexpected = false;
> +	bool has_missing = false;
> +
> +	/* Store env from hook so we can show it in case of errors */
> +	while (getdelim(&line, &line_size, '\0', hook_out_stream) != -1) {
> +		internal_assert(buf_len + strlen(line) + 1 <= sizeof(hook_env_buf));
> +		strcpy(hook_env_buf + buf_len, line);
> +		buf_len += strlen(line) + 1;
> +
> +		if (!strcmp(line, "---"))
> +			break;
> +	}
> +
> +	if (!expected_env && !buf_len) {
> +		/* We have consumed everything and we are done now. */
> +		return false;
> +	}
> +
> +
> +	if (!expected_env) {
> +		printf("Detected unexpected hook execution\n");
> +		has_unexpected = true;
> +		goto out;
> +	}
> +
> +	if (!buf_len) {
> +		printf("Expected more hook execution, but none found\n");
> +		has_missing = true;
> +		goto out;
> +	}
> +
> +
> +	line = hook_env_buf;
> +	while (strcmp(line, "---")) {
> +		for (i = 0; i < num_env_vars; i++) {
> +			if (!strcmp(line, expected_env[i])) {
> +				env_checklist[i] = true;
> +				break;
> +			}
> +		}
> +
> +		if (i == num_env_vars) {
> +			printf("Unexpected envline from hook: %s\n", line);
> +			has_unexpected = true;
> +		}
> +
> +		line += strlen(line) + 1;
> +	}
> +
> +	for (i = 0; i < num_env_vars; i++) {
> +		if (!env_checklist[i]) {
> +			has_missing = true;
> +			printf("Missing expected envline: %s\n", expected_env[i]);
> +		}
> +	}
> +
> +out:
> +	if (has_unexpected || has_missing) {
> +		if (expected_env) {
> +			printf("Expected environment:\n");
> +			for (i = 0; i < num_env_vars; i++)
> +				printf("  %s\n", expected_env[i]);
> +		}
> +
> +		if (buf_len) {
> +			printf("Environment from hook:\n");
> +			line = hook_env_buf;
> +			while (strcmp(line, "---")) {
> +				printf("  %s\n", line);
> +				line += strlen(line) + 1;
> +			}
> +		} else {
> +			printf("No hook execution found\n");
> +		}
> +	}
> +
> +	internal_assert(!has_unexpected);
> +	internal_assert(!has_missing);
> +
> +	/* Ready to consume next hook output. */
> +	return true;
> +}
> +
> +static void run_tests_and_match_env(const char *evt_descriptors, const char **expected_envs[])
> +{
> +	int i;
> +	int ret;
> +	int pipefd[2];
> +	pid_t pid;
> +	FILE *f;
> +
> +	ret = pipe(pipefd);
> +	internal_assert(ret == 0);
> +
> +	/* Use grep to filter only env var set by us. This should ensure that
> +	 * writing to the pipe will not block due to capacity, since we only
> +	 * read from the pipe after the shell command is done. */
> +	sprintf(hook_str,
> +		"%1$s:printenv -0 | grep -z ^IGT_HOOK >&%2$d; echo -en ---\\\\x00 >&%2$d",
> +		evt_descriptors,
> +		pipefd[1]);
> +
> +	pid = do_fork_bg_with_pipes(fake_main, NULL, NULL);
> +	internal_assert(safe_wait(pid, &ret) != -1);
> +	internal_assert_wexited(ret, IGT_EXIT_FAILURE);
> +
> +	close(pipefd[1]);
> +	f = fdopen(pipefd[0], "r");
> +	internal_assert(f);
> +
> +	while (match_env(f, expected_envs[i]))
> +		i++;
> +
> +	fclose(f);
> +
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	{
> +		printf("Check invalid hook string\n");
> +		test_invalid_hook_str();
> +	}
> +
> +	{
> +		const char **expected_envs[] = {
> +			pre_test_env,
> +			pre_subtest_a_env,
> +			pre_dyn_subtest_a_success_env,
> +			post_dyn_subtest_a_success_env,
> +			pre_dyn_subtest_a_failed_env,
> +			post_dyn_subtest_a_failed_env,
> +			pre_dyn_subtest_a_skipped_env,
> +			post_dyn_subtest_a_skipped_env,
> +			post_subtest_a_env,
> +			pre_subtest_b_env,
> +			post_subtest_b_env,
> +			post_test_env,
> +			NULL,
> +		};
> +
> +		printf("Check full event tracking\n");
> +		run_tests_and_match_env("*", expected_envs);
> +	}
> +
> +	{
> +		const char **expected_envs[] = {
> +			pre_dyn_subtest_a_success_env,
> +			pre_dyn_subtest_a_failed_env,
> +			pre_dyn_subtest_a_skipped_env,
> +			NULL,
> +		};
> +
> +		printf("Check single event type tracking\n");
> +		run_tests_and_match_env("pre-dyn-subtest", expected_envs);
> +	}
> +
> +	{
> +		const char **expected_envs[] = {
> +			pre_subtest_a_env,
> +			post_dyn_subtest_a_success_env,
> +			post_dyn_subtest_a_failed_env,
> +			post_dyn_subtest_a_skipped_env,
> +			pre_subtest_b_env,
> +			NULL,
> +		};
> +
> +		printf("Check multiple event types tracking\n");
> +		run_tests_and_match_env("post-dyn-subtest,pre-subtest", expected_envs);
> +	}
> +}
> diff --git a/lib/tests/meson.build b/lib/tests/meson.build
> index fa3d81de6cef..df8092638eca 100644
> --- a/lib/tests/meson.build
> +++ b/lib/tests/meson.build
> @@ -10,6 +10,8 @@ lib_tests = [
>  	'igt_exit_handler',
>  	'igt_fork',
>  	'igt_fork_helper',
> +	'igt_hook',
> +	'igt_hook_integration',
>          'igt_ktap_parser',
>  	'igt_list_only',
>  	'igt_invalid_subtest_name',
> -- 
> 2.45.0
> 

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-09 15:24 ` [PATCH i-g-t 1/3] igt_hook: Add feature Gustavo Sousa
  2024-05-13 17:10   ` Kamil Konieczny
@ 2024-05-15 17:10   ` Kamil Konieczny
  2024-05-15 17:35     ` Gustavo Sousa
  2024-05-21 19:40   ` Lucas De Marchi
  2 siblings, 1 reply; 22+ messages in thread
From: Kamil Konieczny @ 2024-05-15 17:10 UTC (permalink / raw)
  To: igt-dev; +Cc: Gustavo Sousa, Petri Latvala

Hi Gustavo,
On 2024-05-09 at 12:24:29 -0300, Gustavo Sousa wrote:
> For development purposes, sometimes it is useful to have a way of
> running custom scripts at certain points of test executions. A
> real-world example I bumped into recently is to collect information from
> sysfs before and after running each entry of a testlist.
> 
> While it is possible for the user to handcraft a script that calls each
> test with the correct actions before and after execution, we can provide
> a better experience by adding built-in support for running hooks during
> test execution.
> 
> That would be even better when adding the same kind of support for
> igt_runner (which is done in an upcoming change), since the user can
> also nicely resume with igt_resume with the hook already setup in case a
> crash happens during execution of the test list.
> 
> As such provide implement support for hooks, integrate it into
> igt_core and expose the functionality via --hook CLI option on test
> executables.

Hmm, why not just a pre-hook@ and post-hook@ in testlist itself?
It will be easier to handle - just more parsing.

Added Petri to cc.

Regards,
Kamil

> 
> Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
> ---
>  .../igt-gpu-tools/igt-gpu-tools-docs.xml      |   1 +
>  lib/igt_core.c                                | 116 +++-
>  lib/igt_hook.c                                | 499 ++++++++++++++++++
>  lib/igt_hook.h                                |  86 +++
>  lib/meson.build                               |   1 +
>  lib/tests/igt_hook.c                          | 187 +++++++
>  lib/tests/igt_hook_integration.c              | 297 +++++++++++
>  lib/tests/meson.build                         |   2 +
>  8 files changed, 1180 insertions(+), 9 deletions(-)
>  create mode 100644 lib/igt_hook.c
>  create mode 100644 lib/igt_hook.h
>  create mode 100644 lib/tests/igt_hook.c
>  create mode 100644 lib/tests/igt_hook_integration.c
> 
> diff --git a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
> index 9085eb924e85..11458c68124b 100644
> --- a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
> +++ b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
> @@ -32,6 +32,7 @@
>      <xi:include href="xml/igt_fb.xml"/>
>      <xi:include href="xml/igt_frame.xml"/>
>      <xi:include href="xml/igt_gt.xml"/>
> +    <xi:include href="xml/igt_hook.xml"/>
>      <xi:include href="xml/igt_io.xml"/>
>      <xi:include href="xml/igt_kmod.xml"/>
>      <xi:include href="xml/igt_kms.xml"/>
> diff --git a/lib/igt_core.c b/lib/igt_core.c
> index 3ff3e0392316..291d891cf884 100644
> --- a/lib/igt_core.c
> +++ b/lib/igt_core.c
> @@ -70,6 +70,7 @@
>  
>  #include "igt_core.h"
>  #include "igt_aux.h"
> +#include "igt_hook.h"
>  #include "igt_sysfs.h"
>  #include "igt_sysrq.h"
>  #include "igt_rc.h"
> @@ -241,6 +242,9 @@
>   * - '*,!basic*' match any subtest not starting basic
>   * - 'basic*,!basic-render*' match any subtest starting basic but not starting basic-render
>   *
> + * It is possible to run a shell script at certain points of test execution with
> + * "--hook". See the usage description with "--help-hook" for details.
> + *
>   * # Configuration
>   *
>   * Some of IGT's behavior can be configured through a configuration file.
> @@ -273,6 +277,8 @@ static unsigned int exit_handler_count;
>  const char *igt_interactive_debug;
>  bool igt_skip_crc_compare;
>  
> +static struct igt_hook *igt_hook = NULL;
> +
>  /* subtests helpers */
>  static bool show_testlist = false;
>  static bool list_subtests = false;
> @@ -338,6 +344,8 @@ enum {
>  	OPT_INTERACTIVE_DEBUG,
>  	OPT_SKIP_CRC,
>  	OPT_TRACE_OOPS,
> +	OPT_HOOK,
> +	OPT_HELP_HOOK,
>  	OPT_DEVICE,
>  	OPT_VERSION,
>  	OPT_HELP = 'h'
> @@ -810,6 +818,8 @@ static void common_exit_handler(int sig)
>  		bind_fbcon(true);
>  	}
>  
> +	igt_hook_free(igt_hook);
> +
>  	/* When not killed by a signal check that igt_exit() has been properly
>  	 * called. */
>  	assert(sig != 0 || igt_exit_called || igt_is_aborting);
> @@ -907,6 +917,8 @@ static void print_usage(const char *help_str, bool output_on_stderr)
>  		   "  --interactive-debug[=domain]\n"
>  		   "  --skip-crc-compare\n"
>  		   "  --trace-on-oops\n"
> +		   "  --hook [<events>:]<cmd>\n"
> +		   "  --help-hook\n"
>  		   "  --help-description\n"
>  		   "  --describe\n"
>  		   "  --device filters\n"
> @@ -1090,6 +1102,8 @@ static int common_init(int *argc, char **argv,
>  		{"interactive-debug", optional_argument, NULL, OPT_INTERACTIVE_DEBUG},
>  		{"skip-crc-compare",  no_argument,       NULL, OPT_SKIP_CRC},
>  		{"trace-on-oops",     no_argument,       NULL, OPT_TRACE_OOPS},
> +		{"hook",              required_argument, NULL, OPT_HOOK},
> +		{"help-hook",         no_argument,       NULL, OPT_HELP_HOOK},
>  		{"device",            required_argument, NULL, OPT_DEVICE},
>  		{"version",           no_argument,       NULL, OPT_VERSION},
>  		{"help",              no_argument,       NULL, OPT_HELP},
> @@ -1225,6 +1239,24 @@ static int common_init(int *argc, char **argv,
>  		case OPT_TRACE_OOPS:
>  			show_ftrace = true;
>  			break;
> +		case OPT_HOOK:
> +			assert(optarg);
> +			if (igt_hook) {
> +				igt_warn("Overriding previous hook descriptor\n");
> +				igt_hook_free(igt_hook);
> +			}
> +			igt_hook = igt_hook_init(optarg, &ret);
> +			if (!igt_hook) {
> +				igt_critical("Failed to initialize hook data: %s\n",
> +					     igt_hook_error_str(ret));
> +				ret = ret > 0 ? -2 : -3;
> +				goto out;
> +			}
> +			break;
> +		case OPT_HELP_HOOK:
> +			igt_hook_print_help(stdout, "--hook");
> +			ret = -1;
> +			goto out;
>  		case OPT_DEVICE:
>  			assert(optarg);
>  			/* if set by env IGT_DEVICE we need to free it */
> @@ -1274,9 +1306,24 @@ out:
>  			exit(IGT_EXIT_INVALID);
>  	}
>  
> -	if (ret < 0)
> -		/* exit with no error for -h/--help */
> -		exit(ret == -1 ? 0 : IGT_EXIT_INVALID);
> +	if (ret < 0) {
> +		free(igt_hook);
> +		igt_hook = NULL;
> +
> +		switch (ret) {
> +		case -1: /* exit with no error for -h/--help */
> +			exit(0);
> +			break;
> +		case -2:
> +			exit(IGT_EXIT_INVALID);
> +			break;
> +		case -3:
> +			exit(IGT_EXIT_ABORT);
> +			break;
> +		default:
> +			assert(0);
> +		}
> +	}
>  
>  	if (!igt_only_list_subtests()) {
>  		bind_fbcon(false);
> @@ -1284,6 +1331,15 @@ out:
>  		print_version();
>  		igt_srandom();
>  
> +		if (igt_hook) {
> +			struct igt_hook_evt hook_evt = {
> +				.evt_type = IGT_HOOK_PRE_TEST,
> +				.target_name = command_str,
> +			};
> +
> +			igt_hook_push_evt(igt_hook, &hook_evt);
> +		}
> +
>  		sync();
>  		oom_adjust_for_doom();
>  		ftrace_dump_on_oops(show_ftrace);
> @@ -1487,6 +1543,16 @@ bool __igt_run_subtest(const char *subtest_name, const char *file, const int lin
>  	igt_thread_clear_fail_state();
>  
>  	igt_gettime(&subtest_time);
> +
> +	if (igt_hook) {
> +		struct igt_hook_evt hook_evt = {
> +			.evt_type = IGT_HOOK_PRE_SUBTEST,
> +			.target_name = subtest_name,
> +		};
> +
> +		igt_hook_push_evt(igt_hook, &hook_evt);
> +	}
> +
>  	return (in_subtest = subtest_name);
>  }
>  
> @@ -1517,6 +1583,16 @@ bool __igt_run_dynamic_subtest(const char *dynamic_subtest_name)
>  	_igt_dynamic_tests_executed++;
>  
>  	igt_gettime(&dynamic_subtest_time);
> +
> +	if (igt_hook) {
> +		struct igt_hook_evt hook_evt = {
> +			.evt_type = IGT_HOOK_PRE_DYN_SUBTEST,
> +			.target_name = dynamic_subtest_name,
> +		};
> +
> +		igt_hook_push_evt(igt_hook, &hook_evt);
> +	}
> +
>  	return (in_dynamic_subtest = dynamic_subtest_name);
>  }
>  
> @@ -1602,6 +1678,17 @@ __noreturn static void exit_subtest(const char *result)
>  	struct timespec *thentime = in_dynamic_subtest ? &dynamic_subtest_time : &subtest_time;
>  	jmp_buf *jmptarget = in_dynamic_subtest ? &igt_dynamic_jmpbuf : &igt_subtest_jmpbuf;
>  
> +	if (igt_hook) {
> +		struct igt_hook_evt hook_evt = {
> +			.evt_type = (in_dynamic_subtest
> +					? IGT_HOOK_POST_DYN_SUBTEST
> +					: IGT_HOOK_POST_SUBTEST),
> +			.result = result,
> +		};
> +
> +		igt_hook_push_evt(igt_hook, &hook_evt);
> +	}
> +
>  	if (!igt_thread_is_main()) {
>  		igt_thread_fail();
>  		pthread_exit(NULL);
> @@ -2274,6 +2361,7 @@ void __igt_abort(const char *domain, const char *file, const int line,
>  void igt_exit(void)
>  {
>  	int tmp;
> +	const char *result;
>  
>  	if (!test_with_subtests)
>  		igt_thread_assert_no_failures();
> @@ -2318,12 +2406,7 @@ void igt_exit(void)
>  
>  	assert(waitpid(-1, &tmp, WNOHANG) == -1 && errno == ECHILD);
>  
> -	if (!test_with_subtests) {
> -		struct timespec now;
> -		const char *result;
> -
> -		igt_gettime(&now);
> -
> +	if (!test_with_subtests || igt_hook) {
>  		switch (igt_exitcode) {
>  			case IGT_EXIT_SUCCESS:
>  				result = "SUCCESS";
> @@ -2334,6 +2417,12 @@ void igt_exit(void)
>  			default:
>  				result = "FAIL";
>  		}
> +	}
> +
> +	if (!test_with_subtests) {
> +		struct timespec now;
> +
> +		igt_gettime(&now);
>  
>  		if (test_multi_fork_child) /* parent will do the yelling */
>  			_log_line_fprintf(stdout, "dyn_child pid:%d (%.3fs) ends with err=%d\n",
> @@ -2344,6 +2433,15 @@ void igt_exit(void)
>  					  result, igt_time_elapsed(&subtest_time, &now));
>  	}
>  
> +	if (igt_hook) {
> +		struct igt_hook_evt hook_evt = {
> +			.evt_type = IGT_HOOK_POST_TEST,
> +			.result = result,
> +		};
> +
> +		igt_hook_push_evt(igt_hook, &hook_evt);
> +	}
> +
>  	exit(igt_exitcode);
>  }
>  
> diff --git a/lib/igt_hook.c b/lib/igt_hook.c
> new file mode 100644
> index 000000000000..8a39e19e3e5f
> --- /dev/null
> +++ b/lib/igt_hook.c
> @@ -0,0 +1,499 @@
> +/*
> + * Copyright © 2024 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 <assert.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include "igt_hook.h"
> +
> +/**
> + * SECTION:igt_hook
> + * @short_description: Support for running a hook script on test execution
> + * @title: Hook support
> + *
> + * IGT provides support for running a hook script when executing tests. This
> + * support is provided to users via CLI option `--hook` available in test
> + * binaries. Users should use `--help-hook` for detailed usaged description of
> + * the feature.
> + *
> + * The sole user of the exposed API is `igt_core`, which calls @igt_hook_init()
> + * when initializing a test case, then calls @igt_hook_push_evt() for each event
> + * that occurs during that test's execution and finally calls @igt_hook_free()
> + * to clean up at the end.
> + */
> +
> +#define TEST_NAME_INITIAL_SIZE 16
> +
> +typedef uint16_t evt_mask_t;
> +
> +struct igt_hook {
> +	evt_mask_t evt_mask;
> +	char *cmd;
> +	char *test_name;
> +	size_t test_name_size;
> +	char *subtest_name;
> +	size_t subtest_name_size;
> +	char *dyn_subtest_name;
> +	size_t dyn_subtest_name_size;
> +	char *test_fullname;
> +};
> +
> +enum igt_hook_error {
> +	IGT_HOOK_EVT_EMPTY_NAME = 1,
> +	IGT_HOOK_EVT_NO_MATCH,
> +};
> +
> +static_assert(IGT_HOOK_NUM_EVENTS <= sizeof(evt_mask_t) * CHAR_BIT,
> +	      "Number of event types does not fit event type mask");
> +
> +static const char *igt_hook_evt_type_to_name(enum igt_hook_evt_type evt_type)
> +{
> +	switch (evt_type) {
> +	case IGT_HOOK_PRE_TEST:
> +		return "pre-test";
> +	case IGT_HOOK_PRE_SUBTEST:
> +		return "pre-subtest";
> +	case IGT_HOOK_PRE_DYN_SUBTEST:
> +		return "pre-dyn-subtest";
> +	case IGT_HOOK_POST_DYN_SUBTEST:
> +		return "post-dyn-subtest";
> +	case IGT_HOOK_POST_SUBTEST:
> +		return "post-subtest";
> +	case IGT_HOOK_POST_TEST:
> +		return "post-test";
> +	case IGT_HOOK_NUM_EVENTS:
> +		break;
> +	/* No "default:" case, to force a warning from -Wswitch in case we miss
> +	 * any new event type. */
> +	}
> +	return "?";
> +}
> +
> +static int igt_hook_parse_hook_str(const char *hook_str, evt_mask_t *evt_mask, const char **cmd)
> +{
> +	const char *s;
> +
> +	if (!strchr(hook_str, ':')) {
> +		*evt_mask = ~0;
> +		*cmd = hook_str;
> +		return 0;
> +	}
> +
> +	s = hook_str;
> +	*evt_mask = 0;
> +
> +	while (1) {
> +		const char *evt_name;
> +		bool has_match;
> +		bool is_star;
> +		enum igt_hook_evt_type evt_type;
> +
> +		evt_name = s;
> +
> +		while (*s != ':' && *s != ',')
> +			s++;
> +
> +		if (evt_name == s)
> +			return IGT_HOOK_EVT_EMPTY_NAME;
> +
> +		has_match = false;
> +		is_star = *evt_name == '*' && evt_name + 1 == s;
> +
> +		for (evt_type = IGT_HOOK_PRE_TEST; evt_type < IGT_HOOK_NUM_EVENTS; evt_type++) {
> +			if (!is_star) {
> +				const char *this_event_name = igt_hook_evt_type_to_name(evt_type);
> +				size_t len = s - evt_name;
> +
> +				if (len != strlen(this_event_name))
> +					continue;
> +
> +				if (strncmp(evt_name, this_event_name, len))
> +					continue;
> +			}
> +
> +			*evt_mask |= 1 << evt_type;
> +			has_match = true;
> +
> +			if (!is_star)
> +				break;
> +		}
> +
> +		if (!has_match)
> +			return IGT_HOOK_EVT_NO_MATCH;
> +
> +		if (*s++ == ':')
> +			break;
> +	}
> +
> +	*cmd = s;
> +
> +	return 0;
> +}
> +
> +static size_t igt_hook_calc_test_fullname_size(struct igt_hook *igt_hook) {
> +	/* The maximum size of test_fullname will be the maximum length of
> +	 * "igt@<test_name>@<subtest_name>@<dyn_subtest_name>" plus 1 for the
> +	 * null byte. */
> +	return (igt_hook->test_name_size +
> +		igt_hook->subtest_name_size +
> +		igt_hook->dyn_subtest_name_size) + 4;
> +}
> +
> +static void igt_hook_update_test_fullname(struct igt_hook *igt_hook)
> +{
> +	int i;
> +	char *s;
> +	const char *values[3] = {
> +		igt_hook->test_name,
> +		igt_hook->subtest_name,
> +		igt_hook->dyn_subtest_name,
> +	};
> +
> +	if (igt_hook->test_name[0] == '\0') {
> +		igt_hook->test_fullname[0] = '\0';
> +		return;
> +	}
> +
> +	s = stpcpy(igt_hook->test_fullname, "igt");
> +	for (i = 0; i < 3 && values[i][0] != '\0'; i++) {
> +		*s++ = '@';
> +		s = stpcpy(s, values[i]);
> +	}
> +}
> +
> +/**
> + * igt_hook_init:
> + * @hook_str: Hook descriptor string.
> + * @error: Pointer to error number.
> + *
> + * Allocate and initialize an #igt_hook structure.
> + *
> + * This function parses the hook descriptor @hook_str and initializes the struct
> + * to be returned.
> + *
> + * The hook descriptor comes from the argument to `--hook` of the test
> + * executable being run.
> + *
> + * If not #NULL, @error is used to store a non-zero error number if an error
> + * happens. A human-readable string for that error number can be obtained with
> + * @igt_hook_error_str().
> + *
> + * Returns: The pointer to the #igt_hook structure on success or #NULL on error.
> + */
> +struct igt_hook *igt_hook_init(const char *hook_str, int *error)
> +{
> +	int err;
> +	evt_mask_t evt_mask;
> +	const char *cmd;
> +	struct igt_hook *igt_hook = NULL;
> +
> +
> +	err = igt_hook_parse_hook_str(hook_str, &evt_mask, &cmd);
> +	if (err)
> +		goto out;
> +
> +	igt_hook = calloc(1, sizeof(*igt_hook));
> +	igt_hook->evt_mask = evt_mask;
> +
> +	igt_hook->cmd = strdup(cmd);
> +	if (!igt_hook->cmd) {
> +		err = -errno;
> +		goto out;
> +	}
> +
> +	igt_hook->test_name = malloc(TEST_NAME_INITIAL_SIZE);
> +	igt_hook->test_name_size = TEST_NAME_INITIAL_SIZE;
> +	igt_hook->subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
> +	igt_hook->subtest_name_size = TEST_NAME_INITIAL_SIZE;
> +	igt_hook->dyn_subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
> +	igt_hook->dyn_subtest_name_size = TEST_NAME_INITIAL_SIZE;
> +	igt_hook->test_fullname = malloc(igt_hook_calc_test_fullname_size(igt_hook));
> +
> +	igt_hook->test_name[0] = '\0';
> +	igt_hook->subtest_name[0] = '\0';
> +	igt_hook->dyn_subtest_name[0] = '\0';
> +	igt_hook->test_fullname[0] = '\0';
> +
> +out:
> +	if (err) {
> +		if (error)
> +			*error = err;
> +
> +		igt_hook_free(igt_hook);
> +
> +		return NULL;
> +	}
> +
> +	return igt_hook;
> +}
> +
> +/**
> + * igt_hook_free:
> + * @igt_hook: The igt_hook struct.
> + *
> + * De-initialize an igt_hook struct returned by @igt_hook_init().
> + *
> + * This is a no-op if @igt_hook is #NULL.
> + */
> +void igt_hook_free(struct igt_hook *igt_hook)
> +{
> +	if (!igt_hook)
> +		return;
> +
> +	free(igt_hook->cmd);
> +	free(igt_hook->test_name);
> +	free(igt_hook->subtest_name);
> +	free(igt_hook->dyn_subtest_name);
> +	free(igt_hook);
> +}
> +
> +static void igt_hook_update_test_name_pre_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
> +{
> +	char **name_ptr;
> +	size_t *size_ptr;
> +	size_t len;
> +
> +	switch (evt->evt_type) {
> +	case IGT_HOOK_PRE_TEST:
> +		name_ptr = &igt_hook->test_name;
> +		size_ptr = &igt_hook->test_name_size;
> +		break;
> +	case IGT_HOOK_PRE_SUBTEST:
> +		name_ptr = &igt_hook->subtest_name;
> +		size_ptr = &igt_hook->subtest_name_size;
> +		break;
> +	case IGT_HOOK_PRE_DYN_SUBTEST:
> +		name_ptr = &igt_hook->dyn_subtest_name;
> +		size_ptr = &igt_hook->dyn_subtest_name_size;
> +		break;
> +	default:
> +		return;
> +	}
> +
> +	len = strlen(evt->target_name);
> +	if (len + 1 > *size_ptr) {
> +		size_t fullname_size;
> +
> +		*size_ptr *= 2;
> +		*name_ptr = realloc(*name_ptr, *size_ptr);
> +
> +		fullname_size = igt_hook_calc_test_fullname_size(igt_hook);
> +		igt_hook->test_fullname = realloc(igt_hook->test_fullname, fullname_size);
> +	}
> +
> +	strcpy(*name_ptr, evt->target_name);
> +	igt_hook_update_test_fullname(igt_hook);
> +}
> +
> +static void igt_hook_update_test_name_post_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
> +{
> +	switch (evt->evt_type) {
> +	case IGT_HOOK_POST_TEST:
> +		igt_hook->test_name[0] = '\0';
> +		break;
> +	case IGT_HOOK_POST_SUBTEST:
> +		igt_hook->subtest_name[0] = '\0';
> +		break;
> +	case IGT_HOOK_POST_DYN_SUBTEST:
> +		igt_hook->dyn_subtest_name[0] = '\0';
> +		break;
> +	default:
> +		return;
> +	}
> +
> +	igt_hook_update_test_fullname(igt_hook);
> +}
> +
> +static void igt_hook_update_env_vars(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
> +{
> +	setenv("IGT_HOOK_EVENT", igt_hook_evt_type_to_name(evt->evt_type), 1);
> +	setenv("IGT_HOOK_TEST_FULLNAME", igt_hook->test_fullname, 1);
> +	setenv("IGT_HOOK_TEST", igt_hook->test_name, 1);
> +	setenv("IGT_HOOK_SUBTEST", igt_hook->subtest_name, 1);
> +	setenv("IGT_HOOK_DYN_SUBTEST", igt_hook->dyn_subtest_name, 1);
> +	setenv("IGT_HOOK_RESULT", evt->result ?: "", 1);
> +}
> +
> +/**
> + * igt_hook_push_evt:
> + * @igt_hook: The igt_hook structure.
> + * @evt: The event to be pushed.
> + *
> + * Push a new igt_hook event.
> + *
> + * This function must be used to register a new igt_hook event. Calling it will
> + * cause execution of the hook script if the event type matches the filters
> + * provided during initialization of @igt_hook.
> + */
> +void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
> +{
> +	typeof(igt_hook->evt_mask) evt_bit = (1 << evt->evt_type);
> +
> +	igt_hook_update_test_name_pre_call(igt_hook, evt);
> +
> +	if ((evt_bit & igt_hook->evt_mask)) {
> +		igt_hook_update_env_vars(igt_hook, evt);
> +		system(igt_hook->cmd);
> +	}
> +
> +	igt_hook_update_test_name_post_call(igt_hook, evt);
> +}
> +
> +/**
> + * igt_hook_error_str:
> + * @error: Non-zero error number.
> + *
> + * Return a human-readable string containing a description of an error number
> + * generated by one of the `igt_hook_*` functions.
> + *
> + * The string will be the result of strerror() for errors from the C standard
> + * library or a custom description specific to igt_hook.
> + */
> +const char *igt_hook_error_str(int error)
> +{
> +	if (!error)
> +		return "No error";
> +
> +	if (error > 0) {
> +		enum igt_hook_error hook_error = error;
> +
> +		switch (hook_error) {
> +		case IGT_HOOK_EVT_EMPTY_NAME:
> +			return "Empty name in event descriptor";
> +		case IGT_HOOK_EVT_NO_MATCH:
> +			return "Event name in event descriptor does not match any event type";
> +		default:
> +			return "Unknown error";
> +		}
> +	} else {
> +		return strerror(-error);
> +	}
> +}
> +
> +/**
> + * igt_hook_print_help:
> + * @f: File pointer where to write the output.
> + * @option_name: Name of the CLI option that accepts the hook descriptor.
> + *
> + * Print a detailed user help text on hook usage.
> + */
> +void igt_hook_print_help(FILE *f, const char *option_name)
> +{
> +	fprintf(f, "\
> +The option %1$s receives as argument a \"hook descriptor\" and allows the\n\
> +execution of a shell command at different points during execution of tests. Each\n\
> +such a point is called a \"hook event\".\n\
> +\n\
> +Examples:\n\
> +\n\
> +  # Prints hook-specic env vars for every event.\n\
> +  %1$s 'printenv | grep ^IGT_HOOK_'\n\
> +\n\
> +  # Equivalent to the above. Useful if command contains ':'.\n\
> +  %1$s '*:printenv | grep ^IGT_HOOK_'\n\
> +\n\
> +  # Adds a line to out.txt containing the result of each test case.\n\
> +  %1$s 'post-test:echo $IGT_HOOK_TEST_FULLNAME $IGT_HOOK_RESULT >> out.txt'\n\
> +\n\
> +The accepted format for a hook descriptor is `[<events>:]<cmd>`, where:\n\
> +\n\
> +  - <events> is a comma-separated list of event descriptors, which defines the\n\
> +    set of events be tracked. If omitted, all events are tracked.\n\
> +\n\
> +  - <cmd> is a shell command to be executed on the occurrence each tracked\n\
> +    event. If the command contains ':', then passing <events> is required,\n\
> +    otherwise part of the command would be treated as an event descriptor.\n\
> +\n\
> +", option_name);
> +
> +	fprintf(f, "\
> +An \"event descriptor\" is either the name of an event or the string '*'. The\n\
> +latter matches all event names. The list of possible event names is provided\n\
> +below:\n\
> +\n\
> +");
> +
> +	for (enum igt_hook_evt_type et = 0; et < IGT_HOOK_NUM_EVENTS; et++) {
> +		const char *desc;
> +
> +		switch (et) {
> +		case IGT_HOOK_PRE_TEST:
> +			desc = "Occurs before a test case starts.";
> +			break;
> +		case IGT_HOOK_PRE_SUBTEST:
> +			desc = "Occurs before the execution of a subtest.";
> +			break;
> +		case IGT_HOOK_PRE_DYN_SUBTEST:
> +			desc = "Occurs before the execution of a dynamic subtest.";
> +			break;
> +		case IGT_HOOK_POST_DYN_SUBTEST:
> +			desc = "Occurs after the execution of a dynamic subtest.";
> +			break;
> +		case IGT_HOOK_POST_SUBTEST:
> +			desc = "Occurs after the execution of a subtest.";
> +			break;
> +		case IGT_HOOK_POST_TEST:
> +			desc = "Occurs after a test case has finished.";
> +			break;
> +		default:
> +			desc = "MISSING DESCRIPTION";
> +		}
> +
> +		fprintf(f, "  %s\n  %s\n\n", igt_hook_evt_type_to_name(et), desc);
> +	}
> +
> +	fprintf(f, "\
> +For each event matched by <events>, <cmd> is executed as a shell command. The\n\
> +exit status of the command is ignored. The following environment variables are\n\
> +available to the command:\n\
> +\n\
> +  IGT_HOOK_EVENT\n\
> +  Name of the current event.\n\
> +\n\
> +  IGT_HOOK_TEST_FULLNAME\n\
> +  Full name of the test in the format `igt@<test>[@<subtest>[@<dyn_subtest>]]`.\n\
> +\n\
> +  IGT_HOOK_TEST		   \n\
> +  Name of the current test.\n\
> +\n\
> +  IGT_HOOK_SUBTEST\n\
> +  Name of the current subtest. Will be the empty string if not running a\n\
> +  subtest.\n\
> +\n\
> +  IGT_HOOK_DYN_SUBTEST\n\
> +  Name of the current dynamic subtest. Will be the empty string if not running a\n\
> +  dynamic subtest.\n\
> +\n\
> +  IGT_HOOK_RESULT\n\
> +  String representing the result of the test/subtest/dynamic subtest. Possible\n\
> +  values are: SUCCESS, SKIP or FAIL. This is only applicable on \"post-*\"\n\
> +  events and will be the empty string for other types of events.\n\
> +\n\
> +");
> +}
> diff --git a/lib/igt_hook.h b/lib/igt_hook.h
> new file mode 100644
> index 000000000000..6fef7254a317
> --- /dev/null
> +++ b/lib/igt_hook.h
> @@ -0,0 +1,86 @@
> +/*
> + * Copyright © 2024 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.
> + */
> +#ifndef IGT_HOOK_H
> +#define IGT_HOOK_H
> +
> +#include <stdio.h>
> +
> +/**
> + * igt_hook:
> + *
> + * Opaque struct to hold data related to hook support.
> + */
> +struct igt_hook;
> +
> +/**
> + * igt_hook_evt_type:
> + * @IGT_HOOK_PRE_TEST: Occurs before a test case (executable) starts the
> + * test code.
> + * @IGT_HOOK_PRE_SUBTEST: Occurs before the execution of a subtest.
> + * @IGT_HOOK_PRE_DYN_SUBTEST: Occurs before the execution of a dynamic subtest.
> + * @IGT_HOOK_POST_DYN_SUBTEST: Occurs after the execution of a dynamic subtest.
> + * @IGT_HOOK_POST_SUBTEST: Occurs after the execution of a subtest..
> + * @IGT_HOOK_POST_TEST: Occurs after a test case (executable) is finished with
> + * the test code.
> + * @IGT_HOOK_NUM_EVENTS: This is not really an event and represents the number
> + * of possible events tracked by igt_hook.
> + *
> + * Events tracked by igt_hook. Those events occur at specific points during the
> + * execution of a test.
> + */
> +enum igt_hook_evt_type {
> +	IGT_HOOK_PRE_TEST,
> +	IGT_HOOK_PRE_SUBTEST,
> +	IGT_HOOK_PRE_DYN_SUBTEST,
> +	IGT_HOOK_POST_DYN_SUBTEST,
> +	IGT_HOOK_POST_SUBTEST,
> +	IGT_HOOK_POST_TEST,
> +	IGT_HOOK_NUM_EVENTS /* This must always be the last one. */
> +};
> +
> +/**
> + * igt_hook_evt:
> + * @evt_type: Type of event.
> + * @target_name: A string pointing to the name of the test, subtest or dynamic
> + * subtest, depending on @evt_type.
> + * @result: A string containing the result of the test, subtest or dynamic
> + * subtest. This is only applicable for the `IGT_HOOK_POST_\*' event types;
> + * other types must initialize this to #NULL.
> + *
> + * An event tracked by igt_hook, which is done with @igt_hook_push_evt(). This must
> + * be zero initialized and fields relevant to the event type must be set before
> + * passing its reference to @igt_hook_push_evt().
> + */
> +struct igt_hook_evt {
> +	enum igt_hook_evt_type evt_type;
> +	const char *target_name;
> +	const char *result;
> +};
> +
> +struct igt_hook *igt_hook_init(const char *hook_str, int *error);
> +void igt_hook_free(struct igt_hook *igt_hook);
> +void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt);
> +const char *igt_hook_error_str(int error);
> +void igt_hook_print_help(FILE *f, const char *option_name);
> +
> +#endif /* IGT_HOOK_H */
> diff --git a/lib/meson.build b/lib/meson.build
> index e2f740c116f8..10b8066647f2 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -109,6 +109,7 @@ lib_sources = [
>  	'veboxcopy_gen12.c',
>  	'igt_msm.c',
>  	'igt_dsc.c',
> +	'igt_hook.c',
>  	'xe/xe_gt.c',
>  	'xe/xe_ioctl.c',
>  	'xe/xe_mmio.c',
> diff --git a/lib/tests/igt_hook.c b/lib/tests/igt_hook.c
> new file mode 100644
> index 000000000000..bd7e570f9607
> --- /dev/null
> +++ b/lib/tests/igt_hook.c
> @@ -0,0 +1,187 @@
> +/*
> + * Copyright © 2024 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 <stdbool.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +
> +#include "igt_core.h"
> +#include "igt_hook.h"
> +
> +static const char *env_var_names[] = {
> +	"IGT_HOOK_EVENT",
> +	"IGT_HOOK_TEST_FULLNAME",
> +	"IGT_HOOK_TEST",
> +	"IGT_HOOK_SUBTEST",
> +	"IGT_HOOK_DYN_SUBTEST",
> +	"IGT_HOOK_RESULT",
> +};
> +
> +#define num_env_vars (sizeof(env_var_names) / sizeof(env_var_names[0]))
> +
> +static int env_var_name_lookup(char *line)
> +{
> +	int i;
> +	char *c;
> +
> +	c = strchr(line, '=');
> +	if (c)
> +		*c = '\0';
> +
> +	for (i = 0; i < num_env_vars; i++)
> +		if (!strcmp(line, env_var_names[i]))
> +			goto out;
> +
> +	i = -1;
> +out:
> +	if (c)
> +		*c = '=';
> +
> +	return i;
> +}
> +
> +static void test_null_error_pointer(void)
> +{
> +	struct igt_hook *igt_hook;
> +
> +	/* Ensure passing NULL error pointer does not cause issues. */
> +	igt_hook = igt_hook_init("invalid:echo hello", NULL);
> +	igt_assert(igt_hook == NULL);
> +}
> +
> +static void test_invalid_hook_descriptors(void)
> +{
> +	struct {
> +		const char *name;
> +		const char *hook_desc;
> +	} invalid_cases[] = {
> +		{"invalid-event-name", "invalid-event:echo hello"},
> +		{"invalid-empty-event-name", ":echo hello"},
> +		{"invalid-colon-in-cmd", "echo hello:world"},
> +		{},
> +	};
> +
> +	for (int i = 0; invalid_cases[i].name; i++) {
> +		igt_subtest(invalid_cases[i].name) {
> +			int err = 0;
> +			struct igt_hook *igt_hook;
> +
> +			igt_hook = igt_hook_init(invalid_cases[i].hook_desc, &err);
> +			igt_assert(igt_hook == NULL);
> +			igt_assert(err != 0);
> +		}
> +	}
> +}
> +
> +static void test_print_help(void)
> +{
> +	char *buf;
> +	size_t len;
> +	FILE *f;
> +	const char expected_initial_text[] = "The option --hook receives as argument a \"hook descriptor\"";
> +
> +	f = open_memstream(&buf, &len);
> +	igt_assert(f);
> +
> +	igt_hook_print_help(f, "--hook");
> +	fclose(f);
> +
> +	igt_assert(!strncmp(buf, expected_initial_text, sizeof(expected_initial_text) - 1));
> +
> +	/* This is an extra check to catch a case where an event type is added
> +	 * without a proper description. */
> +	igt_assert(!strstr(buf, "MISSING DESCRIPTION"));
> +
> +	free(buf);
> +}
> +
> +static void test_all_env_vars(void)
> +{
> +	struct igt_hook_evt evt = {
> +		.evt_type = IGT_HOOK_PRE_SUBTEST,
> +		.target_name = "foo",
> +	};
> +	bool env_vars_checklist[num_env_vars] = {};
> +	struct igt_hook *igt_hook;
> +	char *hook_str;
> +	FILE *f;
> +	int pipefd[2];
> +	int ret;
> +	int i;
> +	char *line;
> +	size_t line_size;
> +
> +	ret = pipe(pipefd);
> +	igt_assert(ret == 0);
> +
> +	/* Use grep to filter only env var set by us. This should ensure that
> +	 * writing to the pipe will not block due to capacity, since we only
> +	 * read from the pipe after the shell command is done. */
> +	ret = asprintf(&hook_str, "printenv -0 | grep -z ^IGT_HOOK >&%d", pipefd[1]);
> +	igt_assert(ret > 0);
> +
> +	igt_hook = igt_hook_init(hook_str, NULL);
> +	igt_assert(igt_hook);
> +
> +	igt_hook_push_evt(igt_hook, &evt);
> +
> +	close(pipefd[1]);
> +	f = fdopen(pipefd[0], "r");
> +	igt_assert(f);
> +
> +	line = NULL;
> +	line_size = 0;
> +
> +	while (getdelim(&line, &line_size, '\0', f) != -1) {
> +		ret = env_var_name_lookup(line);
> +		igt_assert_f(ret >= 0, "Unexpected env var %s\n", line);
> +		env_vars_checklist[ret] = true;
> +	}
> +
> +	for (i = 0; i < num_env_vars; i++)
> +		igt_assert_f(env_vars_checklist[i], "Missing env var %s\n", env_var_names[i]);
> +
> +	fclose(f);
> +	igt_hook_free(igt_hook);
> +	free(hook_str);
> +	free(line);
> +}
> +
> +igt_main
> +{
> +	igt_subtest("null-error-pointer")
> +		test_null_error_pointer();
> +
> +	test_invalid_hook_descriptors();
> +
> +	igt_subtest("help-description")
> +		test_print_help();
> +
> +	igt_subtest_group {
> +		igt_fixture {
> +			igt_require_f(system(NULL), "Shell seems not to be available\n");
> +		}
> +
> +		igt_subtest("all-env-vars")
> +			test_all_env_vars();
> +	}
> +}
> diff --git a/lib/tests/igt_hook_integration.c b/lib/tests/igt_hook_integration.c
> new file mode 100644
> index 000000000000..56632b13ae81
> --- /dev/null
> +++ b/lib/tests/igt_hook_integration.c
> @@ -0,0 +1,297 @@
> +/*
> + * Copyright © 2024 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 <stdbool.h>
> +#include <stdio.h>
> +#include <string.h>
> +
> +#include "igt_core.h"
> +
> +#include "igt_tests_common.h"
> +
> +char prog[] = "igt_hook_integration";
> +char hook_opt[] = "--hook";
> +char hook_str[128];
> +char *fake_argv[] = {prog, hook_opt, hook_str};
> +int fake_argc = sizeof(fake_argv) / sizeof(fake_argv[0]);
> +
> +#define ENV_ARRAY(evt_name, fullname_suffix, subtest, dyn_subtest, result) \
> +{ \
> +	"IGT_HOOK_EVENT=" evt_name, \
> +	"IGT_HOOK_TEST_FULLNAME=igt@igt_hook_integration" fullname_suffix, \
> +	"IGT_HOOK_TEST=igt_hook_integration", \
> +	"IGT_HOOK_SUBTEST=" subtest, \
> +	"IGT_HOOK_DYN_SUBTEST=" dyn_subtest, \
> +	"IGT_HOOK_RESULT=" result, \
> +}
> +
> +#define TEST_ENV(evt_name, result) \
> +	ENV_ARRAY(evt_name, "", "", "", result)
> +
> +#define SUBTEST_ENV(evt_name, subtest, result) \
> +	ENV_ARRAY(evt_name, "@" subtest, subtest, "", result)
> +
> +#define DYN_SUBTEST_ENV(evt_name, subtest, dyn_subtest, result) \
> +	ENV_ARRAY(evt_name, "@" subtest "@" dyn_subtest, subtest, dyn_subtest, result)
> +
> +const char *pre_test_env[] = TEST_ENV("pre-test", "");
> +const char *pre_subtest_a_env[] = SUBTEST_ENV("pre-subtest", "a", "");
> +const char *pre_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "success", "");
> +const char *post_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "success", "SUCCESS");
> +const char *pre_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "failed", "");
> +const char *post_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "failed", "FAIL");
> +const char *pre_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "skipped", "");
> +const char *post_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "skipped", "SKIP");
> +const char *post_subtest_a_env[] = SUBTEST_ENV("post-subtest", "a", "FAIL");
> +const char *pre_subtest_b_env[] = SUBTEST_ENV("pre-subtest", "b", "");
> +const char *post_subtest_b_env[] = SUBTEST_ENV("post-subtest", "b", "SUCCESS");
> +const char *post_test_env[] = TEST_ENV("post-test", "FAIL");
> +
> +#define num_env_vars (sizeof(pre_test_env) / sizeof(pre_test_env[0]))
> +
> +__noreturn static void fake_main(void)
> +{
> +	igt_subtest_init(fake_argc, fake_argv);
> +
> +	igt_subtest_with_dynamic("a") {
> +		igt_dynamic("success") {
> +			igt_info("...@a@success\n");
> +		}
> +
> +		igt_dynamic("failed") {
> +			igt_assert_f(false, "Fail on purpose\n");
> +			igt_info("...@a@failed\n");
> +		}
> +
> +		igt_dynamic("skipped") {
> +			igt_require_f(false, "Skip on purpose\n");
> +			igt_info("...@a@skipped\n");
> +		}
> +	}
> +
> +	igt_subtest("b") {
> +		igt_info("...@b\n");
> +	}
> +
> +	igt_exit();
> +}
> +
> +static void test_invalid_hook_str(void)
> +{
> +	int status;
> +	pid_t pid;
> +	static char err[4096];
> +	int errfd;
> +
> +	sprintf(hook_str, "invalid-event:echo hello");
> +
> +	pid = do_fork_bg_with_pipes(fake_main, NULL, &errfd);
> +
> +	read_whole_pipe(errfd, err, sizeof(err));
> +
> +	internal_assert(safe_wait(pid, &status) != -1);
> +	internal_assert_wexited(status, IGT_EXIT_INVALID);
> +
> +	internal_assert(strstr(err, "Failed to initialize hook data:"));
> +
> +	close(errfd);
> +}
> +
> +static bool match_env(FILE *hook_out_stream, const char **expected_env)
> +{
> +	int i;
> +	char hook_env_buf[4096];
> +	size_t buf_len = 0;
> +	char *line = NULL;
> +	size_t line_size;
> +	bool env_checklist[num_env_vars] = {};
> +	bool has_unexpected = false;
> +	bool has_missing = false;
> +
> +	/* Store env from hook so we can show it in case of errors */
> +	while (getdelim(&line, &line_size, '\0', hook_out_stream) != -1) {
> +		internal_assert(buf_len + strlen(line) + 1 <= sizeof(hook_env_buf));
> +		strcpy(hook_env_buf + buf_len, line);
> +		buf_len += strlen(line) + 1;
> +
> +		if (!strcmp(line, "---"))
> +			break;
> +	}
> +
> +	if (!expected_env && !buf_len) {
> +		/* We have consumed everything and we are done now. */
> +		return false;
> +	}
> +
> +
> +	if (!expected_env) {
> +		printf("Detected unexpected hook execution\n");
> +		has_unexpected = true;
> +		goto out;
> +	}
> +
> +	if (!buf_len) {
> +		printf("Expected more hook execution, but none found\n");
> +		has_missing = true;
> +		goto out;
> +	}
> +
> +
> +	line = hook_env_buf;
> +	while (strcmp(line, "---")) {
> +		for (i = 0; i < num_env_vars; i++) {
> +			if (!strcmp(line, expected_env[i])) {
> +				env_checklist[i] = true;
> +				break;
> +			}
> +		}
> +
> +		if (i == num_env_vars) {
> +			printf("Unexpected envline from hook: %s\n", line);
> +			has_unexpected = true;
> +		}
> +
> +		line += strlen(line) + 1;
> +	}
> +
> +	for (i = 0; i < num_env_vars; i++) {
> +		if (!env_checklist[i]) {
> +			has_missing = true;
> +			printf("Missing expected envline: %s\n", expected_env[i]);
> +		}
> +	}
> +
> +out:
> +	if (has_unexpected || has_missing) {
> +		if (expected_env) {
> +			printf("Expected environment:\n");
> +			for (i = 0; i < num_env_vars; i++)
> +				printf("  %s\n", expected_env[i]);
> +		}
> +
> +		if (buf_len) {
> +			printf("Environment from hook:\n");
> +			line = hook_env_buf;
> +			while (strcmp(line, "---")) {
> +				printf("  %s\n", line);
> +				line += strlen(line) + 1;
> +			}
> +		} else {
> +			printf("No hook execution found\n");
> +		}
> +	}
> +
> +	internal_assert(!has_unexpected);
> +	internal_assert(!has_missing);
> +
> +	/* Ready to consume next hook output. */
> +	return true;
> +}
> +
> +static void run_tests_and_match_env(const char *evt_descriptors, const char **expected_envs[])
> +{
> +	int i;
> +	int ret;
> +	int pipefd[2];
> +	pid_t pid;
> +	FILE *f;
> +
> +	ret = pipe(pipefd);
> +	internal_assert(ret == 0);
> +
> +	/* Use grep to filter only env var set by us. This should ensure that
> +	 * writing to the pipe will not block due to capacity, since we only
> +	 * read from the pipe after the shell command is done. */
> +	sprintf(hook_str,
> +		"%1$s:printenv -0 | grep -z ^IGT_HOOK >&%2$d; echo -en ---\\\\x00 >&%2$d",
> +		evt_descriptors,
> +		pipefd[1]);
> +
> +	pid = do_fork_bg_with_pipes(fake_main, NULL, NULL);
> +	internal_assert(safe_wait(pid, &ret) != -1);
> +	internal_assert_wexited(ret, IGT_EXIT_FAILURE);
> +
> +	close(pipefd[1]);
> +	f = fdopen(pipefd[0], "r");
> +	internal_assert(f);
> +
> +	while (match_env(f, expected_envs[i]))
> +		i++;
> +
> +	fclose(f);
> +
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	{
> +		printf("Check invalid hook string\n");
> +		test_invalid_hook_str();
> +	}
> +
> +	{
> +		const char **expected_envs[] = {
> +			pre_test_env,
> +			pre_subtest_a_env,
> +			pre_dyn_subtest_a_success_env,
> +			post_dyn_subtest_a_success_env,
> +			pre_dyn_subtest_a_failed_env,
> +			post_dyn_subtest_a_failed_env,
> +			pre_dyn_subtest_a_skipped_env,
> +			post_dyn_subtest_a_skipped_env,
> +			post_subtest_a_env,
> +			pre_subtest_b_env,
> +			post_subtest_b_env,
> +			post_test_env,
> +			NULL,
> +		};
> +
> +		printf("Check full event tracking\n");
> +		run_tests_and_match_env("*", expected_envs);
> +	}
> +
> +	{
> +		const char **expected_envs[] = {
> +			pre_dyn_subtest_a_success_env,
> +			pre_dyn_subtest_a_failed_env,
> +			pre_dyn_subtest_a_skipped_env,
> +			NULL,
> +		};
> +
> +		printf("Check single event type tracking\n");
> +		run_tests_and_match_env("pre-dyn-subtest", expected_envs);
> +	}
> +
> +	{
> +		const char **expected_envs[] = {
> +			pre_subtest_a_env,
> +			post_dyn_subtest_a_success_env,
> +			post_dyn_subtest_a_failed_env,
> +			post_dyn_subtest_a_skipped_env,
> +			pre_subtest_b_env,
> +			NULL,
> +		};
> +
> +		printf("Check multiple event types tracking\n");
> +		run_tests_and_match_env("post-dyn-subtest,pre-subtest", expected_envs);
> +	}
> +}
> diff --git a/lib/tests/meson.build b/lib/tests/meson.build
> index fa3d81de6cef..df8092638eca 100644
> --- a/lib/tests/meson.build
> +++ b/lib/tests/meson.build
> @@ -10,6 +10,8 @@ lib_tests = [
>  	'igt_exit_handler',
>  	'igt_fork',
>  	'igt_fork_helper',
> +	'igt_hook',
> +	'igt_hook_integration',
>          'igt_ktap_parser',
>  	'igt_list_only',
>  	'igt_invalid_subtest_name',
> -- 
> 2.45.0
> 

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-15 17:10   ` Kamil Konieczny
@ 2024-05-15 17:35     ` Gustavo Sousa
  2024-05-16 10:40       ` Kamil Konieczny
  2024-05-20 19:03       ` Lucas De Marchi
  0 siblings, 2 replies; 22+ messages in thread
From: Gustavo Sousa @ 2024-05-15 17:35 UTC (permalink / raw)
  To: Kamil Konieczny, igt-dev; +Cc: Petri Latvala

Quoting Kamil Konieczny (2024-05-15 14:10:55-03:00)
>Hi Gustavo,
>On 2024-05-09 at 12:24:29 -0300, Gustavo Sousa wrote:
>> For development purposes, sometimes it is useful to have a way of
>> running custom scripts at certain points of test executions. A
>> real-world example I bumped into recently is to collect information from
>> sysfs before and after running each entry of a testlist.
>> 
>> While it is possible for the user to handcraft a script that calls each
>> test with the correct actions before and after execution, we can provide
>> a better experience by adding built-in support for running hooks during
>> test execution.
>> 
>> That would be even better when adding the same kind of support for
>> igt_runner (which is done in an upcoming change), since the user can
>> also nicely resume with igt_resume with the hook already setup in case a
>> crash happens during execution of the test list.
>> 
>> As such provide implement support for hooks, integrate it into
>> igt_core and expose the functionality via --hook CLI option on test
>> executables.
>
>Hmm, why not just a pre-hook@ and post-hook@ in testlist itself?
>It will be easier to handle - just more parsing.

How would that work with respect to filters? The current proposal allows
something filtering the events to be tracked. For example, one can use
`--hook "pre-test,pre-dyn-subtest:echo hello"` to run the command only
before test binary starts and before each dynamic subtest.

Also, there are cases where a testlist is not really used. Examples are
calling a test binary directly or calling igt_runner without
--test-list. So, while I believe we could consider support for
describing hooks in testlist, I do not think that would be a substitute
for the --hook option.

On a personal note, my current use case for hooks is more towards
debugging, so for me it is more convenient to have a --hook option than
having to make a copy of a testlist only to add the hook instructions
there.

--
Gustavo Sousa

>
>Added Petri to cc.
>
>Regards,
>Kamil
>
>> 
>> Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
>> ---
>>  .../igt-gpu-tools/igt-gpu-tools-docs.xml      |   1 +
>>  lib/igt_core.c                                | 116 +++-
>>  lib/igt_hook.c                                | 499 ++++++++++++++++++
>>  lib/igt_hook.h                                |  86 +++
>>  lib/meson.build                               |   1 +
>>  lib/tests/igt_hook.c                          | 187 +++++++
>>  lib/tests/igt_hook_integration.c              | 297 +++++++++++
>>  lib/tests/meson.build                         |   2 +
>>  8 files changed, 1180 insertions(+), 9 deletions(-)
>>  create mode 100644 lib/igt_hook.c
>>  create mode 100644 lib/igt_hook.h
>>  create mode 100644 lib/tests/igt_hook.c
>>  create mode 100644 lib/tests/igt_hook_integration.c
>> 
>> diff --git a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>> index 9085eb924e85..11458c68124b 100644
>> --- a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>> +++ b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>> @@ -32,6 +32,7 @@
>>      <xi:include href="xml/igt_fb.xml"/>
>>      <xi:include href="xml/igt_frame.xml"/>
>>      <xi:include href="xml/igt_gt.xml"/>
>> +    <xi:include href="xml/igt_hook.xml"/>
>>      <xi:include href="xml/igt_io.xml"/>
>>      <xi:include href="xml/igt_kmod.xml"/>
>>      <xi:include href="xml/igt_kms.xml"/>
>> diff --git a/lib/igt_core.c b/lib/igt_core.c
>> index 3ff3e0392316..291d891cf884 100644
>> --- a/lib/igt_core.c
>> +++ b/lib/igt_core.c
>> @@ -70,6 +70,7 @@
>>  
>>  #include "igt_core.h"
>>  #include "igt_aux.h"
>> +#include "igt_hook.h"
>>  #include "igt_sysfs.h"
>>  #include "igt_sysrq.h"
>>  #include "igt_rc.h"
>> @@ -241,6 +242,9 @@
>>   * - '*,!basic*' match any subtest not starting basic
>>   * - 'basic*,!basic-render*' match any subtest starting basic but not starting basic-render
>>   *
>> + * It is possible to run a shell script at certain points of test execution with
>> + * "--hook". See the usage description with "--help-hook" for details.
>> + *
>>   * # Configuration
>>   *
>>   * Some of IGT's behavior can be configured through a configuration file.
>> @@ -273,6 +277,8 @@ static unsigned int exit_handler_count;
>>  const char *igt_interactive_debug;
>>  bool igt_skip_crc_compare;
>>  
>> +static struct igt_hook *igt_hook = NULL;
>> +
>>  /* subtests helpers */
>>  static bool show_testlist = false;
>>  static bool list_subtests = false;
>> @@ -338,6 +344,8 @@ enum {
>>          OPT_INTERACTIVE_DEBUG,
>>          OPT_SKIP_CRC,
>>          OPT_TRACE_OOPS,
>> +        OPT_HOOK,
>> +        OPT_HELP_HOOK,
>>          OPT_DEVICE,
>>          OPT_VERSION,
>>          OPT_HELP = 'h'
>> @@ -810,6 +818,8 @@ static void common_exit_handler(int sig)
>>                  bind_fbcon(true);
>>          }
>>  
>> +        igt_hook_free(igt_hook);
>> +
>>          /* When not killed by a signal check that igt_exit() has been properly
>>           * called. */
>>          assert(sig != 0 || igt_exit_called || igt_is_aborting);
>> @@ -907,6 +917,8 @@ static void print_usage(const char *help_str, bool output_on_stderr)
>>                     "  --interactive-debug[=domain]\n"
>>                     "  --skip-crc-compare\n"
>>                     "  --trace-on-oops\n"
>> +                   "  --hook [<events>:]<cmd>\n"
>> +                   "  --help-hook\n"
>>                     "  --help-description\n"
>>                     "  --describe\n"
>>                     "  --device filters\n"
>> @@ -1090,6 +1102,8 @@ static int common_init(int *argc, char **argv,
>>                  {"interactive-debug", optional_argument, NULL, OPT_INTERACTIVE_DEBUG},
>>                  {"skip-crc-compare",  no_argument,       NULL, OPT_SKIP_CRC},
>>                  {"trace-on-oops",     no_argument,       NULL, OPT_TRACE_OOPS},
>> +                {"hook",              required_argument, NULL, OPT_HOOK},
>> +                {"help-hook",         no_argument,       NULL, OPT_HELP_HOOK},
>>                  {"device",            required_argument, NULL, OPT_DEVICE},
>>                  {"version",           no_argument,       NULL, OPT_VERSION},
>>                  {"help",              no_argument,       NULL, OPT_HELP},
>> @@ -1225,6 +1239,24 @@ static int common_init(int *argc, char **argv,
>>                  case OPT_TRACE_OOPS:
>>                          show_ftrace = true;
>>                          break;
>> +                case OPT_HOOK:
>> +                        assert(optarg);
>> +                        if (igt_hook) {
>> +                                igt_warn("Overriding previous hook descriptor\n");
>> +                                igt_hook_free(igt_hook);
>> +                        }
>> +                        igt_hook = igt_hook_init(optarg, &ret);
>> +                        if (!igt_hook) {
>> +                                igt_critical("Failed to initialize hook data: %s\n",
>> +                                             igt_hook_error_str(ret));
>> +                                ret = ret > 0 ? -2 : -3;
>> +                                goto out;
>> +                        }
>> +                        break;
>> +                case OPT_HELP_HOOK:
>> +                        igt_hook_print_help(stdout, "--hook");
>> +                        ret = -1;
>> +                        goto out;
>>                  case OPT_DEVICE:
>>                          assert(optarg);
>>                          /* if set by env IGT_DEVICE we need to free it */
>> @@ -1274,9 +1306,24 @@ out:
>>                          exit(IGT_EXIT_INVALID);
>>          }
>>  
>> -        if (ret < 0)
>> -                /* exit with no error for -h/--help */
>> -                exit(ret == -1 ? 0 : IGT_EXIT_INVALID);
>> +        if (ret < 0) {
>> +                free(igt_hook);
>> +                igt_hook = NULL;
>> +
>> +                switch (ret) {
>> +                case -1: /* exit with no error for -h/--help */
>> +                        exit(0);
>> +                        break;
>> +                case -2:
>> +                        exit(IGT_EXIT_INVALID);
>> +                        break;
>> +                case -3:
>> +                        exit(IGT_EXIT_ABORT);
>> +                        break;
>> +                default:
>> +                        assert(0);
>> +                }
>> +        }
>>  
>>          if (!igt_only_list_subtests()) {
>>                  bind_fbcon(false);
>> @@ -1284,6 +1331,15 @@ out:
>>                  print_version();
>>                  igt_srandom();
>>  
>> +                if (igt_hook) {
>> +                        struct igt_hook_evt hook_evt = {
>> +                                .evt_type = IGT_HOOK_PRE_TEST,
>> +                                .target_name = command_str,
>> +                        };
>> +
>> +                        igt_hook_push_evt(igt_hook, &hook_evt);
>> +                }
>> +
>>                  sync();
>>                  oom_adjust_for_doom();
>>                  ftrace_dump_on_oops(show_ftrace);
>> @@ -1487,6 +1543,16 @@ bool __igt_run_subtest(const char *subtest_name, const char *file, const int lin
>>          igt_thread_clear_fail_state();
>>  
>>          igt_gettime(&subtest_time);
>> +
>> +        if (igt_hook) {
>> +                struct igt_hook_evt hook_evt = {
>> +                        .evt_type = IGT_HOOK_PRE_SUBTEST,
>> +                        .target_name = subtest_name,
>> +                };
>> +
>> +                igt_hook_push_evt(igt_hook, &hook_evt);
>> +        }
>> +
>>          return (in_subtest = subtest_name);
>>  }
>>  
>> @@ -1517,6 +1583,16 @@ bool __igt_run_dynamic_subtest(const char *dynamic_subtest_name)
>>          _igt_dynamic_tests_executed++;
>>  
>>          igt_gettime(&dynamic_subtest_time);
>> +
>> +        if (igt_hook) {
>> +                struct igt_hook_evt hook_evt = {
>> +                        .evt_type = IGT_HOOK_PRE_DYN_SUBTEST,
>> +                        .target_name = dynamic_subtest_name,
>> +                };
>> +
>> +                igt_hook_push_evt(igt_hook, &hook_evt);
>> +        }
>> +
>>          return (in_dynamic_subtest = dynamic_subtest_name);
>>  }
>>  
>> @@ -1602,6 +1678,17 @@ __noreturn static void exit_subtest(const char *result)
>>          struct timespec *thentime = in_dynamic_subtest ? &dynamic_subtest_time : &subtest_time;
>>          jmp_buf *jmptarget = in_dynamic_subtest ? &igt_dynamic_jmpbuf : &igt_subtest_jmpbuf;
>>  
>> +        if (igt_hook) {
>> +                struct igt_hook_evt hook_evt = {
>> +                        .evt_type = (in_dynamic_subtest
>> +                                        ? IGT_HOOK_POST_DYN_SUBTEST
>> +                                        : IGT_HOOK_POST_SUBTEST),
>> +                        .result = result,
>> +                };
>> +
>> +                igt_hook_push_evt(igt_hook, &hook_evt);
>> +        }
>> +
>>          if (!igt_thread_is_main()) {
>>                  igt_thread_fail();
>>                  pthread_exit(NULL);
>> @@ -2274,6 +2361,7 @@ void __igt_abort(const char *domain, const char *file, const int line,
>>  void igt_exit(void)
>>  {
>>          int tmp;
>> +        const char *result;
>>  
>>          if (!test_with_subtests)
>>                  igt_thread_assert_no_failures();
>> @@ -2318,12 +2406,7 @@ void igt_exit(void)
>>  
>>          assert(waitpid(-1, &tmp, WNOHANG) == -1 && errno == ECHILD);
>>  
>> -        if (!test_with_subtests) {
>> -                struct timespec now;
>> -                const char *result;
>> -
>> -                igt_gettime(&now);
>> -
>> +        if (!test_with_subtests || igt_hook) {
>>                  switch (igt_exitcode) {
>>                          case IGT_EXIT_SUCCESS:
>>                                  result = "SUCCESS";
>> @@ -2334,6 +2417,12 @@ void igt_exit(void)
>>                          default:
>>                                  result = "FAIL";
>>                  }
>> +        }
>> +
>> +        if (!test_with_subtests) {
>> +                struct timespec now;
>> +
>> +                igt_gettime(&now);
>>  
>>                  if (test_multi_fork_child) /* parent will do the yelling */
>>                          _log_line_fprintf(stdout, "dyn_child pid:%d (%.3fs) ends with err=%d\n",
>> @@ -2344,6 +2433,15 @@ void igt_exit(void)
>>                                            result, igt_time_elapsed(&subtest_time, &now));
>>          }
>>  
>> +        if (igt_hook) {
>> +                struct igt_hook_evt hook_evt = {
>> +                        .evt_type = IGT_HOOK_POST_TEST,
>> +                        .result = result,
>> +                };
>> +
>> +                igt_hook_push_evt(igt_hook, &hook_evt);
>> +        }
>> +
>>          exit(igt_exitcode);
>>  }
>>  
>> diff --git a/lib/igt_hook.c b/lib/igt_hook.c
>> new file mode 100644
>> index 000000000000..8a39e19e3e5f
>> --- /dev/null
>> +++ b/lib/igt_hook.c
>> @@ -0,0 +1,499 @@
>> +/*
>> + * Copyright © 2024 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 <assert.h>
>> +#include <errno.h>
>> +#include <limits.h>
>> +#include <stdbool.h>
>> +#include <stddef.h>
>> +#include <stdint.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +
>> +#include "igt_hook.h"
>> +
>> +/**
>> + * SECTION:igt_hook
>> + * @short_description: Support for running a hook script on test execution
>> + * @title: Hook support
>> + *
>> + * IGT provides support for running a hook script when executing tests. This
>> + * support is provided to users via CLI option `--hook` available in test
>> + * binaries. Users should use `--help-hook` for detailed usaged description of
>> + * the feature.
>> + *
>> + * The sole user of the exposed API is `igt_core`, which calls @igt_hook_init()
>> + * when initializing a test case, then calls @igt_hook_push_evt() for each event
>> + * that occurs during that test's execution and finally calls @igt_hook_free()
>> + * to clean up at the end.
>> + */
>> +
>> +#define TEST_NAME_INITIAL_SIZE 16
>> +
>> +typedef uint16_t evt_mask_t;
>> +
>> +struct igt_hook {
>> +        evt_mask_t evt_mask;
>> +        char *cmd;
>> +        char *test_name;
>> +        size_t test_name_size;
>> +        char *subtest_name;
>> +        size_t subtest_name_size;
>> +        char *dyn_subtest_name;
>> +        size_t dyn_subtest_name_size;
>> +        char *test_fullname;
>> +};
>> +
>> +enum igt_hook_error {
>> +        IGT_HOOK_EVT_EMPTY_NAME = 1,
>> +        IGT_HOOK_EVT_NO_MATCH,
>> +};
>> +
>> +static_assert(IGT_HOOK_NUM_EVENTS <= sizeof(evt_mask_t) * CHAR_BIT,
>> +              "Number of event types does not fit event type mask");
>> +
>> +static const char *igt_hook_evt_type_to_name(enum igt_hook_evt_type evt_type)
>> +{
>> +        switch (evt_type) {
>> +        case IGT_HOOK_PRE_TEST:
>> +                return "pre-test";
>> +        case IGT_HOOK_PRE_SUBTEST:
>> +                return "pre-subtest";
>> +        case IGT_HOOK_PRE_DYN_SUBTEST:
>> +                return "pre-dyn-subtest";
>> +        case IGT_HOOK_POST_DYN_SUBTEST:
>> +                return "post-dyn-subtest";
>> +        case IGT_HOOK_POST_SUBTEST:
>> +                return "post-subtest";
>> +        case IGT_HOOK_POST_TEST:
>> +                return "post-test";
>> +        case IGT_HOOK_NUM_EVENTS:
>> +                break;
>> +        /* No "default:" case, to force a warning from -Wswitch in case we miss
>> +         * any new event type. */
>> +        }
>> +        return "?";
>> +}
>> +
>> +static int igt_hook_parse_hook_str(const char *hook_str, evt_mask_t *evt_mask, const char **cmd)
>> +{
>> +        const char *s;
>> +
>> +        if (!strchr(hook_str, ':')) {
>> +                *evt_mask = ~0;
>> +                *cmd = hook_str;
>> +                return 0;
>> +        }
>> +
>> +        s = hook_str;
>> +        *evt_mask = 0;
>> +
>> +        while (1) {
>> +                const char *evt_name;
>> +                bool has_match;
>> +                bool is_star;
>> +                enum igt_hook_evt_type evt_type;
>> +
>> +                evt_name = s;
>> +
>> +                while (*s != ':' && *s != ',')
>> +                        s++;
>> +
>> +                if (evt_name == s)
>> +                        return IGT_HOOK_EVT_EMPTY_NAME;
>> +
>> +                has_match = false;
>> +                is_star = *evt_name == '*' && evt_name + 1 == s;
>> +
>> +                for (evt_type = IGT_HOOK_PRE_TEST; evt_type < IGT_HOOK_NUM_EVENTS; evt_type++) {
>> +                        if (!is_star) {
>> +                                const char *this_event_name = igt_hook_evt_type_to_name(evt_type);
>> +                                size_t len = s - evt_name;
>> +
>> +                                if (len != strlen(this_event_name))
>> +                                        continue;
>> +
>> +                                if (strncmp(evt_name, this_event_name, len))
>> +                                        continue;
>> +                        }
>> +
>> +                        *evt_mask |= 1 << evt_type;
>> +                        has_match = true;
>> +
>> +                        if (!is_star)
>> +                                break;
>> +                }
>> +
>> +                if (!has_match)
>> +                        return IGT_HOOK_EVT_NO_MATCH;
>> +
>> +                if (*s++ == ':')
>> +                        break;
>> +        }
>> +
>> +        *cmd = s;
>> +
>> +        return 0;
>> +}
>> +
>> +static size_t igt_hook_calc_test_fullname_size(struct igt_hook *igt_hook) {
>> +        /* The maximum size of test_fullname will be the maximum length of
>> +         * "igt@<test_name>@<subtest_name>@<dyn_subtest_name>" plus 1 for the
>> +         * null byte. */
>> +        return (igt_hook->test_name_size +
>> +                igt_hook->subtest_name_size +
>> +                igt_hook->dyn_subtest_name_size) + 4;
>> +}
>> +
>> +static void igt_hook_update_test_fullname(struct igt_hook *igt_hook)
>> +{
>> +        int i;
>> +        char *s;
>> +        const char *values[3] = {
>> +                igt_hook->test_name,
>> +                igt_hook->subtest_name,
>> +                igt_hook->dyn_subtest_name,
>> +        };
>> +
>> +        if (igt_hook->test_name[0] == '\0') {
>> +                igt_hook->test_fullname[0] = '\0';
>> +                return;
>> +        }
>> +
>> +        s = stpcpy(igt_hook->test_fullname, "igt");
>> +        for (i = 0; i < 3 && values[i][0] != '\0'; i++) {
>> +                *s++ = '@';
>> +                s = stpcpy(s, values[i]);
>> +        }
>> +}
>> +
>> +/**
>> + * igt_hook_init:
>> + * @hook_str: Hook descriptor string.
>> + * @error: Pointer to error number.
>> + *
>> + * Allocate and initialize an #igt_hook structure.
>> + *
>> + * This function parses the hook descriptor @hook_str and initializes the struct
>> + * to be returned.
>> + *
>> + * The hook descriptor comes from the argument to `--hook` of the test
>> + * executable being run.
>> + *
>> + * If not #NULL, @error is used to store a non-zero error number if an error
>> + * happens. A human-readable string for that error number can be obtained with
>> + * @igt_hook_error_str().
>> + *
>> + * Returns: The pointer to the #igt_hook structure on success or #NULL on error.
>> + */
>> +struct igt_hook *igt_hook_init(const char *hook_str, int *error)
>> +{
>> +        int err;
>> +        evt_mask_t evt_mask;
>> +        const char *cmd;
>> +        struct igt_hook *igt_hook = NULL;
>> +
>> +
>> +        err = igt_hook_parse_hook_str(hook_str, &evt_mask, &cmd);
>> +        if (err)
>> +                goto out;
>> +
>> +        igt_hook = calloc(1, sizeof(*igt_hook));
>> +        igt_hook->evt_mask = evt_mask;
>> +
>> +        igt_hook->cmd = strdup(cmd);
>> +        if (!igt_hook->cmd) {
>> +                err = -errno;
>> +                goto out;
>> +        }
>> +
>> +        igt_hook->test_name = malloc(TEST_NAME_INITIAL_SIZE);
>> +        igt_hook->test_name_size = TEST_NAME_INITIAL_SIZE;
>> +        igt_hook->subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
>> +        igt_hook->subtest_name_size = TEST_NAME_INITIAL_SIZE;
>> +        igt_hook->dyn_subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
>> +        igt_hook->dyn_subtest_name_size = TEST_NAME_INITIAL_SIZE;
>> +        igt_hook->test_fullname = malloc(igt_hook_calc_test_fullname_size(igt_hook));
>> +
>> +        igt_hook->test_name[0] = '\0';
>> +        igt_hook->subtest_name[0] = '\0';
>> +        igt_hook->dyn_subtest_name[0] = '\0';
>> +        igt_hook->test_fullname[0] = '\0';
>> +
>> +out:
>> +        if (err) {
>> +                if (error)
>> +                        *error = err;
>> +
>> +                igt_hook_free(igt_hook);
>> +
>> +                return NULL;
>> +        }
>> +
>> +        return igt_hook;
>> +}
>> +
>> +/**
>> + * igt_hook_free:
>> + * @igt_hook: The igt_hook struct.
>> + *
>> + * De-initialize an igt_hook struct returned by @igt_hook_init().
>> + *
>> + * This is a no-op if @igt_hook is #NULL.
>> + */
>> +void igt_hook_free(struct igt_hook *igt_hook)
>> +{
>> +        if (!igt_hook)
>> +                return;
>> +
>> +        free(igt_hook->cmd);
>> +        free(igt_hook->test_name);
>> +        free(igt_hook->subtest_name);
>> +        free(igt_hook->dyn_subtest_name);
>> +        free(igt_hook);
>> +}
>> +
>> +static void igt_hook_update_test_name_pre_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>> +{
>> +        char **name_ptr;
>> +        size_t *size_ptr;
>> +        size_t len;
>> +
>> +        switch (evt->evt_type) {
>> +        case IGT_HOOK_PRE_TEST:
>> +                name_ptr = &igt_hook->test_name;
>> +                size_ptr = &igt_hook->test_name_size;
>> +                break;
>> +        case IGT_HOOK_PRE_SUBTEST:
>> +                name_ptr = &igt_hook->subtest_name;
>> +                size_ptr = &igt_hook->subtest_name_size;
>> +                break;
>> +        case IGT_HOOK_PRE_DYN_SUBTEST:
>> +                name_ptr = &igt_hook->dyn_subtest_name;
>> +                size_ptr = &igt_hook->dyn_subtest_name_size;
>> +                break;
>> +        default:
>> +                return;
>> +        }
>> +
>> +        len = strlen(evt->target_name);
>> +        if (len + 1 > *size_ptr) {
>> +                size_t fullname_size;
>> +
>> +                *size_ptr *= 2;
>> +                *name_ptr = realloc(*name_ptr, *size_ptr);
>> +
>> +                fullname_size = igt_hook_calc_test_fullname_size(igt_hook);
>> +                igt_hook->test_fullname = realloc(igt_hook->test_fullname, fullname_size);
>> +        }
>> +
>> +        strcpy(*name_ptr, evt->target_name);
>> +        igt_hook_update_test_fullname(igt_hook);
>> +}
>> +
>> +static void igt_hook_update_test_name_post_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>> +{
>> +        switch (evt->evt_type) {
>> +        case IGT_HOOK_POST_TEST:
>> +                igt_hook->test_name[0] = '\0';
>> +                break;
>> +        case IGT_HOOK_POST_SUBTEST:
>> +                igt_hook->subtest_name[0] = '\0';
>> +                break;
>> +        case IGT_HOOK_POST_DYN_SUBTEST:
>> +                igt_hook->dyn_subtest_name[0] = '\0';
>> +                break;
>> +        default:
>> +                return;
>> +        }
>> +
>> +        igt_hook_update_test_fullname(igt_hook);
>> +}
>> +
>> +static void igt_hook_update_env_vars(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>> +{
>> +        setenv("IGT_HOOK_EVENT", igt_hook_evt_type_to_name(evt->evt_type), 1);
>> +        setenv("IGT_HOOK_TEST_FULLNAME", igt_hook->test_fullname, 1);
>> +        setenv("IGT_HOOK_TEST", igt_hook->test_name, 1);
>> +        setenv("IGT_HOOK_SUBTEST", igt_hook->subtest_name, 1);
>> +        setenv("IGT_HOOK_DYN_SUBTEST", igt_hook->dyn_subtest_name, 1);
>> +        setenv("IGT_HOOK_RESULT", evt->result ?: "", 1);
>> +}
>> +
>> +/**
>> + * igt_hook_push_evt:
>> + * @igt_hook: The igt_hook structure.
>> + * @evt: The event to be pushed.
>> + *
>> + * Push a new igt_hook event.
>> + *
>> + * This function must be used to register a new igt_hook event. Calling it will
>> + * cause execution of the hook script if the event type matches the filters
>> + * provided during initialization of @igt_hook.
>> + */
>> +void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>> +{
>> +        typeof(igt_hook->evt_mask) evt_bit = (1 << evt->evt_type);
>> +
>> +        igt_hook_update_test_name_pre_call(igt_hook, evt);
>> +
>> +        if ((evt_bit & igt_hook->evt_mask)) {
>> +                igt_hook_update_env_vars(igt_hook, evt);
>> +                system(igt_hook->cmd);
>> +        }
>> +
>> +        igt_hook_update_test_name_post_call(igt_hook, evt);
>> +}
>> +
>> +/**
>> + * igt_hook_error_str:
>> + * @error: Non-zero error number.
>> + *
>> + * Return a human-readable string containing a description of an error number
>> + * generated by one of the `igt_hook_*` functions.
>> + *
>> + * The string will be the result of strerror() for errors from the C standard
>> + * library or a custom description specific to igt_hook.
>> + */
>> +const char *igt_hook_error_str(int error)
>> +{
>> +        if (!error)
>> +                return "No error";
>> +
>> +        if (error > 0) {
>> +                enum igt_hook_error hook_error = error;
>> +
>> +                switch (hook_error) {
>> +                case IGT_HOOK_EVT_EMPTY_NAME:
>> +                        return "Empty name in event descriptor";
>> +                case IGT_HOOK_EVT_NO_MATCH:
>> +                        return "Event name in event descriptor does not match any event type";
>> +                default:
>> +                        return "Unknown error";
>> +                }
>> +        } else {
>> +                return strerror(-error);
>> +        }
>> +}
>> +
>> +/**
>> + * igt_hook_print_help:
>> + * @f: File pointer where to write the output.
>> + * @option_name: Name of the CLI option that accepts the hook descriptor.
>> + *
>> + * Print a detailed user help text on hook usage.
>> + */
>> +void igt_hook_print_help(FILE *f, const char *option_name)
>> +{
>> +        fprintf(f, "\
>> +The option %1$s receives as argument a \"hook descriptor\" and allows the\n\
>> +execution of a shell command at different points during execution of tests. Each\n\
>> +such a point is called a \"hook event\".\n\
>> +\n\
>> +Examples:\n\
>> +\n\
>> +  # Prints hook-specic env vars for every event.\n\
>> +  %1$s 'printenv | grep ^IGT_HOOK_'\n\
>> +\n\
>> +  # Equivalent to the above. Useful if command contains ':'.\n\
>> +  %1$s '*:printenv | grep ^IGT_HOOK_'\n\
>> +\n\
>> +  # Adds a line to out.txt containing the result of each test case.\n\
>> +  %1$s 'post-test:echo $IGT_HOOK_TEST_FULLNAME $IGT_HOOK_RESULT >> out.txt'\n\
>> +\n\
>> +The accepted format for a hook descriptor is `[<events>:]<cmd>`, where:\n\
>> +\n\
>> +  - <events> is a comma-separated list of event descriptors, which defines the\n\
>> +    set of events be tracked. If omitted, all events are tracked.\n\
>> +\n\
>> +  - <cmd> is a shell command to be executed on the occurrence each tracked\n\
>> +    event. If the command contains ':', then passing <events> is required,\n\
>> +    otherwise part of the command would be treated as an event descriptor.\n\
>> +\n\
>> +", option_name);
>> +
>> +        fprintf(f, "\
>> +An \"event descriptor\" is either the name of an event or the string '*'. The\n\
>> +latter matches all event names. The list of possible event names is provided\n\
>> +below:\n\
>> +\n\
>> +");
>> +
>> +        for (enum igt_hook_evt_type et = 0; et < IGT_HOOK_NUM_EVENTS; et++) {
>> +                const char *desc;
>> +
>> +                switch (et) {
>> +                case IGT_HOOK_PRE_TEST:
>> +                        desc = "Occurs before a test case starts.";
>> +                        break;
>> +                case IGT_HOOK_PRE_SUBTEST:
>> +                        desc = "Occurs before the execution of a subtest.";
>> +                        break;
>> +                case IGT_HOOK_PRE_DYN_SUBTEST:
>> +                        desc = "Occurs before the execution of a dynamic subtest.";
>> +                        break;
>> +                case IGT_HOOK_POST_DYN_SUBTEST:
>> +                        desc = "Occurs after the execution of a dynamic subtest.";
>> +                        break;
>> +                case IGT_HOOK_POST_SUBTEST:
>> +                        desc = "Occurs after the execution of a subtest.";
>> +                        break;
>> +                case IGT_HOOK_POST_TEST:
>> +                        desc = "Occurs after a test case has finished.";
>> +                        break;
>> +                default:
>> +                        desc = "MISSING DESCRIPTION";
>> +                }
>> +
>> +                fprintf(f, "  %s\n  %s\n\n", igt_hook_evt_type_to_name(et), desc);
>> +        }
>> +
>> +        fprintf(f, "\
>> +For each event matched by <events>, <cmd> is executed as a shell command. The\n\
>> +exit status of the command is ignored. The following environment variables are\n\
>> +available to the command:\n\
>> +\n\
>> +  IGT_HOOK_EVENT\n\
>> +  Name of the current event.\n\
>> +\n\
>> +  IGT_HOOK_TEST_FULLNAME\n\
>> +  Full name of the test in the format `igt@<test>[@<subtest>[@<dyn_subtest>]]`.\n\
>> +\n\
>> +  IGT_HOOK_TEST                   \n\
>> +  Name of the current test.\n\
>> +\n\
>> +  IGT_HOOK_SUBTEST\n\
>> +  Name of the current subtest. Will be the empty string if not running a\n\
>> +  subtest.\n\
>> +\n\
>> +  IGT_HOOK_DYN_SUBTEST\n\
>> +  Name of the current dynamic subtest. Will be the empty string if not running a\n\
>> +  dynamic subtest.\n\
>> +\n\
>> +  IGT_HOOK_RESULT\n\
>> +  String representing the result of the test/subtest/dynamic subtest. Possible\n\
>> +  values are: SUCCESS, SKIP or FAIL. This is only applicable on \"post-*\"\n\
>> +  events and will be the empty string for other types of events.\n\
>> +\n\
>> +");
>> +}
>> diff --git a/lib/igt_hook.h b/lib/igt_hook.h
>> new file mode 100644
>> index 000000000000..6fef7254a317
>> --- /dev/null
>> +++ b/lib/igt_hook.h
>> @@ -0,0 +1,86 @@
>> +/*
>> + * Copyright © 2024 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.
>> + */
>> +#ifndef IGT_HOOK_H
>> +#define IGT_HOOK_H
>> +
>> +#include <stdio.h>
>> +
>> +/**
>> + * igt_hook:
>> + *
>> + * Opaque struct to hold data related to hook support.
>> + */
>> +struct igt_hook;
>> +
>> +/**
>> + * igt_hook_evt_type:
>> + * @IGT_HOOK_PRE_TEST: Occurs before a test case (executable) starts the
>> + * test code.
>> + * @IGT_HOOK_PRE_SUBTEST: Occurs before the execution of a subtest.
>> + * @IGT_HOOK_PRE_DYN_SUBTEST: Occurs before the execution of a dynamic subtest.
>> + * @IGT_HOOK_POST_DYN_SUBTEST: Occurs after the execution of a dynamic subtest.
>> + * @IGT_HOOK_POST_SUBTEST: Occurs after the execution of a subtest..
>> + * @IGT_HOOK_POST_TEST: Occurs after a test case (executable) is finished with
>> + * the test code.
>> + * @IGT_HOOK_NUM_EVENTS: This is not really an event and represents the number
>> + * of possible events tracked by igt_hook.
>> + *
>> + * Events tracked by igt_hook. Those events occur at specific points during the
>> + * execution of a test.
>> + */
>> +enum igt_hook_evt_type {
>> +        IGT_HOOK_PRE_TEST,
>> +        IGT_HOOK_PRE_SUBTEST,
>> +        IGT_HOOK_PRE_DYN_SUBTEST,
>> +        IGT_HOOK_POST_DYN_SUBTEST,
>> +        IGT_HOOK_POST_SUBTEST,
>> +        IGT_HOOK_POST_TEST,
>> +        IGT_HOOK_NUM_EVENTS /* This must always be the last one. */
>> +};
>> +
>> +/**
>> + * igt_hook_evt:
>> + * @evt_type: Type of event.
>> + * @target_name: A string pointing to the name of the test, subtest or dynamic
>> + * subtest, depending on @evt_type.
>> + * @result: A string containing the result of the test, subtest or dynamic
>> + * subtest. This is only applicable for the `IGT_HOOK_POST_\*' event types;
>> + * other types must initialize this to #NULL.
>> + *
>> + * An event tracked by igt_hook, which is done with @igt_hook_push_evt(). This must
>> + * be zero initialized and fields relevant to the event type must be set before
>> + * passing its reference to @igt_hook_push_evt().
>> + */
>> +struct igt_hook_evt {
>> +        enum igt_hook_evt_type evt_type;
>> +        const char *target_name;
>> +        const char *result;
>> +};
>> +
>> +struct igt_hook *igt_hook_init(const char *hook_str, int *error);
>> +void igt_hook_free(struct igt_hook *igt_hook);
>> +void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt);
>> +const char *igt_hook_error_str(int error);
>> +void igt_hook_print_help(FILE *f, const char *option_name);
>> +
>> +#endif /* IGT_HOOK_H */
>> diff --git a/lib/meson.build b/lib/meson.build
>> index e2f740c116f8..10b8066647f2 100644
>> --- a/lib/meson.build
>> +++ b/lib/meson.build
>> @@ -109,6 +109,7 @@ lib_sources = [
>>          'veboxcopy_gen12.c',
>>          'igt_msm.c',
>>          'igt_dsc.c',
>> +        'igt_hook.c',
>>          'xe/xe_gt.c',
>>          'xe/xe_ioctl.c',
>>          'xe/xe_mmio.c',
>> diff --git a/lib/tests/igt_hook.c b/lib/tests/igt_hook.c
>> new file mode 100644
>> index 000000000000..bd7e570f9607
>> --- /dev/null
>> +++ b/lib/tests/igt_hook.c
>> @@ -0,0 +1,187 @@
>> +/*
>> + * Copyright © 2024 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 <stdbool.h>
>> +#include <stdio.h>
>> +#include <unistd.h>
>> +
>> +#include "igt_core.h"
>> +#include "igt_hook.h"
>> +
>> +static const char *env_var_names[] = {
>> +        "IGT_HOOK_EVENT",
>> +        "IGT_HOOK_TEST_FULLNAME",
>> +        "IGT_HOOK_TEST",
>> +        "IGT_HOOK_SUBTEST",
>> +        "IGT_HOOK_DYN_SUBTEST",
>> +        "IGT_HOOK_RESULT",
>> +};
>> +
>> +#define num_env_vars (sizeof(env_var_names) / sizeof(env_var_names[0]))
>> +
>> +static int env_var_name_lookup(char *line)
>> +{
>> +        int i;
>> +        char *c;
>> +
>> +        c = strchr(line, '=');
>> +        if (c)
>> +                *c = '\0';
>> +
>> +        for (i = 0; i < num_env_vars; i++)
>> +                if (!strcmp(line, env_var_names[i]))
>> +                        goto out;
>> +
>> +        i = -1;
>> +out:
>> +        if (c)
>> +                *c = '=';
>> +
>> +        return i;
>> +}
>> +
>> +static void test_null_error_pointer(void)
>> +{
>> +        struct igt_hook *igt_hook;
>> +
>> +        /* Ensure passing NULL error pointer does not cause issues. */
>> +        igt_hook = igt_hook_init("invalid:echo hello", NULL);
>> +        igt_assert(igt_hook == NULL);
>> +}
>> +
>> +static void test_invalid_hook_descriptors(void)
>> +{
>> +        struct {
>> +                const char *name;
>> +                const char *hook_desc;
>> +        } invalid_cases[] = {
>> +                {"invalid-event-name", "invalid-event:echo hello"},
>> +                {"invalid-empty-event-name", ":echo hello"},
>> +                {"invalid-colon-in-cmd", "echo hello:world"},
>> +                {},
>> +        };
>> +
>> +        for (int i = 0; invalid_cases[i].name; i++) {
>> +                igt_subtest(invalid_cases[i].name) {
>> +                        int err = 0;
>> +                        struct igt_hook *igt_hook;
>> +
>> +                        igt_hook = igt_hook_init(invalid_cases[i].hook_desc, &err);
>> +                        igt_assert(igt_hook == NULL);
>> +                        igt_assert(err != 0);
>> +                }
>> +        }
>> +}
>> +
>> +static void test_print_help(void)
>> +{
>> +        char *buf;
>> +        size_t len;
>> +        FILE *f;
>> +        const char expected_initial_text[] = "The option --hook receives as argument a \"hook descriptor\"";
>> +
>> +        f = open_memstream(&buf, &len);
>> +        igt_assert(f);
>> +
>> +        igt_hook_print_help(f, "--hook");
>> +        fclose(f);
>> +
>> +        igt_assert(!strncmp(buf, expected_initial_text, sizeof(expected_initial_text) - 1));
>> +
>> +        /* This is an extra check to catch a case where an event type is added
>> +         * without a proper description. */
>> +        igt_assert(!strstr(buf, "MISSING DESCRIPTION"));
>> +
>> +        free(buf);
>> +}
>> +
>> +static void test_all_env_vars(void)
>> +{
>> +        struct igt_hook_evt evt = {
>> +                .evt_type = IGT_HOOK_PRE_SUBTEST,
>> +                .target_name = "foo",
>> +        };
>> +        bool env_vars_checklist[num_env_vars] = {};
>> +        struct igt_hook *igt_hook;
>> +        char *hook_str;
>> +        FILE *f;
>> +        int pipefd[2];
>> +        int ret;
>> +        int i;
>> +        char *line;
>> +        size_t line_size;
>> +
>> +        ret = pipe(pipefd);
>> +        igt_assert(ret == 0);
>> +
>> +        /* Use grep to filter only env var set by us. This should ensure that
>> +         * writing to the pipe will not block due to capacity, since we only
>> +         * read from the pipe after the shell command is done. */
>> +        ret = asprintf(&hook_str, "printenv -0 | grep -z ^IGT_HOOK >&%d", pipefd[1]);
>> +        igt_assert(ret > 0);
>> +
>> +        igt_hook = igt_hook_init(hook_str, NULL);
>> +        igt_assert(igt_hook);
>> +
>> +        igt_hook_push_evt(igt_hook, &evt);
>> +
>> +        close(pipefd[1]);
>> +        f = fdopen(pipefd[0], "r");
>> +        igt_assert(f);
>> +
>> +        line = NULL;
>> +        line_size = 0;
>> +
>> +        while (getdelim(&line, &line_size, '\0', f) != -1) {
>> +                ret = env_var_name_lookup(line);
>> +                igt_assert_f(ret >= 0, "Unexpected env var %s\n", line);
>> +                env_vars_checklist[ret] = true;
>> +        }
>> +
>> +        for (i = 0; i < num_env_vars; i++)
>> +                igt_assert_f(env_vars_checklist[i], "Missing env var %s\n", env_var_names[i]);
>> +
>> +        fclose(f);
>> +        igt_hook_free(igt_hook);
>> +        free(hook_str);
>> +        free(line);
>> +}
>> +
>> +igt_main
>> +{
>> +        igt_subtest("null-error-pointer")
>> +                test_null_error_pointer();
>> +
>> +        test_invalid_hook_descriptors();
>> +
>> +        igt_subtest("help-description")
>> +                test_print_help();
>> +
>> +        igt_subtest_group {
>> +                igt_fixture {
>> +                        igt_require_f(system(NULL), "Shell seems not to be available\n");
>> +                }
>> +
>> +                igt_subtest("all-env-vars")
>> +                        test_all_env_vars();
>> +        }
>> +}
>> diff --git a/lib/tests/igt_hook_integration.c b/lib/tests/igt_hook_integration.c
>> new file mode 100644
>> index 000000000000..56632b13ae81
>> --- /dev/null
>> +++ b/lib/tests/igt_hook_integration.c
>> @@ -0,0 +1,297 @@
>> +/*
>> + * Copyright © 2024 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 <stdbool.h>
>> +#include <stdio.h>
>> +#include <string.h>
>> +
>> +#include "igt_core.h"
>> +
>> +#include "igt_tests_common.h"
>> +
>> +char prog[] = "igt_hook_integration";
>> +char hook_opt[] = "--hook";
>> +char hook_str[128];
>> +char *fake_argv[] = {prog, hook_opt, hook_str};
>> +int fake_argc = sizeof(fake_argv) / sizeof(fake_argv[0]);
>> +
>> +#define ENV_ARRAY(evt_name, fullname_suffix, subtest, dyn_subtest, result) \
>> +{ \
>> +        "IGT_HOOK_EVENT=" evt_name, \
>> +        "IGT_HOOK_TEST_FULLNAME=igt@igt_hook_integration" fullname_suffix, \
>> +        "IGT_HOOK_TEST=igt_hook_integration", \
>> +        "IGT_HOOK_SUBTEST=" subtest, \
>> +        "IGT_HOOK_DYN_SUBTEST=" dyn_subtest, \
>> +        "IGT_HOOK_RESULT=" result, \
>> +}
>> +
>> +#define TEST_ENV(evt_name, result) \
>> +        ENV_ARRAY(evt_name, "", "", "", result)
>> +
>> +#define SUBTEST_ENV(evt_name, subtest, result) \
>> +        ENV_ARRAY(evt_name, "@" subtest, subtest, "", result)
>> +
>> +#define DYN_SUBTEST_ENV(evt_name, subtest, dyn_subtest, result) \
>> +        ENV_ARRAY(evt_name, "@" subtest "@" dyn_subtest, subtest, dyn_subtest, result)
>> +
>> +const char *pre_test_env[] = TEST_ENV("pre-test", "");
>> +const char *pre_subtest_a_env[] = SUBTEST_ENV("pre-subtest", "a", "");
>> +const char *pre_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "success", "");
>> +const char *post_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "success", "SUCCESS");
>> +const char *pre_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "failed", "");
>> +const char *post_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "failed", "FAIL");
>> +const char *pre_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "skipped", "");
>> +const char *post_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "skipped", "SKIP");
>> +const char *post_subtest_a_env[] = SUBTEST_ENV("post-subtest", "a", "FAIL");
>> +const char *pre_subtest_b_env[] = SUBTEST_ENV("pre-subtest", "b", "");
>> +const char *post_subtest_b_env[] = SUBTEST_ENV("post-subtest", "b", "SUCCESS");
>> +const char *post_test_env[] = TEST_ENV("post-test", "FAIL");
>> +
>> +#define num_env_vars (sizeof(pre_test_env) / sizeof(pre_test_env[0]))
>> +
>> +__noreturn static void fake_main(void)
>> +{
>> +        igt_subtest_init(fake_argc, fake_argv);
>> +
>> +        igt_subtest_with_dynamic("a") {
>> +                igt_dynamic("success") {
>> +                        igt_info("...@a@success\n");
>> +                }
>> +
>> +                igt_dynamic("failed") {
>> +                        igt_assert_f(false, "Fail on purpose\n");
>> +                        igt_info("...@a@failed\n");
>> +                }
>> +
>> +                igt_dynamic("skipped") {
>> +                        igt_require_f(false, "Skip on purpose\n");
>> +                        igt_info("...@a@skipped\n");
>> +                }
>> +        }
>> +
>> +        igt_subtest("b") {
>> +                igt_info("...@b\n");
>> +        }
>> +
>> +        igt_exit();
>> +}
>> +
>> +static void test_invalid_hook_str(void)
>> +{
>> +        int status;
>> +        pid_t pid;
>> +        static char err[4096];
>> +        int errfd;
>> +
>> +        sprintf(hook_str, "invalid-event:echo hello");
>> +
>> +        pid = do_fork_bg_with_pipes(fake_main, NULL, &errfd);
>> +
>> +        read_whole_pipe(errfd, err, sizeof(err));
>> +
>> +        internal_assert(safe_wait(pid, &status) != -1);
>> +        internal_assert_wexited(status, IGT_EXIT_INVALID);
>> +
>> +        internal_assert(strstr(err, "Failed to initialize hook data:"));
>> +
>> +        close(errfd);
>> +}
>> +
>> +static bool match_env(FILE *hook_out_stream, const char **expected_env)
>> +{
>> +        int i;
>> +        char hook_env_buf[4096];
>> +        size_t buf_len = 0;
>> +        char *line = NULL;
>> +        size_t line_size;
>> +        bool env_checklist[num_env_vars] = {};
>> +        bool has_unexpected = false;
>> +        bool has_missing = false;
>> +
>> +        /* Store env from hook so we can show it in case of errors */
>> +        while (getdelim(&line, &line_size, '\0', hook_out_stream) != -1) {
>> +                internal_assert(buf_len + strlen(line) + 1 <= sizeof(hook_env_buf));
>> +                strcpy(hook_env_buf + buf_len, line);
>> +                buf_len += strlen(line) + 1;
>> +
>> +                if (!strcmp(line, "---"))
>> +                        break;
>> +        }
>> +
>> +        if (!expected_env && !buf_len) {
>> +                /* We have consumed everything and we are done now. */
>> +                return false;
>> +        }
>> +
>> +
>> +        if (!expected_env) {
>> +                printf("Detected unexpected hook execution\n");
>> +                has_unexpected = true;
>> +                goto out;
>> +        }
>> +
>> +        if (!buf_len) {
>> +                printf("Expected more hook execution, but none found\n");
>> +                has_missing = true;
>> +                goto out;
>> +        }
>> +
>> +
>> +        line = hook_env_buf;
>> +        while (strcmp(line, "---")) {
>> +                for (i = 0; i < num_env_vars; i++) {
>> +                        if (!strcmp(line, expected_env[i])) {
>> +                                env_checklist[i] = true;
>> +                                break;
>> +                        }
>> +                }
>> +
>> +                if (i == num_env_vars) {
>> +                        printf("Unexpected envline from hook: %s\n", line);
>> +                        has_unexpected = true;
>> +                }
>> +
>> +                line += strlen(line) + 1;
>> +        }
>> +
>> +        for (i = 0; i < num_env_vars; i++) {
>> +                if (!env_checklist[i]) {
>> +                        has_missing = true;
>> +                        printf("Missing expected envline: %s\n", expected_env[i]);
>> +                }
>> +        }
>> +
>> +out:
>> +        if (has_unexpected || has_missing) {
>> +                if (expected_env) {
>> +                        printf("Expected environment:\n");
>> +                        for (i = 0; i < num_env_vars; i++)
>> +                                printf("  %s\n", expected_env[i]);
>> +                }
>> +
>> +                if (buf_len) {
>> +                        printf("Environment from hook:\n");
>> +                        line = hook_env_buf;
>> +                        while (strcmp(line, "---")) {
>> +                                printf("  %s\n", line);
>> +                                line += strlen(line) + 1;
>> +                        }
>> +                } else {
>> +                        printf("No hook execution found\n");
>> +                }
>> +        }
>> +
>> +        internal_assert(!has_unexpected);
>> +        internal_assert(!has_missing);
>> +
>> +        /* Ready to consume next hook output. */
>> +        return true;
>> +}
>> +
>> +static void run_tests_and_match_env(const char *evt_descriptors, const char **expected_envs[])
>> +{
>> +        int i;
>> +        int ret;
>> +        int pipefd[2];
>> +        pid_t pid;
>> +        FILE *f;
>> +
>> +        ret = pipe(pipefd);
>> +        internal_assert(ret == 0);
>> +
>> +        /* Use grep to filter only env var set by us. This should ensure that
>> +         * writing to the pipe will not block due to capacity, since we only
>> +         * read from the pipe after the shell command is done. */
>> +        sprintf(hook_str,
>> +                "%1$s:printenv -0 | grep -z ^IGT_HOOK >&%2$d; echo -en ---\\\\x00 >&%2$d",
>> +                evt_descriptors,
>> +                pipefd[1]);
>> +
>> +        pid = do_fork_bg_with_pipes(fake_main, NULL, NULL);
>> +        internal_assert(safe_wait(pid, &ret) != -1);
>> +        internal_assert_wexited(ret, IGT_EXIT_FAILURE);
>> +
>> +        close(pipefd[1]);
>> +        f = fdopen(pipefd[0], "r");
>> +        internal_assert(f);
>> +
>> +        while (match_env(f, expected_envs[i]))
>> +                i++;
>> +
>> +        fclose(f);
>> +
>> +}
>> +
>> +int main(int argc, char **argv)
>> +{
>> +        {
>> +                printf("Check invalid hook string\n");
>> +                test_invalid_hook_str();
>> +        }
>> +
>> +        {
>> +                const char **expected_envs[] = {
>> +                        pre_test_env,
>> +                        pre_subtest_a_env,
>> +                        pre_dyn_subtest_a_success_env,
>> +                        post_dyn_subtest_a_success_env,
>> +                        pre_dyn_subtest_a_failed_env,
>> +                        post_dyn_subtest_a_failed_env,
>> +                        pre_dyn_subtest_a_skipped_env,
>> +                        post_dyn_subtest_a_skipped_env,
>> +                        post_subtest_a_env,
>> +                        pre_subtest_b_env,
>> +                        post_subtest_b_env,
>> +                        post_test_env,
>> +                        NULL,
>> +                };
>> +
>> +                printf("Check full event tracking\n");
>> +                run_tests_and_match_env("*", expected_envs);
>> +        }
>> +
>> +        {
>> +                const char **expected_envs[] = {
>> +                        pre_dyn_subtest_a_success_env,
>> +                        pre_dyn_subtest_a_failed_env,
>> +                        pre_dyn_subtest_a_skipped_env,
>> +                        NULL,
>> +                };
>> +
>> +                printf("Check single event type tracking\n");
>> +                run_tests_and_match_env("pre-dyn-subtest", expected_envs);
>> +        }
>> +
>> +        {
>> +                const char **expected_envs[] = {
>> +                        pre_subtest_a_env,
>> +                        post_dyn_subtest_a_success_env,
>> +                        post_dyn_subtest_a_failed_env,
>> +                        post_dyn_subtest_a_skipped_env,
>> +                        pre_subtest_b_env,
>> +                        NULL,
>> +                };
>> +
>> +                printf("Check multiple event types tracking\n");
>> +                run_tests_and_match_env("post-dyn-subtest,pre-subtest", expected_envs);
>> +        }
>> +}
>> diff --git a/lib/tests/meson.build b/lib/tests/meson.build
>> index fa3d81de6cef..df8092638eca 100644
>> --- a/lib/tests/meson.build
>> +++ b/lib/tests/meson.build
>> @@ -10,6 +10,8 @@ lib_tests = [
>>          'igt_exit_handler',
>>          'igt_fork',
>>          'igt_fork_helper',
>> +        'igt_hook',
>> +        'igt_hook_integration',
>>          'igt_ktap_parser',
>>          'igt_list_only',
>>          'igt_invalid_subtest_name',
>> -- 
>> 2.45.0
>>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-15 17:35     ` Gustavo Sousa
@ 2024-05-16 10:40       ` Kamil Konieczny
  2024-05-16 12:19         ` Gustavo Sousa
  2024-05-20 19:03       ` Lucas De Marchi
  1 sibling, 1 reply; 22+ messages in thread
From: Kamil Konieczny @ 2024-05-16 10:40 UTC (permalink / raw)
  To: igt-dev; +Cc: Gustavo Sousa, Petri Latvala

Hi Gustavo,
On 2024-05-15 at 14:35:55 -0300, Gustavo Sousa wrote:
> Quoting Kamil Konieczny (2024-05-15 14:10:55-03:00)
> >Hi Gustavo,
> >On 2024-05-09 at 12:24:29 -0300, Gustavo Sousa wrote:
> >> For development purposes, sometimes it is useful to have a way of
> >> running custom scripts at certain points of test executions. A
> >> real-world example I bumped into recently is to collect information from
> >> sysfs before and after running each entry of a testlist.
> >> 
> >> While it is possible for the user to handcraft a script that calls each
> >> test with the correct actions before and after execution, we can provide
> >> a better experience by adding built-in support for running hooks during
> >> test execution.
> >> 
> >> That would be even better when adding the same kind of support for
> >> igt_runner (which is done in an upcoming change), since the user can
> >> also nicely resume with igt_resume with the hook already setup in case a
> >> crash happens during execution of the test list.
> >> 
> >> As such provide implement support for hooks, integrate it into
> >> igt_core and expose the functionality via --hook CLI option on test
> >> executables.
> >
> >Hmm, why not just a pre-hook@ and post-hook@ in testlist itself?
> >It will be easier to handle - just more parsing.
> 
> How would that work with respect to filters? The current proposal allows
> something filtering the events to be tracked. For example, one can use
> `--hook "pre-test,pre-dyn-subtest:echo hello"` to run the command only
> before test binary starts and before each dynamic subtest.
> 
> Also, there are cases where a testlist is not really used. Examples are
> calling a test binary directly or calling igt_runner without
> --test-list. So, while I believe we could consider support for
> describing hooks in testlist, I do not think that would be a substitute
> for the --hook option.
> 
> On a personal note, my current use case for hooks is more towards
> debugging, so for me it is more convenient to have a --hook option than
> having to make a copy of a testlist only to add the hook instructions
> there.
> 
> --
> Gustavo Sousa
> 
> >
> >Added Petri to cc.
> >
> >Regards,
> >Kamil
> >

Ok, that makes sense, I will look into your patches later (maybe next week).
In meantime please look into GitLab failure here:
https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606

Regards,
Kamil


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-16 10:40       ` Kamil Konieczny
@ 2024-05-16 12:19         ` Gustavo Sousa
  2024-05-16 16:30           ` Kamil Konieczny
  0 siblings, 1 reply; 22+ messages in thread
From: Gustavo Sousa @ 2024-05-16 12:19 UTC (permalink / raw)
  To: Kamil Konieczny, igt-dev; +Cc: Petri Latvala

Quoting Kamil Konieczny (2024-05-16 07:40:58-03:00)
>Hi Gustavo,
>On 2024-05-15 at 14:35:55 -0300, Gustavo Sousa wrote:
>> Quoting Kamil Konieczny (2024-05-15 14:10:55-03:00)
>> >Hi Gustavo,
>> >On 2024-05-09 at 12:24:29 -0300, Gustavo Sousa wrote:
>> >> For development purposes, sometimes it is useful to have a way of
>> >> running custom scripts at certain points of test executions. A
>> >> real-world example I bumped into recently is to collect information from
>> >> sysfs before and after running each entry of a testlist.
>> >> 
>> >> While it is possible for the user to handcraft a script that calls each
>> >> test with the correct actions before and after execution, we can provide
>> >> a better experience by adding built-in support for running hooks during
>> >> test execution.
>> >> 
>> >> That would be even better when adding the same kind of support for
>> >> igt_runner (which is done in an upcoming change), since the user can
>> >> also nicely resume with igt_resume with the hook already setup in case a
>> >> crash happens during execution of the test list.
>> >> 
>> >> As such provide implement support for hooks, integrate it into
>> >> igt_core and expose the functionality via --hook CLI option on test
>> >> executables.
>> >
>> >Hmm, why not just a pre-hook@ and post-hook@ in testlist itself?
>> >It will be easier to handle - just more parsing.
>> 
>> How would that work with respect to filters? The current proposal allows
>> something filtering the events to be tracked. For example, one can use
>> `--hook "pre-test,pre-dyn-subtest:echo hello"` to run the command only
>> before test binary starts and before each dynamic subtest.
>> 
>> Also, there are cases where a testlist is not really used. Examples are
>> calling a test binary directly or calling igt_runner without
>> --test-list. So, while I believe we could consider support for
>> describing hooks in testlist, I do not think that would be a substitute
>> for the --hook option.
>> 
>> On a personal note, my current use case for hooks is more towards
>> debugging, so for me it is more convenient to have a --hook option than
>> having to make a copy of a testlist only to add the hook instructions
>> there.
>> 
>> --
>> Gustavo Sousa
>> 
>> >
>> >Added Petri to cc.
>> >
>> >Regards,
>> >Kamil
>> >
>
>Ok, that makes sense, I will look into your patches later (maybe next week).

Thanks!

>In meantime please look into GitLab failure here:
>https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606

Yeah, already tried to take a look at it a few days ago [1], but as I
mentioned there, I'm not sure how I can get more info (e.g. logs) on the CI
failure. The test works fine for me locally. I wonder if it is possible
to setup the container locally so that I get the same test envionment.

[1] https://lore.kernel.org/all/171527425512.5772.1654105508452706087@gjsousa-mobl2/

--
Gustavo Sousa

>
>Regards,
>Kamil
>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-16 12:19         ` Gustavo Sousa
@ 2024-05-16 16:30           ` Kamil Konieczny
  2024-05-16 17:05             ` Gustavo Sousa
  0 siblings, 1 reply; 22+ messages in thread
From: Kamil Konieczny @ 2024-05-16 16:30 UTC (permalink / raw)
  To: igt-dev; +Cc: Gustavo Sousa, Petri Latvala

Hi Gustavo,
On 2024-05-16 at 09:19:26 -0300, Gustavo Sousa wrote:
> Quoting Kamil Konieczny (2024-05-16 07:40:58-03:00)
> >Hi Gustavo,
> >On 2024-05-15 at 14:35:55 -0300, Gustavo Sousa wrote:
> >> Quoting Kamil Konieczny (2024-05-15 14:10:55-03:00)
> >> >Hi Gustavo,
> >> >On 2024-05-09 at 12:24:29 -0300, Gustavo Sousa wrote:
> >> >> For development purposes, sometimes it is useful to have a way of
> >> >> running custom scripts at certain points of test executions. A
> >> >> real-world example I bumped into recently is to collect information from
> >> >> sysfs before and after running each entry of a testlist.
> >> >> 
> >> >> While it is possible for the user to handcraft a script that calls each
> >> >> test with the correct actions before and after execution, we can provide
> >> >> a better experience by adding built-in support for running hooks during
> >> >> test execution.
> >> >> 
> >> >> That would be even better when adding the same kind of support for
> >> >> igt_runner (which is done in an upcoming change), since the user can
> >> >> also nicely resume with igt_resume with the hook already setup in case a
> >> >> crash happens during execution of the test list.
> >> >> 
> >> >> As such provide implement support for hooks, integrate it into
> >> >> igt_core and expose the functionality via --hook CLI option on test
> >> >> executables.
> >> >
> >> >Hmm, why not just a pre-hook@ and post-hook@ in testlist itself?
> >> >It will be easier to handle - just more parsing.
> >> 
> >> How would that work with respect to filters? The current proposal allows
> >> something filtering the events to be tracked. For example, one can use
> >> `--hook "pre-test,pre-dyn-subtest:echo hello"` to run the command only
> >> before test binary starts and before each dynamic subtest.
> >> 
> >> Also, there are cases where a testlist is not really used. Examples are
> >> calling a test binary directly or calling igt_runner without
> >> --test-list. So, while I believe we could consider support for
> >> describing hooks in testlist, I do not think that would be a substitute
> >> for the --hook option.
> >> 
> >> On a personal note, my current use case for hooks is more towards
> >> debugging, so for me it is more convenient to have a --hook option than
> >> having to make a copy of a testlist only to add the hook instructions
> >> there.
> >> 
> >> --
> >> Gustavo Sousa
> >> 
> >> >
> >> >Added Petri to cc.
> >> >
> >> >Regards,
> >> >Kamil
> >> >
> >
> >Ok, that makes sense, I will look into your patches later (maybe next week).
> 
> Thanks!
> 
> >In meantime please look into GitLab failure here:
> >https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606
> 
> Yeah, already tried to take a look at it a few days ago [1], but as I
> mentioned there, I'm not sure how I can get more info (e.g. logs) on the CI
> failure. The test works fine for me locally. I wonder if it is possible
> to setup the container locally so that I get the same test envionment.
> 
> [1] https://lore.kernel.org/all/171527425512.5772.1654105508452706087@gjsousa-mobl2/
> 
> --
> Gustavo Sousa
> 

Command used is in line 29:
meson test -C build --num-processes ${FDO_CI_CONCURRENT:-4}

and fail is:
13/30 lib igt_hook_integration FAIL 0.72 s (killed by signal 11 SIGSEGV)

I tested this locally after building (so binaries are already there)
using 'meson test -C build' and it fails:
killed by signal 7 SIGBUS

Regards,
Kamil

> >
> >Regards,
> >Kamil
> >

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-16 16:30           ` Kamil Konieczny
@ 2024-05-16 17:05             ` Gustavo Sousa
  0 siblings, 0 replies; 22+ messages in thread
From: Gustavo Sousa @ 2024-05-16 17:05 UTC (permalink / raw)
  To: Kamil Konieczny, igt-dev; +Cc: Petri Latvala

Quoting Kamil Konieczny (2024-05-16 13:30:47-03:00)
>Hi Gustavo,
>On 2024-05-16 at 09:19:26 -0300, Gustavo Sousa wrote:
>> Quoting Kamil Konieczny (2024-05-16 07:40:58-03:00)
>> >Hi Gustavo,
>> >On 2024-05-15 at 14:35:55 -0300, Gustavo Sousa wrote:
>> >> Quoting Kamil Konieczny (2024-05-15 14:10:55-03:00)
>> >> >Hi Gustavo,
>> >> >On 2024-05-09 at 12:24:29 -0300, Gustavo Sousa wrote:
>> >> >> For development purposes, sometimes it is useful to have a way of
>> >> >> running custom scripts at certain points of test executions. A
>> >> >> real-world example I bumped into recently is to collect information from
>> >> >> sysfs before and after running each entry of a testlist.
>> >> >> 
>> >> >> While it is possible for the user to handcraft a script that calls each
>> >> >> test with the correct actions before and after execution, we can provide
>> >> >> a better experience by adding built-in support for running hooks during
>> >> >> test execution.
>> >> >> 
>> >> >> That would be even better when adding the same kind of support for
>> >> >> igt_runner (which is done in an upcoming change), since the user can
>> >> >> also nicely resume with igt_resume with the hook already setup in case a
>> >> >> crash happens during execution of the test list.
>> >> >> 
>> >> >> As such provide implement support for hooks, integrate it into
>> >> >> igt_core and expose the functionality via --hook CLI option on test
>> >> >> executables.
>> >> >
>> >> >Hmm, why not just a pre-hook@ and post-hook@ in testlist itself?
>> >> >It will be easier to handle - just more parsing.
>> >> 
>> >> How would that work with respect to filters? The current proposal allows
>> >> something filtering the events to be tracked. For example, one can use
>> >> `--hook "pre-test,pre-dyn-subtest:echo hello"` to run the command only
>> >> before test binary starts and before each dynamic subtest.
>> >> 
>> >> Also, there are cases where a testlist is not really used. Examples are
>> >> calling a test binary directly or calling igt_runner without
>> >> --test-list. So, while I believe we could consider support for
>> >> describing hooks in testlist, I do not think that would be a substitute
>> >> for the --hook option.
>> >> 
>> >> On a personal note, my current use case for hooks is more towards
>> >> debugging, so for me it is more convenient to have a --hook option than
>> >> having to make a copy of a testlist only to add the hook instructions
>> >> there.
>> >> 
>> >> --
>> >> Gustavo Sousa
>> >> 
>> >> >
>> >> >Added Petri to cc.
>> >> >
>> >> >Regards,
>> >> >Kamil
>> >> >
>> >
>> >Ok, that makes sense, I will look into your patches later (maybe next week).
>> 
>> Thanks!
>> 
>> >In meantime please look into GitLab failure here:
>> >https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606
>> 
>> Yeah, already tried to take a look at it a few days ago [1], but as I
>> mentioned there, I'm not sure how I can get more info (e.g. logs) on the CI
>> failure. The test works fine for me locally. I wonder if it is possible
>> to setup the container locally so that I get the same test envionment.
>> 
>> [1] https://lore.kernel.org/all/171527425512.5772.1654105508452706087@gjsousa-mobl2/
>> 
>> --
>> Gustavo Sousa
>> 
>
>Command used is in line 29:
>meson test -C build --num-processes ${FDO_CI_CONCURRENT:-4}
>
>and fail is:
>13/30 lib igt_hook_integration FAIL 0.72 s (killed by signal 11 SIGSEGV)
>
>I tested this locally after building (so binaries are already there)
>using 'meson test -C build' and it fails:
>killed by signal 7 SIGBUS

I'm getting success result on my side:

   $ sudo meson test -C build
   (...extra lines omitted...)
    11/424 lib igt_hook_integration  OK  0.22s
   (...extra lines omitted...)
   Ok:                 420
   Expected Fail:      4
   Fail:               0
   Unexpected Pass:    0
   Skipped:            0
   Timeout:            0

So I wonder if there are environment differences causing the test to
succeed on my side and fail in other places. Is it possible to get the
image of the container used to run the tests in CI?

--
Gustavo Sousa

>
>Regards,
>Kamil
>
>> >
>> >Regards,
>> >Kamil
>> >

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-15 17:35     ` Gustavo Sousa
  2024-05-16 10:40       ` Kamil Konieczny
@ 2024-05-20 19:03       ` Lucas De Marchi
  1 sibling, 0 replies; 22+ messages in thread
From: Lucas De Marchi @ 2024-05-20 19:03 UTC (permalink / raw)
  To: Gustavo Sousa; +Cc: Kamil Konieczny, igt-dev, Petri Latvala

On Wed, May 15, 2024 at 02:35:55PM GMT, Gustavo Sousa wrote:
>Quoting Kamil Konieczny (2024-05-15 14:10:55-03:00)
>>Hi Gustavo,
>>On 2024-05-09 at 12:24:29 -0300, Gustavo Sousa wrote:
>>> For development purposes, sometimes it is useful to have a way of
>>> running custom scripts at certain points of test executions. A
>>> real-world example I bumped into recently is to collect information from
>>> sysfs before and after running each entry of a testlist.
>>>
>>> While it is possible for the user to handcraft a script that calls each
>>> test with the correct actions before and after execution, we can provide
>>> a better experience by adding built-in support for running hooks during
>>> test execution.
>>>
>>> That would be even better when adding the same kind of support for
>>> igt_runner (which is done in an upcoming change), since the user can
>>> also nicely resume with igt_resume with the hook already setup in case a
>>> crash happens during execution of the test list.
>>>
>>> As such provide implement support for hooks, integrate it into
>>> igt_core and expose the functionality via --hook CLI option on test
>>> executables.
>>
>>Hmm, why not just a pre-hook@ and post-hook@ in testlist itself?
>>It will be easier to handle - just more parsing.
>
>How would that work with respect to filters? The current proposal allows
>something filtering the events to be tracked. For example, one can use
>`--hook "pre-test,pre-dyn-subtest:echo hello"` to run the command only
>before test binary starts and before each dynamic subtest.
>
>Also, there are cases where a testlist is not really used. Examples are
>calling a test binary directly or calling igt_runner without
>--test-list. So, while I believe we could consider support for
>describing hooks in testlist, I do not think that would be a substitute
>for the --hook option.

yeah... I think the closest I can think of is a `git rebase -x`: it will
add whatever command you want to the rebase todo. Here it's a little
more advanced since it may instruct igt_runner to run things "inside" a
single test.

I also like allowing the hooks to be added to the testlist, so maybe a
future extension could be:

	hook@pre-test@...
	hook@pre-subtest@...

>
>On a personal note, my current use case for hooks is more towards
>debugging, so for me it is more convenient to have a --hook option than
>having to make a copy of a testlist only to add the hook instructions
>there.

yeah... I think this will be the most common user and it seems fine to
only implement this for now.

Lucas De Marchi

>
>--
>Gustavo Sousa
>
>>
>>Added Petri to cc.
>>
>>Regards,
>>Kamil
>>
>>>
>>> Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
>>> ---
>>>  .../igt-gpu-tools/igt-gpu-tools-docs.xml      |   1 +
>>>  lib/igt_core.c                                | 116 +++-
>>>  lib/igt_hook.c                                | 499 ++++++++++++++++++
>>>  lib/igt_hook.h                                |  86 +++
>>>  lib/meson.build                               |   1 +
>>>  lib/tests/igt_hook.c                          | 187 +++++++
>>>  lib/tests/igt_hook_integration.c              | 297 +++++++++++
>>>  lib/tests/meson.build                         |   2 +
>>>  8 files changed, 1180 insertions(+), 9 deletions(-)
>>>  create mode 100644 lib/igt_hook.c
>>>  create mode 100644 lib/igt_hook.h
>>>  create mode 100644 lib/tests/igt_hook.c
>>>  create mode 100644 lib/tests/igt_hook_integration.c
>>>
>>> diff --git a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>>> index 9085eb924e85..11458c68124b 100644
>>> --- a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>>> +++ b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>>> @@ -32,6 +32,7 @@
>>>      <xi:include href="xml/igt_fb.xml"/>
>>>      <xi:include href="xml/igt_frame.xml"/>
>>>      <xi:include href="xml/igt_gt.xml"/>
>>> +    <xi:include href="xml/igt_hook.xml"/>
>>>      <xi:include href="xml/igt_io.xml"/>
>>>      <xi:include href="xml/igt_kmod.xml"/>
>>>      <xi:include href="xml/igt_kms.xml"/>
>>> diff --git a/lib/igt_core.c b/lib/igt_core.c
>>> index 3ff3e0392316..291d891cf884 100644
>>> --- a/lib/igt_core.c
>>> +++ b/lib/igt_core.c
>>> @@ -70,6 +70,7 @@
>>>
>>>  #include "igt_core.h"
>>>  #include "igt_aux.h"
>>> +#include "igt_hook.h"
>>>  #include "igt_sysfs.h"
>>>  #include "igt_sysrq.h"
>>>  #include "igt_rc.h"
>>> @@ -241,6 +242,9 @@
>>>   * - '*,!basic*' match any subtest not starting basic
>>>   * - 'basic*,!basic-render*' match any subtest starting basic but not starting basic-render
>>>   *
>>> + * It is possible to run a shell script at certain points of test execution with
>>> + * "--hook". See the usage description with "--help-hook" for details.
>>> + *
>>>   * # Configuration
>>>   *
>>>   * Some of IGT's behavior can be configured through a configuration file.
>>> @@ -273,6 +277,8 @@ static unsigned int exit_handler_count;
>>>  const char *igt_interactive_debug;
>>>  bool igt_skip_crc_compare;
>>>
>>> +static struct igt_hook *igt_hook = NULL;
>>> +
>>>  /* subtests helpers */
>>>  static bool show_testlist = false;
>>>  static bool list_subtests = false;
>>> @@ -338,6 +344,8 @@ enum {
>>>          OPT_INTERACTIVE_DEBUG,
>>>          OPT_SKIP_CRC,
>>>          OPT_TRACE_OOPS,
>>> +        OPT_HOOK,
>>> +        OPT_HELP_HOOK,
>>>          OPT_DEVICE,
>>>          OPT_VERSION,
>>>          OPT_HELP = 'h'
>>> @@ -810,6 +818,8 @@ static void common_exit_handler(int sig)
>>>                  bind_fbcon(true);
>>>          }
>>>
>>> +        igt_hook_free(igt_hook);
>>> +
>>>          /* When not killed by a signal check that igt_exit() has been properly
>>>           * called. */
>>>          assert(sig != 0 || igt_exit_called || igt_is_aborting);
>>> @@ -907,6 +917,8 @@ static void print_usage(const char *help_str, bool output_on_stderr)
>>>                     "  --interactive-debug[=domain]\n"
>>>                     "  --skip-crc-compare\n"
>>>                     "  --trace-on-oops\n"
>>> +                   "  --hook [<events>:]<cmd>\n"
>>> +                   "  --help-hook\n"
>>>                     "  --help-description\n"
>>>                     "  --describe\n"
>>>                     "  --device filters\n"
>>> @@ -1090,6 +1102,8 @@ static int common_init(int *argc, char **argv,
>>>                  {"interactive-debug", optional_argument, NULL, OPT_INTERACTIVE_DEBUG},
>>>                  {"skip-crc-compare",  no_argument,       NULL, OPT_SKIP_CRC},
>>>                  {"trace-on-oops",     no_argument,       NULL, OPT_TRACE_OOPS},
>>> +                {"hook",              required_argument, NULL, OPT_HOOK},
>>> +                {"help-hook",         no_argument,       NULL, OPT_HELP_HOOK},
>>>                  {"device",            required_argument, NULL, OPT_DEVICE},
>>>                  {"version",           no_argument,       NULL, OPT_VERSION},
>>>                  {"help",              no_argument,       NULL, OPT_HELP},
>>> @@ -1225,6 +1239,24 @@ static int common_init(int *argc, char **argv,
>>>                  case OPT_TRACE_OOPS:
>>>                          show_ftrace = true;
>>>                          break;
>>> +                case OPT_HOOK:
>>> +                        assert(optarg);
>>> +                        if (igt_hook) {
>>> +                                igt_warn("Overriding previous hook descriptor\n");
>>> +                                igt_hook_free(igt_hook);
>>> +                        }
>>> +                        igt_hook = igt_hook_init(optarg, &ret);
>>> +                        if (!igt_hook) {
>>> +                                igt_critical("Failed to initialize hook data: %s\n",
>>> +                                             igt_hook_error_str(ret));
>>> +                                ret = ret > 0 ? -2 : -3;
>>> +                                goto out;
>>> +                        }
>>> +                        break;
>>> +                case OPT_HELP_HOOK:
>>> +                        igt_hook_print_help(stdout, "--hook");
>>> +                        ret = -1;
>>> +                        goto out;
>>>                  case OPT_DEVICE:
>>>                          assert(optarg);
>>>                          /* if set by env IGT_DEVICE we need to free it */
>>> @@ -1274,9 +1306,24 @@ out:
>>>                          exit(IGT_EXIT_INVALID);
>>>          }
>>>
>>> -        if (ret < 0)
>>> -                /* exit with no error for -h/--help */
>>> -                exit(ret == -1 ? 0 : IGT_EXIT_INVALID);
>>> +        if (ret < 0) {
>>> +                free(igt_hook);
>>> +                igt_hook = NULL;
>>> +
>>> +                switch (ret) {
>>> +                case -1: /* exit with no error for -h/--help */
>>> +                        exit(0);
>>> +                        break;
>>> +                case -2:
>>> +                        exit(IGT_EXIT_INVALID);
>>> +                        break;
>>> +                case -3:
>>> +                        exit(IGT_EXIT_ABORT);
>>> +                        break;
>>> +                default:
>>> +                        assert(0);
>>> +                }
>>> +        }
>>>
>>>          if (!igt_only_list_subtests()) {
>>>                  bind_fbcon(false);
>>> @@ -1284,6 +1331,15 @@ out:
>>>                  print_version();
>>>                  igt_srandom();
>>>
>>> +                if (igt_hook) {
>>> +                        struct igt_hook_evt hook_evt = {
>>> +                                .evt_type = IGT_HOOK_PRE_TEST,
>>> +                                .target_name = command_str,
>>> +                        };
>>> +
>>> +                        igt_hook_push_evt(igt_hook, &hook_evt);
>>> +                }
>>> +
>>>                  sync();
>>>                  oom_adjust_for_doom();
>>>                  ftrace_dump_on_oops(show_ftrace);
>>> @@ -1487,6 +1543,16 @@ bool __igt_run_subtest(const char *subtest_name, const char *file, const int lin
>>>          igt_thread_clear_fail_state();
>>>
>>>          igt_gettime(&subtest_time);
>>> +
>>> +        if (igt_hook) {
>>> +                struct igt_hook_evt hook_evt = {
>>> +                        .evt_type = IGT_HOOK_PRE_SUBTEST,
>>> +                        .target_name = subtest_name,
>>> +                };
>>> +
>>> +                igt_hook_push_evt(igt_hook, &hook_evt);
>>> +        }
>>> +
>>>          return (in_subtest = subtest_name);
>>>  }
>>>
>>> @@ -1517,6 +1583,16 @@ bool __igt_run_dynamic_subtest(const char *dynamic_subtest_name)
>>>          _igt_dynamic_tests_executed++;
>>>
>>>          igt_gettime(&dynamic_subtest_time);
>>> +
>>> +        if (igt_hook) {
>>> +                struct igt_hook_evt hook_evt = {
>>> +                        .evt_type = IGT_HOOK_PRE_DYN_SUBTEST,
>>> +                        .target_name = dynamic_subtest_name,
>>> +                };
>>> +
>>> +                igt_hook_push_evt(igt_hook, &hook_evt);
>>> +        }
>>> +
>>>          return (in_dynamic_subtest = dynamic_subtest_name);
>>>  }
>>>
>>> @@ -1602,6 +1678,17 @@ __noreturn static void exit_subtest(const char *result)
>>>          struct timespec *thentime = in_dynamic_subtest ? &dynamic_subtest_time : &subtest_time;
>>>          jmp_buf *jmptarget = in_dynamic_subtest ? &igt_dynamic_jmpbuf : &igt_subtest_jmpbuf;
>>>
>>> +        if (igt_hook) {
>>> +                struct igt_hook_evt hook_evt = {
>>> +                        .evt_type = (in_dynamic_subtest
>>> +                                        ? IGT_HOOK_POST_DYN_SUBTEST
>>> +                                        : IGT_HOOK_POST_SUBTEST),
>>> +                        .result = result,
>>> +                };
>>> +
>>> +                igt_hook_push_evt(igt_hook, &hook_evt);
>>> +        }
>>> +
>>>          if (!igt_thread_is_main()) {
>>>                  igt_thread_fail();
>>>                  pthread_exit(NULL);
>>> @@ -2274,6 +2361,7 @@ void __igt_abort(const char *domain, const char *file, const int line,
>>>  void igt_exit(void)
>>>  {
>>>          int tmp;
>>> +        const char *result;
>>>
>>>          if (!test_with_subtests)
>>>                  igt_thread_assert_no_failures();
>>> @@ -2318,12 +2406,7 @@ void igt_exit(void)
>>>
>>>          assert(waitpid(-1, &tmp, WNOHANG) == -1 && errno == ECHILD);
>>>
>>> -        if (!test_with_subtests) {
>>> -                struct timespec now;
>>> -                const char *result;
>>> -
>>> -                igt_gettime(&now);
>>> -
>>> +        if (!test_with_subtests || igt_hook) {
>>>                  switch (igt_exitcode) {
>>>                          case IGT_EXIT_SUCCESS:
>>>                                  result = "SUCCESS";
>>> @@ -2334,6 +2417,12 @@ void igt_exit(void)
>>>                          default:
>>>                                  result = "FAIL";
>>>                  }
>>> +        }
>>> +
>>> +        if (!test_with_subtests) {
>>> +                struct timespec now;
>>> +
>>> +                igt_gettime(&now);
>>>
>>>                  if (test_multi_fork_child) /* parent will do the yelling */
>>>                          _log_line_fprintf(stdout, "dyn_child pid:%d (%.3fs) ends with err=%d\n",
>>> @@ -2344,6 +2433,15 @@ void igt_exit(void)
>>>                                            result, igt_time_elapsed(&subtest_time, &now));
>>>          }
>>>
>>> +        if (igt_hook) {
>>> +                struct igt_hook_evt hook_evt = {
>>> +                        .evt_type = IGT_HOOK_POST_TEST,
>>> +                        .result = result,
>>> +                };
>>> +
>>> +                igt_hook_push_evt(igt_hook, &hook_evt);
>>> +        }
>>> +
>>>          exit(igt_exitcode);
>>>  }
>>>
>>> diff --git a/lib/igt_hook.c b/lib/igt_hook.c
>>> new file mode 100644
>>> index 000000000000..8a39e19e3e5f
>>> --- /dev/null
>>> +++ b/lib/igt_hook.c
>>> @@ -0,0 +1,499 @@
>>> +/*
>>> + * Copyright © 2024 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 <assert.h>
>>> +#include <errno.h>
>>> +#include <limits.h>
>>> +#include <stdbool.h>
>>> +#include <stddef.h>
>>> +#include <stdint.h>
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <string.h>
>>> +
>>> +#include "igt_hook.h"
>>> +
>>> +/**
>>> + * SECTION:igt_hook
>>> + * @short_description: Support for running a hook script on test execution
>>> + * @title: Hook support
>>> + *
>>> + * IGT provides support for running a hook script when executing tests. This
>>> + * support is provided to users via CLI option `--hook` available in test
>>> + * binaries. Users should use `--help-hook` for detailed usaged description of
>>> + * the feature.
>>> + *
>>> + * The sole user of the exposed API is `igt_core`, which calls @igt_hook_init()
>>> + * when initializing a test case, then calls @igt_hook_push_evt() for each event
>>> + * that occurs during that test's execution and finally calls @igt_hook_free()
>>> + * to clean up at the end.
>>> + */
>>> +
>>> +#define TEST_NAME_INITIAL_SIZE 16
>>> +
>>> +typedef uint16_t evt_mask_t;
>>> +
>>> +struct igt_hook {
>>> +        evt_mask_t evt_mask;
>>> +        char *cmd;
>>> +        char *test_name;
>>> +        size_t test_name_size;
>>> +        char *subtest_name;
>>> +        size_t subtest_name_size;
>>> +        char *dyn_subtest_name;
>>> +        size_t dyn_subtest_name_size;
>>> +        char *test_fullname;
>>> +};
>>> +
>>> +enum igt_hook_error {
>>> +        IGT_HOOK_EVT_EMPTY_NAME = 1,
>>> +        IGT_HOOK_EVT_NO_MATCH,
>>> +};
>>> +
>>> +static_assert(IGT_HOOK_NUM_EVENTS <= sizeof(evt_mask_t) * CHAR_BIT,
>>> +              "Number of event types does not fit event type mask");
>>> +
>>> +static const char *igt_hook_evt_type_to_name(enum igt_hook_evt_type evt_type)
>>> +{
>>> +        switch (evt_type) {
>>> +        case IGT_HOOK_PRE_TEST:
>>> +                return "pre-test";
>>> +        case IGT_HOOK_PRE_SUBTEST:
>>> +                return "pre-subtest";
>>> +        case IGT_HOOK_PRE_DYN_SUBTEST:
>>> +                return "pre-dyn-subtest";
>>> +        case IGT_HOOK_POST_DYN_SUBTEST:
>>> +                return "post-dyn-subtest";
>>> +        case IGT_HOOK_POST_SUBTEST:
>>> +                return "post-subtest";
>>> +        case IGT_HOOK_POST_TEST:
>>> +                return "post-test";
>>> +        case IGT_HOOK_NUM_EVENTS:
>>> +                break;
>>> +        /* No "default:" case, to force a warning from -Wswitch in case we miss
>>> +         * any new event type. */
>>> +        }
>>> +        return "?";
>>> +}
>>> +
>>> +static int igt_hook_parse_hook_str(const char *hook_str, evt_mask_t *evt_mask, const char **cmd)
>>> +{
>>> +        const char *s;
>>> +
>>> +        if (!strchr(hook_str, ':')) {
>>> +                *evt_mask = ~0;
>>> +                *cmd = hook_str;
>>> +                return 0;
>>> +        }
>>> +
>>> +        s = hook_str;
>>> +        *evt_mask = 0;
>>> +
>>> +        while (1) {
>>> +                const char *evt_name;
>>> +                bool has_match;
>>> +                bool is_star;
>>> +                enum igt_hook_evt_type evt_type;
>>> +
>>> +                evt_name = s;
>>> +
>>> +                while (*s != ':' && *s != ',')
>>> +                        s++;
>>> +
>>> +                if (evt_name == s)
>>> +                        return IGT_HOOK_EVT_EMPTY_NAME;
>>> +
>>> +                has_match = false;
>>> +                is_star = *evt_name == '*' && evt_name + 1 == s;
>>> +
>>> +                for (evt_type = IGT_HOOK_PRE_TEST; evt_type < IGT_HOOK_NUM_EVENTS; evt_type++) {
>>> +                        if (!is_star) {
>>> +                                const char *this_event_name = igt_hook_evt_type_to_name(evt_type);
>>> +                                size_t len = s - evt_name;
>>> +
>>> +                                if (len != strlen(this_event_name))
>>> +                                        continue;
>>> +
>>> +                                if (strncmp(evt_name, this_event_name, len))
>>> +                                        continue;
>>> +                        }
>>> +
>>> +                        *evt_mask |= 1 << evt_type;
>>> +                        has_match = true;
>>> +
>>> +                        if (!is_star)
>>> +                                break;
>>> +                }
>>> +
>>> +                if (!has_match)
>>> +                        return IGT_HOOK_EVT_NO_MATCH;
>>> +
>>> +                if (*s++ == ':')
>>> +                        break;
>>> +        }
>>> +
>>> +        *cmd = s;
>>> +
>>> +        return 0;
>>> +}
>>> +
>>> +static size_t igt_hook_calc_test_fullname_size(struct igt_hook *igt_hook) {
>>> +        /* The maximum size of test_fullname will be the maximum length of
>>> +         * "igt@<test_name>@<subtest_name>@<dyn_subtest_name>" plus 1 for the
>>> +         * null byte. */
>>> +        return (igt_hook->test_name_size +
>>> +                igt_hook->subtest_name_size +
>>> +                igt_hook->dyn_subtest_name_size) + 4;
>>> +}
>>> +
>>> +static void igt_hook_update_test_fullname(struct igt_hook *igt_hook)
>>> +{
>>> +        int i;
>>> +        char *s;
>>> +        const char *values[3] = {
>>> +                igt_hook->test_name,
>>> +                igt_hook->subtest_name,
>>> +                igt_hook->dyn_subtest_name,
>>> +        };
>>> +
>>> +        if (igt_hook->test_name[0] == '\0') {
>>> +                igt_hook->test_fullname[0] = '\0';
>>> +                return;
>>> +        }
>>> +
>>> +        s = stpcpy(igt_hook->test_fullname, "igt");
>>> +        for (i = 0; i < 3 && values[i][0] != '\0'; i++) {
>>> +                *s++ = '@';
>>> +                s = stpcpy(s, values[i]);
>>> +        }
>>> +}
>>> +
>>> +/**
>>> + * igt_hook_init:
>>> + * @hook_str: Hook descriptor string.
>>> + * @error: Pointer to error number.
>>> + *
>>> + * Allocate and initialize an #igt_hook structure.
>>> + *
>>> + * This function parses the hook descriptor @hook_str and initializes the struct
>>> + * to be returned.
>>> + *
>>> + * The hook descriptor comes from the argument to `--hook` of the test
>>> + * executable being run.
>>> + *
>>> + * If not #NULL, @error is used to store a non-zero error number if an error
>>> + * happens. A human-readable string for that error number can be obtained with
>>> + * @igt_hook_error_str().
>>> + *
>>> + * Returns: The pointer to the #igt_hook structure on success or #NULL on error.
>>> + */
>>> +struct igt_hook *igt_hook_init(const char *hook_str, int *error)
>>> +{
>>> +        int err;
>>> +        evt_mask_t evt_mask;
>>> +        const char *cmd;
>>> +        struct igt_hook *igt_hook = NULL;
>>> +
>>> +
>>> +        err = igt_hook_parse_hook_str(hook_str, &evt_mask, &cmd);
>>> +        if (err)
>>> +                goto out;
>>> +
>>> +        igt_hook = calloc(1, sizeof(*igt_hook));
>>> +        igt_hook->evt_mask = evt_mask;
>>> +
>>> +        igt_hook->cmd = strdup(cmd);
>>> +        if (!igt_hook->cmd) {
>>> +                err = -errno;
>>> +                goto out;
>>> +        }
>>> +
>>> +        igt_hook->test_name = malloc(TEST_NAME_INITIAL_SIZE);
>>> +        igt_hook->test_name_size = TEST_NAME_INITIAL_SIZE;
>>> +        igt_hook->subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
>>> +        igt_hook->subtest_name_size = TEST_NAME_INITIAL_SIZE;
>>> +        igt_hook->dyn_subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
>>> +        igt_hook->dyn_subtest_name_size = TEST_NAME_INITIAL_SIZE;
>>> +        igt_hook->test_fullname = malloc(igt_hook_calc_test_fullname_size(igt_hook));
>>> +
>>> +        igt_hook->test_name[0] = '\0';
>>> +        igt_hook->subtest_name[0] = '\0';
>>> +        igt_hook->dyn_subtest_name[0] = '\0';
>>> +        igt_hook->test_fullname[0] = '\0';
>>> +
>>> +out:
>>> +        if (err) {
>>> +                if (error)
>>> +                        *error = err;
>>> +
>>> +                igt_hook_free(igt_hook);
>>> +
>>> +                return NULL;
>>> +        }
>>> +
>>> +        return igt_hook;
>>> +}
>>> +
>>> +/**
>>> + * igt_hook_free:
>>> + * @igt_hook: The igt_hook struct.
>>> + *
>>> + * De-initialize an igt_hook struct returned by @igt_hook_init().
>>> + *
>>> + * This is a no-op if @igt_hook is #NULL.
>>> + */
>>> +void igt_hook_free(struct igt_hook *igt_hook)
>>> +{
>>> +        if (!igt_hook)
>>> +                return;
>>> +
>>> +        free(igt_hook->cmd);
>>> +        free(igt_hook->test_name);
>>> +        free(igt_hook->subtest_name);
>>> +        free(igt_hook->dyn_subtest_name);
>>> +        free(igt_hook);
>>> +}
>>> +
>>> +static void igt_hook_update_test_name_pre_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>>> +{
>>> +        char **name_ptr;
>>> +        size_t *size_ptr;
>>> +        size_t len;
>>> +
>>> +        switch (evt->evt_type) {
>>> +        case IGT_HOOK_PRE_TEST:
>>> +                name_ptr = &igt_hook->test_name;
>>> +                size_ptr = &igt_hook->test_name_size;
>>> +                break;
>>> +        case IGT_HOOK_PRE_SUBTEST:
>>> +                name_ptr = &igt_hook->subtest_name;
>>> +                size_ptr = &igt_hook->subtest_name_size;
>>> +                break;
>>> +        case IGT_HOOK_PRE_DYN_SUBTEST:
>>> +                name_ptr = &igt_hook->dyn_subtest_name;
>>> +                size_ptr = &igt_hook->dyn_subtest_name_size;
>>> +                break;
>>> +        default:
>>> +                return;
>>> +        }
>>> +
>>> +        len = strlen(evt->target_name);
>>> +        if (len + 1 > *size_ptr) {
>>> +                size_t fullname_size;
>>> +
>>> +                *size_ptr *= 2;
>>> +                *name_ptr = realloc(*name_ptr, *size_ptr);
>>> +
>>> +                fullname_size = igt_hook_calc_test_fullname_size(igt_hook);
>>> +                igt_hook->test_fullname = realloc(igt_hook->test_fullname, fullname_size);
>>> +        }
>>> +
>>> +        strcpy(*name_ptr, evt->target_name);
>>> +        igt_hook_update_test_fullname(igt_hook);
>>> +}
>>> +
>>> +static void igt_hook_update_test_name_post_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>>> +{
>>> +        switch (evt->evt_type) {
>>> +        case IGT_HOOK_POST_TEST:
>>> +                igt_hook->test_name[0] = '\0';
>>> +                break;
>>> +        case IGT_HOOK_POST_SUBTEST:
>>> +                igt_hook->subtest_name[0] = '\0';
>>> +                break;
>>> +        case IGT_HOOK_POST_DYN_SUBTEST:
>>> +                igt_hook->dyn_subtest_name[0] = '\0';
>>> +                break;
>>> +        default:
>>> +                return;
>>> +        }
>>> +
>>> +        igt_hook_update_test_fullname(igt_hook);
>>> +}
>>> +
>>> +static void igt_hook_update_env_vars(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>>> +{
>>> +        setenv("IGT_HOOK_EVENT", igt_hook_evt_type_to_name(evt->evt_type), 1);
>>> +        setenv("IGT_HOOK_TEST_FULLNAME", igt_hook->test_fullname, 1);
>>> +        setenv("IGT_HOOK_TEST", igt_hook->test_name, 1);
>>> +        setenv("IGT_HOOK_SUBTEST", igt_hook->subtest_name, 1);
>>> +        setenv("IGT_HOOK_DYN_SUBTEST", igt_hook->dyn_subtest_name, 1);
>>> +        setenv("IGT_HOOK_RESULT", evt->result ?: "", 1);
>>> +}
>>> +
>>> +/**
>>> + * igt_hook_push_evt:
>>> + * @igt_hook: The igt_hook structure.
>>> + * @evt: The event to be pushed.
>>> + *
>>> + * Push a new igt_hook event.
>>> + *
>>> + * This function must be used to register a new igt_hook event. Calling it will
>>> + * cause execution of the hook script if the event type matches the filters
>>> + * provided during initialization of @igt_hook.
>>> + */
>>> +void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>>> +{
>>> +        typeof(igt_hook->evt_mask) evt_bit = (1 << evt->evt_type);
>>> +
>>> +        igt_hook_update_test_name_pre_call(igt_hook, evt);
>>> +
>>> +        if ((evt_bit & igt_hook->evt_mask)) {
>>> +                igt_hook_update_env_vars(igt_hook, evt);
>>> +                system(igt_hook->cmd);
>>> +        }
>>> +
>>> +        igt_hook_update_test_name_post_call(igt_hook, evt);
>>> +}
>>> +
>>> +/**
>>> + * igt_hook_error_str:
>>> + * @error: Non-zero error number.
>>> + *
>>> + * Return a human-readable string containing a description of an error number
>>> + * generated by one of the `igt_hook_*` functions.
>>> + *
>>> + * The string will be the result of strerror() for errors from the C standard
>>> + * library or a custom description specific to igt_hook.
>>> + */
>>> +const char *igt_hook_error_str(int error)
>>> +{
>>> +        if (!error)
>>> +                return "No error";
>>> +
>>> +        if (error > 0) {
>>> +                enum igt_hook_error hook_error = error;
>>> +
>>> +                switch (hook_error) {
>>> +                case IGT_HOOK_EVT_EMPTY_NAME:
>>> +                        return "Empty name in event descriptor";
>>> +                case IGT_HOOK_EVT_NO_MATCH:
>>> +                        return "Event name in event descriptor does not match any event type";
>>> +                default:
>>> +                        return "Unknown error";
>>> +                }
>>> +        } else {
>>> +                return strerror(-error);
>>> +        }
>>> +}
>>> +
>>> +/**
>>> + * igt_hook_print_help:
>>> + * @f: File pointer where to write the output.
>>> + * @option_name: Name of the CLI option that accepts the hook descriptor.
>>> + *
>>> + * Print a detailed user help text on hook usage.
>>> + */
>>> +void igt_hook_print_help(FILE *f, const char *option_name)
>>> +{
>>> +        fprintf(f, "\
>>> +The option %1$s receives as argument a \"hook descriptor\" and allows the\n\
>>> +execution of a shell command at different points during execution of tests. Each\n\
>>> +such a point is called a \"hook event\".\n\
>>> +\n\
>>> +Examples:\n\
>>> +\n\
>>> +  # Prints hook-specic env vars for every event.\n\
>>> +  %1$s 'printenv | grep ^IGT_HOOK_'\n\
>>> +\n\
>>> +  # Equivalent to the above. Useful if command contains ':'.\n\
>>> +  %1$s '*:printenv | grep ^IGT_HOOK_'\n\
>>> +\n\
>>> +  # Adds a line to out.txt containing the result of each test case.\n\
>>> +  %1$s 'post-test:echo $IGT_HOOK_TEST_FULLNAME $IGT_HOOK_RESULT >> out.txt'\n\
>>> +\n\
>>> +The accepted format for a hook descriptor is `[<events>:]<cmd>`, where:\n\
>>> +\n\
>>> +  - <events> is a comma-separated list of event descriptors, which defines the\n\
>>> +    set of events be tracked. If omitted, all events are tracked.\n\
>>> +\n\
>>> +  - <cmd> is a shell command to be executed on the occurrence each tracked\n\
>>> +    event. If the command contains ':', then passing <events> is required,\n\
>>> +    otherwise part of the command would be treated as an event descriptor.\n\
>>> +\n\
>>> +", option_name);
>>> +
>>> +        fprintf(f, "\
>>> +An \"event descriptor\" is either the name of an event or the string '*'. The\n\
>>> +latter matches all event names. The list of possible event names is provided\n\
>>> +below:\n\
>>> +\n\
>>> +");
>>> +
>>> +        for (enum igt_hook_evt_type et = 0; et < IGT_HOOK_NUM_EVENTS; et++) {
>>> +                const char *desc;
>>> +
>>> +                switch (et) {
>>> +                case IGT_HOOK_PRE_TEST:
>>> +                        desc = "Occurs before a test case starts.";
>>> +                        break;
>>> +                case IGT_HOOK_PRE_SUBTEST:
>>> +                        desc = "Occurs before the execution of a subtest.";
>>> +                        break;
>>> +                case IGT_HOOK_PRE_DYN_SUBTEST:
>>> +                        desc = "Occurs before the execution of a dynamic subtest.";
>>> +                        break;
>>> +                case IGT_HOOK_POST_DYN_SUBTEST:
>>> +                        desc = "Occurs after the execution of a dynamic subtest.";
>>> +                        break;
>>> +                case IGT_HOOK_POST_SUBTEST:
>>> +                        desc = "Occurs after the execution of a subtest.";
>>> +                        break;
>>> +                case IGT_HOOK_POST_TEST:
>>> +                        desc = "Occurs after a test case has finished.";
>>> +                        break;
>>> +                default:
>>> +                        desc = "MISSING DESCRIPTION";
>>> +                }
>>> +
>>> +                fprintf(f, "  %s\n  %s\n\n", igt_hook_evt_type_to_name(et), desc);
>>> +        }
>>> +
>>> +        fprintf(f, "\
>>> +For each event matched by <events>, <cmd> is executed as a shell command. The\n\
>>> +exit status of the command is ignored. The following environment variables are\n\
>>> +available to the command:\n\
>>> +\n\
>>> +  IGT_HOOK_EVENT\n\
>>> +  Name of the current event.\n\
>>> +\n\
>>> +  IGT_HOOK_TEST_FULLNAME\n\
>>> +  Full name of the test in the format `igt@<test>[@<subtest>[@<dyn_subtest>]]`.\n\
>>> +\n\
>>> +  IGT_HOOK_TEST                   \n\
>>> +  Name of the current test.\n\
>>> +\n\
>>> +  IGT_HOOK_SUBTEST\n\
>>> +  Name of the current subtest. Will be the empty string if not running a\n\
>>> +  subtest.\n\
>>> +\n\
>>> +  IGT_HOOK_DYN_SUBTEST\n\
>>> +  Name of the current dynamic subtest. Will be the empty string if not running a\n\
>>> +  dynamic subtest.\n\
>>> +\n\
>>> +  IGT_HOOK_RESULT\n\
>>> +  String representing the result of the test/subtest/dynamic subtest. Possible\n\
>>> +  values are: SUCCESS, SKIP or FAIL. This is only applicable on \"post-*\"\n\
>>> +  events and will be the empty string for other types of events.\n\
>>> +\n\
>>> +");
>>> +}
>>> diff --git a/lib/igt_hook.h b/lib/igt_hook.h
>>> new file mode 100644
>>> index 000000000000..6fef7254a317
>>> --- /dev/null
>>> +++ b/lib/igt_hook.h
>>> @@ -0,0 +1,86 @@
>>> +/*
>>> + * Copyright © 2024 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.
>>> + */
>>> +#ifndef IGT_HOOK_H
>>> +#define IGT_HOOK_H
>>> +
>>> +#include <stdio.h>
>>> +
>>> +/**
>>> + * igt_hook:
>>> + *
>>> + * Opaque struct to hold data related to hook support.
>>> + */
>>> +struct igt_hook;
>>> +
>>> +/**
>>> + * igt_hook_evt_type:
>>> + * @IGT_HOOK_PRE_TEST: Occurs before a test case (executable) starts the
>>> + * test code.
>>> + * @IGT_HOOK_PRE_SUBTEST: Occurs before the execution of a subtest.
>>> + * @IGT_HOOK_PRE_DYN_SUBTEST: Occurs before the execution of a dynamic subtest.
>>> + * @IGT_HOOK_POST_DYN_SUBTEST: Occurs after the execution of a dynamic subtest.
>>> + * @IGT_HOOK_POST_SUBTEST: Occurs after the execution of a subtest..
>>> + * @IGT_HOOK_POST_TEST: Occurs after a test case (executable) is finished with
>>> + * the test code.
>>> + * @IGT_HOOK_NUM_EVENTS: This is not really an event and represents the number
>>> + * of possible events tracked by igt_hook.
>>> + *
>>> + * Events tracked by igt_hook. Those events occur at specific points during the
>>> + * execution of a test.
>>> + */
>>> +enum igt_hook_evt_type {
>>> +        IGT_HOOK_PRE_TEST,
>>> +        IGT_HOOK_PRE_SUBTEST,
>>> +        IGT_HOOK_PRE_DYN_SUBTEST,
>>> +        IGT_HOOK_POST_DYN_SUBTEST,
>>> +        IGT_HOOK_POST_SUBTEST,
>>> +        IGT_HOOK_POST_TEST,
>>> +        IGT_HOOK_NUM_EVENTS /* This must always be the last one. */
>>> +};
>>> +
>>> +/**
>>> + * igt_hook_evt:
>>> + * @evt_type: Type of event.
>>> + * @target_name: A string pointing to the name of the test, subtest or dynamic
>>> + * subtest, depending on @evt_type.
>>> + * @result: A string containing the result of the test, subtest or dynamic
>>> + * subtest. This is only applicable for the `IGT_HOOK_POST_\*' event types;
>>> + * other types must initialize this to #NULL.
>>> + *
>>> + * An event tracked by igt_hook, which is done with @igt_hook_push_evt(). This must
>>> + * be zero initialized and fields relevant to the event type must be set before
>>> + * passing its reference to @igt_hook_push_evt().
>>> + */
>>> +struct igt_hook_evt {
>>> +        enum igt_hook_evt_type evt_type;
>>> +        const char *target_name;
>>> +        const char *result;
>>> +};
>>> +
>>> +struct igt_hook *igt_hook_init(const char *hook_str, int *error);
>>> +void igt_hook_free(struct igt_hook *igt_hook);
>>> +void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt);
>>> +const char *igt_hook_error_str(int error);
>>> +void igt_hook_print_help(FILE *f, const char *option_name);
>>> +
>>> +#endif /* IGT_HOOK_H */
>>> diff --git a/lib/meson.build b/lib/meson.build
>>> index e2f740c116f8..10b8066647f2 100644
>>> --- a/lib/meson.build
>>> +++ b/lib/meson.build
>>> @@ -109,6 +109,7 @@ lib_sources = [
>>>          'veboxcopy_gen12.c',
>>>          'igt_msm.c',
>>>          'igt_dsc.c',
>>> +        'igt_hook.c',
>>>          'xe/xe_gt.c',
>>>          'xe/xe_ioctl.c',
>>>          'xe/xe_mmio.c',
>>> diff --git a/lib/tests/igt_hook.c b/lib/tests/igt_hook.c
>>> new file mode 100644
>>> index 000000000000..bd7e570f9607
>>> --- /dev/null
>>> +++ b/lib/tests/igt_hook.c
>>> @@ -0,0 +1,187 @@
>>> +/*
>>> + * Copyright © 2024 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 <stdbool.h>
>>> +#include <stdio.h>
>>> +#include <unistd.h>
>>> +
>>> +#include "igt_core.h"
>>> +#include "igt_hook.h"
>>> +
>>> +static const char *env_var_names[] = {
>>> +        "IGT_HOOK_EVENT",
>>> +        "IGT_HOOK_TEST_FULLNAME",
>>> +        "IGT_HOOK_TEST",
>>> +        "IGT_HOOK_SUBTEST",
>>> +        "IGT_HOOK_DYN_SUBTEST",
>>> +        "IGT_HOOK_RESULT",
>>> +};
>>> +
>>> +#define num_env_vars (sizeof(env_var_names) / sizeof(env_var_names[0]))
>>> +
>>> +static int env_var_name_lookup(char *line)
>>> +{
>>> +        int i;
>>> +        char *c;
>>> +
>>> +        c = strchr(line, '=');
>>> +        if (c)
>>> +                *c = '\0';
>>> +
>>> +        for (i = 0; i < num_env_vars; i++)
>>> +                if (!strcmp(line, env_var_names[i]))
>>> +                        goto out;
>>> +
>>> +        i = -1;
>>> +out:
>>> +        if (c)
>>> +                *c = '=';
>>> +
>>> +        return i;
>>> +}
>>> +
>>> +static void test_null_error_pointer(void)
>>> +{
>>> +        struct igt_hook *igt_hook;
>>> +
>>> +        /* Ensure passing NULL error pointer does not cause issues. */
>>> +        igt_hook = igt_hook_init("invalid:echo hello", NULL);
>>> +        igt_assert(igt_hook == NULL);
>>> +}
>>> +
>>> +static void test_invalid_hook_descriptors(void)
>>> +{
>>> +        struct {
>>> +                const char *name;
>>> +                const char *hook_desc;
>>> +        } invalid_cases[] = {
>>> +                {"invalid-event-name", "invalid-event:echo hello"},
>>> +                {"invalid-empty-event-name", ":echo hello"},
>>> +                {"invalid-colon-in-cmd", "echo hello:world"},
>>> +                {},
>>> +        };
>>> +
>>> +        for (int i = 0; invalid_cases[i].name; i++) {
>>> +                igt_subtest(invalid_cases[i].name) {
>>> +                        int err = 0;
>>> +                        struct igt_hook *igt_hook;
>>> +
>>> +                        igt_hook = igt_hook_init(invalid_cases[i].hook_desc, &err);
>>> +                        igt_assert(igt_hook == NULL);
>>> +                        igt_assert(err != 0);
>>> +                }
>>> +        }
>>> +}
>>> +
>>> +static void test_print_help(void)
>>> +{
>>> +        char *buf;
>>> +        size_t len;
>>> +        FILE *f;
>>> +        const char expected_initial_text[] = "The option --hook receives as argument a \"hook descriptor\"";
>>> +
>>> +        f = open_memstream(&buf, &len);
>>> +        igt_assert(f);
>>> +
>>> +        igt_hook_print_help(f, "--hook");
>>> +        fclose(f);
>>> +
>>> +        igt_assert(!strncmp(buf, expected_initial_text, sizeof(expected_initial_text) - 1));
>>> +
>>> +        /* This is an extra check to catch a case where an event type is added
>>> +         * without a proper description. */
>>> +        igt_assert(!strstr(buf, "MISSING DESCRIPTION"));
>>> +
>>> +        free(buf);
>>> +}
>>> +
>>> +static void test_all_env_vars(void)
>>> +{
>>> +        struct igt_hook_evt evt = {
>>> +                .evt_type = IGT_HOOK_PRE_SUBTEST,
>>> +                .target_name = "foo",
>>> +        };
>>> +        bool env_vars_checklist[num_env_vars] = {};
>>> +        struct igt_hook *igt_hook;
>>> +        char *hook_str;
>>> +        FILE *f;
>>> +        int pipefd[2];
>>> +        int ret;
>>> +        int i;
>>> +        char *line;
>>> +        size_t line_size;
>>> +
>>> +        ret = pipe(pipefd);
>>> +        igt_assert(ret == 0);
>>> +
>>> +        /* Use grep to filter only env var set by us. This should ensure that
>>> +         * writing to the pipe will not block due to capacity, since we only
>>> +         * read from the pipe after the shell command is done. */
>>> +        ret = asprintf(&hook_str, "printenv -0 | grep -z ^IGT_HOOK >&%d", pipefd[1]);
>>> +        igt_assert(ret > 0);
>>> +
>>> +        igt_hook = igt_hook_init(hook_str, NULL);
>>> +        igt_assert(igt_hook);
>>> +
>>> +        igt_hook_push_evt(igt_hook, &evt);
>>> +
>>> +        close(pipefd[1]);
>>> +        f = fdopen(pipefd[0], "r");
>>> +        igt_assert(f);
>>> +
>>> +        line = NULL;
>>> +        line_size = 0;
>>> +
>>> +        while (getdelim(&line, &line_size, '\0', f) != -1) {
>>> +                ret = env_var_name_lookup(line);
>>> +                igt_assert_f(ret >= 0, "Unexpected env var %s\n", line);
>>> +                env_vars_checklist[ret] = true;
>>> +        }
>>> +
>>> +        for (i = 0; i < num_env_vars; i++)
>>> +                igt_assert_f(env_vars_checklist[i], "Missing env var %s\n", env_var_names[i]);
>>> +
>>> +        fclose(f);
>>> +        igt_hook_free(igt_hook);
>>> +        free(hook_str);
>>> +        free(line);
>>> +}
>>> +
>>> +igt_main
>>> +{
>>> +        igt_subtest("null-error-pointer")
>>> +                test_null_error_pointer();
>>> +
>>> +        test_invalid_hook_descriptors();
>>> +
>>> +        igt_subtest("help-description")
>>> +                test_print_help();
>>> +
>>> +        igt_subtest_group {
>>> +                igt_fixture {
>>> +                        igt_require_f(system(NULL), "Shell seems not to be available\n");
>>> +                }
>>> +
>>> +                igt_subtest("all-env-vars")
>>> +                        test_all_env_vars();
>>> +        }
>>> +}
>>> diff --git a/lib/tests/igt_hook_integration.c b/lib/tests/igt_hook_integration.c
>>> new file mode 100644
>>> index 000000000000..56632b13ae81
>>> --- /dev/null
>>> +++ b/lib/tests/igt_hook_integration.c
>>> @@ -0,0 +1,297 @@
>>> +/*
>>> + * Copyright © 2024 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 <stdbool.h>
>>> +#include <stdio.h>
>>> +#include <string.h>
>>> +
>>> +#include "igt_core.h"
>>> +
>>> +#include "igt_tests_common.h"
>>> +
>>> +char prog[] = "igt_hook_integration";
>>> +char hook_opt[] = "--hook";
>>> +char hook_str[128];
>>> +char *fake_argv[] = {prog, hook_opt, hook_str};
>>> +int fake_argc = sizeof(fake_argv) / sizeof(fake_argv[0]);
>>> +
>>> +#define ENV_ARRAY(evt_name, fullname_suffix, subtest, dyn_subtest, result) \
>>> +{ \
>>> +        "IGT_HOOK_EVENT=" evt_name, \
>>> +        "IGT_HOOK_TEST_FULLNAME=igt@igt_hook_integration" fullname_suffix, \
>>> +        "IGT_HOOK_TEST=igt_hook_integration", \
>>> +        "IGT_HOOK_SUBTEST=" subtest, \
>>> +        "IGT_HOOK_DYN_SUBTEST=" dyn_subtest, \
>>> +        "IGT_HOOK_RESULT=" result, \
>>> +}
>>> +
>>> +#define TEST_ENV(evt_name, result) \
>>> +        ENV_ARRAY(evt_name, "", "", "", result)
>>> +
>>> +#define SUBTEST_ENV(evt_name, subtest, result) \
>>> +        ENV_ARRAY(evt_name, "@" subtest, subtest, "", result)
>>> +
>>> +#define DYN_SUBTEST_ENV(evt_name, subtest, dyn_subtest, result) \
>>> +        ENV_ARRAY(evt_name, "@" subtest "@" dyn_subtest, subtest, dyn_subtest, result)
>>> +
>>> +const char *pre_test_env[] = TEST_ENV("pre-test", "");
>>> +const char *pre_subtest_a_env[] = SUBTEST_ENV("pre-subtest", "a", "");
>>> +const char *pre_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "success", "");
>>> +const char *post_dyn_subtest_a_success_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "success", "SUCCESS");
>>> +const char *pre_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "failed", "");
>>> +const char *post_dyn_subtest_a_failed_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "failed", "FAIL");
>>> +const char *pre_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("pre-dyn-subtest", "a", "skipped", "");
>>> +const char *post_dyn_subtest_a_skipped_env[] = DYN_SUBTEST_ENV("post-dyn-subtest", "a", "skipped", "SKIP");
>>> +const char *post_subtest_a_env[] = SUBTEST_ENV("post-subtest", "a", "FAIL");
>>> +const char *pre_subtest_b_env[] = SUBTEST_ENV("pre-subtest", "b", "");
>>> +const char *post_subtest_b_env[] = SUBTEST_ENV("post-subtest", "b", "SUCCESS");
>>> +const char *post_test_env[] = TEST_ENV("post-test", "FAIL");
>>> +
>>> +#define num_env_vars (sizeof(pre_test_env) / sizeof(pre_test_env[0]))
>>> +
>>> +__noreturn static void fake_main(void)
>>> +{
>>> +        igt_subtest_init(fake_argc, fake_argv);
>>> +
>>> +        igt_subtest_with_dynamic("a") {
>>> +                igt_dynamic("success") {
>>> +                        igt_info("...@a@success\n");
>>> +                }
>>> +
>>> +                igt_dynamic("failed") {
>>> +                        igt_assert_f(false, "Fail on purpose\n");
>>> +                        igt_info("...@a@failed\n");
>>> +                }
>>> +
>>> +                igt_dynamic("skipped") {
>>> +                        igt_require_f(false, "Skip on purpose\n");
>>> +                        igt_info("...@a@skipped\n");
>>> +                }
>>> +        }
>>> +
>>> +        igt_subtest("b") {
>>> +                igt_info("...@b\n");
>>> +        }
>>> +
>>> +        igt_exit();
>>> +}
>>> +
>>> +static void test_invalid_hook_str(void)
>>> +{
>>> +        int status;
>>> +        pid_t pid;
>>> +        static char err[4096];
>>> +        int errfd;
>>> +
>>> +        sprintf(hook_str, "invalid-event:echo hello");
>>> +
>>> +        pid = do_fork_bg_with_pipes(fake_main, NULL, &errfd);
>>> +
>>> +        read_whole_pipe(errfd, err, sizeof(err));
>>> +
>>> +        internal_assert(safe_wait(pid, &status) != -1);
>>> +        internal_assert_wexited(status, IGT_EXIT_INVALID);
>>> +
>>> +        internal_assert(strstr(err, "Failed to initialize hook data:"));
>>> +
>>> +        close(errfd);
>>> +}
>>> +
>>> +static bool match_env(FILE *hook_out_stream, const char **expected_env)
>>> +{
>>> +        int i;
>>> +        char hook_env_buf[4096];
>>> +        size_t buf_len = 0;
>>> +        char *line = NULL;
>>> +        size_t line_size;
>>> +        bool env_checklist[num_env_vars] = {};
>>> +        bool has_unexpected = false;
>>> +        bool has_missing = false;
>>> +
>>> +        /* Store env from hook so we can show it in case of errors */
>>> +        while (getdelim(&line, &line_size, '\0', hook_out_stream) != -1) {
>>> +                internal_assert(buf_len + strlen(line) + 1 <= sizeof(hook_env_buf));
>>> +                strcpy(hook_env_buf + buf_len, line);
>>> +                buf_len += strlen(line) + 1;
>>> +
>>> +                if (!strcmp(line, "---"))
>>> +                        break;
>>> +        }
>>> +
>>> +        if (!expected_env && !buf_len) {
>>> +                /* We have consumed everything and we are done now. */
>>> +                return false;
>>> +        }
>>> +
>>> +
>>> +        if (!expected_env) {
>>> +                printf("Detected unexpected hook execution\n");
>>> +                has_unexpected = true;
>>> +                goto out;
>>> +        }
>>> +
>>> +        if (!buf_len) {
>>> +                printf("Expected more hook execution, but none found\n");
>>> +                has_missing = true;
>>> +                goto out;
>>> +        }
>>> +
>>> +
>>> +        line = hook_env_buf;
>>> +        while (strcmp(line, "---")) {
>>> +                for (i = 0; i < num_env_vars; i++) {
>>> +                        if (!strcmp(line, expected_env[i])) {
>>> +                                env_checklist[i] = true;
>>> +                                break;
>>> +                        }
>>> +                }
>>> +
>>> +                if (i == num_env_vars) {
>>> +                        printf("Unexpected envline from hook: %s\n", line);
>>> +                        has_unexpected = true;
>>> +                }
>>> +
>>> +                line += strlen(line) + 1;
>>> +        }
>>> +
>>> +        for (i = 0; i < num_env_vars; i++) {
>>> +                if (!env_checklist[i]) {
>>> +                        has_missing = true;
>>> +                        printf("Missing expected envline: %s\n", expected_env[i]);
>>> +                }
>>> +        }
>>> +
>>> +out:
>>> +        if (has_unexpected || has_missing) {
>>> +                if (expected_env) {
>>> +                        printf("Expected environment:\n");
>>> +                        for (i = 0; i < num_env_vars; i++)
>>> +                                printf("  %s\n", expected_env[i]);
>>> +                }
>>> +
>>> +                if (buf_len) {
>>> +                        printf("Environment from hook:\n");
>>> +                        line = hook_env_buf;
>>> +                        while (strcmp(line, "---")) {
>>> +                                printf("  %s\n", line);
>>> +                                line += strlen(line) + 1;
>>> +                        }
>>> +                } else {
>>> +                        printf("No hook execution found\n");
>>> +                }
>>> +        }
>>> +
>>> +        internal_assert(!has_unexpected);
>>> +        internal_assert(!has_missing);
>>> +
>>> +        /* Ready to consume next hook output. */
>>> +        return true;
>>> +}
>>> +
>>> +static void run_tests_and_match_env(const char *evt_descriptors, const char **expected_envs[])
>>> +{
>>> +        int i;
>>> +        int ret;
>>> +        int pipefd[2];
>>> +        pid_t pid;
>>> +        FILE *f;
>>> +
>>> +        ret = pipe(pipefd);
>>> +        internal_assert(ret == 0);
>>> +
>>> +        /* Use grep to filter only env var set by us. This should ensure that
>>> +         * writing to the pipe will not block due to capacity, since we only
>>> +         * read from the pipe after the shell command is done. */
>>> +        sprintf(hook_str,
>>> +                "%1$s:printenv -0 | grep -z ^IGT_HOOK >&%2$d; echo -en ---\\\\x00 >&%2$d",
>>> +                evt_descriptors,
>>> +                pipefd[1]);
>>> +
>>> +        pid = do_fork_bg_with_pipes(fake_main, NULL, NULL);
>>> +        internal_assert(safe_wait(pid, &ret) != -1);
>>> +        internal_assert_wexited(ret, IGT_EXIT_FAILURE);
>>> +
>>> +        close(pipefd[1]);
>>> +        f = fdopen(pipefd[0], "r");
>>> +        internal_assert(f);
>>> +
>>> +        while (match_env(f, expected_envs[i]))
>>> +                i++;
>>> +
>>> +        fclose(f);
>>> +
>>> +}
>>> +
>>> +int main(int argc, char **argv)
>>> +{
>>> +        {
>>> +                printf("Check invalid hook string\n");
>>> +                test_invalid_hook_str();
>>> +        }
>>> +
>>> +        {
>>> +                const char **expected_envs[] = {
>>> +                        pre_test_env,
>>> +                        pre_subtest_a_env,
>>> +                        pre_dyn_subtest_a_success_env,
>>> +                        post_dyn_subtest_a_success_env,
>>> +                        pre_dyn_subtest_a_failed_env,
>>> +                        post_dyn_subtest_a_failed_env,
>>> +                        pre_dyn_subtest_a_skipped_env,
>>> +                        post_dyn_subtest_a_skipped_env,
>>> +                        post_subtest_a_env,
>>> +                        pre_subtest_b_env,
>>> +                        post_subtest_b_env,
>>> +                        post_test_env,
>>> +                        NULL,
>>> +                };
>>> +
>>> +                printf("Check full event tracking\n");
>>> +                run_tests_and_match_env("*", expected_envs);
>>> +        }
>>> +
>>> +        {
>>> +                const char **expected_envs[] = {
>>> +                        pre_dyn_subtest_a_success_env,
>>> +                        pre_dyn_subtest_a_failed_env,
>>> +                        pre_dyn_subtest_a_skipped_env,
>>> +                        NULL,
>>> +                };
>>> +
>>> +                printf("Check single event type tracking\n");
>>> +                run_tests_and_match_env("pre-dyn-subtest", expected_envs);
>>> +        }
>>> +
>>> +        {
>>> +                const char **expected_envs[] = {
>>> +                        pre_subtest_a_env,
>>> +                        post_dyn_subtest_a_success_env,
>>> +                        post_dyn_subtest_a_failed_env,
>>> +                        post_dyn_subtest_a_skipped_env,
>>> +                        pre_subtest_b_env,
>>> +                        NULL,
>>> +                };
>>> +
>>> +                printf("Check multiple event types tracking\n");
>>> +                run_tests_and_match_env("post-dyn-subtest,pre-subtest", expected_envs);
>>> +        }
>>> +}
>>> diff --git a/lib/tests/meson.build b/lib/tests/meson.build
>>> index fa3d81de6cef..df8092638eca 100644
>>> --- a/lib/tests/meson.build
>>> +++ b/lib/tests/meson.build
>>> @@ -10,6 +10,8 @@ lib_tests = [
>>>          'igt_exit_handler',
>>>          'igt_fork',
>>>          'igt_fork_helper',
>>> +        'igt_hook',
>>> +        'igt_hook_integration',
>>>          'igt_ktap_parser',
>>>          'igt_list_only',
>>>          'igt_invalid_subtest_name',
>>> --
>>> 2.45.0
>>>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: ✗ GitLab.Pipeline: warning for Add support for hook script
  2024-05-09 17:04   ` Gustavo Sousa
@ 2024-05-20 19:44     ` Lucas De Marchi
  2024-06-19 19:07       ` Gustavo Sousa
  0 siblings, 1 reply; 22+ messages in thread
From: Lucas De Marchi @ 2024-05-20 19:44 UTC (permalink / raw)
  To: Gustavo Sousa; +Cc: Patchwork, igt-dev

On Thu, May 09, 2024 at 02:04:15PM GMT, Gustavo Sousa wrote:
>Quoting Patchwork (2024-05-09 13:13:43-03:00)
>>== Series Details ==
>>
>>Series: Add support for hook script
>>URL   : https://patchwork.freedesktop.org/series/133391/
>>State : warning
>>
>>== Summary ==
>>
>>Pipeline status: FAILED.
>>
>>see https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/pipelines/1173738 for the overview.
>>
>>test:ninja-test-minimal has failed (https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606):
>>  26/30 lib i915_perf_data_alignment            OK       0.42 s
>>  27/30 lib bad_subtest_type                    EXPECTEDFAIL 0.37 s
>>  28/30 lib igt_no_subtest                      EXPECTEDFAIL 0.42 s
>>  29/30 lib igt_simple_test_subtests            EXPECTEDFAIL 0.27 s
>>  30/30 lib igt_timeout                         EXPECTEDFAIL 1.32 s
>>
>>  Ok:                   25
>>  Expected Fail:         4
>>  Fail:                  1
>>  Unexpected Pass:       0
>>  Skipped:               0
>>  Timeout:               0
>>
>>  Full log written to /builds/gfx-ci/igt-ci-tags/build/meson-logs/testlog.txt
>>  section_end:1715270916:step_script
>>  section_start:1715270916:cleanup_file_variables
>>  Cleaning up project directory and file based variables
>>  section_end:1715270917:cleanup_file_variables
>>  ERROR: Job failed: exit code 1
>>
>>== Logs ==
>>
>>For more details see: https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/pipelines/1173738
>
>Looking at the output on the page linked above, I see that the test I
>introduced (igt_hook_integration) is failing but I passes locally for
>me.
>
>Is there any way I get more details on the failure to investigate? The
>job output says:
>
>    "Full log written to
>    /builds/gfx-ci/igt-ci-tags/build/meson-logs/testlog.txt"
>
>, but I'm not sure if that is available somewhere for me to check.

GitLab.Pipeline is the one executed in fdo infra, not in the intel
infra. You use the container it's using to repro it locally:

https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606

	Using docker image sha256:08904a47f4efcc161569a9b7f88c458fc6e3e85a2225132246af9cf5cc6c4e5b
	for registry.freedesktop.org/gfx-ci/igt-ci-tags/build-debian-minimal:commit-c21f9e2242955d29c7bb2fd9703230bd9799eb8c
	with digest registry.freedesktop.org/gfx-ci/igt-ci-tags/build-debian-minimal@sha256:8b51a86fd81e64c501c9521c37fc8ad6f2550976931c510eb0ff4f6a328d477a ...

Note that this is the "test" phase. You should also reproduce the build
phase. From the logs:

	Downloading artifacts for build:tests-debian-minimal (58540184)...

which points to: https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540184

The builder and test container are the same, so you can use to fetch:

	$ c=registry.freedesktop.org/gfx-ci/igt-ci-tags/build-debian-minimal:commit-c21f9e2242955d29c7bb2fd9703230bd9799eb8c
	$ podman pull $c

and to run (first line I conveniently alias to "pod" locally):

	$ podman run --rm -v $(pwd):/src --net host -w /src -it $c /bin/bash

	/src # meson -Dtests=disabled -Dlibdrm_drivers="" build
	/src # ninja -C build -j${FDO_CI_CONCURRENT:-4} || ninja -C build -j 1
	/src # meson test -C build --num-processes ${FDO_CI_CONCURRENT:-4}
	ninja: Entering directory `/src/build'
	[2/2] Generating intel-ci-tests with a custom command.
	...
	13/30 lib igt_hook_integration                FAIL     0.12 s (killed by signal 11 SIGSEGV)
	...
	Full log written to /src/build/meson-logs/testlog.txt


I see a bunch of lines like

	Unexpected envline from hook:
	Unexpected envline from hook:
	Unexpected envline from hook:
	Unexpected envline from hook:
	Unexpected envline from hook:
	Unexpected envline from hook:
	Unexpected envline from hook:
	Unexpected envline from hook:
	Unexpected envline from hook:

until it's eventually killed and line is truncated:

	Unexpected envli


Given the fact it passed in some distros, I think that debian minimal
installation doesn't like something in

                 "%1$s:printenv -0 | grep -z ^IGT_HOOK >&%2$d; echo -en ---\\\\x00 >&%2$d",


Lucas De Marchi

>
>--
>Gustavo

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-09 15:24 ` [PATCH i-g-t 1/3] igt_hook: Add feature Gustavo Sousa
  2024-05-13 17:10   ` Kamil Konieczny
  2024-05-15 17:10   ` Kamil Konieczny
@ 2024-05-21 19:40   ` Lucas De Marchi
  2024-06-19 21:12     ` Gustavo Sousa
  2 siblings, 1 reply; 22+ messages in thread
From: Lucas De Marchi @ 2024-05-21 19:40 UTC (permalink / raw)
  To: Gustavo Sousa; +Cc: igt-dev

On Thu, May 09, 2024 at 12:24:29PM GMT, Gustavo Sousa wrote:
>For development purposes, sometimes it is useful to have a way of
>running custom scripts at certain points of test executions. A
>real-world example I bumped into recently is to collect information from
>sysfs before and after running each entry of a testlist.
>
>While it is possible for the user to handcraft a script that calls each
>test with the correct actions before and after execution, we can provide
>a better experience by adding built-in support for running hooks during
>test execution.
>
>That would be even better when adding the same kind of support for
>igt_runner (which is done in an upcoming change), since the user can
>also nicely resume with igt_resume with the hook already setup in case a
>crash happens during execution of the test list.
>
>As such provide implement support for hooks, integrate it into
>igt_core and expose the functionality via --hook CLI option on test
>executables.
>
>Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
>---
> .../igt-gpu-tools/igt-gpu-tools-docs.xml      |   1 +
> lib/igt_core.c                                | 116 +++-
> lib/igt_hook.c                                | 499 ++++++++++++++++++
> lib/igt_hook.h                                |  86 +++
> lib/meson.build                               |   1 +
> lib/tests/igt_hook.c                          | 187 +++++++
> lib/tests/igt_hook_integration.c              | 297 +++++++++++
> lib/tests/meson.build                         |   2 +
> 8 files changed, 1180 insertions(+), 9 deletions(-)
> create mode 100644 lib/igt_hook.c
> create mode 100644 lib/igt_hook.h
> create mode 100644 lib/tests/igt_hook.c
> create mode 100644 lib/tests/igt_hook_integration.c
>
>diff --git a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>index 9085eb924e85..11458c68124b 100644
>--- a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>+++ b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>@@ -32,6 +32,7 @@
>     <xi:include href="xml/igt_fb.xml"/>
>     <xi:include href="xml/igt_frame.xml"/>
>     <xi:include href="xml/igt_gt.xml"/>
>+    <xi:include href="xml/igt_hook.xml"/>
>     <xi:include href="xml/igt_io.xml"/>
>     <xi:include href="xml/igt_kmod.xml"/>
>     <xi:include href="xml/igt_kms.xml"/>
>diff --git a/lib/igt_core.c b/lib/igt_core.c
>index 3ff3e0392316..291d891cf884 100644
>--- a/lib/igt_core.c
>+++ b/lib/igt_core.c
>@@ -70,6 +70,7 @@
>
> #include "igt_core.h"
> #include "igt_aux.h"
>+#include "igt_hook.h"
> #include "igt_sysfs.h"
> #include "igt_sysrq.h"
> #include "igt_rc.h"
>@@ -241,6 +242,9 @@
>  * - '*,!basic*' match any subtest not starting basic
>  * - 'basic*,!basic-render*' match any subtest starting basic but not starting basic-render
>  *
>+ * It is possible to run a shell script at certain points of test execution with
>+ * "--hook". See the usage description with "--help-hook" for details.
>+ *
>  * # Configuration
>  *
>  * Some of IGT's behavior can be configured through a configuration file.
>@@ -273,6 +277,8 @@ static unsigned int exit_handler_count;
> const char *igt_interactive_debug;
> bool igt_skip_crc_compare;
>
>+static struct igt_hook *igt_hook = NULL;
>+
> /* subtests helpers */
> static bool show_testlist = false;
> static bool list_subtests = false;
>@@ -338,6 +344,8 @@ enum {
> 	OPT_INTERACTIVE_DEBUG,
> 	OPT_SKIP_CRC,
> 	OPT_TRACE_OOPS,
>+	OPT_HOOK,
>+	OPT_HELP_HOOK,
> 	OPT_DEVICE,
> 	OPT_VERSION,
> 	OPT_HELP = 'h'
>@@ -810,6 +818,8 @@ static void common_exit_handler(int sig)
> 		bind_fbcon(true);
> 	}
>
>+	igt_hook_free(igt_hook);
>+
> 	/* When not killed by a signal check that igt_exit() has been properly
> 	 * called. */
> 	assert(sig != 0 || igt_exit_called || igt_is_aborting);
>@@ -907,6 +917,8 @@ static void print_usage(const char *help_str, bool output_on_stderr)
> 		   "  --interactive-debug[=domain]\n"
> 		   "  --skip-crc-compare\n"
> 		   "  --trace-on-oops\n"
>+		   "  --hook [<events>:]<cmd>\n"
>+		   "  --help-hook\n"
> 		   "  --help-description\n"
> 		   "  --describe\n"
> 		   "  --device filters\n"
>@@ -1090,6 +1102,8 @@ static int common_init(int *argc, char **argv,
> 		{"interactive-debug", optional_argument, NULL, OPT_INTERACTIVE_DEBUG},
> 		{"skip-crc-compare",  no_argument,       NULL, OPT_SKIP_CRC},
> 		{"trace-on-oops",     no_argument,       NULL, OPT_TRACE_OOPS},
>+		{"hook",              required_argument, NULL, OPT_HOOK},
>+		{"help-hook",         no_argument,       NULL, OPT_HELP_HOOK},
> 		{"device",            required_argument, NULL, OPT_DEVICE},
> 		{"version",           no_argument,       NULL, OPT_VERSION},
> 		{"help",              no_argument,       NULL, OPT_HELP},
>@@ -1225,6 +1239,24 @@ static int common_init(int *argc, char **argv,
> 		case OPT_TRACE_OOPS:
> 			show_ftrace = true;
> 			break;
>+		case OPT_HOOK:
>+			assert(optarg);
>+			if (igt_hook) {
>+				igt_warn("Overriding previous hook descriptor\n");
>+				igt_hook_free(igt_hook);
>+			}
>+			igt_hook = igt_hook_init(optarg, &ret);

this is a pair with igt_hook_free(). I think it's more common to
call it either _new() or _create() rather than _init().


>+			if (!igt_hook) {
>+				igt_critical("Failed to initialize hook data: %s\n",
>+					     igt_hook_error_str(ret));
>+				ret = ret > 0 ? -2 : -3;
>+				goto out;
>+			}
>+			break;
>+		case OPT_HELP_HOOK:
>+			igt_hook_print_help(stdout, "--hook");
>+			ret = -1;
>+			goto out;
> 		case OPT_DEVICE:
> 			assert(optarg);
> 			/* if set by env IGT_DEVICE we need to free it */
>@@ -1274,9 +1306,24 @@ out:
> 			exit(IGT_EXIT_INVALID);
> 	}
>
>-	if (ret < 0)
>-		/* exit with no error for -h/--help */
>-		exit(ret == -1 ? 0 : IGT_EXIT_INVALID);
>+	if (ret < 0) {
>+		free(igt_hook);
>+		igt_hook = NULL;
>+
>+		switch (ret) {
>+		case -1: /* exit with no error for -h/--help */
>+			exit(0);
>+			break;
>+		case -2:
>+			exit(IGT_EXIT_INVALID);
>+			break;
>+		case -3:
>+			exit(IGT_EXIT_ABORT);
>+			break;
>+		default:
>+			assert(0);
>+		}
>+	}
>
> 	if (!igt_only_list_subtests()) {
> 		bind_fbcon(false);
>@@ -1284,6 +1331,15 @@ out:
> 		print_version();
> 		igt_srandom();
>
>+		if (igt_hook) {
>+			struct igt_hook_evt hook_evt = {
>+				.evt_type = IGT_HOOK_PRE_TEST,
>+				.target_name = command_str,
>+			};
>+
>+			igt_hook_push_evt(igt_hook, &hook_evt);
>+		}
>+
> 		sync();
> 		oom_adjust_for_doom();
> 		ftrace_dump_on_oops(show_ftrace);
>@@ -1487,6 +1543,16 @@ bool __igt_run_subtest(const char *subtest_name, const char *file, const int lin
> 	igt_thread_clear_fail_state();
>
> 	igt_gettime(&subtest_time);
>+
>+	if (igt_hook) {
>+		struct igt_hook_evt hook_evt = {
>+			.evt_type = IGT_HOOK_PRE_SUBTEST,
>+			.target_name = subtest_name,
>+		};
>+
>+		igt_hook_push_evt(igt_hook, &hook_evt);
>+	}
>+
> 	return (in_subtest = subtest_name);
> }
>
>@@ -1517,6 +1583,16 @@ bool __igt_run_dynamic_subtest(const char *dynamic_subtest_name)
> 	_igt_dynamic_tests_executed++;
>
> 	igt_gettime(&dynamic_subtest_time);
>+
>+	if (igt_hook) {
>+		struct igt_hook_evt hook_evt = {
>+			.evt_type = IGT_HOOK_PRE_DYN_SUBTEST,
>+			.target_name = dynamic_subtest_name,
>+		};
>+
>+		igt_hook_push_evt(igt_hook, &hook_evt);
>+	}
>+
> 	return (in_dynamic_subtest = dynamic_subtest_name);
> }
>
>@@ -1602,6 +1678,17 @@ __noreturn static void exit_subtest(const char *result)
> 	struct timespec *thentime = in_dynamic_subtest ? &dynamic_subtest_time : &subtest_time;
> 	jmp_buf *jmptarget = in_dynamic_subtest ? &igt_dynamic_jmpbuf : &igt_subtest_jmpbuf;
>
>+	if (igt_hook) {
>+		struct igt_hook_evt hook_evt = {
>+			.evt_type = (in_dynamic_subtest
>+					? IGT_HOOK_POST_DYN_SUBTEST
>+					: IGT_HOOK_POST_SUBTEST),
>+			.result = result,
>+		};
>+
>+		igt_hook_push_evt(igt_hook, &hook_evt);
>+	}
>+
> 	if (!igt_thread_is_main()) {
> 		igt_thread_fail();
> 		pthread_exit(NULL);
>@@ -2274,6 +2361,7 @@ void __igt_abort(const char *domain, const char *file, const int line,
> void igt_exit(void)
> {
> 	int tmp;
>+	const char *result;
>
> 	if (!test_with_subtests)
> 		igt_thread_assert_no_failures();
>@@ -2318,12 +2406,7 @@ void igt_exit(void)
>
> 	assert(waitpid(-1, &tmp, WNOHANG) == -1 && errno == ECHILD);
>
>-	if (!test_with_subtests) {
>-		struct timespec now;
>-		const char *result;
>-
>-		igt_gettime(&now);
>-
>+	if (!test_with_subtests || igt_hook) {
> 		switch (igt_exitcode) {
> 			case IGT_EXIT_SUCCESS:
> 				result = "SUCCESS";
>@@ -2334,6 +2417,12 @@ void igt_exit(void)
> 			default:
> 				result = "FAIL";
> 		}
>+	}
>+
>+	if (!test_with_subtests) {
>+		struct timespec now;
>+
>+		igt_gettime(&now);
>
> 		if (test_multi_fork_child) /* parent will do the yelling */
> 			_log_line_fprintf(stdout, "dyn_child pid:%d (%.3fs) ends with err=%d\n",
>@@ -2344,6 +2433,15 @@ void igt_exit(void)
> 					  result, igt_time_elapsed(&subtest_time, &now));
> 	}
>
>+	if (igt_hook) {
>+		struct igt_hook_evt hook_evt = {
>+			.evt_type = IGT_HOOK_POST_TEST,
>+			.result = result,
>+		};
>+
>+		igt_hook_push_evt(igt_hook, &hook_evt);
>+	}
>+
> 	exit(igt_exitcode);
> }
>
>diff --git a/lib/igt_hook.c b/lib/igt_hook.c
>new file mode 100644
>index 000000000000..8a39e19e3e5f
>--- /dev/null
>+++ b/lib/igt_hook.c
>@@ -0,0 +1,499 @@
>+/*
>+ * Copyright © 2024 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.


I think this should follow kernel and some of our igt and just use SPDX.

>+ */
>+#include <assert.h>
>+#include <errno.h>
>+#include <limits.h>
>+#include <stdbool.h>
>+#include <stddef.h>
>+#include <stdint.h>
>+#include <stdio.h>
>+#include <stdlib.h>
>+#include <string.h>
>+
>+#include "igt_hook.h"
>+
>+/**
>+ * SECTION:igt_hook
>+ * @short_description: Support for running a hook script on test execution
>+ * @title: Hook support
>+ *
>+ * IGT provides support for running a hook script when executing tests. This
>+ * support is provided to users via CLI option `--hook` available in test
>+ * binaries. Users should use `--help-hook` for detailed usaged description of
>+ * the feature.
>+ *
>+ * The sole user of the exposed API is `igt_core`, which calls @igt_hook_init()
>+ * when initializing a test case, then calls @igt_hook_push_evt() for each event
>+ * that occurs during that test's execution and finally calls @igt_hook_free()
>+ * to clean up at the end.
>+ */
>+
>+#define TEST_NAME_INITIAL_SIZE 16
>+
>+typedef uint16_t evt_mask_t;
>+
>+struct igt_hook {
>+	evt_mask_t evt_mask;
>+	char *cmd;
>+	char *test_name;
>+	size_t test_name_size;
>+	char *subtest_name;
>+	size_t subtest_name_size;
>+	char *dyn_subtest_name;
>+	size_t dyn_subtest_name_size;
>+	char *test_fullname;
>+};
>+
>+enum igt_hook_error {
>+	IGT_HOOK_EVT_EMPTY_NAME = 1,
>+	IGT_HOOK_EVT_NO_MATCH,
>+};
>+
>+static_assert(IGT_HOOK_NUM_EVENTS <= sizeof(evt_mask_t) * CHAR_BIT,
>+	      "Number of event types does not fit event type mask");
>+
>+static const char *igt_hook_evt_type_to_name(enum igt_hook_evt_type evt_type)
>+{
>+	switch (evt_type) {
>+	case IGT_HOOK_PRE_TEST:
>+		return "pre-test";
>+	case IGT_HOOK_PRE_SUBTEST:
>+		return "pre-subtest";
>+	case IGT_HOOK_PRE_DYN_SUBTEST:
>+		return "pre-dyn-subtest";
>+	case IGT_HOOK_POST_DYN_SUBTEST:
>+		return "post-dyn-subtest";
>+	case IGT_HOOK_POST_SUBTEST:
>+		return "post-subtest";
>+	case IGT_HOOK_POST_TEST:
>+		return "post-test";
>+	case IGT_HOOK_NUM_EVENTS:
>+		break;
>+	/* No "default:" case, to force a warning from -Wswitch in case we miss
>+	 * any new event type. */

yeah, I like -Wswitch for this purpose, but I don't think we need a
comment. Instead return something here.

>+	}
>+	return "?";

and return nothing here 

>+}
>+
>+static int igt_hook_parse_hook_str(const char *hook_str, evt_mask_t *evt_mask, const char **cmd)
>+{
>+	const char *s;
>+
>+	if (!strchr(hook_str, ':')) {
>+		*evt_mask = ~0;
>+		*cmd = hook_str;
>+		return 0;
>+	}
>+
>+	s = hook_str;
>+	*evt_mask = 0;
>+
>+	while (1) {
>+		const char *evt_name;
>+		bool has_match;
>+		bool is_star;
>+		enum igt_hook_evt_type evt_type;
>+
>+		evt_name = s;
>+
>+		while (*s != ':' && *s != ',')
>+			s++;
>+
>+		if (evt_name == s)
>+			return IGT_HOOK_EVT_EMPTY_NAME;
>+
>+		has_match = false;
>+		is_star = *evt_name == '*' && evt_name + 1 == s;
>+
>+		for (evt_type = IGT_HOOK_PRE_TEST; evt_type < IGT_HOOK_NUM_EVENTS; evt_type++) {
>+			if (!is_star) {
>+				const char *this_event_name = igt_hook_evt_type_to_name(evt_type);
>+				size_t len = s - evt_name;
>+
>+				if (len != strlen(this_event_name))
>+					continue;
>+
>+				if (strncmp(evt_name, this_event_name, len))
>+					continue;
>+			}
>+
>+			*evt_mask |= 1 << evt_type;

pre-test,pre-test,pre-test:echo foo

^ shouldn't this cause a warning?

but see below for alternative interface

>+			has_match = true;
>+
>+			if (!is_star)
>+				break;
>+		}
>+
>+		if (!has_match)
>+			return IGT_HOOK_EVT_NO_MATCH;
>+
>+		if (*s++ == ':')
>+			break;
>+	}
>+
>+	*cmd = s;
>+
>+	return 0;
>+}
>+
>+static size_t igt_hook_calc_test_fullname_size(struct igt_hook *igt_hook) {
>+	/* The maximum size of test_fullname will be the maximum length of
>+	 * "igt@<test_name>@<subtest_name>@<dyn_subtest_name>" plus 1 for the
>+	 * null byte. */
>+	return (igt_hook->test_name_size +
>+		igt_hook->subtest_name_size +
>+		igt_hook->dyn_subtest_name_size) + 4;
>+}
>+
>+static void igt_hook_update_test_fullname(struct igt_hook *igt_hook)
>+{
>+	int i;
>+	char *s;
>+	const char *values[3] = {
>+		igt_hook->test_name,
>+		igt_hook->subtest_name,
>+		igt_hook->dyn_subtest_name,
>+	};
>+
>+	if (igt_hook->test_name[0] == '\0') {
>+		igt_hook->test_fullname[0] = '\0';
>+		return;
>+	}
>+
>+	s = stpcpy(igt_hook->test_fullname, "igt");
>+	for (i = 0; i < 3 && values[i][0] != '\0'; i++) {

			^ ARRAY_SIZE()

>+		*s++ = '@';
>+		s = stpcpy(s, values[i]);
>+	}
>+}
>+
>+/**
>+ * igt_hook_init:
>+ * @hook_str: Hook descriptor string.
>+ * @error: Pointer to error number.
>+ *
>+ * Allocate and initialize an #igt_hook structure.
>+ *
>+ * This function parses the hook descriptor @hook_str and initializes the struct
>+ * to be returned.
>+ *
>+ * The hook descriptor comes from the argument to `--hook` of the test
>+ * executable being run.
>+ *
>+ * If not #NULL, @error is used to store a non-zero error number if an error
>+ * happens. A human-readable string for that error number can be obtained with
>+ * @igt_hook_error_str().
>+ *
>+ * Returns: The pointer to the #igt_hook structure on success or #NULL on error.
>+ */
>+struct igt_hook *igt_hook_init(const char *hook_str, int *error)
>+{
>+	int err;
>+	evt_mask_t evt_mask;
>+	const char *cmd;
>+	struct igt_hook *igt_hook = NULL;
>+
>+
>+	err = igt_hook_parse_hook_str(hook_str, &evt_mask, &cmd);
>+	if (err)
>+		goto out;
>+
>+	igt_hook = calloc(1, sizeof(*igt_hook));
>+	igt_hook->evt_mask = evt_mask;
>+
>+	igt_hook->cmd = strdup(cmd);
>+	if (!igt_hook->cmd) {
>+		err = -errno;
>+		goto out;
>+	}
>+
>+	igt_hook->test_name = malloc(TEST_NAME_INITIAL_SIZE);
>+	igt_hook->test_name_size = TEST_NAME_INITIAL_SIZE;
>+	igt_hook->subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
>+	igt_hook->subtest_name_size = TEST_NAME_INITIAL_SIZE;
>+	igt_hook->dyn_subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
>+	igt_hook->dyn_subtest_name_size = TEST_NAME_INITIAL_SIZE;

up to you, but in this kind of things I tend to prefer the strings in
the outer struct so we have fewer separate malloc()'s and thus fewer
chances of leaking them

just calculate the size ahead and then update igt_hook->test_name,
igt_hook->subtest_name and igt_hook->dyn_subtest_name to point to the right part.

>+	igt_hook->test_fullname = malloc(igt_hook_calc_test_fullname_size(igt_hook));
>+
>+	igt_hook->test_name[0] = '\0';
>+	igt_hook->subtest_name[0] = '\0';
>+	igt_hook->dyn_subtest_name[0] = '\0';
>+	igt_hook->test_fullname[0] = '\0';

with the benefit that then these are covered by the calloc.

up to you.

>+
>+out:
>+	if (err) {
>+		if (error)
>+			*error = err;
>+
>+		igt_hook_free(igt_hook);
>+
>+		return NULL;
>+	}
>+
>+	return igt_hook;
>+}
>+
>+/**
>+ * igt_hook_free:
>+ * @igt_hook: The igt_hook struct.
>+ *
>+ * De-initialize an igt_hook struct returned by @igt_hook_init().
>+ *
>+ * This is a no-op if @igt_hook is #NULL.
>+ */
>+void igt_hook_free(struct igt_hook *igt_hook)
>+{
>+	if (!igt_hook)
>+		return;
>+
>+	free(igt_hook->cmd);
>+	free(igt_hook->test_name);
>+	free(igt_hook->subtest_name);
>+	free(igt_hook->dyn_subtest_name);
>+	free(igt_hook);
>+}
>+
>+static void igt_hook_update_test_name_pre_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>+{
>+	char **name_ptr;
>+	size_t *size_ptr;
>+	size_t len;
>+
>+	switch (evt->evt_type) {
>+	case IGT_HOOK_PRE_TEST:
>+		name_ptr = &igt_hook->test_name;
>+		size_ptr = &igt_hook->test_name_size;
>+		break;
>+	case IGT_HOOK_PRE_SUBTEST:
>+		name_ptr = &igt_hook->subtest_name;
>+		size_ptr = &igt_hook->subtest_name_size;
>+		break;
>+	case IGT_HOOK_PRE_DYN_SUBTEST:
>+		name_ptr = &igt_hook->dyn_subtest_name;
>+		size_ptr = &igt_hook->dyn_subtest_name_size;
>+		break;
>+	default:
>+		return;
>+	}
>+
>+	len = strlen(evt->target_name);
>+	if (len + 1 > *size_ptr) {
>+		size_t fullname_size;
>+
>+		*size_ptr *= 2;
>+		*name_ptr = realloc(*name_ptr, *size_ptr);
>+
>+		fullname_size = igt_hook_calc_test_fullname_size(igt_hook);
>+		igt_hook->test_fullname = realloc(igt_hook->test_fullname, fullname_size);
>+	}
>+
>+	strcpy(*name_ptr, evt->target_name);
>+	igt_hook_update_test_fullname(igt_hook);
>+}
>+
>+static void igt_hook_update_test_name_post_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>+{
>+	switch (evt->evt_type) {
>+	case IGT_HOOK_POST_TEST:
>+		igt_hook->test_name[0] = '\0';
>+		break;
>+	case IGT_HOOK_POST_SUBTEST:
>+		igt_hook->subtest_name[0] = '\0';
>+		break;
>+	case IGT_HOOK_POST_DYN_SUBTEST:
>+		igt_hook->dyn_subtest_name[0] = '\0';
>+		break;
>+	default:
>+		return;
>+	}
>+
>+	igt_hook_update_test_fullname(igt_hook);
>+}
>+
>+static void igt_hook_update_env_vars(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>+{
>+	setenv("IGT_HOOK_EVENT", igt_hook_evt_type_to_name(evt->evt_type), 1);
>+	setenv("IGT_HOOK_TEST_FULLNAME", igt_hook->test_fullname, 1);
>+	setenv("IGT_HOOK_TEST", igt_hook->test_name, 1);
>+	setenv("IGT_HOOK_SUBTEST", igt_hook->subtest_name, 1);
>+	setenv("IGT_HOOK_DYN_SUBTEST", igt_hook->dyn_subtest_name, 1);
>+	setenv("IGT_HOOK_RESULT", evt->result ?: "", 1);
>+}
>+
>+/**
>+ * igt_hook_push_evt:
>+ * @igt_hook: The igt_hook structure.
>+ * @evt: The event to be pushed.
>+ *
>+ * Push a new igt_hook event.
>+ *
>+ * This function must be used to register a new igt_hook event. Calling it will
>+ * cause execution of the hook script if the event type matches the filters
>+ * provided during initialization of @igt_hook.
>+ */
>+void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>+{
>+	typeof(igt_hook->evt_mask) evt_bit = (1 << evt->evt_type);
>+
>+	igt_hook_update_test_name_pre_call(igt_hook, evt);
>+
>+	if ((evt_bit & igt_hook->evt_mask)) {
>+		igt_hook_update_env_vars(igt_hook, evt);
>+		system(igt_hook->cmd);

this polutes the env for the current process just so the child process
has those env vars. We could either duplicate the environ on startup and
use execle(), or to be simpler, just make sure to zap our env just after
running the command.  This avoids undesirable behavior because we didn't
clean up the environ.


>+	}
>+
>+	igt_hook_update_test_name_post_call(igt_hook, evt);
>+}
>+
>+/**
>+ * igt_hook_error_str:
>+ * @error: Non-zero error number.
>+ *
>+ * Return a human-readable string containing a description of an error number
>+ * generated by one of the `igt_hook_*` functions.
>+ *
>+ * The string will be the result of strerror() for errors from the C standard
>+ * library or a custom description specific to igt_hook.
>+ */
>+const char *igt_hook_error_str(int error)
>+{
>+	if (!error)
>+		return "No error";
>+
>+	if (error > 0) {
>+		enum igt_hook_error hook_error = error;
>+
>+		switch (hook_error) {
>+		case IGT_HOOK_EVT_EMPTY_NAME:
>+			return "Empty name in event descriptor";
>+		case IGT_HOOK_EVT_NO_MATCH:
>+			return "Event name in event descriptor does not match any event type";
>+		default:
>+			return "Unknown error";
>+		}
>+	} else {
>+		return strerror(-error);
>+	}
>+}
>+
>+/**
>+ * igt_hook_print_help:
>+ * @f: File pointer where to write the output.
>+ * @option_name: Name of the CLI option that accepts the hook descriptor.
>+ *
>+ * Print a detailed user help text on hook usage.
>+ */
>+void igt_hook_print_help(FILE *f, const char *option_name)
>+{
>+	fprintf(f, "\
>+The option %1$s receives as argument a \"hook descriptor\" and allows the\n\
>+execution of a shell command at different points during execution of tests. Each\n\
>+such a point is called a \"hook event\".\n\
>+\n\
>+Examples:\n\
>+\n\
>+  # Prints hook-specic env vars for every event.\n\
>+  %1$s 'printenv | grep ^IGT_HOOK_'\n\
>+\n\
>+  # Equivalent to the above. Useful if command contains ':'.\n\
>+  %1$s '*:printenv | grep ^IGT_HOOK_'\n\
>+\n\
>+  # Adds a line to out.txt containing the result of each test case.\n\
>+  %1$s 'post-test:echo $IGT_HOOK_TEST_FULLNAME $IGT_HOOK_RESULT >> out.txt'\n\
>+\n\
>+The accepted format for a hook descriptor is `[<events>:]<cmd>`, where:\n\
>+\n\
>+  - <events> is a comma-separated list of event descriptors, which defines the\n\
>+    set of events be tracked. If omitted, all events are tracked.\n\
>+\n\


I think the interface git-rebase is more natural for this kind of
thing. You have 2 concepts: event and hook with n:1 relationship
because of the interface chosen.

Why not simply use 1:1 and then allow the program to have multiple
hooks?


Just in git-rebase we do  `git rebase -x "echo foo" -x "echo bar"` and
the commands are appended, we could have:

igt_runner -x 'pre-test:echo foo' -x 'post-test:echo bar'. Then you only
warn when you user incorrectly used the same hook type.  I think it
would be more natural to eventually embed the support on a test list
(with the exception we'd not warn anymore), with something like:

hook@pre-test@echo foo
hook@post-test@echo bar
igt@...
igt@...
igt@...
hook@pre-test@ ....
igt@...




>+  - <cmd> is a shell command to be executed on the occurrence each tracked\n\
>+    event. If the command contains ':', then passing <events> is required,\n\
>+    otherwise part of the command would be treated as an event descriptor.\n\
>+\n\
>+", option_name);
>+
>+	fprintf(f, "\
>+An \"event descriptor\" is either the name of an event or the string '*'. The\n\
>+latter matches all event names. The list of possible event names is provided\n\
>+below:\n\
>+\n\
>+");
>+
>+	for (enum igt_hook_evt_type et = 0; et < IGT_HOOK_NUM_EVENTS; et++) {
>+		const char *desc;
>+
>+		switch (et) {
>+		case IGT_HOOK_PRE_TEST:
>+			desc = "Occurs before a test case starts.";
>+			break;
>+		case IGT_HOOK_PRE_SUBTEST:
>+			desc = "Occurs before the execution of a subtest.";
>+			break;
>+		case IGT_HOOK_PRE_DYN_SUBTEST:
>+			desc = "Occurs before the execution of a dynamic subtest.";
>+			break;
>+		case IGT_HOOK_POST_DYN_SUBTEST:
>+			desc = "Occurs after the execution of a dynamic subtest.";
>+			break;
>+		case IGT_HOOK_POST_SUBTEST:
>+			desc = "Occurs after the execution of a subtest.";
>+			break;
>+		case IGT_HOOK_POST_TEST:
>+			desc = "Occurs after a test case has finished.";
>+			break;
>+		default:
>+			desc = "MISSING DESCRIPTION";
>+		}
>+
>+		fprintf(f, "  %s\n  %s\n\n", igt_hook_evt_type_to_name(et), desc);
>+	}
>+
>+	fprintf(f, "\
>+For each event matched by <events>, <cmd> is executed as a shell command. The\n\
>+exit status of the command is ignored. The following environment variables are\n\
>+available to the command:\n\
>+\n\
>+  IGT_HOOK_EVENT\n\
>+  Name of the current event.\n\
>+\n\
>+  IGT_HOOK_TEST_FULLNAME\n\
>+  Full name of the test in the format `igt@<test>[@<subtest>[@<dyn_subtest>]]`.\n\
>+\n\
>+  IGT_HOOK_TEST		   \n\
>+  Name of the current test.\n\
>+\n\
>+  IGT_HOOK_SUBTEST\n\
>+  Name of the current subtest. Will be the empty string if not running a\n\
>+  subtest.\n\
>+\n\
>+  IGT_HOOK_DYN_SUBTEST\n\
>+  Name of the current dynamic subtest. Will be the empty string if not running a\n\
>+  dynamic subtest.\n\
>+\n\
>+  IGT_HOOK_RESULT\n\
>+  String representing the result of the test/subtest/dynamic subtest. Possible\n\
>+  values are: SUCCESS, SKIP or FAIL. This is only applicable on \"post-*\"\n\
>+  events and will be the empty string for other types of events.\n\
>+\n\
>+");
>+}
>diff --git a/lib/igt_hook.h b/lib/igt_hook.h
>new file mode 100644
>index 000000000000..6fef7254a317
>--- /dev/null
>+++ b/lib/igt_hook.h
>@@ -0,0 +1,86 @@
>+/*
>+ * Copyright © 2024 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.
>+ */

ditto on SPDX


Lucas De Marchi

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: ✗ GitLab.Pipeline: warning for Add support for hook script
  2024-05-20 19:44     ` Lucas De Marchi
@ 2024-06-19 19:07       ` Gustavo Sousa
  0 siblings, 0 replies; 22+ messages in thread
From: Gustavo Sousa @ 2024-06-19 19:07 UTC (permalink / raw)
  To: Lucas De Marchi; +Cc: Patchwork, igt-dev

Quoting Lucas De Marchi (2024-05-20 16:44:15-03:00)
>On Thu, May 09, 2024 at 02:04:15PM GMT, Gustavo Sousa wrote:
>>Quoting Patchwork (2024-05-09 13:13:43-03:00)
>>>== Series Details ==
>>>
>>>Series: Add support for hook script
>>>URL   : https://patchwork.freedesktop.org/series/133391/
>>>State : warning
>>>
>>>== Summary ==
>>>
>>>Pipeline status: FAILED.
>>>
>>>see https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/pipelines/1173738 for the overview.
>>>
>>>test:ninja-test-minimal has failed (https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606):
>>>  26/30 lib i915_perf_data_alignment            OK       0.42 s
>>>  27/30 lib bad_subtest_type                    EXPECTEDFAIL 0.37 s
>>>  28/30 lib igt_no_subtest                      EXPECTEDFAIL 0.42 s
>>>  29/30 lib igt_simple_test_subtests            EXPECTEDFAIL 0.27 s
>>>  30/30 lib igt_timeout                         EXPECTEDFAIL 1.32 s
>>>
>>>  Ok:                   25
>>>  Expected Fail:         4
>>>  Fail:                  1
>>>  Unexpected Pass:       0
>>>  Skipped:               0
>>>  Timeout:               0
>>>
>>>  Full log written to /builds/gfx-ci/igt-ci-tags/build/meson-logs/testlog.txt
>>>  section_end:1715270916:step_script
>>>  section_start:1715270916:cleanup_file_variables
>>>  Cleaning up project directory and file based variables
>>>  section_end:1715270917:cleanup_file_variables
>>>  ERROR: Job failed: exit code 1
>>>
>>>== Logs ==
>>>
>>>For more details see: https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/pipelines/1173738
>>
>>Looking at the output on the page linked above, I see that the test I
>>introduced (igt_hook_integration) is failing but I passes locally for
>>me.
>>
>>Is there any way I get more details on the failure to investigate? The
>>job output says:
>>
>>    "Full log written to
>>    /builds/gfx-ci/igt-ci-tags/build/meson-logs/testlog.txt"
>>
>>, but I'm not sure if that is available somewhere for me to check.
>
>GitLab.Pipeline is the one executed in fdo infra, not in the intel
>infra. You use the container it's using to repro it locally:
>
>https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540606
>
>        Using docker image sha256:08904a47f4efcc161569a9b7f88c458fc6e3e85a2225132246af9cf5cc6c4e5b
>        for registry.freedesktop.org/gfx-ci/igt-ci-tags/build-debian-minimal:commit-c21f9e2242955d29c7bb2fd9703230bd9799eb8c
>        with digest registry.freedesktop.org/gfx-ci/igt-ci-tags/build-debian-minimal@sha256:8b51a86fd81e64c501c9521c37fc8ad6f2550976931c510eb0ff4f6a328d477a ...
>
>Note that this is the "test" phase. You should also reproduce the build
>phase. From the logs:
>
>        Downloading artifacts for build:tests-debian-minimal (58540184)...
>
>which points to: https://gitlab.freedesktop.org/gfx-ci/igt-ci-tags/-/jobs/58540184
>
>The builder and test container are the same, so you can use to fetch:
>
>        $ c=registry.freedesktop.org/gfx-ci/igt-ci-tags/build-debian-minimal:commit-c21f9e2242955d29c7bb2fd9703230bd9799eb8c
>        $ podman pull $c
>
>and to run (first line I conveniently alias to "pod" locally):
>
>        $ podman run --rm -v $(pwd):/src --net host -w /src -it $c /bin/bash
>
>        /src # meson -Dtests=disabled -Dlibdrm_drivers="" build
>        /src # ninja -C build -j${FDO_CI_CONCURRENT:-4} || ninja -C build -j 1
>        /src # meson test -C build --num-processes ${FDO_CI_CONCURRENT:-4}
>        ninja: Entering directory `/src/build'
>        [2/2] Generating intel-ci-tests with a custom command.
>        ...
>        13/30 lib igt_hook_integration                FAIL     0.12 s (killed by signal 11 SIGSEGV)
>        ...
>        Full log written to /src/build/meson-logs/testlog.txt
>
>
>I see a bunch of lines like
>
>        Unexpected envline from hook:
>        Unexpected envline from hook:
>        Unexpected envline from hook:
>        Unexpected envline from hook:
>        Unexpected envline from hook:
>        Unexpected envline from hook:
>        Unexpected envline from hook:
>        Unexpected envline from hook:
>        Unexpected envline from hook:
>
>until it's eventually killed and line is truncated:
>
>        Unexpected envli
>
>
>Given the fact it passed in some distros, I think that debian minimal
>installation doesn't like something in
>
>                 "%1$s:printenv -0 | grep -z ^IGT_HOOK >&%2$d; echo -en ---\\\\x00 >&%2$d",

Thanks, Lucas, for the help in reproducing it!

I was able to download the container image and reproduce the issue
locally. Looks like the "echo" built-in in that image's default shell
does not support "-en", which ends up spilled directly to the output.

Replacing that echo command with "printf -- ---\\\\00" fixes the issue.
The next version of this series will contain that fix. From my tests,
looks like that printf form is compatible with both sh and bash.

Thanks a lot!

--
Gustavo Sousa

>
>
>Lucas De Marchi
>
>>
>>--
>>Gustavo

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH i-g-t 1/3] igt_hook: Add feature
  2024-05-21 19:40   ` Lucas De Marchi
@ 2024-06-19 21:12     ` Gustavo Sousa
  0 siblings, 0 replies; 22+ messages in thread
From: Gustavo Sousa @ 2024-06-19 21:12 UTC (permalink / raw)
  To: Lucas De Marchi; +Cc: igt-dev

Quoting Lucas De Marchi (2024-05-21 16:40:56-03:00)
>On Thu, May 09, 2024 at 12:24:29PM GMT, Gustavo Sousa wrote:
>>For development purposes, sometimes it is useful to have a way of
>>running custom scripts at certain points of test executions. A
>>real-world example I bumped into recently is to collect information from
>>sysfs before and after running each entry of a testlist.
>>
>>While it is possible for the user to handcraft a script that calls each
>>test with the correct actions before and after execution, we can provide
>>a better experience by adding built-in support for running hooks during
>>test execution.
>>
>>That would be even better when adding the same kind of support for
>>igt_runner (which is done in an upcoming change), since the user can
>>also nicely resume with igt_resume with the hook already setup in case a
>>crash happens during execution of the test list.
>>
>>As such provide implement support for hooks, integrate it into
>>igt_core and expose the functionality via --hook CLI option on test
>>executables.
>>
>>Signed-off-by: Gustavo Sousa <gustavo.sousa@intel.com>
>>---
>> .../igt-gpu-tools/igt-gpu-tools-docs.xml      |   1 +
>> lib/igt_core.c                                | 116 +++-
>> lib/igt_hook.c                                | 499 ++++++++++++++++++
>> lib/igt_hook.h                                |  86 +++
>> lib/meson.build                               |   1 +
>> lib/tests/igt_hook.c                          | 187 +++++++
>> lib/tests/igt_hook_integration.c              | 297 +++++++++++
>> lib/tests/meson.build                         |   2 +
>> 8 files changed, 1180 insertions(+), 9 deletions(-)
>> create mode 100644 lib/igt_hook.c
>> create mode 100644 lib/igt_hook.h
>> create mode 100644 lib/tests/igt_hook.c
>> create mode 100644 lib/tests/igt_hook_integration.c
>>
>>diff --git a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>>index 9085eb924e85..11458c68124b 100644
>>--- a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>>+++ b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
>>@@ -32,6 +32,7 @@
>>     <xi:include href="xml/igt_fb.xml"/>
>>     <xi:include href="xml/igt_frame.xml"/>
>>     <xi:include href="xml/igt_gt.xml"/>
>>+    <xi:include href="xml/igt_hook.xml"/>
>>     <xi:include href="xml/igt_io.xml"/>
>>     <xi:include href="xml/igt_kmod.xml"/>
>>     <xi:include href="xml/igt_kms.xml"/>
>>diff --git a/lib/igt_core.c b/lib/igt_core.c
>>index 3ff3e0392316..291d891cf884 100644
>>--- a/lib/igt_core.c
>>+++ b/lib/igt_core.c
>>@@ -70,6 +70,7 @@
>>
>> #include "igt_core.h"
>> #include "igt_aux.h"
>>+#include "igt_hook.h"
>> #include "igt_sysfs.h"
>> #include "igt_sysrq.h"
>> #include "igt_rc.h"
>>@@ -241,6 +242,9 @@
>>  * - '*,!basic*' match any subtest not starting basic
>>  * - 'basic*,!basic-render*' match any subtest starting basic but not starting basic-render
>>  *
>>+ * It is possible to run a shell script at certain points of test execution with
>>+ * "--hook". See the usage description with "--help-hook" for details.
>>+ *
>>  * # Configuration
>>  *
>>  * Some of IGT's behavior can be configured through a configuration file.
>>@@ -273,6 +277,8 @@ static unsigned int exit_handler_count;
>> const char *igt_interactive_debug;
>> bool igt_skip_crc_compare;
>>
>>+static struct igt_hook *igt_hook = NULL;
>>+
>> /* subtests helpers */
>> static bool show_testlist = false;
>> static bool list_subtests = false;
>>@@ -338,6 +344,8 @@ enum {
>>         OPT_INTERACTIVE_DEBUG,
>>         OPT_SKIP_CRC,
>>         OPT_TRACE_OOPS,
>>+        OPT_HOOK,
>>+        OPT_HELP_HOOK,
>>         OPT_DEVICE,
>>         OPT_VERSION,
>>         OPT_HELP = 'h'
>>@@ -810,6 +818,8 @@ static void common_exit_handler(int sig)
>>                 bind_fbcon(true);
>>         }
>>
>>+        igt_hook_free(igt_hook);
>>+
>>         /* When not killed by a signal check that igt_exit() has been properly
>>          * called. */
>>         assert(sig != 0 || igt_exit_called || igt_is_aborting);
>>@@ -907,6 +917,8 @@ static void print_usage(const char *help_str, bool output_on_stderr)
>>                    "  --interactive-debug[=domain]\n"
>>                    "  --skip-crc-compare\n"
>>                    "  --trace-on-oops\n"
>>+                   "  --hook [<events>:]<cmd>\n"
>>+                   "  --help-hook\n"
>>                    "  --help-description\n"
>>                    "  --describe\n"
>>                    "  --device filters\n"
>>@@ -1090,6 +1102,8 @@ static int common_init(int *argc, char **argv,
>>                 {"interactive-debug", optional_argument, NULL, OPT_INTERACTIVE_DEBUG},
>>                 {"skip-crc-compare",  no_argument,       NULL, OPT_SKIP_CRC},
>>                 {"trace-on-oops",     no_argument,       NULL, OPT_TRACE_OOPS},
>>+                {"hook",              required_argument, NULL, OPT_HOOK},
>>+                {"help-hook",         no_argument,       NULL, OPT_HELP_HOOK},
>>                 {"device",            required_argument, NULL, OPT_DEVICE},
>>                 {"version",           no_argument,       NULL, OPT_VERSION},
>>                 {"help",              no_argument,       NULL, OPT_HELP},
>>@@ -1225,6 +1239,24 @@ static int common_init(int *argc, char **argv,
>>                 case OPT_TRACE_OOPS:
>>                         show_ftrace = true;
>>                         break;
>>+                case OPT_HOOK:
>>+                        assert(optarg);
>>+                        if (igt_hook) {
>>+                                igt_warn("Overriding previous hook descriptor\n");
>>+                                igt_hook_free(igt_hook);
>>+                        }
>>+                        igt_hook = igt_hook_init(optarg, &ret);
>
>this is a pair with igt_hook_free(). I think it's more common to
>call it either _new() or _create() rather than _init().

Okay. Decided to go with igt_hook_create().

>
>
>>+                        if (!igt_hook) {
>>+                                igt_critical("Failed to initialize hook data: %s\n",
>>+                                             igt_hook_error_str(ret));
>>+                                ret = ret > 0 ? -2 : -3;
>>+                                goto out;
>>+                        }
>>+                        break;
>>+                case OPT_HELP_HOOK:
>>+                        igt_hook_print_help(stdout, "--hook");
>>+                        ret = -1;
>>+                        goto out;
>>                 case OPT_DEVICE:
>>                         assert(optarg);
>>                         /* if set by env IGT_DEVICE we need to free it */
>>@@ -1274,9 +1306,24 @@ out:
>>                         exit(IGT_EXIT_INVALID);
>>         }
>>
>>-        if (ret < 0)
>>-                /* exit with no error for -h/--help */
>>-                exit(ret == -1 ? 0 : IGT_EXIT_INVALID);
>>+        if (ret < 0) {
>>+                free(igt_hook);
>>+                igt_hook = NULL;
>>+
>>+                switch (ret) {
>>+                case -1: /* exit with no error for -h/--help */
>>+                        exit(0);
>>+                        break;
>>+                case -2:
>>+                        exit(IGT_EXIT_INVALID);
>>+                        break;
>>+                case -3:
>>+                        exit(IGT_EXIT_ABORT);
>>+                        break;
>>+                default:
>>+                        assert(0);
>>+                }
>>+        }
>>
>>         if (!igt_only_list_subtests()) {
>>                 bind_fbcon(false);
>>@@ -1284,6 +1331,15 @@ out:
>>                 print_version();
>>                 igt_srandom();
>>
>>+                if (igt_hook) {
>>+                        struct igt_hook_evt hook_evt = {
>>+                                .evt_type = IGT_HOOK_PRE_TEST,
>>+                                .target_name = command_str,
>>+                        };
>>+
>>+                        igt_hook_push_evt(igt_hook, &hook_evt);
>>+                }
>>+
>>                 sync();
>>                 oom_adjust_for_doom();
>>                 ftrace_dump_on_oops(show_ftrace);
>>@@ -1487,6 +1543,16 @@ bool __igt_run_subtest(const char *subtest_name, const char *file, const int lin
>>         igt_thread_clear_fail_state();
>>
>>         igt_gettime(&subtest_time);
>>+
>>+        if (igt_hook) {
>>+                struct igt_hook_evt hook_evt = {
>>+                        .evt_type = IGT_HOOK_PRE_SUBTEST,
>>+                        .target_name = subtest_name,
>>+                };
>>+
>>+                igt_hook_push_evt(igt_hook, &hook_evt);
>>+        }
>>+
>>         return (in_subtest = subtest_name);
>> }
>>
>>@@ -1517,6 +1583,16 @@ bool __igt_run_dynamic_subtest(const char *dynamic_subtest_name)
>>         _igt_dynamic_tests_executed++;
>>
>>         igt_gettime(&dynamic_subtest_time);
>>+
>>+        if (igt_hook) {
>>+                struct igt_hook_evt hook_evt = {
>>+                        .evt_type = IGT_HOOK_PRE_DYN_SUBTEST,
>>+                        .target_name = dynamic_subtest_name,
>>+                };
>>+
>>+                igt_hook_push_evt(igt_hook, &hook_evt);
>>+        }
>>+
>>         return (in_dynamic_subtest = dynamic_subtest_name);
>> }
>>
>>@@ -1602,6 +1678,17 @@ __noreturn static void exit_subtest(const char *result)
>>         struct timespec *thentime = in_dynamic_subtest ? &dynamic_subtest_time : &subtest_time;
>>         jmp_buf *jmptarget = in_dynamic_subtest ? &igt_dynamic_jmpbuf : &igt_subtest_jmpbuf;
>>
>>+        if (igt_hook) {
>>+                struct igt_hook_evt hook_evt = {
>>+                        .evt_type = (in_dynamic_subtest
>>+                                        ? IGT_HOOK_POST_DYN_SUBTEST
>>+                                        : IGT_HOOK_POST_SUBTEST),
>>+                        .result = result,
>>+                };
>>+
>>+                igt_hook_push_evt(igt_hook, &hook_evt);
>>+        }
>>+
>>         if (!igt_thread_is_main()) {
>>                 igt_thread_fail();
>>                 pthread_exit(NULL);
>>@@ -2274,6 +2361,7 @@ void __igt_abort(const char *domain, const char *file, const int line,
>> void igt_exit(void)
>> {
>>         int tmp;
>>+        const char *result;
>>
>>         if (!test_with_subtests)
>>                 igt_thread_assert_no_failures();
>>@@ -2318,12 +2406,7 @@ void igt_exit(void)
>>
>>         assert(waitpid(-1, &tmp, WNOHANG) == -1 && errno == ECHILD);
>>
>>-        if (!test_with_subtests) {
>>-                struct timespec now;
>>-                const char *result;
>>-
>>-                igt_gettime(&now);
>>-
>>+        if (!test_with_subtests || igt_hook) {
>>                 switch (igt_exitcode) {
>>                         case IGT_EXIT_SUCCESS:
>>                                 result = "SUCCESS";
>>@@ -2334,6 +2417,12 @@ void igt_exit(void)
>>                         default:
>>                                 result = "FAIL";
>>                 }
>>+        }
>>+
>>+        if (!test_with_subtests) {
>>+                struct timespec now;
>>+
>>+                igt_gettime(&now);
>>
>>                 if (test_multi_fork_child) /* parent will do the yelling */
>>                         _log_line_fprintf(stdout, "dyn_child pid:%d (%.3fs) ends with err=%d\n",
>>@@ -2344,6 +2433,15 @@ void igt_exit(void)
>>                                           result, igt_time_elapsed(&subtest_time, &now));
>>         }
>>
>>+        if (igt_hook) {
>>+                struct igt_hook_evt hook_evt = {
>>+                        .evt_type = IGT_HOOK_POST_TEST,
>>+                        .result = result,
>>+                };
>>+
>>+                igt_hook_push_evt(igt_hook, &hook_evt);
>>+        }
>>+
>>         exit(igt_exitcode);
>> }
>>
>>diff --git a/lib/igt_hook.c b/lib/igt_hook.c
>>new file mode 100644
>>index 000000000000..8a39e19e3e5f
>>--- /dev/null
>>+++ b/lib/igt_hook.c
>>@@ -0,0 +1,499 @@
>>+/*
>>+ * Copyright © 2024 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.
>
>
>I think this should follow kernel and some of our igt and just use SPDX.

Done. Thanks.

>
>>+ */
>>+#include <assert.h>
>>+#include <errno.h>
>>+#include <limits.h>
>>+#include <stdbool.h>
>>+#include <stddef.h>
>>+#include <stdint.h>
>>+#include <stdio.h>
>>+#include <stdlib.h>
>>+#include <string.h>
>>+
>>+#include "igt_hook.h"
>>+
>>+/**
>>+ * SECTION:igt_hook
>>+ * @short_description: Support for running a hook script on test execution
>>+ * @title: Hook support
>>+ *
>>+ * IGT provides support for running a hook script when executing tests. This
>>+ * support is provided to users via CLI option `--hook` available in test
>>+ * binaries. Users should use `--help-hook` for detailed usaged description of
>>+ * the feature.
>>+ *
>>+ * The sole user of the exposed API is `igt_core`, which calls @igt_hook_init()
>>+ * when initializing a test case, then calls @igt_hook_push_evt() for each event
>>+ * that occurs during that test's execution and finally calls @igt_hook_free()
>>+ * to clean up at the end.
>>+ */
>>+
>>+#define TEST_NAME_INITIAL_SIZE 16
>>+
>>+typedef uint16_t evt_mask_t;
>>+
>>+struct igt_hook {
>>+        evt_mask_t evt_mask;
>>+        char *cmd;
>>+        char *test_name;
>>+        size_t test_name_size;
>>+        char *subtest_name;
>>+        size_t subtest_name_size;
>>+        char *dyn_subtest_name;
>>+        size_t dyn_subtest_name_size;
>>+        char *test_fullname;
>>+};
>>+
>>+enum igt_hook_error {
>>+        IGT_HOOK_EVT_EMPTY_NAME = 1,
>>+        IGT_HOOK_EVT_NO_MATCH,
>>+};
>>+
>>+static_assert(IGT_HOOK_NUM_EVENTS <= sizeof(evt_mask_t) * CHAR_BIT,
>>+              "Number of event types does not fit event type mask");
>>+
>>+static const char *igt_hook_evt_type_to_name(enum igt_hook_evt_type evt_type)
>>+{
>>+        switch (evt_type) {
>>+        case IGT_HOOK_PRE_TEST:
>>+                return "pre-test";
>>+        case IGT_HOOK_PRE_SUBTEST:
>>+                return "pre-subtest";
>>+        case IGT_HOOK_PRE_DYN_SUBTEST:
>>+                return "pre-dyn-subtest";
>>+        case IGT_HOOK_POST_DYN_SUBTEST:
>>+                return "post-dyn-subtest";
>>+        case IGT_HOOK_POST_SUBTEST:
>>+                return "post-subtest";
>>+        case IGT_HOOK_POST_TEST:
>>+                return "post-test";
>>+        case IGT_HOOK_NUM_EVENTS:
>>+                break;
>>+        /* No "default:" case, to force a warning from -Wswitch in case we miss
>>+         * any new event type. */
>
>yeah, I like -Wswitch for this purpose, but I don't think we need a
>comment. Instead return something here.
>
>>+        }
>>+        return "?";
>
>and return nothing here 

Not sure I understand your suggestion. Since we do not have a default
case, we would need this return at the end of the function, no? Without
this return the build fails because of -Werror=return-type:

   error: control reaches end of non-void function [-Werror=return-type]

Also note that the -Wswitch is not being treated as error with the
current build config.

>
>>+}
>>+
>>+static int igt_hook_parse_hook_str(const char *hook_str, evt_mask_t *evt_mask, const char **cmd)
>>+{
>>+        const char *s;
>>+
>>+        if (!strchr(hook_str, ':')) {
>>+                *evt_mask = ~0;
>>+                *cmd = hook_str;
>>+                return 0;
>>+        }
>>+
>>+        s = hook_str;
>>+        *evt_mask = 0;
>>+
>>+        while (1) {
>>+                const char *evt_name;
>>+                bool has_match;
>>+                bool is_star;
>>+                enum igt_hook_evt_type evt_type;
>>+
>>+                evt_name = s;
>>+
>>+                while (*s != ':' && *s != ',')
>>+                        s++;
>>+
>>+                if (evt_name == s)
>>+                        return IGT_HOOK_EVT_EMPTY_NAME;
>>+
>>+                has_match = false;
>>+                is_star = *evt_name == '*' && evt_name + 1 == s;
>>+
>>+                for (evt_type = IGT_HOOK_PRE_TEST; evt_type < IGT_HOOK_NUM_EVENTS; evt_type++) {
>>+                        if (!is_star) {
>>+                                const char *this_event_name = igt_hook_evt_type_to_name(evt_type);
>>+                                size_t len = s - evt_name;
>>+
>>+                                if (len != strlen(this_event_name))
>>+                                        continue;
>>+
>>+                                if (strncmp(evt_name, this_event_name, len))
>>+                                        continue;
>>+                        }
>>+
>>+                        *evt_mask |= 1 << evt_type;
>
>pre-test,pre-test,pre-test:echo foo
>
>^ shouldn't this cause a warning?

I don't think it shouldn't. There is no harm in passing the same event
type multiple times and I don't see strong reasons to complain about it.

>
>but see below for alternative interface
>
>>+                        has_match = true;
>>+
>>+                        if (!is_star)
>>+                                break;
>>+                }
>>+
>>+                if (!has_match)
>>+                        return IGT_HOOK_EVT_NO_MATCH;
>>+
>>+                if (*s++ == ':')
>>+                        break;
>>+        }
>>+
>>+        *cmd = s;
>>+
>>+        return 0;
>>+}
>>+
>>+static size_t igt_hook_calc_test_fullname_size(struct igt_hook *igt_hook) {
>>+        /* The maximum size of test_fullname will be the maximum length of
>>+         * "igt@<test_name>@<subtest_name>@<dyn_subtest_name>" plus 1 for the
>>+         * null byte. */
>>+        return (igt_hook->test_name_size +
>>+                igt_hook->subtest_name_size +
>>+                igt_hook->dyn_subtest_name_size) + 4;
>>+}
>>+
>>+static void igt_hook_update_test_fullname(struct igt_hook *igt_hook)
>>+{
>>+        int i;
>>+        char *s;
>>+        const char *values[3] = {
>>+                igt_hook->test_name,
>>+                igt_hook->subtest_name,
>>+                igt_hook->dyn_subtest_name,
>>+        };
>>+
>>+        if (igt_hook->test_name[0] == '\0') {
>>+                igt_hook->test_fullname[0] = '\0';
>>+                return;
>>+        }
>>+
>>+        s = stpcpy(igt_hook->test_fullname, "igt");
>>+        for (i = 0; i < 3 && values[i][0] != '\0'; i++) {
>
>                        ^ ARRAY_SIZE()

Decided to fix this with a sentinel value (NULL).

>
>>+                *s++ = '@';
>>+                s = stpcpy(s, values[i]);
>>+        }
>>+}
>>+
>>+/**
>>+ * igt_hook_init:
>>+ * @hook_str: Hook descriptor string.
>>+ * @error: Pointer to error number.
>>+ *
>>+ * Allocate and initialize an #igt_hook structure.
>>+ *
>>+ * This function parses the hook descriptor @hook_str and initializes the struct
>>+ * to be returned.
>>+ *
>>+ * The hook descriptor comes from the argument to `--hook` of the test
>>+ * executable being run.
>>+ *
>>+ * If not #NULL, @error is used to store a non-zero error number if an error
>>+ * happens. A human-readable string for that error number can be obtained with
>>+ * @igt_hook_error_str().
>>+ *
>>+ * Returns: The pointer to the #igt_hook structure on success or #NULL on error.
>>+ */
>>+struct igt_hook *igt_hook_init(const char *hook_str, int *error)
>>+{
>>+        int err;
>>+        evt_mask_t evt_mask;
>>+        const char *cmd;
>>+        struct igt_hook *igt_hook = NULL;
>>+
>>+
>>+        err = igt_hook_parse_hook_str(hook_str, &evt_mask, &cmd);
>>+        if (err)
>>+                goto out;
>>+
>>+        igt_hook = calloc(1, sizeof(*igt_hook));
>>+        igt_hook->evt_mask = evt_mask;
>>+
>>+        igt_hook->cmd = strdup(cmd);
>>+        if (!igt_hook->cmd) {
>>+                err = -errno;
>>+                goto out;
>>+        }
>>+
>>+        igt_hook->test_name = malloc(TEST_NAME_INITIAL_SIZE);
>>+        igt_hook->test_name_size = TEST_NAME_INITIAL_SIZE;
>>+        igt_hook->subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
>>+        igt_hook->subtest_name_size = TEST_NAME_INITIAL_SIZE;
>>+        igt_hook->dyn_subtest_name = malloc(TEST_NAME_INITIAL_SIZE);
>>+        igt_hook->dyn_subtest_name_size = TEST_NAME_INITIAL_SIZE;
>
>up to you, but in this kind of things I tend to prefer the strings in
>the outer struct so we have fewer separate malloc()'s and thus fewer
>chances of leaking them

What would be the "outer struct" in this case?

>
>just calculate the size ahead and then update igt_hook->test_name,
>igt_hook->subtest_name and igt_hook->dyn_subtest_name to point to the right part.

Hm... Do you mean to have a single memory allocation to keep those three
strings? One issue I see with this approach is the complication when new
names would cause overlap, which would require us to shift stuff when
doing realloc().

>
>>+        igt_hook->test_fullname = malloc(igt_hook_calc_test_fullname_size(igt_hook));
>>+
>>+        igt_hook->test_name[0] = '\0';
>>+        igt_hook->subtest_name[0] = '\0';
>>+        igt_hook->dyn_subtest_name[0] = '\0';
>>+        igt_hook->test_fullname[0] = '\0';
>
>with the benefit that then these are covered by the calloc.
>
>up to you.

I'll send the v2 with this part as is for now. We can discuss more if
you'd like.

>
>>+
>>+out:
>>+        if (err) {
>>+                if (error)
>>+                        *error = err;
>>+
>>+                igt_hook_free(igt_hook);
>>+
>>+                return NULL;
>>+        }
>>+
>>+        return igt_hook;
>>+}
>>+
>>+/**
>>+ * igt_hook_free:
>>+ * @igt_hook: The igt_hook struct.
>>+ *
>>+ * De-initialize an igt_hook struct returned by @igt_hook_init().
>>+ *
>>+ * This is a no-op if @igt_hook is #NULL.
>>+ */
>>+void igt_hook_free(struct igt_hook *igt_hook)
>>+{
>>+        if (!igt_hook)
>>+                return;
>>+
>>+        free(igt_hook->cmd);
>>+        free(igt_hook->test_name);
>>+        free(igt_hook->subtest_name);
>>+        free(igt_hook->dyn_subtest_name);
>>+        free(igt_hook);
>>+}
>>+
>>+static void igt_hook_update_test_name_pre_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>>+{
>>+        char **name_ptr;
>>+        size_t *size_ptr;
>>+        size_t len;
>>+
>>+        switch (evt->evt_type) {
>>+        case IGT_HOOK_PRE_TEST:
>>+                name_ptr = &igt_hook->test_name;
>>+                size_ptr = &igt_hook->test_name_size;
>>+                break;
>>+        case IGT_HOOK_PRE_SUBTEST:
>>+                name_ptr = &igt_hook->subtest_name;
>>+                size_ptr = &igt_hook->subtest_name_size;
>>+                break;
>>+        case IGT_HOOK_PRE_DYN_SUBTEST:
>>+                name_ptr = &igt_hook->dyn_subtest_name;
>>+                size_ptr = &igt_hook->dyn_subtest_name_size;
>>+                break;
>>+        default:
>>+                return;
>>+        }
>>+
>>+        len = strlen(evt->target_name);
>>+        if (len + 1 > *size_ptr) {
>>+                size_t fullname_size;
>>+
>>+                *size_ptr *= 2;
>>+                *name_ptr = realloc(*name_ptr, *size_ptr);
>>+
>>+                fullname_size = igt_hook_calc_test_fullname_size(igt_hook);
>>+                igt_hook->test_fullname = realloc(igt_hook->test_fullname, fullname_size);
>>+        }
>>+
>>+        strcpy(*name_ptr, evt->target_name);
>>+        igt_hook_update_test_fullname(igt_hook);
>>+}
>>+
>>+static void igt_hook_update_test_name_post_call(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>>+{
>>+        switch (evt->evt_type) {
>>+        case IGT_HOOK_POST_TEST:
>>+                igt_hook->test_name[0] = '\0';
>>+                break;
>>+        case IGT_HOOK_POST_SUBTEST:
>>+                igt_hook->subtest_name[0] = '\0';
>>+                break;
>>+        case IGT_HOOK_POST_DYN_SUBTEST:
>>+                igt_hook->dyn_subtest_name[0] = '\0';
>>+                break;
>>+        default:
>>+                return;
>>+        }
>>+
>>+        igt_hook_update_test_fullname(igt_hook);
>>+}
>>+
>>+static void igt_hook_update_env_vars(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>>+{
>>+        setenv("IGT_HOOK_EVENT", igt_hook_evt_type_to_name(evt->evt_type), 1);
>>+        setenv("IGT_HOOK_TEST_FULLNAME", igt_hook->test_fullname, 1);
>>+        setenv("IGT_HOOK_TEST", igt_hook->test_name, 1);
>>+        setenv("IGT_HOOK_SUBTEST", igt_hook->subtest_name, 1);
>>+        setenv("IGT_HOOK_DYN_SUBTEST", igt_hook->dyn_subtest_name, 1);
>>+        setenv("IGT_HOOK_RESULT", evt->result ?: "", 1);
>>+}
>>+
>>+/**
>>+ * igt_hook_push_evt:
>>+ * @igt_hook: The igt_hook structure.
>>+ * @evt: The event to be pushed.
>>+ *
>>+ * Push a new igt_hook event.
>>+ *
>>+ * This function must be used to register a new igt_hook event. Calling it will
>>+ * cause execution of the hook script if the event type matches the filters
>>+ * provided during initialization of @igt_hook.
>>+ */
>>+void igt_hook_push_evt(struct igt_hook *igt_hook, struct igt_hook_evt *evt)
>>+{
>>+        typeof(igt_hook->evt_mask) evt_bit = (1 << evt->evt_type);
>>+
>>+        igt_hook_update_test_name_pre_call(igt_hook, evt);
>>+
>>+        if ((evt_bit & igt_hook->evt_mask)) {
>>+                igt_hook_update_env_vars(igt_hook, evt);
>>+                system(igt_hook->cmd);
>
>this polutes the env for the current process just so the child process
>has those env vars. We could either duplicate the environ on startup and
>use execle(), or to be simpler, just make sure to zap our env just after
>running the command.  This avoids undesirable behavior because we didn't
>clean up the environ.

Yeah, that makes sense. Decided to go with:

		struct igt_helper_process proc = {};

		igt_fork_helper(&proc) {
			igt_hook_update_env_vars(igt_hook, evt);
			system(igt_hook->cmd);
		}
		igt_wait_helper(&proc);

This does end up creating a "grandchild" process because we are still
using system(), but it keeps the code simple.

>
>
>>+        }
>>+
>>+        igt_hook_update_test_name_post_call(igt_hook, evt);
>>+}
>>+
>>+/**
>>+ * igt_hook_error_str:
>>+ * @error: Non-zero error number.
>>+ *
>>+ * Return a human-readable string containing a description of an error number
>>+ * generated by one of the `igt_hook_*` functions.
>>+ *
>>+ * The string will be the result of strerror() for errors from the C standard
>>+ * library or a custom description specific to igt_hook.
>>+ */
>>+const char *igt_hook_error_str(int error)
>>+{
>>+        if (!error)
>>+                return "No error";
>>+
>>+        if (error > 0) {
>>+                enum igt_hook_error hook_error = error;
>>+
>>+                switch (hook_error) {
>>+                case IGT_HOOK_EVT_EMPTY_NAME:
>>+                        return "Empty name in event descriptor";
>>+                case IGT_HOOK_EVT_NO_MATCH:
>>+                        return "Event name in event descriptor does not match any event type";
>>+                default:
>>+                        return "Unknown error";
>>+                }
>>+        } else {
>>+                return strerror(-error);
>>+        }
>>+}
>>+
>>+/**
>>+ * igt_hook_print_help:
>>+ * @f: File pointer where to write the output.
>>+ * @option_name: Name of the CLI option that accepts the hook descriptor.
>>+ *
>>+ * Print a detailed user help text on hook usage.
>>+ */
>>+void igt_hook_print_help(FILE *f, const char *option_name)
>>+{
>>+        fprintf(f, "\
>>+The option %1$s receives as argument a \"hook descriptor\" and allows the\n\
>>+execution of a shell command at different points during execution of tests. Each\n\
>>+such a point is called a \"hook event\".\n\
>>+\n\
>>+Examples:\n\
>>+\n\
>>+  # Prints hook-specic env vars for every event.\n\
>>+  %1$s 'printenv | grep ^IGT_HOOK_'\n\
>>+\n\
>>+  # Equivalent to the above. Useful if command contains ':'.\n\
>>+  %1$s '*:printenv | grep ^IGT_HOOK_'\n\
>>+\n\
>>+  # Adds a line to out.txt containing the result of each test case.\n\
>>+  %1$s 'post-test:echo $IGT_HOOK_TEST_FULLNAME $IGT_HOOK_RESULT >> out.txt'\n\
>>+\n\
>>+The accepted format for a hook descriptor is `[<events>:]<cmd>`, where:\n\
>>+\n\
>>+  - <events> is a comma-separated list of event descriptors, which defines the\n\
>>+    set of events be tracked. If omitted, all events are tracked.\n\
>>+\n\
>
>
>I think the interface git-rebase is more natural for this kind of
>thing. You have 2 concepts: event and hook with n:1 relationship
>because of the interface chosen.
>
>Why not simply use 1:1 and then allow the program to have multiple
>hooks?

Yep, I thought about doing it, but was a bit lazy and decided to have a
single hook string instead of an array :-P

>
>
>Just in git-rebase we do  `git rebase -x "echo foo" -x "echo bar"` and
>the commands are appended, we could have:
>
>igt_runner -x 'pre-test:echo foo' -x 'post-test:echo bar'. Then you only
>warn when you user incorrectly used the same hook type.  I think it

I like this.

I personally do not see a problem with having multiple hook commands for
the same event. We would just end up running each of them in turn.

I guess we could basically extend the current code to accept multiple
`--hook` options, in the same format as with the current implementation
here. That would allow something like

  my_test --hook pre-test,pre-subtest:some-command-common-to-both \
          --hook post-test:some-command-only-for-post-test

I'll work on this.

>would be more natural to eventually embed the support on a test list
>(with the exception we'd not warn anymore), with something like:
>
>hook@pre-test@echo foo
>hook@post-test@echo bar
>igt@...
>igt@...
>igt@...
>hook@pre-test@ ....
>igt@...
>

Yeah. Maybe in a future series :-)

Thanks for all the feedback!

--
Gustavo Sousa

>
>
>
>
>>+  - <cmd> is a shell command to be executed on the occurrence each tracked\n\
>>+    event. If the command contains ':', then passing <events> is required,\n\
>>+    otherwise part of the command would be treated as an event descriptor.\n\
>>+\n\
>>+", option_name);
>>+
>>+        fprintf(f, "\
>>+An \"event descriptor\" is either the name of an event or the string '*'. The\n\
>>+latter matches all event names. The list of possible event names is provided\n\
>>+below:\n\
>>+\n\
>>+");
>>+
>>+        for (enum igt_hook_evt_type et = 0; et < IGT_HOOK_NUM_EVENTS; et++) {
>>+                const char *desc;
>>+
>>+                switch (et) {
>>+                case IGT_HOOK_PRE_TEST:
>>+                        desc = "Occurs before a test case starts.";
>>+                        break;
>>+                case IGT_HOOK_PRE_SUBTEST:
>>+                        desc = "Occurs before the execution of a subtest.";
>>+                        break;
>>+                case IGT_HOOK_PRE_DYN_SUBTEST:
>>+                        desc = "Occurs before the execution of a dynamic subtest.";
>>+                        break;
>>+                case IGT_HOOK_POST_DYN_SUBTEST:
>>+                        desc = "Occurs after the execution of a dynamic subtest.";
>>+                        break;
>>+                case IGT_HOOK_POST_SUBTEST:
>>+                        desc = "Occurs after the execution of a subtest.";
>>+                        break;
>>+                case IGT_HOOK_POST_TEST:
>>+                        desc = "Occurs after a test case has finished.";
>>+                        break;
>>+                default:
>>+                        desc = "MISSING DESCRIPTION";
>>+                }
>>+
>>+                fprintf(f, "  %s\n  %s\n\n", igt_hook_evt_type_to_name(et), desc);
>>+        }
>>+
>>+        fprintf(f, "\
>>+For each event matched by <events>, <cmd> is executed as a shell command. The\n\
>>+exit status of the command is ignored. The following environment variables are\n\
>>+available to the command:\n\
>>+\n\
>>+  IGT_HOOK_EVENT\n\
>>+  Name of the current event.\n\
>>+\n\
>>+  IGT_HOOK_TEST_FULLNAME\n\
>>+  Full name of the test in the format `igt@<test>[@<subtest>[@<dyn_subtest>]]`.\n\
>>+\n\
>>+  IGT_HOOK_TEST                   \n\
>>+  Name of the current test.\n\
>>+\n\
>>+  IGT_HOOK_SUBTEST\n\
>>+  Name of the current subtest. Will be the empty string if not running a\n\
>>+  subtest.\n\
>>+\n\
>>+  IGT_HOOK_DYN_SUBTEST\n\
>>+  Name of the current dynamic subtest. Will be the empty string if not running a\n\
>>+  dynamic subtest.\n\
>>+\n\
>>+  IGT_HOOK_RESULT\n\
>>+  String representing the result of the test/subtest/dynamic subtest. Possible\n\
>>+  values are: SUCCESS, SKIP or FAIL. This is only applicable on \"post-*\"\n\
>>+  events and will be the empty string for other types of events.\n\
>>+\n\
>>+");
>>+}
>>diff --git a/lib/igt_hook.h b/lib/igt_hook.h
>>new file mode 100644
>>index 000000000000..6fef7254a317
>>--- /dev/null
>>+++ b/lib/igt_hook.h
>>@@ -0,0 +1,86 @@
>>+/*
>>+ * Copyright © 2024 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.
>>+ */
>
>ditto on SPDX
>
>
>Lucas De Marchi

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2024-06-19 21:13 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-05-09 15:24 [PATCH i-g-t 0/3] Add support for hook script Gustavo Sousa
2024-05-09 15:24 ` [PATCH i-g-t 1/3] igt_hook: Add feature Gustavo Sousa
2024-05-13 17:10   ` Kamil Konieczny
2024-05-15 17:10   ` Kamil Konieczny
2024-05-15 17:35     ` Gustavo Sousa
2024-05-16 10:40       ` Kamil Konieczny
2024-05-16 12:19         ` Gustavo Sousa
2024-05-16 16:30           ` Kamil Konieczny
2024-05-16 17:05             ` Gustavo Sousa
2024-05-20 19:03       ` Lucas De Marchi
2024-05-21 19:40   ` Lucas De Marchi
2024-06-19 21:12     ` Gustavo Sousa
2024-05-09 15:24 ` [PATCH i-g-t 2/3] runner: Make it easier to extend argv Gustavo Sousa
2024-05-09 15:24 ` [PATCH i-g-t 3/3] runner: Add option --hook Gustavo Sousa
2024-05-09 16:13 ` ✗ GitLab.Pipeline: warning for Add support for hook script Patchwork
2024-05-09 17:04   ` Gustavo Sousa
2024-05-20 19:44     ` Lucas De Marchi
2024-06-19 19:07       ` Gustavo Sousa
2024-05-09 16:24 ` ✓ CI.xeBAT: success " Patchwork
2024-05-09 16:33 ` ✓ Fi.CI.BAT: " Patchwork
2024-05-09 21:12 ` ✗ CI.xeFULL: failure " Patchwork
2024-05-10  5:30 ` ✗ Fi.CI.IGT: " Patchwork

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox