Commit 313b6ffc authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'linux-kselftest-kunit-5.16-rc1' of...

Merge tag 'linux-kselftest-kunit-5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest

Pull KUnit updates from Shuah Khan:
 "Several enhancements and fixes:

   - ability to run each test suite and test separately

   - support for timing test run

   - several fixes and improvements"

* tag 'linux-kselftest-kunit-5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest:
  kunit: tool: fix typecheck errors about loading qemu configs
  kunit: tool: continue past invalid utf-8 output
  kunit: Reset suite count after running tests
  kunit: tool: improve compatibility of kunit_parser with KTAP specification
  kunit: tool: yield output from run_kernel in real time
  kunit: tool: support running each suite/test separately
  kunit: tool: actually track how long it took to run tests
  kunit: tool: factor exec + parse steps into a function
  kunit: add 'kunit.action' param to allow listing out tests
  kunit: tool: show list of valid --arch options when invalid
  kunit: tool: misc fixes (unused vars, imports, leaked files)
  kunit: fix too small allocation when using suite-only kunit.filter_glob
  kunit: tool: allow filtering test cases via glob
  kunit: drop assumption in kunit-log-test about current suite
parents 84924e2e 52a5d80a
...@@ -25,8 +25,8 @@ It can be handy to create a bash function like: ...@@ -25,8 +25,8 @@ It can be handy to create a bash function like:
Running a subset of tests Running a subset of tests
------------------------- -------------------------
``kunit.py run`` accepts an optional glob argument to filter tests. Currently ``kunit.py run`` accepts an optional glob argument to filter tests. The format
this only matches against suite names, but this may change in the future. is ``"<suite_glob>[.test_glob]"``.
Say that we wanted to run the sysctl tests, we could do so via: Say that we wanted to run the sysctl tests, we could do so via:
...@@ -35,6 +35,13 @@ Say that we wanted to run the sysctl tests, we could do so via: ...@@ -35,6 +35,13 @@ Say that we wanted to run the sysctl tests, we could do so via:
$ echo -e 'CONFIG_KUNIT=y\nCONFIG_KUNIT_ALL_TESTS=y' > .kunit/.kunitconfig $ echo -e 'CONFIG_KUNIT=y\nCONFIG_KUNIT_ALL_TESTS=y' > .kunit/.kunitconfig
$ ./tools/testing/kunit/kunit.py run 'sysctl*' $ ./tools/testing/kunit/kunit.py run 'sysctl*'
We can filter down to just the "write" tests via:
.. code-block:: bash
$ echo -e 'CONFIG_KUNIT=y\nCONFIG_KUNIT_ALL_TESTS=y' > .kunit/.kunitconfig
$ ./tools/testing/kunit/kunit.py run 'sysctl*.*write*'
We're paying the cost of building more tests than we need this way, but it's We're paying the cost of building more tests than we need this way, but it's
easier than fiddling with ``.kunitconfig`` files or commenting out easier than fiddling with ``.kunitconfig`` files or commenting out
``kunit_suite``'s. ``kunit_suite``'s.
......
...@@ -15,23 +15,89 @@ extern struct kunit_suite * const * const __kunit_suites_end[]; ...@@ -15,23 +15,89 @@ extern struct kunit_suite * const * const __kunit_suites_end[];
#if IS_BUILTIN(CONFIG_KUNIT) #if IS_BUILTIN(CONFIG_KUNIT)
static char *filter_glob_param; static char *filter_glob_param;
static char *action_param;
module_param_named(filter_glob, filter_glob_param, charp, 0); module_param_named(filter_glob, filter_glob_param, charp, 0);
MODULE_PARM_DESC(filter_glob, MODULE_PARM_DESC(filter_glob,
"Filter which KUnit test suites run at boot-time, e.g. list*"); "Filter which KUnit test suites/tests run at boot-time, e.g. list* or list*.*del_test");
module_param_named(action, action_param, charp, 0);
MODULE_PARM_DESC(action,
"Changes KUnit executor behavior, valid values are:\n"
"<none>: run the tests like normal\n"
"'list' to list test names instead of running them.\n");
/* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */
struct kunit_test_filter {
char *suite_glob;
char *test_glob;
};
/* Split "suite_glob.test_glob" into two. Assumes filter_glob is not empty. */
static void kunit_parse_filter_glob(struct kunit_test_filter *parsed,
const char *filter_glob)
{
const int len = strlen(filter_glob);
const char *period = strchr(filter_glob, '.');
if (!period) {
parsed->suite_glob = kzalloc(len + 1, GFP_KERNEL);
parsed->test_glob = NULL;
strcpy(parsed->suite_glob, filter_glob);
return;
}
parsed->suite_glob = kzalloc(period - filter_glob + 1, GFP_KERNEL);
parsed->test_glob = kzalloc(len - (period - filter_glob) + 1, GFP_KERNEL);
strncpy(parsed->suite_glob, filter_glob, period - filter_glob);
strncpy(parsed->test_glob, period + 1, len - (period - filter_glob));
}
/* Create a copy of suite with only tests that match test_glob. */
static struct kunit_suite *
kunit_filter_tests(struct kunit_suite *const suite, const char *test_glob)
{
int n = 0;
struct kunit_case *filtered, *test_case;
struct kunit_suite *copy;
kunit_suite_for_each_test_case(suite, test_case) {
if (!test_glob || glob_match(test_glob, test_case->name))
++n;
}
if (n == 0)
return NULL;
/* Use memcpy to workaround copy->name being const. */
copy = kmalloc(sizeof(*copy), GFP_KERNEL);
memcpy(copy, suite, sizeof(*copy));
filtered = kcalloc(n + 1, sizeof(*filtered), GFP_KERNEL);
n = 0;
kunit_suite_for_each_test_case(suite, test_case) {
if (!test_glob || glob_match(test_glob, test_case->name))
filtered[n++] = *test_case;
}
copy->test_cases = filtered;
return copy;
}
static char *kunit_shutdown; static char *kunit_shutdown;
core_param(kunit_shutdown, kunit_shutdown, charp, 0644); core_param(kunit_shutdown, kunit_shutdown, charp, 0644);
static struct kunit_suite * const * static struct kunit_suite * const *
kunit_filter_subsuite(struct kunit_suite * const * const subsuite, kunit_filter_subsuite(struct kunit_suite * const * const subsuite,
const char *filter_glob) struct kunit_test_filter *filter)
{ {
int i, n = 0; int i, n = 0;
struct kunit_suite **filtered; struct kunit_suite **filtered, *filtered_suite;
n = 0; n = 0;
for (i = 0; subsuite[i] != NULL; ++i) { for (i = 0; subsuite[i]; ++i) {
if (glob_match(filter_glob, subsuite[i]->name)) if (glob_match(filter->suite_glob, subsuite[i]->name))
++n; ++n;
} }
...@@ -44,8 +110,11 @@ kunit_filter_subsuite(struct kunit_suite * const * const subsuite, ...@@ -44,8 +110,11 @@ kunit_filter_subsuite(struct kunit_suite * const * const subsuite,
n = 0; n = 0;
for (i = 0; subsuite[i] != NULL; ++i) { for (i = 0; subsuite[i] != NULL; ++i) {
if (glob_match(filter_glob, subsuite[i]->name)) if (!glob_match(filter->suite_glob, subsuite[i]->name))
filtered[n++] = subsuite[i]; continue;
filtered_suite = kunit_filter_tests(subsuite[i], filter->test_glob);
if (filtered_suite)
filtered[n++] = filtered_suite;
} }
filtered[n] = NULL; filtered[n] = NULL;
...@@ -57,12 +126,32 @@ struct suite_set { ...@@ -57,12 +126,32 @@ struct suite_set {
struct kunit_suite * const * const *end; struct kunit_suite * const * const *end;
}; };
static void kunit_free_subsuite(struct kunit_suite * const *subsuite)
{
unsigned int i;
for (i = 0; subsuite[i]; i++)
kfree(subsuite[i]);
kfree(subsuite);
}
static void kunit_free_suite_set(struct suite_set suite_set)
{
struct kunit_suite * const * const *suites;
for (suites = suite_set.start; suites < suite_set.end; suites++)
kunit_free_subsuite(*suites);
kfree(suite_set.start);
}
static struct suite_set kunit_filter_suites(const struct suite_set *suite_set, static struct suite_set kunit_filter_suites(const struct suite_set *suite_set,
const char *filter_glob) const char *filter_glob)
{ {
int i; int i;
struct kunit_suite * const **copy, * const *filtered_subsuite; struct kunit_suite * const **copy, * const *filtered_subsuite;
struct suite_set filtered; struct suite_set filtered;
struct kunit_test_filter filter;
const size_t max = suite_set->end - suite_set->start; const size_t max = suite_set->end - suite_set->start;
...@@ -73,12 +162,17 @@ static struct suite_set kunit_filter_suites(const struct suite_set *suite_set, ...@@ -73,12 +162,17 @@ static struct suite_set kunit_filter_suites(const struct suite_set *suite_set,
return filtered; return filtered;
} }
kunit_parse_filter_glob(&filter, filter_glob);
for (i = 0; i < max; ++i) { for (i = 0; i < max; ++i) {
filtered_subsuite = kunit_filter_subsuite(suite_set->start[i], filter_glob); filtered_subsuite = kunit_filter_subsuite(suite_set->start[i], &filter);
if (filtered_subsuite) if (filtered_subsuite)
*copy++ = filtered_subsuite; *copy++ = filtered_subsuite;
} }
filtered.end = copy; filtered.end = copy;
kfree(filter.suite_glob);
kfree(filter.test_glob);
return filtered; return filtered;
} }
...@@ -109,9 +203,35 @@ static void kunit_print_tap_header(struct suite_set *suite_set) ...@@ -109,9 +203,35 @@ static void kunit_print_tap_header(struct suite_set *suite_set)
pr_info("1..%d\n", num_of_suites); pr_info("1..%d\n", num_of_suites);
} }
int kunit_run_all_tests(void) static void kunit_exec_run_tests(struct suite_set *suite_set)
{
struct kunit_suite * const * const *suites;
kunit_print_tap_header(suite_set);
for (suites = suite_set->start; suites < suite_set->end; suites++)
__kunit_test_suites_init(*suites);
}
static void kunit_exec_list_tests(struct suite_set *suite_set)
{ {
unsigned int i;
struct kunit_suite * const * const *suites; struct kunit_suite * const * const *suites;
struct kunit_case *test_case;
/* Hack: print a tap header so kunit.py can find the start of KUnit output. */
pr_info("TAP version 14\n");
for (suites = suite_set->start; suites < suite_set->end; suites++)
for (i = 0; (*suites)[i] != NULL; i++) {
kunit_suite_for_each_test_case((*suites)[i], test_case) {
pr_info("%s.%s\n", (*suites)[i]->name, test_case->name);
}
}
}
int kunit_run_all_tests(void)
{
struct suite_set suite_set = { struct suite_set suite_set = {
.start = __kunit_suites_start, .start = __kunit_suites_start,
.end = __kunit_suites_end, .end = __kunit_suites_end,
...@@ -120,15 +240,15 @@ int kunit_run_all_tests(void) ...@@ -120,15 +240,15 @@ int kunit_run_all_tests(void)
if (filter_glob_param) if (filter_glob_param)
suite_set = kunit_filter_suites(&suite_set, filter_glob_param); suite_set = kunit_filter_suites(&suite_set, filter_glob_param);
kunit_print_tap_header(&suite_set); if (!action_param)
kunit_exec_run_tests(&suite_set);
for (suites = suite_set.start; suites < suite_set.end; suites++) else if (strcmp(action_param, "list") == 0)
__kunit_test_suites_init(*suites); kunit_exec_list_tests(&suite_set);
else
pr_err("kunit executor: unknown action '%s'\n", action_param);
if (filter_glob_param) { /* a copy was made of each array */ if (filter_glob_param) { /* a copy was made of each array */
for (suites = suite_set.start; suites < suite_set.end; suites++) kunit_free_suite_set(suite_set);
kfree(*suites);
kfree(suite_set.start);
} }
kunit_handle_shutdown(); kunit_handle_shutdown();
......
...@@ -9,38 +9,103 @@ ...@@ -9,38 +9,103 @@
#include <kunit/test.h> #include <kunit/test.h>
static void kfree_at_end(struct kunit *test, const void *to_free); static void kfree_at_end(struct kunit *test, const void *to_free);
static void free_subsuite_at_end(struct kunit *test,
struct kunit_suite *const *to_free);
static struct kunit_suite *alloc_fake_suite(struct kunit *test, static struct kunit_suite *alloc_fake_suite(struct kunit *test,
const char *suite_name); const char *suite_name,
struct kunit_case *test_cases);
static void dummy_test(struct kunit *test) {}
static struct kunit_case dummy_test_cases[] = {
/* .run_case is not important, just needs to be non-NULL */
{ .name = "test1", .run_case = dummy_test },
{ .name = "test2", .run_case = dummy_test },
{},
};
static void parse_filter_test(struct kunit *test)
{
struct kunit_test_filter filter = {NULL, NULL};
kunit_parse_filter_glob(&filter, "suite");
KUNIT_EXPECT_STREQ(test, filter.suite_glob, "suite");
KUNIT_EXPECT_FALSE(test, filter.test_glob);
kfree(filter.suite_glob);
kfree(filter.test_glob);
kunit_parse_filter_glob(&filter, "suite.test");
KUNIT_EXPECT_STREQ(test, filter.suite_glob, "suite");
KUNIT_EXPECT_STREQ(test, filter.test_glob, "test");
kfree(filter.suite_glob);
kfree(filter.test_glob);
}
static void filter_subsuite_test(struct kunit *test) static void filter_subsuite_test(struct kunit *test)
{ {
struct kunit_suite *subsuite[3] = {NULL, NULL, NULL}; struct kunit_suite *subsuite[3] = {NULL, NULL, NULL};
struct kunit_suite * const *filtered; struct kunit_suite * const *filtered;
struct kunit_test_filter filter = {
.suite_glob = "suite2",
.test_glob = NULL,
};
subsuite[0] = alloc_fake_suite(test, "suite1"); subsuite[0] = alloc_fake_suite(test, "suite1", dummy_test_cases);
subsuite[1] = alloc_fake_suite(test, "suite2"); subsuite[1] = alloc_fake_suite(test, "suite2", dummy_test_cases);
/* Want: suite1, suite2, NULL -> suite2, NULL */ /* Want: suite1, suite2, NULL -> suite2, NULL */
filtered = kunit_filter_subsuite(subsuite, "suite2*"); filtered = kunit_filter_subsuite(subsuite, &filter);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered);
kfree_at_end(test, filtered); free_subsuite_at_end(test, filtered);
/* Validate we just have suite2 */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered[0]); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered[0]);
KUNIT_EXPECT_STREQ(test, (const char *)filtered[0]->name, "suite2"); KUNIT_EXPECT_STREQ(test, (const char *)filtered[0]->name, "suite2");
KUNIT_EXPECT_FALSE(test, filtered[1]);
}
static void filter_subsuite_test_glob_test(struct kunit *test)
{
struct kunit_suite *subsuite[3] = {NULL, NULL, NULL};
struct kunit_suite * const *filtered;
struct kunit_test_filter filter = {
.suite_glob = "suite2",
.test_glob = "test2",
};
subsuite[0] = alloc_fake_suite(test, "suite1", dummy_test_cases);
subsuite[1] = alloc_fake_suite(test, "suite2", dummy_test_cases);
/* Want: suite1, suite2, NULL -> suite2 (just test1), NULL */
filtered = kunit_filter_subsuite(subsuite, &filter);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered);
free_subsuite_at_end(test, filtered);
/* Validate we just have suite2 */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered[0]);
KUNIT_EXPECT_STREQ(test, (const char *)filtered[0]->name, "suite2");
KUNIT_EXPECT_FALSE(test, filtered[1]); KUNIT_EXPECT_FALSE(test, filtered[1]);
/* Now validate we just have test2 */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered[0]->test_cases);
KUNIT_EXPECT_STREQ(test, (const char *)filtered[0]->test_cases[0].name, "test2");
KUNIT_EXPECT_FALSE(test, filtered[0]->test_cases[1].name);
} }
static void filter_subsuite_to_empty_test(struct kunit *test) static void filter_subsuite_to_empty_test(struct kunit *test)
{ {
struct kunit_suite *subsuite[3] = {NULL, NULL, NULL}; struct kunit_suite *subsuite[3] = {NULL, NULL, NULL};
struct kunit_suite * const *filtered; struct kunit_suite * const *filtered;
struct kunit_test_filter filter = {
.suite_glob = "not_found",
.test_glob = NULL,
};
subsuite[0] = alloc_fake_suite(test, "suite1"); subsuite[0] = alloc_fake_suite(test, "suite1", dummy_test_cases);
subsuite[1] = alloc_fake_suite(test, "suite2"); subsuite[1] = alloc_fake_suite(test, "suite2", dummy_test_cases);
filtered = kunit_filter_subsuite(subsuite, "not_found"); filtered = kunit_filter_subsuite(subsuite, &filter);
kfree_at_end(test, filtered); /* just in case */ free_subsuite_at_end(test, filtered); /* just in case */
KUNIT_EXPECT_FALSE_MSG(test, filtered, KUNIT_EXPECT_FALSE_MSG(test, filtered,
"should be NULL to indicate no match"); "should be NULL to indicate no match");
...@@ -52,7 +117,7 @@ static void kfree_subsuites_at_end(struct kunit *test, struct suite_set *suite_s ...@@ -52,7 +117,7 @@ static void kfree_subsuites_at_end(struct kunit *test, struct suite_set *suite_s
kfree_at_end(test, suite_set->start); kfree_at_end(test, suite_set->start);
for (suites = suite_set->start; suites < suite_set->end; suites++) for (suites = suite_set->start; suites < suite_set->end; suites++)
kfree_at_end(test, *suites); free_subsuite_at_end(test, *suites);
} }
static void filter_suites_test(struct kunit *test) static void filter_suites_test(struct kunit *test)
...@@ -74,8 +139,8 @@ static void filter_suites_test(struct kunit *test) ...@@ -74,8 +139,8 @@ static void filter_suites_test(struct kunit *test)
struct suite_set filtered = {.start = NULL, .end = NULL}; struct suite_set filtered = {.start = NULL, .end = NULL};
/* Emulate two files, each having one suite */ /* Emulate two files, each having one suite */
subsuites[0][0] = alloc_fake_suite(test, "suite0"); subsuites[0][0] = alloc_fake_suite(test, "suite0", dummy_test_cases);
subsuites[1][0] = alloc_fake_suite(test, "suite1"); subsuites[1][0] = alloc_fake_suite(test, "suite1", dummy_test_cases);
/* Filter out suite1 */ /* Filter out suite1 */
filtered = kunit_filter_suites(&suite_set, "suite0"); filtered = kunit_filter_suites(&suite_set, "suite0");
...@@ -84,11 +149,14 @@ static void filter_suites_test(struct kunit *test) ...@@ -84,11 +149,14 @@ static void filter_suites_test(struct kunit *test)
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered.start); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered.start);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered.start[0]); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered.start[0]);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filtered.start[0][0]);
KUNIT_EXPECT_STREQ(test, (const char *)filtered.start[0][0]->name, "suite0"); KUNIT_EXPECT_STREQ(test, (const char *)filtered.start[0][0]->name, "suite0");
} }
static struct kunit_case executor_test_cases[] = { static struct kunit_case executor_test_cases[] = {
KUNIT_CASE(parse_filter_test),
KUNIT_CASE(filter_subsuite_test), KUNIT_CASE(filter_subsuite_test),
KUNIT_CASE(filter_subsuite_test_glob_test),
KUNIT_CASE(filter_subsuite_to_empty_test), KUNIT_CASE(filter_subsuite_to_empty_test),
KUNIT_CASE(filter_suites_test), KUNIT_CASE(filter_suites_test),
{} {}
...@@ -120,14 +188,30 @@ static void kfree_at_end(struct kunit *test, const void *to_free) ...@@ -120,14 +188,30 @@ static void kfree_at_end(struct kunit *test, const void *to_free)
(void *)to_free); (void *)to_free);
} }
static void free_subsuite_res_free(struct kunit_resource *res)
{
kunit_free_subsuite(res->data);
}
static void free_subsuite_at_end(struct kunit *test,
struct kunit_suite *const *to_free)
{
if (IS_ERR_OR_NULL(to_free))
return;
kunit_alloc_resource(test, NULL, free_subsuite_res_free,
GFP_KERNEL, (void *)to_free);
}
static struct kunit_suite *alloc_fake_suite(struct kunit *test, static struct kunit_suite *alloc_fake_suite(struct kunit *test,
const char *suite_name) const char *suite_name,
struct kunit_case *test_cases)
{ {
struct kunit_suite *suite; struct kunit_suite *suite;
/* We normally never expect to allocate suites, hence the non-const cast. */ /* We normally never expect to allocate suites, hence the non-const cast. */
suite = kunit_kzalloc(test, sizeof(*suite), GFP_KERNEL); suite = kunit_kzalloc(test, sizeof(*suite), GFP_KERNEL);
strncpy((char *)suite->name, suite_name, sizeof(suite->name) - 1); strncpy((char *)suite->name, suite_name, sizeof(suite->name) - 1);
suite->test_cases = test_cases;
return suite; return suite;
} }
...@@ -415,12 +415,15 @@ static struct kunit_suite kunit_log_test_suite = { ...@@ -415,12 +415,15 @@ static struct kunit_suite kunit_log_test_suite = {
static void kunit_log_test(struct kunit *test) static void kunit_log_test(struct kunit *test)
{ {
struct kunit_suite *suite = &kunit_log_test_suite; struct kunit_suite suite;
suite.log = kunit_kzalloc(test, KUNIT_LOG_SIZE, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log);
kunit_log(KERN_INFO, test, "put this in log."); kunit_log(KERN_INFO, test, "put this in log.");
kunit_log(KERN_INFO, test, "this too."); kunit_log(KERN_INFO, test, "this too.");
kunit_log(KERN_INFO, suite, "add to suite log."); kunit_log(KERN_INFO, &suite, "add to suite log.");
kunit_log(KERN_INFO, suite, "along with this."); kunit_log(KERN_INFO, &suite, "along with this.");
#ifdef CONFIG_KUNIT_DEBUGFS #ifdef CONFIG_KUNIT_DEBUGFS
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
...@@ -428,12 +431,11 @@ static void kunit_log_test(struct kunit *test) ...@@ -428,12 +431,11 @@ static void kunit_log_test(struct kunit *test)
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
strstr(test->log, "this too.")); strstr(test->log, "this too."));
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
strstr(suite->log, "add to suite log.")); strstr(suite.log, "add to suite log."));
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
strstr(suite->log, "along with this.")); strstr(suite.log, "along with this."));
#else #else
KUNIT_EXPECT_PTR_EQ(test, test->log, (char *)NULL); KUNIT_EXPECT_PTR_EQ(test, test->log, (char *)NULL);
KUNIT_EXPECT_PTR_EQ(test, suite->log, (char *)NULL);
#endif #endif
} }
......
...@@ -190,10 +190,10 @@ enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite) ...@@ -190,10 +190,10 @@ enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite)
} }
EXPORT_SYMBOL_GPL(kunit_suite_has_succeeded); EXPORT_SYMBOL_GPL(kunit_suite_has_succeeded);
static size_t kunit_suite_counter = 1;
static void kunit_print_subtest_end(struct kunit_suite *suite) static void kunit_print_subtest_end(struct kunit_suite *suite)
{ {
static size_t kunit_suite_counter = 1;
kunit_print_ok_not_ok((void *)suite, false, kunit_print_ok_not_ok((void *)suite, false,
kunit_suite_has_succeeded(suite), kunit_suite_has_succeeded(suite),
kunit_suite_counter++, kunit_suite_counter++,
...@@ -583,6 +583,8 @@ void __kunit_test_suites_exit(struct kunit_suite **suites) ...@@ -583,6 +583,8 @@ void __kunit_test_suites_exit(struct kunit_suite **suites)
for (i = 0; suites[i] != NULL; i++) for (i = 0; suites[i] != NULL; i++)
kunit_exit_suite(suites[i]); kunit_exit_suite(suites[i]);
kunit_suite_counter = 1;
} }
EXPORT_SYMBOL_GPL(__kunit_test_suites_exit); EXPORT_SYMBOL_GPL(__kunit_test_suites_exit);
......
...@@ -8,17 +8,17 @@ ...@@ -8,17 +8,17 @@
# Author: Brendan Higgins <brendanhiggins@google.com> # Author: Brendan Higgins <brendanhiggins@google.com>
import argparse import argparse
import sys
import os import os
import re
import sys
import time import time
assert sys.version_info >= (3, 7), "Python version is too old" assert sys.version_info >= (3, 7), "Python version is too old"
from collections import namedtuple from collections import namedtuple
from enum import Enum, auto from enum import Enum, auto
from typing import Iterable, Sequence from typing import Iterable, Sequence, List
import kunit_config
import kunit_json import kunit_json
import kunit_kernel import kunit_kernel
import kunit_parser import kunit_parser
...@@ -31,13 +31,13 @@ KunitBuildRequest = namedtuple('KunitBuildRequest', ...@@ -31,13 +31,13 @@ KunitBuildRequest = namedtuple('KunitBuildRequest',
['jobs', 'build_dir', 'alltests', ['jobs', 'build_dir', 'alltests',
'make_options']) 'make_options'])
KunitExecRequest = namedtuple('KunitExecRequest', KunitExecRequest = namedtuple('KunitExecRequest',
['timeout', 'build_dir', 'alltests', ['timeout', 'build_dir', 'alltests',
'filter_glob', 'kernel_args']) 'filter_glob', 'kernel_args', 'run_isolated'])
KunitParseRequest = namedtuple('KunitParseRequest', KunitParseRequest = namedtuple('KunitParseRequest',
['raw_output', 'input_data', 'build_dir', 'json']) ['raw_output', 'build_dir', 'json'])
KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs',
'build_dir', 'alltests', 'filter_glob', 'build_dir', 'alltests', 'filter_glob',
'kernel_args', 'json', 'make_options']) 'kernel_args', 'run_isolated', 'json', 'make_options'])
KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0] KernelDirectoryPath = sys.argv[0].split('tools/testing/kunit/')[0]
...@@ -91,31 +91,93 @@ def build_tests(linux: kunit_kernel.LinuxSourceTree, ...@@ -91,31 +91,93 @@ def build_tests(linux: kunit_kernel.LinuxSourceTree,
'built kernel successfully', 'built kernel successfully',
build_end - build_start) build_end - build_start)
def exec_tests(linux: kunit_kernel.LinuxSourceTree, def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]:
request: KunitExecRequest) -> KunitResult: args = ['kunit.action=list']
kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') if request.kernel_args:
test_start = time.time() args.extend(request.kernel_args)
result = linux.run_kernel(
args=request.kernel_args, output = linux.run_kernel(args=args,
timeout=None if request.alltests else request.timeout, timeout=None if request.alltests else request.timeout,
filter_glob=request.filter_glob, filter_glob=request.filter_glob,
build_dir=request.build_dir) build_dir=request.build_dir)
lines = kunit_parser.extract_tap_lines(output)
test_end = time.time() # Hack! Drop the dummy TAP version header that the executor prints out.
lines.pop()
return KunitResult(KunitStatus.SUCCESS,
result, # Filter out any extraneous non-test output that might have gotten mixed in.
test_end - test_start) return [l for l in lines if re.match('^[^\s.]+\.[^\s.]+$', l)]
def _suites_from_test_list(tests: List[str]) -> List[str]:
"""Extracts all the suites from an ordered list of tests."""
suites = [] # type: List[str]
for t in tests:
parts = t.split('.', maxsplit=2)
if len(parts) != 2:
raise ValueError(f'internal KUnit error, test name should be of the form "<suite>.<test>", got "{t}"')
suite, case = parts
if not suites or suites[-1] != suite:
suites.append(suite)
return suites
def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest,
parse_request: KunitParseRequest) -> KunitResult:
filter_globs = [request.filter_glob]
if request.run_isolated:
tests = _list_tests(linux, request)
if request.run_isolated == 'test':
filter_globs = tests
if request.run_isolated == 'suite':
filter_globs = _suites_from_test_list(tests)
# Apply the test-part of the user's glob, if present.
if '.' in request.filter_glob:
test_glob = request.filter_glob.split('.', maxsplit=2)[1]
filter_globs = [g + '.'+ test_glob for g in filter_globs]
test_counts = kunit_parser.TestCounts()
exec_time = 0.0
for i, filter_glob in enumerate(filter_globs):
kunit_parser.print_with_timestamp('Starting KUnit Kernel ({}/{})...'.format(i+1, len(filter_globs)))
test_start = time.time()
run_result = linux.run_kernel(
args=request.kernel_args,
timeout=None if request.alltests else request.timeout,
filter_glob=filter_glob,
build_dir=request.build_dir)
result = parse_tests(parse_request, run_result)
# run_kernel() doesn't block on the kernel exiting.
# That only happens after we get the last line of output from `run_result`.
# So exec_time here actually contains parsing + execution time, which is fine.
test_end = time.time()
exec_time += test_end - test_start
test_counts.add_subtest_counts(result.result.test.counts)
kunit_status = _map_to_overall_status(test_counts.get_status())
return KunitResult(status=kunit_status, result=result.result, elapsed_time=exec_time)
def _map_to_overall_status(test_status: kunit_parser.TestStatus) -> KunitStatus:
if test_status in (kunit_parser.TestStatus.SUCCESS, kunit_parser.TestStatus.SKIPPED):
return KunitStatus.SUCCESS
else:
return KunitStatus.TEST_FAILURE
def parse_tests(request: KunitParseRequest) -> KunitResult: def parse_tests(request: KunitParseRequest, input_data: Iterable[str]) -> KunitResult:
parse_start = time.time() parse_start = time.time()
test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS, test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS,
[], kunit_parser.Test(),
'Tests not Parsed.') 'Tests not Parsed.')
if request.raw_output: if request.raw_output:
output: Iterable[str] = request.input_data # Treat unparsed results as one passing test.
test_result.test.status = kunit_parser.TestStatus.SUCCESS
test_result.test.counts.passed = 1
output: Iterable[str] = input_data
if request.raw_output == 'all': if request.raw_output == 'all':
pass pass
elif request.raw_output == 'kunit': elif request.raw_output == 'kunit':
...@@ -126,7 +188,7 @@ def parse_tests(request: KunitParseRequest) -> KunitResult: ...@@ -126,7 +188,7 @@ def parse_tests(request: KunitParseRequest) -> KunitResult:
print(line.rstrip()) print(line.rstrip())
else: else:
test_result = kunit_parser.parse_run_tests(request.input_data) test_result = kunit_parser.parse_run_tests(input_data)
parse_end = time.time() parse_end = time.time()
if request.json: if request.json:
...@@ -164,16 +226,12 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree, ...@@ -164,16 +226,12 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree,
exec_request = KunitExecRequest(request.timeout, request.build_dir, exec_request = KunitExecRequest(request.timeout, request.build_dir,
request.alltests, request.filter_glob, request.alltests, request.filter_glob,
request.kernel_args) request.kernel_args, request.run_isolated)
exec_result = exec_tests(linux, exec_request)
if exec_result.status != KunitStatus.SUCCESS:
return exec_result
parse_request = KunitParseRequest(request.raw_output, parse_request = KunitParseRequest(request.raw_output,
exec_result.result,
request.build_dir, request.build_dir,
request.json) request.json)
parse_result = parse_tests(parse_request)
exec_result = exec_tests(linux, exec_request, parse_request)
run_end = time.time() run_end = time.time()
...@@ -184,7 +242,7 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree, ...@@ -184,7 +242,7 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree,
config_result.elapsed_time, config_result.elapsed_time,
build_result.elapsed_time, build_result.elapsed_time,
exec_result.elapsed_time)) exec_result.elapsed_time))
return parse_result return exec_result
# Problem: # Problem:
# $ kunit.py run --json # $ kunit.py run --json
...@@ -263,9 +321,8 @@ def add_exec_opts(parser) -> None: ...@@ -263,9 +321,8 @@ def add_exec_opts(parser) -> None:
default=300, default=300,
metavar='timeout') metavar='timeout')
parser.add_argument('filter_glob', parser.add_argument('filter_glob',
help='maximum number of seconds to allow for all tests ' help='Filter which KUnit test suites/tests run at '
'to run. This does not include time taken to build the ' 'boot-time, e.g. list* or list*.*del_test',
'tests.',
type=str, type=str,
nargs='?', nargs='?',
default='', default='',
...@@ -273,6 +330,12 @@ def add_exec_opts(parser) -> None: ...@@ -273,6 +330,12 @@ def add_exec_opts(parser) -> None:
parser.add_argument('--kernel_args', parser.add_argument('--kernel_args',
help='Kernel command-line parameters. Maybe be repeated', help='Kernel command-line parameters. Maybe be repeated',
action='append') action='append')
parser.add_argument('--run_isolated', help='If set, boot the kernel for each '
'individual suite/test. This is can be useful for debugging '
'a non-hermetic test, one that might pass/fail based on '
'what ran before it.',
type=str,
choices=['suite', 'test']),
def add_parse_opts(parser) -> None: def add_parse_opts(parser) -> None:
parser.add_argument('--raw_output', help='If set don\'t format output from kernel. ' parser.add_argument('--raw_output', help='If set don\'t format output from kernel. '
...@@ -346,6 +409,7 @@ def main(argv, linux=None): ...@@ -346,6 +409,7 @@ def main(argv, linux=None):
cli_args.alltests, cli_args.alltests,
cli_args.filter_glob, cli_args.filter_glob,
cli_args.kernel_args, cli_args.kernel_args,
cli_args.run_isolated,
cli_args.json, cli_args.json,
cli_args.make_options) cli_args.make_options)
result = run_tests(linux, request) result = run_tests(linux, request)
...@@ -401,29 +465,27 @@ def main(argv, linux=None): ...@@ -401,29 +465,27 @@ def main(argv, linux=None):
cli_args.build_dir, cli_args.build_dir,
cli_args.alltests, cli_args.alltests,
cli_args.filter_glob, cli_args.filter_glob,
cli_args.kernel_args) cli_args.kernel_args,
exec_result = exec_tests(linux, exec_request) cli_args.run_isolated)
parse_request = KunitParseRequest(cli_args.raw_output, parse_request = KunitParseRequest(cli_args.raw_output,
exec_result.result,
cli_args.build_dir, cli_args.build_dir,
cli_args.json) cli_args.json)
result = parse_tests(parse_request) result = exec_tests(linux, exec_request, parse_request)
kunit_parser.print_with_timestamp(( kunit_parser.print_with_timestamp((
'Elapsed time: %.3fs\n') % ( 'Elapsed time: %.3fs\n') % (result.elapsed_time))
exec_result.elapsed_time))
if result.status != KunitStatus.SUCCESS: if result.status != KunitStatus.SUCCESS:
sys.exit(1) sys.exit(1)
elif cli_args.subcommand == 'parse': elif cli_args.subcommand == 'parse':
if cli_args.file == None: if cli_args.file == None:
sys.stdin.reconfigure(errors='backslashreplace') # pytype: disable=attribute-error
kunit_output = sys.stdin kunit_output = sys.stdin
else: else:
with open(cli_args.file, 'r') as f: with open(cli_args.file, 'r', errors='backslashreplace') as f:
kunit_output = f.read().splitlines() kunit_output = f.read().splitlines()
request = KunitParseRequest(cli_args.raw_output, request = KunitParseRequest(cli_args.raw_output,
kunit_output,
None, None,
cli_args.json) cli_args.json)
result = parse_tests(request) result = parse_tests(request, kunit_output)
if result.status != KunitStatus.SUCCESS: if result.status != KunitStatus.SUCCESS:
sys.exit(1) sys.exit(1)
else: else:
......
...@@ -11,47 +11,47 @@ import os ...@@ -11,47 +11,47 @@ import os
import kunit_parser import kunit_parser
from kunit_parser import TestStatus from kunit_parser import Test, TestResult, TestStatus
from typing import Any, Dict, Optional
def get_json_result(test_result, def_config, build_dir, json_path) -> str:
sub_groups = [] JsonObj = Dict[str, Any]
# Each test suite is mapped to a KernelCI sub_group def _get_group_json(test: Test, def_config: str,
for test_suite in test_result.suites: build_dir: Optional[str]) -> JsonObj:
sub_group = { sub_groups = [] # List[JsonObj]
"name": test_suite.name, test_cases = [] # List[JsonObj]
"arch": "UM",
"defconfig": def_config, for subtest in test.subtests:
"build_environment": build_dir, if len(subtest.subtests):
"test_cases": [], sub_group = _get_group_json(subtest, def_config,
"lab_name": None, build_dir)
"kernel": None, sub_groups.append(sub_group)
"job": None, else:
"git_branch": "kselftest", test_case = {"name": subtest.name, "status": "FAIL"}
} if subtest.status == TestStatus.SUCCESS:
test_cases = []
# TODO: Add attachments attribute in test_case with detailed
# failure message, see https://api.kernelci.org/schema-test-case.html#get
for case in test_suite.cases:
test_case = {"name": case.name, "status": "FAIL"}
if case.status == TestStatus.SUCCESS:
test_case["status"] = "PASS" test_case["status"] = "PASS"
elif case.status == TestStatus.TEST_CRASHED: elif subtest.status == TestStatus.TEST_CRASHED:
test_case["status"] = "ERROR" test_case["status"] = "ERROR"
test_cases.append(test_case) test_cases.append(test_case)
sub_group["test_cases"] = test_cases
sub_groups.append(sub_group)
test_group = { test_group = {
"name": "KUnit Test Group", "name": test.name,
"arch": "UM", "arch": "UM",
"defconfig": def_config, "defconfig": def_config,
"build_environment": build_dir, "build_environment": build_dir,
"sub_groups": sub_groups, "sub_groups": sub_groups,
"test_cases": test_cases,
"lab_name": None, "lab_name": None,
"kernel": None, "kernel": None,
"job": None, "job": None,
"git_branch": "kselftest", "git_branch": "kselftest",
} }
return test_group
def get_json_result(test_result: TestResult, def_config: str,
build_dir: Optional[str], json_path: str) -> str:
test_group = _get_group_json(test_result.test, def_config, build_dir)
test_group["name"] = "KUnit Test Group"
json_obj = json.dumps(test_group, indent=4) json_obj = json.dumps(test_group, indent=4)
if json_path != 'stdout': if json_path != 'stdout':
with open(json_path, 'w') as result_path: with open(json_path, 'w') as result_path:
......
...@@ -12,11 +12,8 @@ import subprocess ...@@ -12,11 +12,8 @@ import subprocess
import os import os
import shutil import shutil
import signal import signal
from typing import Iterator, Optional, Tuple import threading
from typing import Iterator, List, Optional, Tuple
from contextlib import ExitStack
from collections import namedtuple
import kunit_config import kunit_config
import kunit_parser import kunit_parser
...@@ -103,8 +100,8 @@ class LinuxSourceTreeOperations(object): ...@@ -103,8 +100,8 @@ class LinuxSourceTreeOperations(object):
if stderr: # likely only due to build warnings if stderr: # likely only due to build warnings
print(stderr.decode()) print(stderr.decode())
def run(self, params, timeout, build_dir, outfile) -> None: def start(self, params: List[str], build_dir: str) -> subprocess.Popen:
pass raise RuntimeError('not implemented!')
class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations):
...@@ -123,7 +120,7 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): ...@@ -123,7 +120,7 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations):
kconfig.parse_from_string(self._kconfig) kconfig.parse_from_string(self._kconfig)
base_kunitconfig.merge_in_entries(kconfig) base_kunitconfig.merge_in_entries(kconfig)
def run(self, params, timeout, build_dir, outfile): def start(self, params: List[str], build_dir: str) -> subprocess.Popen:
kernel_path = os.path.join(build_dir, self._kernel_path) kernel_path = os.path.join(build_dir, self._kernel_path)
qemu_command = ['qemu-system-' + self._qemu_arch, qemu_command = ['qemu-system-' + self._qemu_arch,
'-nodefaults', '-nodefaults',
...@@ -134,18 +131,11 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): ...@@ -134,18 +131,11 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations):
'-nographic', '-nographic',
'-serial stdio'] + self._extra_qemu_params '-serial stdio'] + self._extra_qemu_params
print('Running tests with:\n$', ' '.join(qemu_command)) print('Running tests with:\n$', ' '.join(qemu_command))
with open(outfile, 'w') as output: return subprocess.Popen(' '.join(qemu_command),
process = subprocess.Popen(' '.join(qemu_command), stdin=subprocess.PIPE,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stdout=output, stderr=subprocess.STDOUT,
stderr=subprocess.STDOUT, text=True, shell=True, errors='backslashreplace')
text=True, shell=True)
try:
process.wait(timeout=timeout)
except Exception as e:
print(e)
process.terminate()
return process
class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations):
"""An abstraction over command line operations performed on a source tree.""" """An abstraction over command line operations performed on a source tree."""
...@@ -168,24 +158,21 @@ class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): ...@@ -168,24 +158,21 @@ class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations):
process.wait() process.wait()
kunit_parser.print_with_timestamp( kunit_parser.print_with_timestamp(
'Disabling broken configs to run KUnit tests...') 'Disabling broken configs to run KUnit tests...')
with ExitStack() as es:
config = open(get_kconfig_path(build_dir), 'a') with open(get_kconfig_path(build_dir), 'a') as config:
disable = open(BROKEN_ALLCONFIG_PATH, 'r').read() with open(BROKEN_ALLCONFIG_PATH, 'r') as disable:
config.write(disable) config.write(disable.read())
kunit_parser.print_with_timestamp( kunit_parser.print_with_timestamp(
'Starting Kernel with all configs takes a few minutes...') 'Starting Kernel with all configs takes a few minutes...')
def run(self, params, timeout, build_dir, outfile): def start(self, params: List[str], build_dir: str) -> subprocess.Popen:
"""Runs the Linux UML binary. Must be named 'linux'.""" """Runs the Linux UML binary. Must be named 'linux'."""
linux_bin = get_file_path(build_dir, 'linux') linux_bin = get_file_path(build_dir, 'linux')
outfile = get_outfile_path(build_dir) return subprocess.Popen([linux_bin] + params,
with open(outfile, 'w') as output: stdin=subprocess.PIPE,
process = subprocess.Popen([linux_bin] + params, stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
stdout=output, text=True, errors='backslashreplace')
stderr=subprocess.STDOUT,
text=True)
process.wait(timeout)
def get_kconfig_path(build_dir) -> str: def get_kconfig_path(build_dir) -> str:
return get_file_path(build_dir, KCONFIG_PATH) return get_file_path(build_dir, KCONFIG_PATH)
...@@ -202,8 +189,9 @@ def get_source_tree_ops(arch: str, cross_compile: Optional[str]) -> LinuxSourceT ...@@ -202,8 +189,9 @@ def get_source_tree_ops(arch: str, cross_compile: Optional[str]) -> LinuxSourceT
return LinuxSourceTreeOperationsUml(cross_compile=cross_compile) return LinuxSourceTreeOperationsUml(cross_compile=cross_compile)
elif os.path.isfile(config_path): elif os.path.isfile(config_path):
return get_source_tree_ops_from_qemu_config(config_path, cross_compile)[1] return get_source_tree_ops_from_qemu_config(config_path, cross_compile)[1]
else:
raise ConfigError(arch + ' is not a valid arch') options = [f[:-3] for f in os.listdir(QEMU_CONFIGS_DIR) if f.endswith('.py')]
raise ConfigError(arch + ' is not a valid arch, options are ' + str(sorted(options)))
def get_source_tree_ops_from_qemu_config(config_path: str, def get_source_tree_ops_from_qemu_config(config_path: str,
cross_compile: Optional[str]) -> Tuple[ cross_compile: Optional[str]) -> Tuple[
...@@ -219,12 +207,15 @@ def get_source_tree_ops_from_qemu_config(config_path: str, ...@@ -219,12 +207,15 @@ def get_source_tree_ops_from_qemu_config(config_path: str,
module_path = '.' + os.path.join(os.path.basename(QEMU_CONFIGS_DIR), os.path.basename(config_path)) module_path = '.' + os.path.join(os.path.basename(QEMU_CONFIGS_DIR), os.path.basename(config_path))
spec = importlib.util.spec_from_file_location(module_path, config_path) spec = importlib.util.spec_from_file_location(module_path, config_path)
config = importlib.util.module_from_spec(spec) config = importlib.util.module_from_spec(spec)
# TODO(brendanhiggins@google.com): I looked this up and apparently other # See https://github.com/python/typeshed/pull/2626 for context.
# Python projects have noted that pytype complains that "No attribute assert isinstance(spec.loader, importlib.abc.Loader)
# 'exec_module' on _importlib_modulespec._Loader". Disabling for now. spec.loader.exec_module(config)
spec.loader.exec_module(config) # pytype: disable=attribute-error
return config.QEMU_ARCH.linux_arch, LinuxSourceTreeOperationsQemu( if not hasattr(config, 'QEMU_ARCH'):
config.QEMU_ARCH, cross_compile=cross_compile) raise ValueError('qemu_config module missing "QEMU_ARCH": ' + config_path)
params: qemu_config.QemuArchParams = config.QEMU_ARCH # type: ignore
return params.linux_arch, LinuxSourceTreeOperationsQemu(
params, cross_compile=cross_compile)
class LinuxSourceTree(object): class LinuxSourceTree(object):
"""Represents a Linux kernel source tree with KUnit tests.""" """Represents a Linux kernel source tree with KUnit tests."""
...@@ -330,12 +321,36 @@ class LinuxSourceTree(object): ...@@ -330,12 +321,36 @@ class LinuxSourceTree(object):
args.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) args.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt'])
if filter_glob: if filter_glob:
args.append('kunit.filter_glob='+filter_glob) args.append('kunit.filter_glob='+filter_glob)
outfile = get_outfile_path(build_dir)
self._ops.run(args, timeout, build_dir, outfile) process = self._ops.start(args, build_dir)
subprocess.call(['stty', 'sane']) assert process.stdout is not None # tell mypy it's set
with open(outfile, 'r') as file:
for line in file: # Enforce the timeout in a background thread.
def _wait_proc():
try:
process.wait(timeout=timeout)
except Exception as e:
print(e)
process.terminate()
process.wait()
waiter = threading.Thread(target=_wait_proc)
waiter.start()
output = open(get_outfile_path(build_dir), 'w')
try:
# Tee the output to the file and to our caller in real time.
for line in process.stdout:
output.write(line)
yield line yield line
# This runs even if our caller doesn't consume every line.
finally:
# Flush any leftover output to the file
output.write(process.stdout.read())
output.close()
process.stdout.close()
waiter.join()
subprocess.call(['stty', 'sane'])
def signal_handler(self, sig, frame) -> None: def signal_handler(self, sig, frame) -> None:
logging.error('Build interruption occurred. Cleaning console.') logging.error('Build interruption occurred. Cleaning console.')
......
This diff is collapsed.
This diff is collapsed.
TAP version 14
1..2
# Subtest: sysctl_test
1..4
# sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed
ok 1 - sysctl_test_dointvec_null_tbl_data
# Subtest: example
1..2
init_suite
# example_simple_test: initializing
# example_simple_test: example_simple_test passed
ok 1 - example_simple_test
# example_mock_test: initializing
# example_mock_test: example_mock_test passed
ok 2 - example_mock_test
kunit example: all tests passed
ok 2 - example
# sysctl_test_dointvec_table_len_is_zero: sysctl_test_dointvec_table_len_is_zero passed
ok 3 - sysctl_test_dointvec_table_len_is_zero
# sysctl_test_dointvec_table_read_but_position_set: sysctl_test_dointvec_table_read_but_position_set passed
ok 4 - sysctl_test_dointvec_table_read_but_position_set
kunit sysctl_test: all tests passed
ok 1 - sysctl_test
# Subtest: example
1..2
init_suite
# example_simple_test: initializing
# example_simple_test: example_simple_test passed
ok 1 - example_simple_test
# example_mock_test: initializing
# example_mock_test: example_mock_test passed
ok 2 - example_mock_test
kunit example: all tests passed
ok 2 - example
TAP version 13
1..2
# selftests: membarrier: membarrier_test_single_thread
# TAP version 13
# 1..2
# ok 1 sys_membarrier available
# ok 2 sys membarrier invalid command test: command = -1, flags = 0, errno = 22. Failed as expected
ok 1 selftests: membarrier: membarrier_test_single_thread
# selftests: membarrier: membarrier_test_multi_thread
# TAP version 13
# 1..2
# ok 1 sys_membarrier available
# ok 2 sys membarrier invalid command test: command = -1, flags = 0, errno = 22. Failed as expected
ok 2 selftests: membarrier: membarrier_test_multi_thread
KTAP version 1
# Subtest: sysctl_test
# sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed
ok 1 - sysctl_test_dointvec_null_tbl_data
# sysctl_test_dointvec_table_maxlen_unset: sysctl_test_dointvec_table_maxlen_unset passed
ok 2 - sysctl_test_dointvec_table_maxlen_unset
# sysctl_test_dointvec_table_len_is_zero: sysctl_test_dointvec_table_len_is_zero passed
ok 3 - sysctl_test_dointvec_table_len_is_zero
# sysctl_test_dointvec_table_read_but_position_set: sysctl_test_dointvec_table_read_but_position_set passed
ok 4 - sysctl_test_dointvec_table_read_but_position_set
# sysctl_test_dointvec_happy_single_positive: sysctl_test_dointvec_happy_single_positive passed
ok 5 - sysctl_test_dointvec_happy_single_positive
# sysctl_test_dointvec_happy_single_negative: sysctl_test_dointvec_happy_single_negative passed
ok 6 - sysctl_test_dointvec_happy_single_negative
# sysctl_test_dointvec_single_less_int_min: sysctl_test_dointvec_single_less_int_min passed
ok 7 - sysctl_test_dointvec_single_less_int_min
# sysctl_test_dointvec_single_greater_int_max: sysctl_test_dointvec_single_greater_int_max passed
ok 8 - sysctl_test_dointvec_single_greater_int_max
kunit sysctl_test: all tests passed
ok 1 - sysctl_test
# Subtest: example
1..2
init_suite
# example_simple_test: initializing
# example_simple_test: example_simple_test passed
ok 1 - example_simple_test
# example_mock_test: initializing
# example_mock_test: example_mock_test passed
ok 2 - example_mock_test
kunit example: all tests passed
ok 2 - example
KTAP version 1
1..2
# Subtest: sysctl_test
1..1
# sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed
ok 1 - sysctl_test_dointvec_null_tbl_data
kunit sysctl_test: all tests passed
ok 1 - sysctl_test
# Subtest: example
1..1
init_suite
# example_simple_test: initializing
# example_simple_test: example_simple_test passed
ok 1 example_simple_test
kunit example: all tests passed
ok 2 example
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment