[igt-dev] [PATCH i-g-t 1/2] igt_kmod: add compatibility for KUnit
Isabella Basso
isabbasso at riseup.net
Fri Jun 17 15:47:52 UTC 2022
This adds functions for both executing the tests as well as parsing (K)TAP
kmsg output, as per the KTAP spec [1].
[1] https://www.kernel.org/doc/html/latest/dev-tools/ktap.html
Signed-off-by: Isabella Basso <isabbasso at riseup.net>
---
lib/igt_kmod.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++++
lib/igt_kmod.h | 3 +
2 files changed, 272 insertions(+)
diff --git a/lib/igt_kmod.c b/lib/igt_kmod.c
index dfdcfcc5..d38cd221 100644
--- a/lib/igt_kmod.c
+++ b/lib/igt_kmod.c
@@ -25,6 +25,7 @@
#include <signal.h>
#include <errno.h>
#include <sys/utsname.h>
+#include <limits.h>
#include "igt_aux.h"
#include "igt_core.h"
@@ -32,6 +33,8 @@
#include "igt_sysfs.h"
#include "igt_taints.h"
+#define BUF_LEN 4096
+
/**
* SECTION:igt_kmod
* @short_description: Wrappers around libkmod for module loading/unloading
@@ -372,6 +375,272 @@ static void *strdup_realloc(char *origptr, const char *strdata)
return newptr;
}
+static void find_next_tap_subtest(char *record, int *ret, bool is_subtest)
+{
+ char test_count_ptr[12];
+ const char *test_search_str, *ver_search_str, *lend,
+ *ver_res_ptr, *test_res_ptr;
+ int test_count_strlen;
+ long test_count;
+
+ /* total tests will appear as 0..N at the beginning of a run */
+ test_search_str = "..";
+ ver_search_str = "TAP version ";
+
+ ver_res_ptr = strstr(record, ver_search_str);
+ test_res_ptr = strstr(record, test_search_str);
+
+ *ret = -1;
+
+ if (test_res_ptr == NULL)
+ return;
+
+ test_res_ptr += strlen(test_search_str);
+ lend = strchrnul(test_res_ptr, '\n');
+ /* in case line > 4096 */
+ if (*lend == '\0') {
+ igt_info("%s\n", record);
+ return;
+ }
+
+ test_count_strlen = (int)(lend - test_res_ptr);
+ if (test_count_strlen > sizeof(test_count_ptr) - 1)
+ return;
+ strncpy(test_count_ptr, test_res_ptr, test_count_strlen);
+
+ test_count_ptr[test_count_strlen] = '\0';
+ test_count = strtol(test_count_ptr, NULL, 10);
+ if (test_count == LONG_MIN || test_count == LONG_MAX)
+ return;
+
+ /* "(K)TAP version XX" should be the first line on all (sub)tests as per
+ * https://www.kernel.org/doc/html/latest/dev-tools/ktap.html#version-lines
+ * but actually isn't, as it currently depends on who writes the test
+ * to print this */
+ if (ver_res_ptr == NULL)
+ igt_info("Missing test version string\n");
+
+ if (is_subtest)
+ igt_info("running %d subtests...\n", (int)test_count);
+ else
+ igt_info("running %d tests...\n", (int)test_count);
+
+ *ret = (int)test_count;
+ return;
+}
+
+static void print_tap(int fd, char *record, int *sublevel, bool *failed_tests)
+{
+ const char *test_fail_search_str;
+ ssize_t r;
+ /* for each sublevel we have 2 spaces indent */
+ const int indent_len = (*sublevel + 1) * 2;;
+
+ test_fail_search_str = "not ok ";
+
+ for (; *sublevel > 0;) {
+ const char *lend, *test_fail_res, *lstart, *i_end;
+ int *ret, retval, llen;
+
+ ret = &retval;
+
+ lend = strchrnul(record, '\n');
+
+ if (*lend != '\0')
+ lseek(fd, (int)(lend - record), SEEK_CUR);
+
+ r = read(fd, record, BUF_LEN);
+
+ if (r <= 0) {
+ switch (errno) {
+ case EINTR:
+ continue;
+ case EPIPE:
+ igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
+ continue;
+ case !EAGAIN:
+ igt_warn("kmsg truncated: unknown error (%m)\n");
+ default:
+ break;
+ }
+ break;
+ }
+
+ find_next_tap_subtest(record, ret, true);
+ if (*ret != -1) {
+ (*sublevel)++;
+ print_tap(fd, record, sublevel, failed_tests);
+ }
+
+ lend = strchrnul(record, '\n');
+
+ /* in case line > 4096 */
+ if (*lend == '\0') {
+ igt_info("%s\n", record);
+ continue;
+ }
+
+ lstart = strchrnul(record, ';');
+
+ /* check if we're still in a subtest */
+ if (*lstart == '\0') {
+ igt_warn("kmsg truncated: output malformed (%m)\n");
+ igt_fail(IGT_EXIT_FAILURE);
+ } else {
+ lstart++;
+ i_end = lstart;
+ while (isspace(*i_end))
+ i_end++;
+ llen = (int)(i_end - lstart);
+ if (llen != indent_len)
+ break;
+ }
+
+ test_fail_res = strstr(record, test_fail_search_str);
+ if (test_fail_res != NULL) {
+ igt_warn("kmsg> %.*s\n",
+ (int)(lend - lstart) - 1, lstart);
+ *failed_tests = true;
+ continue;
+ }
+
+ igt_info("kmsg> %.*s\n",
+ (int)(lend - lstart) - 1, lstart);
+ }
+ (*sublevel)--;
+}
+
+static void parse_kmsg_for_tap(int fd)
+{
+ char record[BUF_LEN + 1];
+ int test_sublevel;
+ bool failed_tests, skip, found_tests;
+
+ test_sublevel = 0;
+
+ skip = true;
+ failed_tests = false;
+ found_tests = false;
+
+ if (fd == -1) {
+ igt_warn("Unable to retrieve kernel log (from /dev/kmsg)\n");
+ return;
+ }
+
+ record[BUF_LEN] = '\0';
+
+ while (true) {
+ const char *lend;
+ int *ret, retval;
+ ssize_t r;
+ ret = &retval;
+
+ lend = strchrnul(record, '\n');
+
+ if (!skip && *lend != '\0')
+ lseek(fd, (int) (lend - record), SEEK_CUR);
+
+ r = read(fd, record, BUF_LEN);
+
+ if (!skip)
+ lend = strchrnul(record, '\n');
+ else
+ skip = false;
+
+ if (r <= 0) {
+ switch (errno) {
+ case EINTR:
+ continue;
+ case EPIPE:
+ igt_warn("kmsg truncated: too many messages. You may want to increase log_buf_len in kmcdline\n");
+ continue;
+ case !EAGAIN:
+ igt_warn("kmsg truncated: unknown error (%m)\n");
+ default:
+ break;
+ }
+ break;
+ }
+
+ find_next_tap_subtest(record, ret, false);
+ if (*ret != -1) {
+ found_tests = true;
+ test_sublevel++;
+ print_tap(fd, record, &test_sublevel, &failed_tests);
+ }
+ }
+
+ if (failed_tests || !found_tests)
+ igt_fail(IGT_EXIT_FAILURE);
+ else
+ igt_success();
+}
+
+/**
+ * igt_kunit: tests a kunit module
+ * @mod_name: the name of the module
+ * @options: options to load the module
+ * @req_mods: array with additional dependencies to run the KUnit test
+ * @req_mod_len: size of req_mods array
+ *
+ * Loads the kunit module, parses its dmesg output, then unloads it
+ */
+void igt_kunit(const char *mod_name, const char *options,
+ const char **req_mods, int req_mod_len)
+{
+ int kmsg_fd, cmi = 0;
+ struct igt_kselftest tst;
+ const char *norm_name;
+
+ /* get normalized module name */
+ igt_require(igt_kselftest_init(&tst, mod_name) == 0);
+
+ norm_name = kmod_module_get_name(tst.kmod);
+
+ if (!igt_kmod_is_loaded(norm_name)) {
+ for (cmi = 0; cmi < req_mod_len; cmi++) {
+ if (igt_kmod_load(req_mods[cmi], NULL))
+ goto unload;
+ }
+
+ /* The kunit module is required for running any kunit tests */
+ if (!igt_kmod_is_loaded("kunit"))
+ igt_require(igt_kmod_load("kunit", NULL) == 0);
+
+ } else {
+ /* Unload module if loaded (maybe previous run bailed out?) */
+ igt_kmod_unload(mod_name, 0);
+ }
+
+ kmsg_fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
+
+ if (kmsg_fd < 0) {
+ igt_warn("Could not open /dev/kmsg");
+ goto unload;
+ }
+
+ if (lseek(kmsg_fd, 0, SEEK_END)) {
+ igt_warn("Could not seek the end of /dev/kmsg");
+ close(kmsg_fd);
+ goto unload;
+ }
+
+ igt_require(igt_kmod_load(mod_name, options) == 0);
+
+ parse_kmsg_for_tap(kmsg_fd);
+
+ igt_kmod_unload(mod_name, 0);
+
+ close(kmsg_fd);
+unload:
+ for (cmi--; cmi >= 0; cmi--) {
+ igt_kmod_unload(req_mods[cmi], 0);
+ }
+
+ if (igt_kmod_is_loaded("kunit"))
+ igt_require(igt_kmod_load("kunit", NULL) == 0);
+}
+
/**
* igt_i915_driver_load:
* @opts: options to pass to i915 driver
diff --git a/lib/igt_kmod.h b/lib/igt_kmod.h
index f98dd29f..6d8b5968 100644
--- a/lib/igt_kmod.h
+++ b/lib/igt_kmod.h
@@ -38,6 +38,9 @@ int igt_kmod_unload(const char *mod_name, unsigned int flags);
int igt_audio_driver_unload(char **whom);
+void igt_kunit(const char *mod_name, const char *options,
+ const char **req_mods, int req_mod_len);
+
int igt_i915_driver_load(const char *opts);
int igt_i915_driver_unload(void);
int __igt_i915_driver_unload(char **whom);
--
2.36.1
More information about the igt-dev
mailing list