diff options
Diffstat (limited to 'lib/kunit')
| -rw-r--r-- | lib/kunit/.kunitconfig | 3 | ||||
| -rw-r--r-- | lib/kunit/Kconfig | 144 | ||||
| -rw-r--r-- | lib/kunit/Makefile | 31 | ||||
| -rw-r--r-- | lib/kunit/assert.c | 275 | ||||
| -rw-r--r-- | lib/kunit/assert_test.c | 388 | ||||
| -rw-r--r-- | lib/kunit/attributes.c | 474 | ||||
| -rw-r--r-- | lib/kunit/debugfs.c | 230 | ||||
| -rw-r--r-- | lib/kunit/debugfs.h | 30 | ||||
| -rw-r--r-- | lib/kunit/device-impl.h | 19 | ||||
| -rw-r--r-- | lib/kunit/device.c | 202 | ||||
| -rw-r--r-- | lib/kunit/executor.c | 429 | ||||
| -rw-r--r-- | lib/kunit/executor_test.c | 293 | ||||
| -rw-r--r-- | lib/kunit/hooks-impl.h | 31 | ||||
| -rw-r--r-- | lib/kunit/hooks.c | 21 | ||||
| -rw-r--r-- | lib/kunit/kunit-example-test.c | 595 | ||||
| -rw-r--r-- | lib/kunit/kunit-test.c | 924 | ||||
| -rw-r--r-- | lib/kunit/platform-test.c | 224 | ||||
| -rw-r--r-- | lib/kunit/platform.c | 302 | ||||
| -rw-r--r-- | lib/kunit/resource.c | 178 | ||||
| -rw-r--r-- | lib/kunit/static_stub.c | 123 | ||||
| -rw-r--r-- | lib/kunit/string-stream-test.c | 537 | ||||
| -rw-r--r-- | lib/kunit/string-stream.c | 207 | ||||
| -rw-r--r-- | lib/kunit/string-stream.h | 62 | ||||
| -rw-r--r-- | lib/kunit/test.c | 1063 | ||||
| -rw-r--r-- | lib/kunit/try-catch-impl.h | 29 | ||||
| -rw-r--r-- | lib/kunit/try-catch.c | 91 | ||||
| -rw-r--r-- | lib/kunit/user_alloc.c | 117 |
27 files changed, 7022 insertions, 0 deletions
diff --git a/lib/kunit/.kunitconfig b/lib/kunit/.kunitconfig new file mode 100644 index 000000000000..9235b7d42d38 --- /dev/null +++ b/lib/kunit/.kunitconfig @@ -0,0 +1,3 @@ +CONFIG_KUNIT=y +CONFIG_KUNIT_TEST=y +CONFIG_KUNIT_EXAMPLE_TEST=y diff --git a/lib/kunit/Kconfig b/lib/kunit/Kconfig new file mode 100644 index 000000000000..50ecf55d2b9c --- /dev/null +++ b/lib/kunit/Kconfig @@ -0,0 +1,144 @@ +# +# KUnit base configuration +# + +menuconfig KUNIT + tristate "KUnit - Enable support for unit tests" + select GLOB + help + Enables support for kernel unit tests (KUnit), a lightweight unit + testing and mocking framework for the Linux kernel. These tests are + able to be run locally on a developer's workstation without a VM or + special hardware when using UML. Can also be used on most other + architectures. For more information, please see + Documentation/dev-tools/kunit/. + +if KUNIT + +config KUNIT_DEBUGFS + bool "KUnit - Enable /sys/kernel/debug/kunit debugfs representation" if !KUNIT_ALL_TESTS + default KUNIT_ALL_TESTS + help + Enable debugfs representation for kunit. Currently this consists + of /sys/kernel/debug/kunit/<test_suite>/results files for each + test suite, which allow users to see results of the last test suite + run that occurred. + +config KUNIT_FAULT_TEST + bool "Enable KUnit tests which print BUG stacktraces" + depends on KUNIT_TEST + depends on !UML + default y + help + Enables fault handling tests for the KUnit framework. These tests may + trigger a kernel BUG(), and the associated stack trace, even when they + pass. If this conflicts with your test infrastrcture (or is confusing + or annoying), they can be disabled by setting this to N. + +config KUNIT_TEST + tristate "KUnit test for KUnit" if !KUNIT_ALL_TESTS + default KUNIT_ALL_TESTS + help + Enables the unit tests for the KUnit test framework. These tests test + the KUnit test framework itself; the tests are both written using + KUnit and test KUnit. This option should only be enabled for testing + purposes by developers interested in testing that KUnit works as + expected. + +config KUNIT_EXAMPLE_TEST + tristate "Example test for KUnit" if !KUNIT_ALL_TESTS + default KUNIT_ALL_TESTS + help + Enables an example unit test that illustrates some of the basic + features of KUnit. This test only exists to help new users understand + what KUnit is and how it is used. Please refer to the example test + itself, lib/kunit/example-test.c, for more information. This option + is intended for curious hackers who would like to understand how to + use KUnit for kernel development. + +config KUNIT_ALL_TESTS + tristate "All KUnit tests with satisfied dependencies" + help + Enables all KUnit tests, if they can be enabled. + KUnit tests run during boot and output the results to the debug log + in TAP format (http://testanything.org/). Only useful for kernel devs + running the KUnit test harness, and not intended for inclusion into a + production build. + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + +config KUNIT_DEFAULT_ENABLED + bool "Default value of kunit.enable" + default y + help + Sets the default value of kunit.enable. If set to N then KUnit + tests will not execute unless kunit.enable=1 is passed to the + kernel command line. + + In most cases this should be left as Y. Only if additional opt-in + behavior is needed should this be set to N. + +config KUNIT_AUTORUN_ENABLED + bool "Default value of kunit.autorun" + default y + help + Sets the default value of kunit.autorun. If set to N then KUnit + tests will not run after initialization unless kunit.autorun=1 is + passed to the kernel command line. The test can still be run manually + via debugfs interface. + + In most cases this should be left as Y. Only if additional opt-in + behavior is needed should this be set to N. + +config KUNIT_DEFAULT_FILTER_GLOB + string "Default value of the filter_glob module parameter" + help + Sets the default value of kunit.filter_glob. If set to a non-empty + string only matching tests are executed. + + If unsure, leave empty so all tests are executed. + +config KUNIT_DEFAULT_FILTER + string "Default value of the filter module parameter" + help + Sets the default value of kunit.filter. If set to a non-empty + string only matching tests are executed. + + If unsure, leave empty so all tests are executed. + +config KUNIT_DEFAULT_FILTER_ACTION + string "Default value of the filter_action module parameter" + help + Sets the default value of kunit.filter_action. If set to a non-empty + string only matching tests are executed. + + If unsure, leave empty so all tests are executed. + +config KUNIT_DEFAULT_TIMEOUT + int "Default value of the timeout module parameter" + default 300 + help + Sets the default timeout, in seconds, for Kunit test cases. This value + is further multiplied by a factor determined by the assigned speed + setting: 1x for `DEFAULT`, 3x for `KUNIT_SPEED_SLOW`, and 12x for + `KUNIT_SPEED_VERY_SLOW`. This allows slower tests on slower machines + sufficient time to complete. + + If unsure, the default timeout of 300 seconds is suitable for most + cases. + +config KUNIT_UML_PCI + bool "KUnit UML PCI Support" + depends on UML + select UML_PCI + help + Enables the PCI subsystem on UML for use by KUnit tests. + Some KUnit tests require the PCI core which is not enabled by + default on UML. + + If unsure, say N. + +endif # KUNIT diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile new file mode 100644 index 000000000000..656f1fa35abc --- /dev/null +++ b/lib/kunit/Makefile @@ -0,0 +1,31 @@ +obj-$(CONFIG_KUNIT) += kunit.o + +kunit-objs += test.o \ + resource.o \ + user_alloc.o \ + static_stub.o \ + string-stream.o \ + assert.o \ + try-catch.o \ + executor.o \ + attributes.o \ + device.o \ + platform.o + +ifeq ($(CONFIG_KUNIT_DEBUGFS),y) +kunit-objs += debugfs.o +endif + +# KUnit 'hooks' are built-in even when KUnit is built as a module. +obj-$(if $(CONFIG_KUNIT),y) += hooks.o + +obj-$(CONFIG_KUNIT_TEST) += kunit-test.o +obj-$(CONFIG_KUNIT_TEST) += platform-test.o + +# string-stream-test compiles built-in only. +ifeq ($(CONFIG_KUNIT_TEST),y) +obj-$(CONFIG_KUNIT_TEST) += string-stream-test.o +obj-$(CONFIG_KUNIT_TEST) += assert_test.o +endif + +obj-$(CONFIG_KUNIT_EXAMPLE_TEST) += kunit-example-test.o diff --git a/lib/kunit/assert.c b/lib/kunit/assert.c new file mode 100644 index 000000000000..867aa5c4bccf --- /dev/null +++ b/lib/kunit/assert.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Assertion and expectation serialization API. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ +#include <kunit/assert.h> +#include <kunit/test.h> +#include <kunit/visibility.h> + +#include "string-stream.h" + +void kunit_assert_prologue(const struct kunit_loc *loc, + enum kunit_assert_type type, + struct string_stream *stream) +{ + const char *expect_or_assert = NULL; + + switch (type) { + case KUNIT_EXPECTATION: + expect_or_assert = "EXPECTATION"; + break; + case KUNIT_ASSERTION: + expect_or_assert = "ASSERTION"; + break; + } + + string_stream_add(stream, "%s FAILED at %s:%d\n", + expect_or_assert, loc->file, loc->line); +} +EXPORT_SYMBOL_GPL(kunit_assert_prologue); + +VISIBLE_IF_KUNIT +void kunit_assert_print_msg(const struct va_format *message, + struct string_stream *stream) +{ + if (message->fmt) + string_stream_add(stream, "\n%pV", message); +} + +void kunit_fail_assert_format(const struct kunit_assert *assert, + const struct va_format *message, + struct string_stream *stream) +{ + string_stream_add(stream, "%pV", message); +} +EXPORT_SYMBOL_GPL(kunit_fail_assert_format); + +void kunit_unary_assert_format(const struct kunit_assert *assert, + const struct va_format *message, + struct string_stream *stream) +{ + struct kunit_unary_assert *unary_assert; + + unary_assert = container_of(assert, struct kunit_unary_assert, assert); + + if (unary_assert->expected_true) + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s to be true, but is false\n", + unary_assert->condition); + else + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s to be false, but is true\n", + unary_assert->condition); + kunit_assert_print_msg(message, stream); +} +EXPORT_SYMBOL_GPL(kunit_unary_assert_format); + +void kunit_ptr_not_err_assert_format(const struct kunit_assert *assert, + const struct va_format *message, + struct string_stream *stream) +{ + struct kunit_ptr_not_err_assert *ptr_assert; + + ptr_assert = container_of(assert, struct kunit_ptr_not_err_assert, + assert); + + if (!ptr_assert->value) { + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s is not null, but is\n", + ptr_assert->text); + } else if (IS_ERR(ptr_assert->value)) { + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s is not error, but is: %ld\n", + ptr_assert->text, + PTR_ERR(ptr_assert->value)); + } + kunit_assert_print_msg(message, stream); +} +EXPORT_SYMBOL_GPL(kunit_ptr_not_err_assert_format); + +/* Checks if `text` is a literal representing `value`, e.g. "5" and 5 */ +VISIBLE_IF_KUNIT bool is_literal(const char *text, long long value) +{ + char *buffer; + int len; + bool ret; + + len = snprintf(NULL, 0, "%lld", value); + if (strlen(text) != len) + return false; + + buffer = kmalloc(len+1, GFP_KERNEL); + if (!buffer) + return false; + + snprintf(buffer, len+1, "%lld", value); + ret = strncmp(buffer, text, len) == 0; + + kfree(buffer); + + return ret; +} + +void kunit_binary_assert_format(const struct kunit_assert *assert, + const struct va_format *message, + struct string_stream *stream) +{ + struct kunit_binary_assert *binary_assert; + + binary_assert = container_of(assert, struct kunit_binary_assert, + assert); + + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s %s %s, but\n", + binary_assert->text->left_text, + binary_assert->text->operation, + binary_assert->text->right_text); + if (!is_literal(binary_assert->text->left_text, binary_assert->left_value)) + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld (0x%llx)\n", + binary_assert->text->left_text, + binary_assert->left_value, + binary_assert->left_value); + if (!is_literal(binary_assert->text->right_text, binary_assert->right_value)) + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld (0x%llx)", + binary_assert->text->right_text, + binary_assert->right_value, + binary_assert->right_value); + kunit_assert_print_msg(message, stream); +} +EXPORT_SYMBOL_GPL(kunit_binary_assert_format); + +void kunit_binary_ptr_assert_format(const struct kunit_assert *assert, + const struct va_format *message, + struct string_stream *stream) +{ + struct kunit_binary_ptr_assert *binary_assert; + + binary_assert = container_of(assert, struct kunit_binary_ptr_assert, + assert); + + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s %s %s, but\n", + binary_assert->text->left_text, + binary_assert->text->operation, + binary_assert->text->right_text); + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %px\n", + binary_assert->text->left_text, + binary_assert->left_value); + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %px", + binary_assert->text->right_text, + binary_assert->right_value); + kunit_assert_print_msg(message, stream); +} +EXPORT_SYMBOL_GPL(kunit_binary_ptr_assert_format); + +/* Checks if KUNIT_EXPECT_STREQ() args were string literals. + * Note: `text` will have ""s where as `value` will not. + */ +VISIBLE_IF_KUNIT bool is_str_literal(const char *text, const char *value) +{ + int len; + + len = strlen(text); + if (len < 2) + return false; + if (text[0] != '\"' || text[len - 1] != '\"') + return false; + + return strncmp(text + 1, value, len - 2) == 0; +} + +void kunit_binary_str_assert_format(const struct kunit_assert *assert, + const struct va_format *message, + struct string_stream *stream) +{ + struct kunit_binary_str_assert *binary_assert; + + binary_assert = container_of(assert, struct kunit_binary_str_assert, + assert); + + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s %s %s, but\n", + binary_assert->text->left_text, + binary_assert->text->operation, + binary_assert->text->right_text); + if (!is_str_literal(binary_assert->text->left_text, binary_assert->left_value)) + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == \"%s\"\n", + binary_assert->text->left_text, + binary_assert->left_value); + if (!is_str_literal(binary_assert->text->right_text, binary_assert->right_value)) + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == \"%s\"", + binary_assert->text->right_text, + binary_assert->right_value); + kunit_assert_print_msg(message, stream); +} +EXPORT_SYMBOL_GPL(kunit_binary_str_assert_format); + +/* Adds a hexdump of a buffer to a string_stream comparing it with + * a second buffer. The different bytes are marked with <>. + */ +VISIBLE_IF_KUNIT +void kunit_assert_hexdump(struct string_stream *stream, + const void *buf, + const void *compared_buf, + const size_t len) +{ + size_t i; + const u8 *buf1 = buf; + const u8 *buf2 = compared_buf; + + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT); + + for (i = 0; i < len; ++i) { + if (!(i % 16) && i) + string_stream_add(stream, "\n" KUNIT_SUBSUBTEST_INDENT); + + if (buf1[i] != buf2[i]) + string_stream_add(stream, "<%02x>", buf1[i]); + else + string_stream_add(stream, " %02x ", buf1[i]); + } +} + +void kunit_mem_assert_format(const struct kunit_assert *assert, + const struct va_format *message, + struct string_stream *stream) +{ + struct kunit_mem_assert *mem_assert; + + mem_assert = container_of(assert, struct kunit_mem_assert, + assert); + + if (!mem_assert->left_value) { + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s is not null, but is\n", + mem_assert->text->left_text); + } else if (!mem_assert->right_value) { + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s is not null, but is\n", + mem_assert->text->right_text); + } else { + string_stream_add(stream, + KUNIT_SUBTEST_INDENT "Expected %s %s %s, but\n", + mem_assert->text->left_text, + mem_assert->text->operation, + mem_assert->text->right_text); + + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s ==\n", + mem_assert->text->left_text); + kunit_assert_hexdump(stream, mem_assert->left_value, + mem_assert->right_value, mem_assert->size); + + string_stream_add(stream, "\n"); + + string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s ==\n", + mem_assert->text->right_text); + kunit_assert_hexdump(stream, mem_assert->right_value, + mem_assert->left_value, mem_assert->size); + + kunit_assert_print_msg(message, stream); + } +} +EXPORT_SYMBOL_GPL(kunit_mem_assert_format); diff --git a/lib/kunit/assert_test.c b/lib/kunit/assert_test.c new file mode 100644 index 000000000000..4a5967712186 --- /dev/null +++ b/lib/kunit/assert_test.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * KUnit test for the assertion formatting functions. + * Author: Ivan Orlov <ivan.orlov0322@gmail.com> + */ +#include <kunit/test.h> +#include "string-stream.h" + +#define TEST_PTR_EXPECTED_BUF_SIZE 32 +#define HEXDUMP_TEST_BUF_LEN 5 +#define ASSERT_TEST_EXPECT_CONTAIN(test, str, substr) KUNIT_EXPECT_TRUE(test, strstr(str, substr)) +#define ASSERT_TEST_EXPECT_NCONTAIN(test, str, substr) KUNIT_EXPECT_FALSE(test, strstr(str, substr)) + +static void kunit_test_is_literal(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, is_literal("5", 5)); + KUNIT_EXPECT_TRUE(test, is_literal("0", 0)); + KUNIT_EXPECT_TRUE(test, is_literal("1234567890", 1234567890)); + KUNIT_EXPECT_TRUE(test, is_literal("-1234567890", -1234567890)); + KUNIT_EXPECT_FALSE(test, is_literal("05", 5)); + KUNIT_EXPECT_FALSE(test, is_literal("", 0)); + KUNIT_EXPECT_FALSE(test, is_literal("-0", 0)); + KUNIT_EXPECT_FALSE(test, is_literal("12#45", 1245)); +} + +static void kunit_test_is_str_literal(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, is_str_literal("\"Hello, World!\"", "Hello, World!")); + KUNIT_EXPECT_TRUE(test, is_str_literal("\"\"", "")); + KUNIT_EXPECT_TRUE(test, is_str_literal("\"\"\"", "\"")); + KUNIT_EXPECT_FALSE(test, is_str_literal("", "")); + KUNIT_EXPECT_FALSE(test, is_str_literal("\"", "\"")); + KUNIT_EXPECT_FALSE(test, is_str_literal("\"Abacaba", "Abacaba")); + KUNIT_EXPECT_FALSE(test, is_str_literal("Abacaba\"", "Abacaba")); + KUNIT_EXPECT_FALSE(test, is_str_literal("\"Abacaba\"", "\"Abacaba\"")); +} + +KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *); + +/* this function is used to get a "char *" string from the string stream and defer its cleanup */ +static char *get_str_from_stream(struct kunit *test, struct string_stream *stream) +{ + char *str = string_stream_get_string(stream); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, str); + kunit_add_action(test, kfree_wrapper, (void *)str); + + return str; +} + +static void kunit_test_assert_prologue(struct kunit *test) +{ + struct string_stream *stream; + char *str; + const struct kunit_loc location = { + .file = "testfile.c", + .line = 1337, + }; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + /* Test an expectation fail prologue */ + kunit_assert_prologue(&location, KUNIT_EXPECTATION, stream); + str = get_str_from_stream(test, stream); + ASSERT_TEST_EXPECT_CONTAIN(test, str, "EXPECTATION"); + ASSERT_TEST_EXPECT_CONTAIN(test, str, "testfile.c"); + ASSERT_TEST_EXPECT_CONTAIN(test, str, "1337"); + + /* Test an assertion fail prologue */ + string_stream_clear(stream); + kunit_assert_prologue(&location, KUNIT_ASSERTION, stream); + str = get_str_from_stream(test, stream); + ASSERT_TEST_EXPECT_CONTAIN(test, str, "ASSERTION"); + ASSERT_TEST_EXPECT_CONTAIN(test, str, "testfile.c"); + ASSERT_TEST_EXPECT_CONTAIN(test, str, "1337"); +} + +/* + * This function accepts an arbitrary count of parameters and generates a va_format struct, + * which can be used to validate kunit_assert_print_msg function + */ +static void verify_assert_print_msg(struct kunit *test, + struct string_stream *stream, + char *expected, const char *format, ...) +{ + va_list list; + const struct va_format vformat = { + .fmt = format, + .va = &list, + }; + + va_start(list, format); + string_stream_clear(stream); + kunit_assert_print_msg(&vformat, stream); + KUNIT_EXPECT_STREQ(test, get_str_from_stream(test, stream), expected); +} + +static void kunit_test_assert_print_msg(struct kunit *test) +{ + struct string_stream *stream; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + verify_assert_print_msg(test, stream, "\nTest", "Test"); + verify_assert_print_msg(test, stream, "\nAbacaba -123 234", "%s %d %u", + "Abacaba", -123, 234U); + verify_assert_print_msg(test, stream, "", NULL); +} + +/* + * Further code contains the tests for different assert format functions. + * This helper function accepts the assert format function, executes it and + * validates the result string from the stream by checking that all of the + * substrings exist in the output. + */ +static void validate_assert(assert_format_t format_func, struct kunit *test, + const struct kunit_assert *assert, + struct string_stream *stream, int num_checks, ...) +{ + size_t i; + va_list checks; + char *cur_substr_exp; + struct va_format message = { NULL, NULL }; + + va_start(checks, num_checks); + string_stream_clear(stream); + format_func(assert, &message, stream); + + for (i = 0; i < num_checks; i++) { + cur_substr_exp = va_arg(checks, char *); + ASSERT_TEST_EXPECT_CONTAIN(test, get_str_from_stream(test, stream), cur_substr_exp); + } +} + +static void kunit_test_unary_assert_format(struct kunit *test) +{ + struct string_stream *stream; + struct kunit_assert assert = {}; + struct kunit_unary_assert un_assert = { + .assert = assert, + .condition = "expr", + .expected_true = true, + }; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + validate_assert(kunit_unary_assert_format, test, &un_assert.assert, + stream, 2, "true", "is false"); + + un_assert.expected_true = false; + validate_assert(kunit_unary_assert_format, test, &un_assert.assert, + stream, 2, "false", "is true"); +} + +static void kunit_test_ptr_not_err_assert_format(struct kunit *test) +{ + struct string_stream *stream; + struct kunit_assert assert = {}; + struct kunit_ptr_not_err_assert not_err_assert = { + .assert = assert, + .text = "expr", + .value = NULL, + }; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + /* Value is NULL. The corresponding message should be printed out */ + validate_assert(kunit_ptr_not_err_assert_format, test, + ¬_err_assert.assert, + stream, 1, "null"); + + /* Value is not NULL, but looks like an error pointer. Error should be printed out */ + not_err_assert.value = (void *)-12; + validate_assert(kunit_ptr_not_err_assert_format, test, + ¬_err_assert.assert, stream, 2, + "error", "-12"); +} + +static void kunit_test_binary_assert_format(struct kunit *test) +{ + struct string_stream *stream; + struct kunit_assert assert = {}; + struct kunit_binary_assert_text text = { + .left_text = "1 + 2", + .operation = "==", + .right_text = "2", + }; + const struct kunit_binary_assert binary_assert = { + .assert = assert, + .text = &text, + .left_value = 3, + .right_value = 2, + }; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + /* + * Printed values should depend on the input we provide: the left text, right text, left + * value and the right value. + */ + validate_assert(kunit_binary_assert_format, test, &binary_assert.assert, + stream, 4, "1 + 2", "2", "3", "=="); + + text.right_text = "4 - 2"; + validate_assert(kunit_binary_assert_format, test, &binary_assert.assert, + stream, 3, "==", "1 + 2", "4 - 2"); + + text.left_text = "3"; + validate_assert(kunit_binary_assert_format, test, &binary_assert.assert, + stream, 4, "3", "4 - 2", "2", "=="); + + text.right_text = "2"; + validate_assert(kunit_binary_assert_format, test, &binary_assert.assert, + stream, 3, "3", "2", "=="); +} + +static void kunit_test_binary_ptr_assert_format(struct kunit *test) +{ + struct string_stream *stream; + struct kunit_assert assert = {}; + char *addr_var_a, *addr_var_b; + static const void *var_a = (void *)0xDEADBEEF; + static const void *var_b = (void *)0xBADDCAFE; + struct kunit_binary_assert_text text = { + .left_text = "var_a", + .operation = "==", + .right_text = "var_b", + }; + struct kunit_binary_ptr_assert binary_ptr_assert = { + .assert = assert, + .text = &text, + .left_value = var_a, + .right_value = var_b, + }; + + addr_var_a = kunit_kzalloc(test, TEST_PTR_EXPECTED_BUF_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, addr_var_a); + addr_var_b = kunit_kzalloc(test, TEST_PTR_EXPECTED_BUF_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, addr_var_b); + /* + * Print the addresses to the buffers first. + * This is necessary as we may have different count of leading zeros in the pointer + * on different architectures. + */ + snprintf(addr_var_a, TEST_PTR_EXPECTED_BUF_SIZE, "%px", var_a); + snprintf(addr_var_b, TEST_PTR_EXPECTED_BUF_SIZE, "%px", var_b); + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + validate_assert(kunit_binary_ptr_assert_format, test, &binary_ptr_assert.assert, + stream, 3, addr_var_a, addr_var_b, "=="); +} + +static void kunit_test_binary_str_assert_format(struct kunit *test) +{ + struct string_stream *stream; + struct kunit_assert assert = {}; + static const char *var_a = "abacaba"; + static const char *var_b = "kernel"; + struct kunit_binary_assert_text text = { + .left_text = "var_a", + .operation = "==", + .right_text = "var_b", + }; + struct kunit_binary_str_assert binary_str_assert = { + .assert = assert, + .text = &text, + .left_value = var_a, + .right_value = var_b, + }; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + validate_assert(kunit_binary_str_assert_format, test, + &binary_str_assert.assert, + stream, 5, "var_a", "var_b", "\"abacaba\"", + "\"kernel\"", "=="); + + text.left_text = "\"abacaba\""; + validate_assert(kunit_binary_str_assert_format, test, &binary_str_assert.assert, + stream, 4, "\"abacaba\"", "var_b", "\"kernel\"", "=="); + + text.right_text = "\"kernel\""; + validate_assert(kunit_binary_str_assert_format, test, &binary_str_assert.assert, + stream, 3, "\"abacaba\"", "\"kernel\"", "=="); +} + +static const u8 hex_testbuf1[] = { 0x26, 0x74, 0x6b, 0x9c, 0x55, + 0x45, 0x9d, 0x47, 0xd6, 0x47, + 0x2, 0x89, 0x8c, 0x81, 0x94, + 0x12, 0xfe, 0x01 }; +static const u8 hex_testbuf2[] = { 0x26, 0x74, 0x6b, 0x9c, 0x55, + 0x45, 0x9d, 0x47, 0x21, 0x47, + 0xcd, 0x89, 0x24, 0x50, 0x94, + 0x12, 0xba, 0x01 }; +static void kunit_test_assert_hexdump(struct kunit *test) +{ + struct string_stream *stream; + char *str; + size_t i; + char buf[HEXDUMP_TEST_BUF_LEN]; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + /* Check that we are getting output like <xx> for non-matching numbers. */ + kunit_assert_hexdump(stream, hex_testbuf1, hex_testbuf2, sizeof(hex_testbuf1)); + str = get_str_from_stream(test, stream); + for (i = 0; i < sizeof(hex_testbuf1); i++) { + snprintf(buf, HEXDUMP_TEST_BUF_LEN, "<%02x>", hex_testbuf1[i]); + if (hex_testbuf1[i] != hex_testbuf2[i]) + ASSERT_TEST_EXPECT_CONTAIN(test, str, buf); + } + /* We shouldn't get any <xx> numbers when comparing the buffer with itself. */ + string_stream_clear(stream); + kunit_assert_hexdump(stream, hex_testbuf1, hex_testbuf1, sizeof(hex_testbuf1)); + str = get_str_from_stream(test, stream); + ASSERT_TEST_EXPECT_NCONTAIN(test, str, "<"); + ASSERT_TEST_EXPECT_NCONTAIN(test, str, ">"); +} + +static void kunit_test_mem_assert_format(struct kunit *test) +{ + struct string_stream *stream; + struct string_stream *expected_stream; + struct kunit_assert assert = {}; + static const struct kunit_binary_assert_text text = { + .left_text = "hex_testbuf1", + .operation = "==", + .right_text = "hex_testbuf2", + }; + struct kunit_mem_assert mem_assert = { + .assert = assert, + .text = &text, + .left_value = NULL, + .right_value = hex_testbuf2, + .size = sizeof(hex_testbuf1), + }; + + expected_stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, expected_stream); + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + /* The left value is NULL */ + validate_assert(kunit_mem_assert_format, test, &mem_assert.assert, + stream, 2, "hex_testbuf1", "is not null"); + + /* The right value is NULL, the left value is not NULL */ + mem_assert.left_value = hex_testbuf1; + mem_assert.right_value = NULL; + validate_assert(kunit_mem_assert_format, test, &mem_assert.assert, + stream, 2, "hex_testbuf2", "is not null"); + + /* Both arguments are not null */ + mem_assert.left_value = hex_testbuf1; + mem_assert.right_value = hex_testbuf2; + + validate_assert(kunit_mem_assert_format, test, &mem_assert.assert, + stream, 3, "hex_testbuf1", "hex_testbuf2", "=="); +} + +static struct kunit_case assert_test_cases[] = { + KUNIT_CASE(kunit_test_is_literal), + KUNIT_CASE(kunit_test_is_str_literal), + KUNIT_CASE(kunit_test_assert_prologue), + KUNIT_CASE(kunit_test_assert_print_msg), + KUNIT_CASE(kunit_test_unary_assert_format), + KUNIT_CASE(kunit_test_ptr_not_err_assert_format), + KUNIT_CASE(kunit_test_binary_assert_format), + KUNIT_CASE(kunit_test_binary_ptr_assert_format), + KUNIT_CASE(kunit_test_binary_str_assert_format), + KUNIT_CASE(kunit_test_assert_hexdump), + KUNIT_CASE(kunit_test_mem_assert_format), + {} +}; + +static struct kunit_suite assert_test_suite = { + .name = "kunit-assert", + .test_cases = assert_test_cases, +}; + +kunit_test_suites(&assert_test_suite); diff --git a/lib/kunit/attributes.c b/lib/kunit/attributes.c new file mode 100644 index 000000000000..2cf04cc09372 --- /dev/null +++ b/lib/kunit/attributes.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit API to save and access test attributes + * + * Copyright (C) 2023, Google LLC. + * Author: Rae Moar <rmoar@google.com> + */ + +#include <kunit/test.h> +#include <kunit/attributes.h> + +/* Options for printing attributes: + * PRINT_ALWAYS - attribute is printed for every test case and suite if set + * PRINT_SUITE - attribute is printed for every suite if set but not for test cases + * PRINT_NEVER - attribute is never printed + */ +enum print_ops { + PRINT_ALWAYS, + PRINT_SUITE, + PRINT_NEVER, +}; + +/** + * struct kunit_attr - represents a test attribute and holds flexible + * helper functions to interact with attribute. + * + * @name: name of test attribute, eg. speed + * @get_attr: function to return attribute value given a test + * @to_string: function to return string representation of given + * attribute value + * @filter: function to indicate whether a given attribute value passes a + * filter + * @attr_default: default attribute value used during filtering + * @print: value of enum print_ops to indicate when to print attribute + */ +struct kunit_attr { + const char *name; + void *(*get_attr)(void *test_or_suite, bool is_test); + const char *(*to_string)(void *attr, bool *to_free); + int (*filter)(void *attr, const char *input, int *err); + void *attr_default; + enum print_ops print; +}; + +/* String Lists for enum Attributes */ + +static const char * const speed_str_list[] = {"unset", "very_slow", "slow", "normal"}; + +/* To String Methods */ + +static const char *attr_enum_to_string(void *attr, const char * const str_list[], bool *to_free) +{ + long val = (long)attr; + + *to_free = false; + if (!val) + return NULL; + return str_list[val]; +} + +static const char *attr_bool_to_string(void *attr, bool *to_free) +{ + bool val = (bool)attr; + + *to_free = false; + if (val) + return "true"; + return "false"; +} + +static const char *attr_speed_to_string(void *attr, bool *to_free) +{ + return attr_enum_to_string(attr, speed_str_list, to_free); +} + +static const char *attr_string_to_string(void *attr, bool *to_free) +{ + *to_free = false; + return (char *) attr; +} + +/* Filter Methods */ + +static const char op_list[] = "<>!="; + +/* + * Returns whether the inputted integer value matches the filter given + * by the operation string and inputted integer. + */ +static int int_filter(long val, const char *op, int input, int *err) +{ + if (!strncmp(op, "<=", 2)) + return (val <= input); + else if (!strncmp(op, ">=", 2)) + return (val >= input); + else if (!strncmp(op, "!=", 2)) + return (val != input); + else if (!strncmp(op, ">", 1)) + return (val > input); + else if (!strncmp(op, "<", 1)) + return (val < input); + else if (!strncmp(op, "=", 1)) + return (val == input); + *err = -EINVAL; + pr_err("kunit executor: invalid filter operation: %s\n", op); + return false; +} + +/* + * Returns whether the inputted enum value "attr" matches the filter given + * by the input string. Note: the str_list includes the corresponding string + * list to the enum values. + */ +static int attr_enum_filter(void *attr, const char *input, int *err, + const char * const str_list[], int max) +{ + int i, j, input_int = -1; + long test_val = (long)attr; + const char *input_val = NULL; + + for (i = 0; input[i]; i++) { + if (!strchr(op_list, input[i])) { + input_val = input + i; + break; + } + } + + if (!input_val) { + *err = -EINVAL; + pr_err("kunit executor: filter value not found: %s\n", input); + return false; + } + + for (j = 0; j <= max; j++) { + if (!strcmp(input_val, str_list[j])) + input_int = j; + } + + if (input_int < 0) { + *err = -EINVAL; + pr_err("kunit executor: invalid filter input: %s\n", input); + return false; + } + + return int_filter(test_val, input, input_int, err); +} + +static int attr_speed_filter(void *attr, const char *input, int *err) +{ + return attr_enum_filter(attr, input, err, speed_str_list, KUNIT_SPEED_MAX); +} + +/* + * Returns whether the inputted string value (attr) matches the filter given + * by the input string. + */ +static int attr_string_filter(void *attr, const char *input, int *err) +{ + char *str = attr; + + if (!strncmp(input, "<", 1)) { + *err = -EINVAL; + pr_err("kunit executor: invalid filter input: %s\n", input); + return false; + } else if (!strncmp(input, ">", 1)) { + *err = -EINVAL; + pr_err("kunit executor: invalid filter input: %s\n", input); + return false; + } else if (!strncmp(input, "!=", 2)) { + return (strcmp(input + 2, str) != 0); + } else if (!strncmp(input, "=", 1)) { + return (strcmp(input + 1, str) == 0); + } + *err = -EINVAL; + pr_err("kunit executor: invalid filter operation: %s\n", input); + return false; +} + +static int attr_bool_filter(void *attr, const char *input, int *err) +{ + int i, input_int = -1; + long val = (long)attr; + const char *input_str = NULL; + + for (i = 0; input[i]; i++) { + if (!strchr(op_list, input[i])) { + input_str = input + i; + break; + } + } + + if (!input_str) { + *err = -EINVAL; + pr_err("kunit executor: filter value not found: %s\n", input); + return false; + } + + if (!strcmp(input_str, "true")) + input_int = (int)true; + else if (!strcmp(input_str, "false")) + input_int = (int)false; + else { + *err = -EINVAL; + pr_err("kunit executor: invalid filter input: %s\n", input); + return false; + } + + return int_filter(val, input, input_int, err); +} + +/* Get Attribute Methods */ + +static void *attr_speed_get(void *test_or_suite, bool is_test) +{ + struct kunit_suite *suite = is_test ? NULL : test_or_suite; + struct kunit_case *test = is_test ? test_or_suite : NULL; + + if (test) + return ((void *) test->attr.speed); + else + return ((void *) suite->attr.speed); +} + +static void *attr_module_get(void *test_or_suite, bool is_test) +{ + struct kunit_suite *suite = is_test ? NULL : test_or_suite; + struct kunit_case *test = is_test ? test_or_suite : NULL; + + // Suites get their module attribute from their first test_case + if (test) + return ((void *) test->module_name); + else if (kunit_suite_num_test_cases(suite) > 0) + return ((void *) suite->test_cases[0].module_name); + else + return (void *) ""; +} + +static void *attr_is_init_get(void *test_or_suite, bool is_test) +{ + struct kunit_suite *suite = is_test ? NULL : test_or_suite; + struct kunit_case *test = is_test ? test_or_suite : NULL; + + if (test) + return ((void *) NULL); + else + return ((void *) suite->is_init); +} + +/* List of all Test Attributes */ + +static struct kunit_attr kunit_attr_list[] = { + { + .name = "speed", + .get_attr = attr_speed_get, + .to_string = attr_speed_to_string, + .filter = attr_speed_filter, + .attr_default = (void *)KUNIT_SPEED_NORMAL, + .print = PRINT_ALWAYS, + }, + { + .name = "module", + .get_attr = attr_module_get, + .to_string = attr_string_to_string, + .filter = attr_string_filter, + .attr_default = (void *)"", + .print = PRINT_SUITE, + }, + { + .name = "is_init", + .get_attr = attr_is_init_get, + .to_string = attr_bool_to_string, + .filter = attr_bool_filter, + .attr_default = (void *)false, + .print = PRINT_SUITE, + } +}; + +/* Helper Functions to Access Attributes */ + +const char *kunit_attr_filter_name(struct kunit_attr_filter filter) +{ + return filter.attr->name; +} + +void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level) +{ + int i; + bool to_free = false; + void *attr; + const char *attr_name, *attr_str; + struct kunit_suite *suite = is_test ? NULL : test_or_suite; + struct kunit_case *test = is_test ? test_or_suite : NULL; + + for (i = 0; i < ARRAY_SIZE(kunit_attr_list); i++) { + if (kunit_attr_list[i].print == PRINT_NEVER || + (test && kunit_attr_list[i].print == PRINT_SUITE)) + continue; + attr = kunit_attr_list[i].get_attr(test_or_suite, is_test); + if (attr) { + attr_name = kunit_attr_list[i].name; + attr_str = kunit_attr_list[i].to_string(attr, &to_free); + if (test) { + kunit_log(KERN_INFO, test, "%*s# %s.%s: %s", + KUNIT_INDENT_LEN * test_level, "", test->name, + attr_name, attr_str); + } else { + kunit_log(KERN_INFO, suite, "%*s# %s: %s", + KUNIT_INDENT_LEN * test_level, "", attr_name, attr_str); + } + + /* Free to_string of attribute if needed */ + if (to_free) + kfree(attr_str); + } + } +} + +/* Helper Functions to Filter Attributes */ + +int kunit_get_filter_count(char *input) +{ + int i, comma_index = 0, count = 0; + + for (i = 0; input[i]; i++) { + if (input[i] == ',') { + if ((i - comma_index) > 1) + count++; + comma_index = i; + } + } + if ((i - comma_index) > 0) + count++; + return count; +} + +struct kunit_attr_filter kunit_next_attr_filter(char **filters, int *err) +{ + struct kunit_attr_filter filter = {}; + int i, j, comma_index = 0, new_start_index = 0; + int op_index = -1, attr_index = -1; + char op; + char *input = *filters; + + /* Parse input until operation */ + for (i = 0; input[i]; i++) { + if (op_index < 0 && strchr(op_list, input[i])) { + op_index = i; + } else if (!comma_index && input[i] == ',') { + comma_index = i; + } else if (comma_index && input[i] != ' ') { + new_start_index = i; + break; + } + } + + if (op_index <= 0) { + *err = -EINVAL; + pr_err("kunit executor: filter operation not found: %s\n", input); + return filter; + } + + /* Temporarily set operator to \0 character. */ + op = input[op_index]; + input[op_index] = '\0'; + + /* Find associated kunit_attr object */ + for (j = 0; j < ARRAY_SIZE(kunit_attr_list); j++) { + if (!strcmp(input, kunit_attr_list[j].name)) { + attr_index = j; + break; + } + } + + input[op_index] = op; + + if (attr_index < 0) { + *err = -EINVAL; + pr_err("kunit executor: attribute not found: %s\n", input); + } else { + filter.attr = &kunit_attr_list[attr_index]; + } + + if (comma_index > 0) { + input[comma_index] = '\0'; + filter.input = input + op_index; + input = input + new_start_index; + } else { + filter.input = input + op_index; + input = NULL; + } + + *filters = input; + + return filter; +} + +struct kunit_suite *kunit_filter_attr_tests(const struct kunit_suite *const suite, + struct kunit_attr_filter filter, char *action, int *err) +{ + int n = 0; + struct kunit_case *filtered, *test_case; + struct kunit_suite *copy; + void *suite_val, *test_val; + bool suite_result, test_result, default_result, result; + + /* Allocate memory for new copy of suite and list of test cases */ + copy = kmemdup(suite, sizeof(*copy), GFP_KERNEL); + if (!copy) + return ERR_PTR(-ENOMEM); + + kunit_suite_for_each_test_case(suite, test_case) { n++; } + + filtered = kcalloc(n + 1, sizeof(*filtered), GFP_KERNEL); + if (!filtered) { + kfree(copy); + return ERR_PTR(-ENOMEM); + } + + n = 0; + + /* Save filtering result on default value */ + default_result = filter.attr->filter(filter.attr->attr_default, filter.input, err); + if (*err) + goto err; + + /* Save suite attribute value and filtering result on that value */ + suite_val = filter.attr->get_attr((void *)suite, false); + suite_result = filter.attr->filter(suite_val, filter.input, err); + if (*err) + goto err; + + /* For each test case, save test case if passes filtering. */ + kunit_suite_for_each_test_case(suite, test_case) { + test_val = filter.attr->get_attr((void *) test_case, true); + test_result = filter.attr->filter(filter.attr->get_attr(test_case, true), + filter.input, err); + if (*err) + goto err; + + /* + * If attribute value of test case is set, filter on that value. + * If not, filter on suite value if set. If not, filter on + * default value. + */ + result = false; + if (test_val) { + if (test_result) + result = true; + } else if (suite_val) { + if (suite_result) + result = true; + } else if (default_result) { + result = true; + } + + if (result) { + filtered[n++] = *test_case; + } else if (action && strcmp(action, "skip") == 0) { + test_case->status = KUNIT_SKIPPED; + filtered[n++] = *test_case; + } + } + +err: + if (n == 0 || *err) { + kfree(copy); + kfree(filtered); + return NULL; + } + + copy->test_cases = filtered; + + return copy; +} diff --git a/lib/kunit/debugfs.c b/lib/kunit/debugfs.c new file mode 100644 index 000000000000..9c326f1837bd --- /dev/null +++ b/lib/kunit/debugfs.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020, Oracle and/or its affiliates. + * Author: Alan Maguire <alan.maguire@oracle.com> + */ + +#include <linux/debugfs.h> +#include <linux/module.h> + +#include <kunit/test.h> +#include <kunit/test-bug.h> + +#include "string-stream.h" +#include "debugfs.h" + +#define KUNIT_DEBUGFS_ROOT "kunit" +#define KUNIT_DEBUGFS_RESULTS "results" +#define KUNIT_DEBUGFS_RUN "run" + +/* + * Create a debugfs representation of test suites: + * + * Path Semantics + * /sys/kernel/debug/kunit/<testsuite>/results Show results of last run for + * testsuite + * /sys/kernel/debug/kunit/<testsuite>/run Write to this file to trigger + * testsuite to run + * + */ + +static struct dentry *debugfs_rootdir; + +void kunit_debugfs_cleanup(void) +{ + debugfs_remove_recursive(debugfs_rootdir); +} + +void kunit_debugfs_init(void) +{ + if (!debugfs_rootdir) + debugfs_rootdir = debugfs_create_dir(KUNIT_DEBUGFS_ROOT, NULL); +} + +static void debugfs_print_result(struct seq_file *seq, struct string_stream *log) +{ + struct string_stream_fragment *frag_container; + + if (!log) + return; + + /* + * Walk the fragments so we don't need to allocate a temporary + * buffer to hold the entire string. + */ + spin_lock(&log->lock); + list_for_each_entry(frag_container, &log->fragments, node) + seq_printf(seq, "%s", frag_container->fragment); + spin_unlock(&log->lock); +} + +/* + * /sys/kernel/debug/kunit/<testsuite>/results shows all results for testsuite. + */ +static int debugfs_print_results(struct seq_file *seq, void *v) +{ + struct kunit_suite *suite = (struct kunit_suite *)seq->private; + enum kunit_status success; + struct kunit_case *test_case; + + if (!suite) + return 0; + + success = kunit_suite_has_succeeded(suite); + + /* Print KTAP header so the debugfs log can be parsed as valid KTAP. */ + seq_puts(seq, "KTAP version 1\n"); + seq_puts(seq, "1..1\n"); + + /* Print suite header because it is not stored in the test logs. */ + seq_puts(seq, KUNIT_SUBTEST_INDENT "KTAP version 1\n"); + seq_printf(seq, KUNIT_SUBTEST_INDENT "# Subtest: %s\n", suite->name); + seq_printf(seq, KUNIT_SUBTEST_INDENT "1..%zd\n", kunit_suite_num_test_cases(suite)); + + kunit_suite_for_each_test_case(suite, test_case) + debugfs_print_result(seq, test_case->log); + + debugfs_print_result(seq, suite->log); + + seq_printf(seq, "%s %d %s\n", + kunit_status_to_ok_not_ok(success), 1, suite->name); + return 0; +} + +static int debugfs_release(struct inode *inode, struct file *file) +{ + return single_release(inode, file); +} + +static int debugfs_results_open(struct inode *inode, struct file *file) +{ + struct kunit_suite *suite; + + suite = (struct kunit_suite *)inode->i_private; + + return single_open(file, debugfs_print_results, suite); +} + +/* + * Print a usage message to the debugfs "run" file + * (/sys/kernel/debug/kunit/<testsuite>/run) if opened. + */ +static int debugfs_print_run(struct seq_file *seq, void *v) +{ + struct kunit_suite *suite = (struct kunit_suite *)seq->private; + + seq_puts(seq, "Write to this file to trigger the test suite to run.\n"); + seq_printf(seq, "usage: echo \"any string\" > /sys/kernel/debugfs/kunit/%s/run\n", + suite->name); + return 0; +} + +/* + * The debugfs "run" file (/sys/kernel/debug/kunit/<testsuite>/run) + * contains no information. Write to the file to trigger the test suite + * to run. + */ +static int debugfs_run_open(struct inode *inode, struct file *file) +{ + struct kunit_suite *suite; + + suite = (struct kunit_suite *)inode->i_private; + + return single_open(file, debugfs_print_run, suite); +} + +/* + * Trigger a test suite to run by writing to the suite's "run" debugfs + * file found at: /sys/kernel/debug/kunit/<testsuite>/run + * + * Note: what is written to this file will not be saved. + */ +static ssize_t debugfs_run(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct inode *f_inode = file->f_inode; + struct kunit_suite *suite = (struct kunit_suite *) f_inode->i_private; + + __kunit_test_suites_init(&suite, 1, true); + + return count; +} + +static const struct file_operations debugfs_results_fops = { + .open = debugfs_results_open, + .read = seq_read, + .llseek = seq_lseek, + .release = debugfs_release, +}; + +static const struct file_operations debugfs_run_fops = { + .open = debugfs_run_open, + .read = seq_read, + .write = debugfs_run, + .llseek = seq_lseek, + .release = debugfs_release, +}; + +void kunit_debugfs_create_suite(struct kunit_suite *suite) +{ + struct kunit_case *test_case; + struct string_stream *stream; + + /* If suite log already allocated, do not create new debugfs files. */ + if (suite->log) + return; + + /* + * Allocate logs before creating debugfs representation. + * The suite->log and test_case->log pointer are expected to be NULL + * if there isn't a log, so only set it if the log stream was created + * successfully. + */ + stream = alloc_string_stream(GFP_KERNEL); + if (IS_ERR(stream)) + return; + + string_stream_set_append_newlines(stream, true); + suite->log = stream; + + kunit_suite_for_each_test_case(suite, test_case) { + stream = alloc_string_stream(GFP_KERNEL); + if (IS_ERR(stream)) + goto err; + + string_stream_set_append_newlines(stream, true); + test_case->log = stream; + } + + suite->debugfs = debugfs_create_dir(suite->name, debugfs_rootdir); + + debugfs_create_file(KUNIT_DEBUGFS_RESULTS, S_IFREG | 0444, + suite->debugfs, + suite, &debugfs_results_fops); + + /* Do not create file to re-run test if test runs on init */ + if (!suite->is_init) { + debugfs_create_file(KUNIT_DEBUGFS_RUN, S_IFREG | 0644, + suite->debugfs, + suite, &debugfs_run_fops); + } + return; + +err: + string_stream_destroy(suite->log); + suite->log = NULL; + kunit_suite_for_each_test_case(suite, test_case) { + string_stream_destroy(test_case->log); + test_case->log = NULL; + } +} + +void kunit_debugfs_destroy_suite(struct kunit_suite *suite) +{ + struct kunit_case *test_case; + + debugfs_remove_recursive(suite->debugfs); + string_stream_destroy(suite->log); + kunit_suite_for_each_test_case(suite, test_case) + string_stream_destroy(test_case->log); +} diff --git a/lib/kunit/debugfs.h b/lib/kunit/debugfs.h new file mode 100644 index 000000000000..dcc7d7556107 --- /dev/null +++ b/lib/kunit/debugfs.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020, Oracle and/or its affiliates. + */ + +#ifndef _KUNIT_DEBUGFS_H +#define _KUNIT_DEBUGFS_H + +#include <kunit/test.h> + +#ifdef CONFIG_KUNIT_DEBUGFS + +void kunit_debugfs_create_suite(struct kunit_suite *suite); +void kunit_debugfs_destroy_suite(struct kunit_suite *suite); +void kunit_debugfs_init(void); +void kunit_debugfs_cleanup(void); + +#else + +static inline void kunit_debugfs_create_suite(struct kunit_suite *suite) { } + +static inline void kunit_debugfs_destroy_suite(struct kunit_suite *suite) { } + +static inline void kunit_debugfs_init(void) { } + +static inline void kunit_debugfs_cleanup(void) { } + +#endif /* CONFIG_KUNIT_DEBUGFS */ + +#endif /* _KUNIT_DEBUGFS_H */ diff --git a/lib/kunit/device-impl.h b/lib/kunit/device-impl.h new file mode 100644 index 000000000000..5fcd48ff0f36 --- /dev/null +++ b/lib/kunit/device-impl.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * KUnit internal header for device helpers + * + * Header for KUnit-internal driver / bus management. + * + * Copyright (C) 2023, Google LLC. + * Author: David Gow <davidgow@google.com> + */ + +#ifndef _KUNIT_DEVICE_IMPL_H +#define _KUNIT_DEVICE_IMPL_H + +// For internal use only -- registers the kunit_bus. +int kunit_bus_init(void); +// For internal use only -- unregisters the kunit_bus. +void kunit_bus_shutdown(void); + +#endif //_KUNIT_DEVICE_IMPL_H diff --git a/lib/kunit/device.c b/lib/kunit/device.c new file mode 100644 index 000000000000..520c1fccee8a --- /dev/null +++ b/lib/kunit/device.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit-managed device implementation + * + * Implementation of struct kunit_device helpers for fake devices whose + * lifecycle is managed by KUnit. + * + * Copyright (C) 2023, Google LLC. + * Author: David Gow <davidgow@google.com> + */ + +#include <linux/device.h> +#include <linux/dma-mapping.h> + +#include <kunit/test.h> +#include <kunit/device.h> +#include <kunit/resource.h> + +#include "device-impl.h" + +/* Wrappers for use with kunit_add_action() */ +KUNIT_DEFINE_ACTION_WRAPPER(device_unregister_wrapper, device_unregister, struct device *); +KUNIT_DEFINE_ACTION_WRAPPER(driver_unregister_wrapper, driver_unregister, struct device_driver *); + +/* The root device for the KUnit bus, parent of all kunit_devices. */ +static struct device *kunit_bus_device; + +/* A device owned by a KUnit test. */ +struct kunit_device { + struct device dev; + /* The KUnit test which owns this device. */ + struct kunit *owner; + /* If the driver is managed by KUnit and unique to this device. */ + const struct device_driver *driver; +}; + +#define to_kunit_device(d) container_of_const(d, struct kunit_device, dev) + +static const struct bus_type kunit_bus_type = { + .name = "kunit", +}; + +/* Register the 'kunit_bus' used for fake devices. */ +int kunit_bus_init(void) +{ + int error; + + kunit_bus_device = root_device_register("kunit"); + if (IS_ERR(kunit_bus_device)) + return PTR_ERR(kunit_bus_device); + + error = bus_register(&kunit_bus_type); + if (error) + root_device_unregister(kunit_bus_device); + return error; +} + +/* Unregister the 'kunit_bus' in case the KUnit module is unloaded. */ +void kunit_bus_shutdown(void) +{ + /* Make sure the bus exists before we unregister it. */ + if (IS_ERR_OR_NULL(kunit_bus_device)) + return; + + bus_unregister(&kunit_bus_type); + + root_device_unregister(kunit_bus_device); + + kunit_bus_device = NULL; +} + +/* Release a 'fake' KUnit device. */ +static void kunit_device_release(struct device *d) +{ + kfree(to_kunit_device(d)); +} + +/* + * Create and register a KUnit-managed struct device_driver on the kunit_bus. + * Returns an error pointer on failure. + */ +struct device_driver *kunit_driver_create(struct kunit *test, const char *name) +{ + struct device_driver *driver; + int err = -ENOMEM; + + driver = kunit_kzalloc(test, sizeof(*driver), GFP_KERNEL); + + if (!driver) + return ERR_PTR(err); + + driver->name = kunit_kstrdup_const(test, name, GFP_KERNEL); + driver->bus = &kunit_bus_type; + driver->owner = THIS_MODULE; + + err = driver_register(driver); + if (err) { + kunit_kfree(test, driver); + return ERR_PTR(err); + } + + kunit_add_action(test, driver_unregister_wrapper, driver); + return driver; +} +EXPORT_SYMBOL_GPL(kunit_driver_create); + +/* Helper which creates a kunit_device, attaches it to the kunit_bus*/ +static struct kunit_device *kunit_device_register_internal(struct kunit *test, + const char *name, + const struct device_driver *drv) +{ + struct kunit_device *kunit_dev; + int err = -ENOMEM; + + kunit_dev = kzalloc(sizeof(*kunit_dev), GFP_KERNEL); + if (!kunit_dev) + return ERR_PTR(err); + + kunit_dev->owner = test; + + err = dev_set_name(&kunit_dev->dev, "%s.%s", test->name, name); + if (err) { + kfree(kunit_dev); + return ERR_PTR(err); + } + + kunit_dev->dev.release = kunit_device_release; + kunit_dev->dev.bus = &kunit_bus_type; + kunit_dev->dev.parent = kunit_bus_device; + + err = device_register(&kunit_dev->dev); + if (err) { + put_device(&kunit_dev->dev); + return ERR_PTR(err); + } + + kunit_dev->dev.dma_mask = &kunit_dev->dev.coherent_dma_mask; + kunit_dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + kunit_add_action(test, device_unregister_wrapper, &kunit_dev->dev); + + return kunit_dev; +} + +/* + * Create and register a new KUnit-managed device, using the user-supplied device_driver. + * On failure, returns an error pointer. + */ +struct device *kunit_device_register_with_driver(struct kunit *test, + const char *name, + const struct device_driver *drv) +{ + struct kunit_device *kunit_dev = kunit_device_register_internal(test, name, drv); + + if (IS_ERR_OR_NULL(kunit_dev)) + return ERR_CAST(kunit_dev); + + return &kunit_dev->dev; +} +EXPORT_SYMBOL_GPL(kunit_device_register_with_driver); + +/* + * Create and register a new KUnit-managed device, including a matching device_driver. + * On failure, returns an error pointer. + */ +struct device *kunit_device_register(struct kunit *test, const char *name) +{ + struct device_driver *drv; + struct kunit_device *dev; + + drv = kunit_driver_create(test, name); + if (IS_ERR(drv)) + return ERR_CAST(drv); + + dev = kunit_device_register_internal(test, name, drv); + if (IS_ERR(dev)) { + kunit_release_action(test, driver_unregister_wrapper, (void *)drv); + return ERR_CAST(dev); + } + + /* Request the driver be freed. */ + dev->driver = drv; + + + return &dev->dev; +} +EXPORT_SYMBOL_GPL(kunit_device_register); + +/* Unregisters a KUnit-managed device early (including the driver, if automatically created). */ +void kunit_device_unregister(struct kunit *test, struct device *dev) +{ + const struct device_driver *driver = to_kunit_device(dev)->driver; + + kunit_release_action(test, device_unregister_wrapper, dev); + if (driver) { + const char *driver_name = driver->name; + kunit_release_action(test, driver_unregister_wrapper, (void *)driver); + kunit_kfree_const(test, driver_name); + } +} +EXPORT_SYMBOL_GPL(kunit_device_unregister); + diff --git a/lib/kunit/executor.c b/lib/kunit/executor.c new file mode 100644 index 000000000000..02ff380ab793 --- /dev/null +++ b/lib/kunit/executor.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/reboot.h> +#include <kunit/test.h> +#include <kunit/attributes.h> +#include <linux/glob.h> +#include <linux/moduleparam.h> + +/* + * These symbols point to the .kunit_test_suites section and are defined in + * include/asm-generic/vmlinux.lds.h, and consequently must be extern. + */ +extern struct kunit_suite * const __kunit_suites_start[]; +extern struct kunit_suite * const __kunit_suites_end[]; +extern struct kunit_suite * const __kunit_init_suites_start[]; +extern struct kunit_suite * const __kunit_init_suites_end[]; + +static char *action_param; + +module_param_named(action, action_param, charp, 0400); +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" + "'list_attr' to list test names and attributes instead of running them.\n"); + +const char *kunit_action(void) +{ + return action_param; +} + +/* + * Run KUnit tests after initialization + */ +#ifdef CONFIG_KUNIT_AUTORUN_ENABLED +static bool autorun_param = true; +#else +static bool autorun_param; +#endif +module_param_named(autorun, autorun_param, bool, 0); +MODULE_PARM_DESC(autorun, "Run KUnit tests after initialization"); + +bool kunit_autorun(void) +{ + return autorun_param; +} + +#define PARAM_FROM_CONFIG(config) (config[0] ? config : NULL) + +static char *filter_glob_param = PARAM_FROM_CONFIG(CONFIG_KUNIT_DEFAULT_FILTER_GLOB); +static char *filter_param = PARAM_FROM_CONFIG(CONFIG_KUNIT_DEFAULT_FILTER); +static char *filter_action_param = PARAM_FROM_CONFIG(CONFIG_KUNIT_DEFAULT_FILTER_ACTION); + +module_param_named(filter_glob, filter_glob_param, charp, 0600); +MODULE_PARM_DESC(filter_glob, + "Filter which KUnit test suites/tests run at boot-time, e.g. list* or list*.*del_test"); +module_param_named(filter, filter_param, charp, 0600); +MODULE_PARM_DESC(filter, + "Filter which KUnit test suites/tests run at boot-time using attributes, e.g. speed>slow"); +module_param_named(filter_action, filter_action_param, charp, 0600); +MODULE_PARM_DESC(filter_action, + "Changes behavior of filtered tests using attributes, valid values are:\n" + "<none>: do not run filtered tests as normal\n" + "'skip': skip all filtered tests instead so tests will appear in output\n"); + +const char *kunit_filter_glob(void) +{ + return filter_glob_param; +} + +char *kunit_filter(void) +{ + return filter_param; +} + +char *kunit_filter_action(void) +{ + return filter_action_param; +} + +/* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */ +struct kunit_glob_filter { + char *suite_glob; + char *test_glob; +}; + +/* Split "suite_glob.test_glob" into two. Assumes filter_glob is not empty. */ +static int kunit_parse_glob_filter(struct kunit_glob_filter *parsed, + const char *filter_glob) +{ + const char *period = strchr(filter_glob, '.'); + + if (!period) { + parsed->suite_glob = kstrdup(filter_glob, GFP_KERNEL); + if (!parsed->suite_glob) + return -ENOMEM; + parsed->test_glob = NULL; + return 0; + } + + parsed->suite_glob = kstrndup(filter_glob, period - filter_glob, GFP_KERNEL); + if (!parsed->suite_glob) + return -ENOMEM; + + parsed->test_glob = kstrdup(period + 1, GFP_KERNEL); + if (!parsed->test_glob) { + kfree(parsed->suite_glob); + return -ENOMEM; + } + + return 0; +} + +/* Create a copy of suite with only tests that match test_glob. */ +static struct kunit_suite * +kunit_filter_glob_tests(const 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; + + copy = kmemdup(suite, sizeof(*copy), GFP_KERNEL); + if (!copy) + return ERR_PTR(-ENOMEM); + + filtered = kcalloc(n + 1, sizeof(*filtered), GFP_KERNEL); + if (!filtered) { + kfree(copy); + return ERR_PTR(-ENOMEM); + } + + 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; +} + +void kunit_free_suite_set(struct kunit_suite_set suite_set) +{ + struct kunit_suite * const *suites; + + for (suites = suite_set.start; suites < suite_set.end; suites++) { + kfree((*suites)->test_cases); + kfree(*suites); + } + kfree(suite_set.start); +} + +/* + * Filter and reallocate test suites. Must return the filtered test suites set + * allocated at a valid virtual address or NULL in case of error. + */ +struct kunit_suite_set +kunit_filter_suites(const struct kunit_suite_set *suite_set, + const char *filter_glob, + char *filters, + char *filter_action, + int *err) +{ + int i, j, k; + int filter_count = 0; + struct kunit_suite **copy, **copy_start, *filtered_suite, *new_filtered_suite; + struct kunit_suite_set filtered = {NULL, NULL}; + struct kunit_glob_filter parsed_glob; + struct kunit_attr_filter *parsed_filters = NULL; + struct kunit_suite * const *suites; + + const size_t max = suite_set->end - suite_set->start; + + copy = kcalloc(max, sizeof(*copy), GFP_KERNEL); + if (!copy) { /* won't be able to run anything, return an empty set */ + return filtered; + } + copy_start = copy; + + if (filter_glob) { + *err = kunit_parse_glob_filter(&parsed_glob, filter_glob); + if (*err) + goto free_copy; + } + + /* Parse attribute filters */ + if (filters) { + filter_count = kunit_get_filter_count(filters); + parsed_filters = kcalloc(filter_count, sizeof(*parsed_filters), GFP_KERNEL); + if (!parsed_filters) { + *err = -ENOMEM; + goto free_parsed_glob; + } + for (j = 0; j < filter_count; j++) + parsed_filters[j] = kunit_next_attr_filter(&filters, err); + if (*err) + goto free_parsed_filters; + } + + for (i = 0; &suite_set->start[i] != suite_set->end; i++) { + filtered_suite = suite_set->start[i]; + if (filter_glob) { + if (!glob_match(parsed_glob.suite_glob, filtered_suite->name)) + continue; + filtered_suite = kunit_filter_glob_tests(filtered_suite, + parsed_glob.test_glob); + if (IS_ERR(filtered_suite)) { + *err = PTR_ERR(filtered_suite); + goto free_filtered_suite; + } + } + if (filter_count > 0 && parsed_filters != NULL) { + for (k = 0; k < filter_count; k++) { + new_filtered_suite = kunit_filter_attr_tests(filtered_suite, + parsed_filters[k], filter_action, err); + + /* Free previous copy of suite */ + if (k > 0 || filter_glob) { + kfree(filtered_suite->test_cases); + kfree(filtered_suite); + } + + filtered_suite = new_filtered_suite; + + if (*err) + goto free_filtered_suite; + + if (IS_ERR(filtered_suite)) { + *err = PTR_ERR(filtered_suite); + goto free_filtered_suite; + } + if (!filtered_suite) + break; + } + } + + if (!filtered_suite) + continue; + + *copy++ = filtered_suite; + } + filtered.start = copy_start; + filtered.end = copy; + +free_filtered_suite: + if (*err) { + for (suites = copy_start; suites < copy; suites++) { + kfree((*suites)->test_cases); + kfree(*suites); + } + } + +free_parsed_filters: + if (filter_count) + kfree(parsed_filters); + +free_parsed_glob: + if (filter_glob) { + kfree(parsed_glob.suite_glob); + kfree(parsed_glob.test_glob); + } + +free_copy: + if (*err) + kfree(copy_start); + + return filtered; +} + +void kunit_exec_run_tests(struct kunit_suite_set *suite_set, bool builtin) +{ + size_t num_suites = suite_set->end - suite_set->start; + bool autorun = kunit_autorun(); + + if (autorun && (builtin || num_suites)) { + pr_info("KTAP version 1\n"); + pr_info("1..%zu\n", num_suites); + } + + __kunit_test_suites_init(suite_set->start, num_suites, autorun); +} + +void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr) +{ + struct kunit_suite * const *suites; + struct kunit_case *test_case; + + /* Hack: print a ktap header so kunit.py can find the start of KUnit output. */ + pr_info("KTAP version 1\n"); + + for (suites = suite_set->start; suites < suite_set->end; suites++) { + /* Print suite name and suite attributes */ + pr_info("%s\n", (*suites)->name); + if (include_attr) + kunit_print_attr((void *)(*suites), false, 0); + + /* Print test case name and attributes in suite */ + kunit_suite_for_each_test_case((*suites), test_case) { + pr_info("%s.%s\n", (*suites)->name, test_case->name); + if (include_attr) + kunit_print_attr((void *)test_case, true, 0); + } + } +} + +struct kunit_suite_set kunit_merge_suite_sets(struct kunit_suite_set init_suite_set, + struct kunit_suite_set suite_set) +{ + struct kunit_suite_set total_suite_set = {NULL, NULL}; + struct kunit_suite **total_suite_start = NULL; + size_t init_num_suites, num_suites, suite_size; + int i = 0; + + init_num_suites = init_suite_set.end - init_suite_set.start; + num_suites = suite_set.end - suite_set.start; + suite_size = sizeof(suite_set.start); + + /* Allocate memory for array of all kunit suites */ + total_suite_start = kmalloc_array(init_num_suites + num_suites, suite_size, GFP_KERNEL); + if (!total_suite_start) + return total_suite_set; + + /* Append and mark init suites and then append all other kunit suites */ + memcpy(total_suite_start, init_suite_set.start, init_num_suites * suite_size); + for (i = 0; i < init_num_suites; i++) + total_suite_start[i]->is_init = true; + + memcpy(total_suite_start + init_num_suites, suite_set.start, num_suites * suite_size); + + /* Set kunit suite set start and end */ + total_suite_set.start = total_suite_start; + total_suite_set.end = total_suite_start + (init_num_suites + num_suites); + + return total_suite_set; +} + +#if IS_BUILTIN(CONFIG_KUNIT) + +static char *kunit_shutdown; +core_param(kunit_shutdown, kunit_shutdown, charp, 0644); + +static void kunit_handle_shutdown(void) +{ + if (!kunit_shutdown) + return; + + if (!strcmp(kunit_shutdown, "poweroff")) + kernel_power_off(); + else if (!strcmp(kunit_shutdown, "halt")) + kernel_halt(); + else if (!strcmp(kunit_shutdown, "reboot")) + kernel_restart(NULL); + +} + +int kunit_run_all_tests(void) +{ + struct kunit_suite_set suite_set = {NULL, NULL}; + struct kunit_suite_set filtered_suite_set = {NULL, NULL}; + struct kunit_suite_set init_suite_set = { + __kunit_init_suites_start, __kunit_init_suites_end, + }; + struct kunit_suite_set normal_suite_set = { + __kunit_suites_start, __kunit_suites_end, + }; + size_t init_num_suites = init_suite_set.end - init_suite_set.start; + int err = 0; + + if (init_num_suites > 0) { + suite_set = kunit_merge_suite_sets(init_suite_set, normal_suite_set); + if (!suite_set.start) + goto out; + } else + suite_set = normal_suite_set; + + if (!kunit_enabled()) { + pr_info("kunit: disabled\n"); + goto free_out; + } + + if (filter_glob_param || filter_param) { + filtered_suite_set = kunit_filter_suites(&suite_set, filter_glob_param, + filter_param, filter_action_param, &err); + + /* Free original suite set before using filtered suite set */ + if (init_num_suites > 0) + kfree(suite_set.start); + suite_set = filtered_suite_set; + + if (err) { + pr_err("kunit executor: error filtering suites: %d\n", err); + goto free_out; + } + } + + if (!action_param) + kunit_exec_run_tests(&suite_set, true); + else if (strcmp(action_param, "list") == 0) + kunit_exec_list_tests(&suite_set, false); + else if (strcmp(action_param, "list_attr") == 0) + kunit_exec_list_tests(&suite_set, true); + else + pr_err("kunit executor: unknown action '%s'\n", action_param); + +free_out: + if (filter_glob_param || filter_param) + kunit_free_suite_set(suite_set); + else if (init_num_suites > 0) + /* Don't use kunit_free_suite_set because suites aren't individually allocated */ + kfree(suite_set.start); + +out: + kunit_handle_shutdown(); + return err; +} + +#if IS_BUILTIN(CONFIG_KUNIT_TEST) +#include "executor_test.c" +#endif + +#endif /* IS_BUILTIN(CONFIG_KUNIT) */ diff --git a/lib/kunit/executor_test.c b/lib/kunit/executor_test.c new file mode 100644 index 000000000000..f0090c2729cd --- /dev/null +++ b/lib/kunit/executor_test.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for the KUnit executor. + * + * Copyright (C) 2021, Google LLC. + * Author: Daniel Latypov <dlatypov@google.com> + */ + +#include <kunit/test.h> +#include <kunit/attributes.h> + +static void free_suite_set_at_end(struct kunit *test, const void *to_free); +static struct kunit_suite *alloc_fake_suite(struct kunit *test, + 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_glob_filter filter = {NULL, NULL}; + + kunit_parse_glob_filter(&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_glob_filter(&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_suites_test(struct kunit *test) +{ + struct kunit_suite *subsuite[3] = {NULL, NULL}; + struct kunit_suite_set suite_set = { + .start = subsuite, .end = &subsuite[2], + }; + struct kunit_suite_set got; + int err = 0; + + 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, NULL */ + got = kunit_filter_suites(&suite_set, "suite2", NULL, NULL, &err); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start); + KUNIT_ASSERT_EQ(test, err, 0); + free_suite_set_at_end(test, &got); + + /* Validate we just have suite2 */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]); + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->name, "suite2"); + + /* Contains one element (end is 1 past end) */ + KUNIT_ASSERT_EQ(test, got.end - got.start, 1); +} + +static void filter_suites_test_glob_test(struct kunit *test) +{ + struct kunit_suite *subsuite[3] = {NULL, NULL}; + struct kunit_suite_set suite_set = { + .start = subsuite, .end = &subsuite[2], + }; + struct kunit_suite_set got; + int err = 0; + + 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 */ + got = kunit_filter_suites(&suite_set, "suite2.test2", NULL, NULL, &err); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start); + KUNIT_ASSERT_EQ(test, err, 0); + free_suite_set_at_end(test, &got); + + /* Validate we just have suite2 */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]); + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->name, "suite2"); + KUNIT_ASSERT_EQ(test, got.end - got.start, 1); + + /* Now validate we just have test2 */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases); + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[0].name, "test2"); + KUNIT_EXPECT_FALSE(test, got.start[0]->test_cases[1].name); +} + +static void filter_suites_to_empty_test(struct kunit *test) +{ + struct kunit_suite *subsuite[3] = {NULL, NULL}; + struct kunit_suite_set suite_set = { + .start = subsuite, .end = &subsuite[2], + }; + struct kunit_suite_set got; + int err = 0; + + subsuite[0] = alloc_fake_suite(test, "suite1", dummy_test_cases); + subsuite[1] = alloc_fake_suite(test, "suite2", dummy_test_cases); + + got = kunit_filter_suites(&suite_set, "not_found", NULL, NULL, &err); + KUNIT_ASSERT_EQ(test, err, 0); + free_suite_set_at_end(test, &got); /* just in case */ + + KUNIT_EXPECT_PTR_EQ_MSG(test, got.start, got.end, + "should be empty to indicate no match"); +} + +static void parse_filter_attr_test(struct kunit *test) +{ + int j, filter_count; + struct kunit_attr_filter *parsed_filters; + char filters[] = "speed>slow, module!=example", *filter = filters; + int err = 0; + + filter_count = kunit_get_filter_count(filters); + KUNIT_EXPECT_EQ(test, filter_count, 2); + + parsed_filters = kunit_kcalloc(test, filter_count, sizeof(*parsed_filters), + GFP_KERNEL); + for (j = 0; j < filter_count; j++) { + parsed_filters[j] = kunit_next_attr_filter(&filter, &err); + KUNIT_ASSERT_EQ_MSG(test, err, 0, "failed to parse filter from '%s'", filters); + } + + KUNIT_EXPECT_STREQ(test, kunit_attr_filter_name(parsed_filters[0]), "speed"); + KUNIT_EXPECT_STREQ(test, parsed_filters[0].input, ">slow"); + + KUNIT_EXPECT_STREQ(test, kunit_attr_filter_name(parsed_filters[1]), "module"); + KUNIT_EXPECT_STREQ(test, parsed_filters[1].input, "!=example"); +} + +static struct kunit_case dummy_attr_test_cases[] = { + /* .run_case is not important, just needs to be non-NULL */ + { .name = "slow", .run_case = dummy_test, .module_name = "dummy", + .attr.speed = KUNIT_SPEED_SLOW }, + { .name = "normal", .run_case = dummy_test, .module_name = "dummy" }, + {}, +}; + +static void filter_attr_test(struct kunit *test) +{ + struct kunit_suite *subsuite[3] = {NULL, NULL}; + struct kunit_suite_set suite_set = { + .start = subsuite, .end = &subsuite[2], + }; + struct kunit_suite_set got; + char filter[] = "speed>slow"; + int err = 0; + + subsuite[0] = alloc_fake_suite(test, "normal_suite", dummy_attr_test_cases); + subsuite[1] = alloc_fake_suite(test, "slow_suite", dummy_attr_test_cases); + subsuite[1]->attr.speed = KUNIT_SPEED_SLOW; // Set suite attribute + + /* + * Want: normal_suite(slow, normal), slow_suite(slow, normal), + * NULL -> normal_suite(normal), NULL + * + * The normal test in slow_suite is filtered out because the speed + * attribute is unset and thus, the filtering is based on the parent attribute + * of slow. + */ + got = kunit_filter_suites(&suite_set, NULL, filter, NULL, &err); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start); + KUNIT_ASSERT_EQ(test, err, 0); + free_suite_set_at_end(test, &got); + + /* Validate we just have normal_suite */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]); + KUNIT_EXPECT_STREQ(test, got.start[0]->name, "normal_suite"); + KUNIT_ASSERT_EQ(test, got.end - got.start, 1); + + /* Now validate we just have normal test case */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases); + KUNIT_EXPECT_STREQ(test, got.start[0]->test_cases[0].name, "normal"); + KUNIT_EXPECT_FALSE(test, got.start[0]->test_cases[1].name); +} + +static void filter_attr_empty_test(struct kunit *test) +{ + struct kunit_suite *subsuite[3] = {NULL, NULL}; + struct kunit_suite_set suite_set = { + .start = subsuite, .end = &subsuite[2], + }; + struct kunit_suite_set got; + char filter[] = "module!=dummy"; + int err = 0; + + subsuite[0] = alloc_fake_suite(test, "suite1", dummy_attr_test_cases); + subsuite[1] = alloc_fake_suite(test, "suite2", dummy_attr_test_cases); + + got = kunit_filter_suites(&suite_set, NULL, filter, NULL, &err); + KUNIT_ASSERT_EQ(test, err, 0); + free_suite_set_at_end(test, &got); /* just in case */ + + KUNIT_EXPECT_PTR_EQ_MSG(test, got.start, got.end, + "should be empty to indicate no match"); +} + +static void filter_attr_skip_test(struct kunit *test) +{ + struct kunit_suite *subsuite[2] = {NULL}; + struct kunit_suite_set suite_set = { + .start = subsuite, .end = &subsuite[1], + }; + struct kunit_suite_set got; + char filter[] = "speed>slow"; + int err = 0; + + subsuite[0] = alloc_fake_suite(test, "suite", dummy_attr_test_cases); + + /* Want: suite(slow, normal), NULL -> suite(slow with SKIP, normal), NULL */ + got = kunit_filter_suites(&suite_set, NULL, filter, "skip", &err); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start); + KUNIT_ASSERT_EQ(test, err, 0); + free_suite_set_at_end(test, &got); + + /* Validate we have both the slow and normal test */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases); + KUNIT_ASSERT_EQ(test, kunit_suite_num_test_cases(got.start[0]), 2); + KUNIT_EXPECT_STREQ(test, got.start[0]->test_cases[0].name, "slow"); + KUNIT_EXPECT_STREQ(test, got.start[0]->test_cases[1].name, "normal"); + + /* Now ensure slow is skipped and normal is not */ + KUNIT_EXPECT_EQ(test, got.start[0]->test_cases[0].status, KUNIT_SKIPPED); + KUNIT_EXPECT_FALSE(test, got.start[0]->test_cases[1].status); +} + +static struct kunit_case executor_test_cases[] = { + KUNIT_CASE(parse_filter_test), + KUNIT_CASE(filter_suites_test), + KUNIT_CASE(filter_suites_test_glob_test), + KUNIT_CASE(filter_suites_to_empty_test), + KUNIT_CASE(parse_filter_attr_test), + KUNIT_CASE(filter_attr_test), + KUNIT_CASE(filter_attr_empty_test), + KUNIT_CASE(filter_attr_skip_test), + {} +}; + +static struct kunit_suite executor_test_suite = { + .name = "kunit_executor_test", + .test_cases = executor_test_cases, +}; + +kunit_test_suites(&executor_test_suite); + +/* Test helpers */ + +static void free_suite_set(void *suite_set) +{ + kunit_free_suite_set(*(struct kunit_suite_set *)suite_set); + kfree(suite_set); +} + +/* Use the resource API to register a call to free_suite_set. + * Since we never actually use the resource, it's safe to use on const data. + */ +static void free_suite_set_at_end(struct kunit *test, const void *to_free) +{ + struct kunit_suite_set *free; + + if (!((struct kunit_suite_set *)to_free)->start) + return; + + free = kzalloc(sizeof(struct kunit_suite_set), GFP_KERNEL); + *free = *(struct kunit_suite_set *)to_free; + + kunit_add_action(test, free_suite_set, (void *)free); +} + +static struct kunit_suite *alloc_fake_suite(struct kunit *test, + const char *suite_name, + struct kunit_case *test_cases) +{ + struct kunit_suite *suite; + + /* We normally never expect to allocate suites, hence the non-const cast. */ + suite = kunit_kzalloc(test, sizeof(*suite), GFP_KERNEL); + strscpy((char *)suite->name, suite_name, sizeof(suite->name)); + suite->test_cases = test_cases; + + return suite; +} diff --git a/lib/kunit/hooks-impl.h b/lib/kunit/hooks-impl.h new file mode 100644 index 000000000000..4e71b2d0143b --- /dev/null +++ b/lib/kunit/hooks-impl.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Declarations for hook implementations. + * + * These will be set as the function pointers in struct kunit_hook_table, + * found in include/kunit/test-bug.h. + * + * Copyright (C) 2023, Google LLC. + * Author: David Gow <davidgow@google.com> + */ + +#ifndef _KUNIT_HOOKS_IMPL_H +#define _KUNIT_HOOKS_IMPL_H + +#include <kunit/test-bug.h> + +/* List of declarations. */ +void __printf(3, 4) __kunit_fail_current_test_impl(const char *file, + int line, + const char *fmt, ...); +void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr); + +/* Code to set all of the function pointers. */ +static inline void kunit_install_hooks(void) +{ + /* Install the KUnit hook functions. */ + kunit_hooks.fail_current_test = __kunit_fail_current_test_impl; + kunit_hooks.get_static_stub_address = __kunit_get_static_stub_address_impl; +} + +#endif /* _KUNIT_HOOKS_IMPL_H */ diff --git a/lib/kunit/hooks.c b/lib/kunit/hooks.c new file mode 100644 index 000000000000..365d98d4953c --- /dev/null +++ b/lib/kunit/hooks.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit 'Hooks' implementation. + * + * This file contains code / structures which should be built-in even when + * KUnit itself is built as a module. + * + * Copyright (C) 2022, Google LLC. + * Author: David Gow <davidgow@google.com> + */ + + +#include <kunit/test-bug.h> + +DEFINE_STATIC_KEY_FALSE(kunit_running); +EXPORT_SYMBOL(kunit_running); + +/* Function pointers for hooks. */ +struct kunit_hooks_table kunit_hooks; +EXPORT_SYMBOL(kunit_hooks); + diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c new file mode 100644 index 000000000000..9452b163956f --- /dev/null +++ b/lib/kunit/kunit-example-test.c @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Example KUnit test to show how to use KUnit. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ + +#include <kunit/test.h> +#include <kunit/static_stub.h> + +/* + * This is the most fundamental element of KUnit, the test case. A test case + * makes a set EXPECTATIONs and ASSERTIONs about the behavior of some code; if + * any expectations or assertions are not met, the test fails; otherwise, the + * test passes. + * + * In KUnit, a test case is just a function with the signature + * `void (*)(struct kunit *)`. `struct kunit` is a context object that stores + * information about the current test. + */ +static void example_simple_test(struct kunit *test) +{ + /* + * This is an EXPECTATION; it is how KUnit tests things. When you want + * to test a piece of code, you set some expectations about what the + * code should do. KUnit then runs the test and verifies that the code's + * behavior matched what was expected. + */ + KUNIT_EXPECT_EQ(test, 1 + 1, 2); +} + +/* + * This is run once before each test case, see the comment on + * example_test_suite for more information. + */ +static int example_test_init(struct kunit *test) +{ + kunit_info(test, "initializing\n"); + + return 0; +} + +/* + * This is run once after each test case, see the comment on + * example_test_suite for more information. + */ +static void example_test_exit(struct kunit *test) +{ + kunit_info(test, "cleaning up\n"); +} + + +/* + * This is run once before all test cases in the suite. + * See the comment on example_test_suite for more information. + */ +static int example_test_init_suite(struct kunit_suite *suite) +{ + kunit_info(suite, "initializing suite\n"); + + return 0; +} + +/* + * This is run once after all test cases in the suite. + * See the comment on example_test_suite for more information. + */ +static void example_test_exit_suite(struct kunit_suite *suite) +{ + kunit_info(suite, "exiting suite\n"); +} + + +/* + * This test should always be skipped. + */ +static void example_skip_test(struct kunit *test) +{ + /* This line should run */ + kunit_info(test, "You should not see a line below."); + + /* Skip (and abort) the test */ + kunit_skip(test, "this test should be skipped"); + + /* This line should not execute */ + KUNIT_FAIL(test, "You should not see this line."); +} + +/* + * This test should always be marked skipped. + */ +static void example_mark_skipped_test(struct kunit *test) +{ + /* This line should run */ + kunit_info(test, "You should see a line below."); + + /* Skip (but do not abort) the test */ + kunit_mark_skipped(test, "this test should be skipped"); + + /* This line should run */ + kunit_info(test, "You should see this line."); +} + +/* + * This test shows off all the types of KUNIT_EXPECT macros. + */ +static void example_all_expect_macros_test(struct kunit *test) +{ + const u32 array1[] = { 0x0F, 0xFF }; + const u32 array2[] = { 0x1F, 0xFF }; + + /* Boolean assertions */ + KUNIT_EXPECT_TRUE(test, true); + KUNIT_EXPECT_FALSE(test, false); + + /* Integer assertions */ + KUNIT_EXPECT_EQ(test, 1, 1); /* check == */ + KUNIT_EXPECT_GE(test, 1, 1); /* check >= */ + KUNIT_EXPECT_LE(test, 1, 1); /* check <= */ + KUNIT_EXPECT_NE(test, 1, 0); /* check != */ + KUNIT_EXPECT_GT(test, 1, 0); /* check > */ + KUNIT_EXPECT_LT(test, 0, 1); /* check < */ + + /* Pointer assertions */ + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, test); + KUNIT_EXPECT_PTR_EQ(test, NULL, NULL); + KUNIT_EXPECT_PTR_NE(test, test, NULL); + KUNIT_EXPECT_NULL(test, NULL); + KUNIT_EXPECT_NOT_NULL(test, test); + + /* String assertions */ + KUNIT_EXPECT_STREQ(test, "hi", "hi"); + KUNIT_EXPECT_STRNEQ(test, "hi", "bye"); + + /* Memory block assertions */ + KUNIT_EXPECT_MEMEQ(test, array1, array1, sizeof(array1)); + KUNIT_EXPECT_MEMNEQ(test, array1, array2, sizeof(array1)); + + /* + * There are also ASSERT variants of all of the above that abort test + * execution if they fail. Useful for memory allocations, etc. + */ + KUNIT_ASSERT_GT(test, sizeof(char), 0); + + /* + * There are also _MSG variants of all of the above that let you include + * additional text on failure. + */ + KUNIT_EXPECT_GT_MSG(test, sizeof(int), 0, "Your ints are 0-bit?!"); + KUNIT_ASSERT_GT_MSG(test, sizeof(int), 0, "Your ints are 0-bit?!"); +} + +/* This is a function we'll replace with static stubs. */ +static int add_one(int i) +{ + /* This will trigger the stub if active. */ + KUNIT_STATIC_STUB_REDIRECT(add_one, i); + + return i + 1; +} + +/* This is used as a replacement for the above function. */ +static int subtract_one(int i) +{ + /* We don't need to trigger the stub from the replacement. */ + + return i - 1; +} + +/* + * If the function to be replaced is static within a module it is + * useful to export a pointer to that function instead of having + * to change the static function to a non-static exported function. + * + * This pointer simulates a module exporting a pointer to a static + * function. + */ +static int (* const add_one_fn_ptr)(int i) = add_one; + +/* + * This test shows the use of static stubs. + */ +static void example_static_stub_test(struct kunit *test) +{ + /* By default, function is not stubbed. */ + KUNIT_EXPECT_EQ(test, add_one(1), 2); + + /* Replace add_one() with subtract_one(). */ + kunit_activate_static_stub(test, add_one, subtract_one); + + /* add_one() is now replaced. */ + KUNIT_EXPECT_EQ(test, add_one(1), 0); + + /* Return add_one() to normal. */ + kunit_deactivate_static_stub(test, add_one); + KUNIT_EXPECT_EQ(test, add_one(1), 2); +} + +/* + * This test shows the use of static stubs when the function being + * replaced is provided as a pointer-to-function instead of the + * actual function. This is useful for providing access to static + * functions in a module by exporting a pointer to that function + * instead of having to change the static function to a non-static + * exported function. + */ +static void example_static_stub_using_fn_ptr_test(struct kunit *test) +{ + /* By default, function is not stubbed. */ + KUNIT_EXPECT_EQ(test, add_one(1), 2); + + /* Replace add_one() with subtract_one(). */ + kunit_activate_static_stub(test, add_one_fn_ptr, subtract_one); + + /* add_one() is now replaced. */ + KUNIT_EXPECT_EQ(test, add_one(1), 0); + + /* Return add_one() to normal. */ + kunit_deactivate_static_stub(test, add_one_fn_ptr); + KUNIT_EXPECT_EQ(test, add_one(1), 2); +} + +static const struct example_param { + int value; +} example_params_array[] = { + { .value = 3, }, + { .value = 2, }, + { .value = 1, }, + { .value = 0, }, +}; + +static void example_param_get_desc(const struct example_param *p, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "example value %d", p->value); +} + +KUNIT_ARRAY_PARAM(example, example_params_array, example_param_get_desc); + +/* + * This test shows the use of params. + */ +static void example_params_test(struct kunit *test) +{ + const struct example_param *param = test->param_value; + + /* By design, param pointer will not be NULL */ + KUNIT_ASSERT_NOT_NULL(test, param); + + /* Test can be skipped on unsupported param values */ + if (!is_power_of_2(param->value)) + kunit_skip(test, "unsupported param value %d", param->value); + + /* You can use param values for parameterized testing */ + KUNIT_EXPECT_EQ(test, param->value % param->value, 0); +} + +/* + * This test shows the use of test->priv. + */ +static void example_priv_test(struct kunit *test) +{ + /* unless setup in suite->init(), test->priv is NULL */ + KUNIT_ASSERT_NULL(test, test->priv); + + /* but can be used to pass arbitrary data to other functions */ + test->priv = kunit_kzalloc(test, 1, GFP_KERNEL); + KUNIT_EXPECT_NOT_NULL(test, test->priv); + KUNIT_ASSERT_PTR_EQ(test, test->priv, kunit_get_current_test()->priv); +} + +/* + * This test should always pass. Can be used to practice filtering attributes. + */ +static void example_slow_test(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, 1 + 1, 2); +} + +/* + * This custom function allocates memory and sets the information we want + * stored in the kunit_resource->data field. + */ +static int example_resource_init(struct kunit_resource *res, void *context) +{ + int *info = kmalloc(sizeof(*info), GFP_KERNEL); + + if (!info) + return -ENOMEM; + *info = *(int *)context; + res->data = info; + return 0; +} + +/* + * This function deallocates memory for the kunit_resource->data field. + */ +static void example_resource_free(struct kunit_resource *res) +{ + kfree(res->data); +} + +/* + * This match function is invoked by kunit_find_resource() to locate + * a test resource based on certain criteria. + */ +static bool example_resource_alloc_match(struct kunit *test, + struct kunit_resource *res, + void *match_data) +{ + return res->data && res->free == example_resource_free; +} + +/* + * This is an example of a function that provides a description for each of the + * parameters in a parameterized test. + */ +static void example_param_array_get_desc(struct kunit *test, const void *p, char *desc) +{ + const struct example_param *param = p; + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, + "example check if %d is less than or equal to 3", param->value); +} + +/* + * This function gets passed in the parameterized test context i.e. the + * struct kunit belonging to the parameterized test. You can use this function + * to add resources you want shared across the whole parameterized test or + * for additional setup. + */ +static int example_param_init(struct kunit *test) +{ + int ctx = 3; /* Data to be stored. */ + size_t arr_size = ARRAY_SIZE(example_params_array); + + /* + * This allocates a struct kunit_resource, sets its data field to + * ctx, and adds it to the struct kunit's resources list. Note that + * this is parameterized test managed. So, it doesn't need to have + * a custom exit function to deallocation as it will get cleaned up at + * the end of the parameterized test. + */ + void *data = kunit_alloc_resource(test, example_resource_init, example_resource_free, + GFP_KERNEL, &ctx); + + if (!data) + return -ENOMEM; + /* + * Pass the parameter array information to the parameterized test context + * struct kunit. Note that you will need to provide kunit_array_gen_params() + * as the generator function to KUNIT_CASE_PARAM_WITH_INIT() when registering + * a parameter array this route. + */ + kunit_register_params_array(test, example_params_array, arr_size, + example_param_array_get_desc); + return 0; +} + +/* + * This is an example of a test that uses shared resources available in the + * parameterized test context. + */ +static void example_params_test_with_init(struct kunit *test) +{ + int threshold; + struct kunit_resource *res; + const struct example_param *param = test->param_value; + + /* By design, param pointer will not be NULL. */ + KUNIT_ASSERT_NOT_NULL(test, param); + + /* + * Here we pass test->parent to search for shared resources in the + * parameterized test context. + */ + res = kunit_find_resource(test->parent, example_resource_alloc_match, NULL); + + KUNIT_ASSERT_NOT_NULL(test, res); + + /* Since kunit_resource->data is a void pointer we need to typecast it. */ + threshold = *((int *)res->data); + + /* Assert that the parameter is less than or equal to a certain threshold. */ + KUNIT_ASSERT_LE(test, param->value, threshold); + + /* This decreases the reference count after calling kunit_find_resource(). */ + kunit_put_resource(res); +} + +/* + * Helper function to create a parameter array of Fibonacci numbers. This example + * highlights a parameter generation scenario that is: + * 1. Not feasible to fully pre-generate at compile time. + * 2. Challenging to implement with a standard generate_params() function, + * as it only provides the previous parameter, while Fibonacci requires + * access to two preceding values for calculation. + */ +static void *make_fibonacci_params(struct kunit *test, size_t seq_size) +{ + int *seq; + + if (seq_size <= 0) + return NULL; + /* + * Using kunit_kmalloc_array here ties the lifetime of the array to + * the parameterized test i.e. it will get automatically cleaned up + * by KUnit after the parameterized test finishes. + */ + seq = kunit_kmalloc_array(test, seq_size, sizeof(int), GFP_KERNEL); + + if (!seq) + return NULL; + if (seq_size >= 1) + seq[0] = 0; + if (seq_size >= 2) + seq[1] = 1; + for (int i = 2; i < seq_size; i++) + seq[i] = seq[i - 1] + seq[i - 2]; + return seq; +} + +/* + * This is an example of a function that provides a description for each of the + * parameters. + */ +static void example_param_dynamic_arr_get_desc(struct kunit *test, const void *p, char *desc) +{ + const int *fib_num = p; + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "fibonacci param: %d", *fib_num); +} + +/* + * Example of a parameterized test param_init() function that registers a dynamic + * array of parameters. + */ +static int example_param_init_dynamic_arr(struct kunit *test) +{ + size_t seq_size; + int *fibonacci_params; + + kunit_info(test, "initializing parameterized test\n"); + + seq_size = 6; + fibonacci_params = make_fibonacci_params(test, seq_size); + + if (!fibonacci_params) + return -ENOMEM; + + /* + * Passes the dynamic parameter array information to the parameterized test + * context struct kunit. The array and its metadata will be stored in + * test->parent->params_array. The array itself will be located in + * params_data.params. + * + * Note that you will need to pass kunit_array_gen_params() as the + * generator function to KUNIT_CASE_PARAM_WITH_INIT() when registering + * a parameter array this route. + */ + kunit_register_params_array(test, fibonacci_params, seq_size, + example_param_dynamic_arr_get_desc); + return 0; +} + +/* + * Example of a parameterized test param_exit() function that outputs a log + * at the end of the parameterized test. It could also be used for any other + * teardown logic. + */ +static void example_param_exit_dynamic_arr(struct kunit *test) +{ + kunit_info(test, "exiting parameterized test\n"); +} + +/* + * Example of test that uses the registered dynamic array to perform assertions + * and expectations. + */ +static void example_params_test_with_init_dynamic_arr(struct kunit *test) +{ + const int *param = test->param_value; + int param_val; + + /* By design, param pointer will not be NULL. */ + KUNIT_ASSERT_NOT_NULL(test, param); + + param_val = *param; + KUNIT_EXPECT_EQ(test, param_val - param_val, 0); +} + +/* + * Here we make a list of all the test cases we want to add to the test suite + * below. + */ +static struct kunit_case example_test_cases[] = { + /* + * This is a helper to create a test case object from a test case + * function; its exact function is not important to understand how to + * use KUnit, just know that this is how you associate test cases with a + * test suite. + */ + KUNIT_CASE(example_simple_test), + KUNIT_CASE(example_skip_test), + KUNIT_CASE(example_mark_skipped_test), + KUNIT_CASE(example_all_expect_macros_test), + KUNIT_CASE(example_static_stub_test), + KUNIT_CASE(example_static_stub_using_fn_ptr_test), + KUNIT_CASE(example_priv_test), + KUNIT_CASE_PARAM(example_params_test, example_gen_params), + KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init, kunit_array_gen_params, + example_param_init, NULL), + KUNIT_CASE_PARAM_WITH_INIT(example_params_test_with_init_dynamic_arr, + kunit_array_gen_params, example_param_init_dynamic_arr, + example_param_exit_dynamic_arr), + KUNIT_CASE_SLOW(example_slow_test), + {} +}; + +/* + * This defines a suite or grouping of tests. + * + * Test cases are defined as belonging to the suite by adding them to + * `kunit_cases`. + * + * Often it is desirable to run some function which will set up things which + * will be used by every test; this is accomplished with an `init` function + * which runs before each test case is invoked. Similarly, an `exit` function + * may be specified which runs after every test case and can be used to for + * cleanup. For clarity, running tests in a test suite would behave as follows: + * + * suite.suite_init(suite); + * suite.init(test); + * suite.test_case[0](test); + * suite.exit(test); + * suite.init(test); + * suite.test_case[1](test); + * suite.exit(test); + * suite.suite_exit(suite); + * ...; + */ +static struct kunit_suite example_test_suite = { + .name = "example", + .init = example_test_init, + .exit = example_test_exit, + .suite_init = example_test_init_suite, + .suite_exit = example_test_exit_suite, + .test_cases = example_test_cases, +}; + +/* + * This registers the above test suite telling KUnit that this is a suite of + * tests that need to be run. + */ +kunit_test_suites(&example_test_suite); + +static int __init init_add(int x, int y) +{ + return (x + y); +} + +/* + * This test should always pass. Can be used to test init suites. + */ +static void __init example_init_test(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, init_add(1, 1), 2); +} + +/* + * The kunit_case struct cannot be marked as __initdata as this will be + * used in debugfs to retrieve results after test has run + */ +static struct kunit_case __refdata example_init_test_cases[] = { + KUNIT_CASE(example_init_test), + {} +}; + +/* + * The kunit_suite struct cannot be marked as __initdata as this will be + * used in debugfs to retrieve results after test has run + */ +static struct kunit_suite example_init_test_suite = { + .name = "example_init", + .test_cases = example_init_test_cases, +}; + +/* + * This registers the test suite and marks the suite as using init data + * and/or functions. + */ +kunit_test_init_section_suites(&example_init_test_suite); + +MODULE_DESCRIPTION("Example KUnit test suite"); +MODULE_LICENSE("GPL v2"); diff --git a/lib/kunit/kunit-test.c b/lib/kunit/kunit-test.c new file mode 100644 index 000000000000..63130a48e237 --- /dev/null +++ b/lib/kunit/kunit-test.c @@ -0,0 +1,924 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for core test infrastructure. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ +#include "linux/gfp_types.h" +#include <kunit/test.h> +#include <kunit/test-bug.h> +#include <kunit/static_stub.h> + +#include <linux/device.h> +#include <kunit/device.h> + +#include "string-stream.h" +#include "try-catch-impl.h" + +struct kunit_try_catch_test_context { + struct kunit_try_catch *try_catch; + bool function_called; +}; + +static void kunit_test_successful_try(void *data) +{ + struct kunit *test = data; + struct kunit_try_catch_test_context *ctx = test->priv; + + ctx->function_called = true; +} + +static void kunit_test_no_catch(void *data) +{ + struct kunit *test = data; + + KUNIT_FAIL(test, "Catch should not be called\n"); +} + +static void kunit_test_try_catch_successful_try_no_catch(struct kunit *test) +{ + struct kunit_try_catch_test_context *ctx = test->priv; + struct kunit_try_catch *try_catch = ctx->try_catch; + + kunit_try_catch_init(try_catch, + test, + kunit_test_successful_try, + kunit_test_no_catch, + 300 * msecs_to_jiffies(MSEC_PER_SEC)); + kunit_try_catch_run(try_catch, test); + + KUNIT_EXPECT_TRUE(test, ctx->function_called); +} + +static void kunit_test_unsuccessful_try(void *data) +{ + struct kunit *test = data; + struct kunit_try_catch_test_context *ctx = test->priv; + struct kunit_try_catch *try_catch = ctx->try_catch; + + kunit_try_catch_throw(try_catch); + KUNIT_FAIL(test, "This line should never be reached\n"); +} + +static void kunit_test_catch(void *data) +{ + struct kunit *test = data; + struct kunit_try_catch_test_context *ctx = test->priv; + + ctx->function_called = true; +} + +static void kunit_test_try_catch_unsuccessful_try_does_catch(struct kunit *test) +{ + struct kunit_try_catch_test_context *ctx = test->priv; + struct kunit_try_catch *try_catch = ctx->try_catch; + + kunit_try_catch_init(try_catch, + test, + kunit_test_unsuccessful_try, + kunit_test_catch, + 300 * msecs_to_jiffies(MSEC_PER_SEC)); + kunit_try_catch_run(try_catch, test); + + KUNIT_EXPECT_TRUE(test, ctx->function_called); +} + +static int kunit_try_catch_test_init(struct kunit *test) +{ + struct kunit_try_catch_test_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + test->priv = ctx; + + ctx->try_catch = kunit_kmalloc(test, + sizeof(*ctx->try_catch), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->try_catch); + + return 0; +} + +static struct kunit_case kunit_try_catch_test_cases[] = { + KUNIT_CASE(kunit_test_try_catch_successful_try_no_catch), + KUNIT_CASE(kunit_test_try_catch_unsuccessful_try_does_catch), + {} +}; + +static struct kunit_suite kunit_try_catch_test_suite = { + .name = "kunit-try-catch-test", + .init = kunit_try_catch_test_init, + .test_cases = kunit_try_catch_test_cases, +}; + +#if IS_ENABLED(CONFIG_KUNIT_FAULT_TEST) + +static void kunit_test_null_dereference(void *data) +{ + struct kunit *test = data; + int *null = NULL; + + *null = 0; + + KUNIT_FAIL(test, "This line should never be reached\n"); +} + +static void kunit_test_fault_null_dereference(struct kunit *test) +{ + struct kunit_try_catch_test_context *ctx = test->priv; + struct kunit_try_catch *try_catch = ctx->try_catch; + + kunit_try_catch_init(try_catch, + test, + kunit_test_null_dereference, + kunit_test_catch, + 300 * msecs_to_jiffies(MSEC_PER_SEC)); + kunit_try_catch_run(try_catch, test); + + KUNIT_EXPECT_EQ(test, try_catch->try_result, -EINTR); + KUNIT_EXPECT_TRUE(test, ctx->function_called); +} + +#endif /* CONFIG_KUNIT_FAULT_TEST */ + +static struct kunit_case kunit_fault_test_cases[] = { +#if IS_ENABLED(CONFIG_KUNIT_FAULT_TEST) + KUNIT_CASE(kunit_test_fault_null_dereference), +#endif /* CONFIG_KUNIT_FAULT_TEST */ + {} +}; + +static struct kunit_suite kunit_fault_test_suite = { + .name = "kunit_fault", + .init = kunit_try_catch_test_init, + .test_cases = kunit_fault_test_cases, +}; + +/* + * Context for testing test managed resources + * is_resource_initialized is used to test arbitrary resources + */ +struct kunit_test_resource_context { + struct kunit test; + bool is_resource_initialized; + int allocate_order[2]; + int free_order[4]; +}; + +static int fake_resource_init(struct kunit_resource *res, void *context) +{ + struct kunit_test_resource_context *ctx = context; + + res->data = &ctx->is_resource_initialized; + ctx->is_resource_initialized = true; + return 0; +} + +static void fake_resource_free(struct kunit_resource *res) +{ + bool *is_resource_initialized = res->data; + + *is_resource_initialized = false; +} + +static void kunit_resource_test_init_resources(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + + kunit_init_test(&ctx->test, "testing_test_init_test", NULL); + + KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_alloc_resource(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + struct kunit_resource *res; + kunit_resource_free_t free = fake_resource_free; + + res = kunit_alloc_and_get_resource(&ctx->test, + fake_resource_init, + fake_resource_free, + GFP_KERNEL, + ctx); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res); + KUNIT_EXPECT_PTR_EQ(test, + &ctx->is_resource_initialized, + (bool *)res->data); + KUNIT_EXPECT_TRUE(test, list_is_last(&res->node, &ctx->test.resources)); + KUNIT_EXPECT_PTR_EQ(test, free, res->free); + + kunit_put_resource(res); +} + +static inline bool kunit_resource_instance_match(struct kunit *test, + struct kunit_resource *res, + void *match_data) +{ + return res->data == match_data; +} + +/* + * Note: tests below use kunit_alloc_and_get_resource(), so as a consequence + * they have a reference to the associated resource that they must release + * via kunit_put_resource(). In normal operation, users will only + * have to do this for cases where they use kunit_find_resource(), and the + * kunit_alloc_resource() function will be used (which does not take a + * resource reference). + */ +static void kunit_resource_test_destroy_resource(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + struct kunit_resource *res = kunit_alloc_and_get_resource( + &ctx->test, + fake_resource_init, + fake_resource_free, + GFP_KERNEL, + ctx); + + kunit_put_resource(res); + + KUNIT_ASSERT_FALSE(test, + kunit_destroy_resource(&ctx->test, + kunit_resource_instance_match, + res->data)); + + KUNIT_EXPECT_FALSE(test, ctx->is_resource_initialized); + KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_remove_resource(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + struct kunit_resource *res = kunit_alloc_and_get_resource( + &ctx->test, + fake_resource_init, + fake_resource_free, + GFP_KERNEL, + ctx); + + /* The resource is in the list */ + KUNIT_EXPECT_FALSE(test, list_empty(&ctx->test.resources)); + + /* Remove the resource. The pointer is still valid, but it can't be + * found. + */ + kunit_remove_resource(test, res); + KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); + /* We haven't been freed yet. */ + KUNIT_EXPECT_TRUE(test, ctx->is_resource_initialized); + + /* Removing the resource multiple times is valid. */ + kunit_remove_resource(test, res); + KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); + /* Despite having been removed twice (from only one reference), the + * resource still has not been freed. + */ + KUNIT_EXPECT_TRUE(test, ctx->is_resource_initialized); + + /* Free the resource. */ + kunit_put_resource(res); + KUNIT_EXPECT_FALSE(test, ctx->is_resource_initialized); +} + +static void kunit_resource_test_cleanup_resources(struct kunit *test) +{ + int i; + struct kunit_test_resource_context *ctx = test->priv; + struct kunit_resource *resources[5]; + + for (i = 0; i < ARRAY_SIZE(resources); i++) { + resources[i] = kunit_alloc_and_get_resource(&ctx->test, + fake_resource_init, + fake_resource_free, + GFP_KERNEL, + ctx); + kunit_put_resource(resources[i]); + } + + kunit_cleanup(&ctx->test); + + KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_mark_order(int order_array[], + size_t order_size, + int key) +{ + int i; + + for (i = 0; i < order_size && order_array[i]; i++) + ; + + order_array[i] = key; +} + +#define KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, order_field, key) \ + kunit_resource_test_mark_order(ctx->order_field, \ + ARRAY_SIZE(ctx->order_field), \ + key) + +static int fake_resource_2_init(struct kunit_resource *res, void *context) +{ + struct kunit_test_resource_context *ctx = context; + + KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, allocate_order, 2); + + res->data = ctx; + + return 0; +} + +static void fake_resource_2_free(struct kunit_resource *res) +{ + struct kunit_test_resource_context *ctx = res->data; + + KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, free_order, 2); +} + +static int fake_resource_1_init(struct kunit_resource *res, void *context) +{ + struct kunit_test_resource_context *ctx = context; + struct kunit_resource *res2; + + res2 = kunit_alloc_and_get_resource(&ctx->test, + fake_resource_2_init, + fake_resource_2_free, + GFP_KERNEL, + ctx); + + KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, allocate_order, 1); + + res->data = ctx; + + kunit_put_resource(res2); + + return 0; +} + +static void fake_resource_1_free(struct kunit_resource *res) +{ + struct kunit_test_resource_context *ctx = res->data; + + KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, free_order, 1); +} + +/* + * TODO(brendanhiggins@google.com): replace the arrays that keep track of the + * order of allocation and freeing with strict mocks using the IN_SEQUENCE macro + * to assert allocation and freeing order when the feature becomes available. + */ +static void kunit_resource_test_proper_free_ordering(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + struct kunit_resource *res; + + /* fake_resource_1 allocates a fake_resource_2 in its init. */ + res = kunit_alloc_and_get_resource(&ctx->test, + fake_resource_1_init, + fake_resource_1_free, + GFP_KERNEL, + ctx); + + /* + * Since fake_resource_2_init calls KUNIT_RESOURCE_TEST_MARK_ORDER + * before returning to fake_resource_1_init, it should be the first to + * put its key in the allocate_order array. + */ + KUNIT_EXPECT_EQ(test, ctx->allocate_order[0], 2); + KUNIT_EXPECT_EQ(test, ctx->allocate_order[1], 1); + + kunit_put_resource(res); + + kunit_cleanup(&ctx->test); + + /* + * Because fake_resource_2 finishes allocation before fake_resource_1, + * fake_resource_1 should be freed first since it could depend on + * fake_resource_2. + */ + KUNIT_EXPECT_EQ(test, ctx->free_order[0], 1); + KUNIT_EXPECT_EQ(test, ctx->free_order[1], 2); +} + +static void kunit_resource_test_static(struct kunit *test) +{ + struct kunit_test_resource_context ctx; + struct kunit_resource res; + + KUNIT_EXPECT_EQ(test, kunit_add_resource(test, NULL, NULL, &res, &ctx), + 0); + + KUNIT_EXPECT_PTR_EQ(test, res.data, (void *)&ctx); + + kunit_cleanup(test); + + KUNIT_EXPECT_TRUE(test, list_empty(&test->resources)); +} + +static void kunit_resource_test_named(struct kunit *test) +{ + struct kunit_resource res1, res2, *found = NULL; + struct kunit_test_resource_context ctx; + + KUNIT_EXPECT_EQ(test, + kunit_add_named_resource(test, NULL, NULL, &res1, + "resource_1", &ctx), + 0); + KUNIT_EXPECT_PTR_EQ(test, res1.data, (void *)&ctx); + + KUNIT_EXPECT_EQ(test, + kunit_add_named_resource(test, NULL, NULL, &res1, + "resource_1", &ctx), + -EEXIST); + + KUNIT_EXPECT_EQ(test, + kunit_add_named_resource(test, NULL, NULL, &res2, + "resource_2", &ctx), + 0); + + found = kunit_find_named_resource(test, "resource_1"); + + KUNIT_EXPECT_PTR_EQ(test, found, &res1); + + if (found) + kunit_put_resource(&res1); + + KUNIT_EXPECT_EQ(test, kunit_destroy_named_resource(test, "resource_2"), + 0); + + kunit_cleanup(test); + + KUNIT_EXPECT_TRUE(test, list_empty(&test->resources)); +} + +static void increment_int(void *ctx) +{ + int *i = (int *)ctx; + (*i)++; +} + +static void kunit_resource_test_action(struct kunit *test) +{ + int num_actions = 0; + + kunit_add_action(test, increment_int, &num_actions); + KUNIT_EXPECT_EQ(test, num_actions, 0); + kunit_cleanup(test); + KUNIT_EXPECT_EQ(test, num_actions, 1); + + /* Once we've cleaned up, the action queue is empty. */ + kunit_cleanup(test); + KUNIT_EXPECT_EQ(test, num_actions, 1); + + /* Check the same function can be deferred multiple times. */ + kunit_add_action(test, increment_int, &num_actions); + kunit_add_action(test, increment_int, &num_actions); + kunit_cleanup(test); + KUNIT_EXPECT_EQ(test, num_actions, 3); +} +static void kunit_resource_test_remove_action(struct kunit *test) +{ + int num_actions = 0; + + kunit_add_action(test, increment_int, &num_actions); + KUNIT_EXPECT_EQ(test, num_actions, 0); + + kunit_remove_action(test, increment_int, &num_actions); + kunit_cleanup(test); + KUNIT_EXPECT_EQ(test, num_actions, 0); +} +static void kunit_resource_test_release_action(struct kunit *test) +{ + int num_actions = 0; + + kunit_add_action(test, increment_int, &num_actions); + KUNIT_EXPECT_EQ(test, num_actions, 0); + /* Runs immediately on trigger. */ + kunit_release_action(test, increment_int, &num_actions); + KUNIT_EXPECT_EQ(test, num_actions, 1); + + /* Doesn't run again on test exit. */ + kunit_cleanup(test); + KUNIT_EXPECT_EQ(test, num_actions, 1); +} +static void action_order_1(void *ctx) +{ + struct kunit_test_resource_context *res_ctx = (struct kunit_test_resource_context *)ctx; + + KUNIT_RESOURCE_TEST_MARK_ORDER(res_ctx, free_order, 1); + kunit_log(KERN_INFO, current->kunit_test, "action_order_1"); +} +static void action_order_2(void *ctx) +{ + struct kunit_test_resource_context *res_ctx = (struct kunit_test_resource_context *)ctx; + + KUNIT_RESOURCE_TEST_MARK_ORDER(res_ctx, free_order, 2); + kunit_log(KERN_INFO, current->kunit_test, "action_order_2"); +} +static void kunit_resource_test_action_ordering(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + + kunit_add_action(test, action_order_1, ctx); + kunit_add_action(test, action_order_2, ctx); + kunit_add_action(test, action_order_1, ctx); + kunit_add_action(test, action_order_2, ctx); + kunit_remove_action(test, action_order_1, ctx); + kunit_release_action(test, action_order_2, ctx); + kunit_cleanup(test); + + /* [2 is triggered] [2], [(1 is cancelled)] [1] */ + KUNIT_EXPECT_EQ(test, ctx->free_order[0], 2); + KUNIT_EXPECT_EQ(test, ctx->free_order[1], 2); + KUNIT_EXPECT_EQ(test, ctx->free_order[2], 1); +} + +static int kunit_resource_test_init(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = + kzalloc(sizeof(*ctx), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + test->priv = ctx; + + kunit_init_test(&ctx->test, "test_test_context", NULL); + + return 0; +} + +static void kunit_resource_test_exit(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + + kunit_cleanup(&ctx->test); + kfree(ctx); +} + +static struct kunit_case kunit_resource_test_cases[] = { + KUNIT_CASE(kunit_resource_test_init_resources), + KUNIT_CASE(kunit_resource_test_alloc_resource), + KUNIT_CASE(kunit_resource_test_destroy_resource), + KUNIT_CASE(kunit_resource_test_remove_resource), + KUNIT_CASE(kunit_resource_test_cleanup_resources), + KUNIT_CASE(kunit_resource_test_proper_free_ordering), + KUNIT_CASE(kunit_resource_test_static), + KUNIT_CASE(kunit_resource_test_named), + KUNIT_CASE(kunit_resource_test_action), + KUNIT_CASE(kunit_resource_test_remove_action), + KUNIT_CASE(kunit_resource_test_release_action), + KUNIT_CASE(kunit_resource_test_action_ordering), + {} +}; + +static struct kunit_suite kunit_resource_test_suite = { + .name = "kunit-resource-test", + .init = kunit_resource_test_init, + .exit = kunit_resource_test_exit, + .test_cases = kunit_resource_test_cases, +}; + +/* + * Log tests call string_stream functions, which aren't exported. So only + * build this code if this test is built-in. + */ +#if IS_BUILTIN(CONFIG_KUNIT_TEST) + +/* This avoids a cast warning if kfree() is passed direct to kunit_add_action(). */ +KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *); + +static void kunit_log_test(struct kunit *test) +{ + struct kunit_suite suite; +#ifdef CONFIG_KUNIT_DEBUGFS + char *full_log; +#endif + suite.log = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log); + string_stream_set_append_newlines(suite.log, true); + + kunit_log(KERN_INFO, test, "put this in log."); + kunit_log(KERN_INFO, test, "this too."); + kunit_log(KERN_INFO, &suite, "add to suite log."); + kunit_log(KERN_INFO, &suite, "along with this."); + +#ifdef CONFIG_KUNIT_DEBUGFS + KUNIT_EXPECT_TRUE(test, test->log->append_newlines); + + full_log = string_stream_get_string(test->log); + kunit_add_action(test, kfree_wrapper, full_log); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, + strstr(full_log, "put this in log.")); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, + strstr(full_log, "this too.")); + + full_log = string_stream_get_string(suite.log); + kunit_add_action(test, kfree_wrapper, full_log); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, + strstr(full_log, "add to suite log.")); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, + strstr(full_log, "along with this.")); +#else + KUNIT_EXPECT_NULL(test, test->log); +#endif +} + +static void kunit_log_newline_test(struct kunit *test) +{ + char *full_log; + + kunit_info(test, "Add newline\n"); + if (test->log) { + full_log = string_stream_get_string(test->log); + kunit_add_action(test, kfree_wrapper, full_log); + KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(full_log, "Add newline\n"), + "Missing log line, full log:\n%s", full_log); + KUNIT_EXPECT_NULL(test, strstr(full_log, "Add newline\n\n")); + } else { + kunit_skip(test, "only useful when debugfs is enabled"); + } +} +#else +static void kunit_log_test(struct kunit *test) +{ + kunit_skip(test, "Log tests only run when built-in"); +} + +static void kunit_log_newline_test(struct kunit *test) +{ + kunit_skip(test, "Log tests only run when built-in"); +} +#endif /* IS_BUILTIN(CONFIG_KUNIT_TEST) */ + +static struct kunit_case kunit_log_test_cases[] = { + KUNIT_CASE(kunit_log_test), + KUNIT_CASE(kunit_log_newline_test), + {} +}; + +static struct kunit_suite kunit_log_test_suite = { + .name = "kunit-log-test", + .test_cases = kunit_log_test_cases, +}; + +static void kunit_status_set_failure_test(struct kunit *test) +{ + struct kunit fake; + + kunit_init_test(&fake, "fake test", NULL); + + KUNIT_EXPECT_EQ(test, fake.status, (enum kunit_status)KUNIT_SUCCESS); + kunit_set_failure(&fake); + KUNIT_EXPECT_EQ(test, fake.status, (enum kunit_status)KUNIT_FAILURE); +} + +static void kunit_status_mark_skipped_test(struct kunit *test) +{ + struct kunit fake; + + kunit_init_test(&fake, "fake test", NULL); + + /* Before: Should be SUCCESS with no comment. */ + KUNIT_EXPECT_EQ(test, fake.status, KUNIT_SUCCESS); + KUNIT_EXPECT_STREQ(test, fake.status_comment, ""); + + /* Mark the test as skipped. */ + kunit_mark_skipped(&fake, "Accepts format string: %s", "YES"); + + /* After: Should be SKIPPED with our comment. */ + KUNIT_EXPECT_EQ(test, fake.status, (enum kunit_status)KUNIT_SKIPPED); + KUNIT_EXPECT_STREQ(test, fake.status_comment, "Accepts format string: YES"); +} + +static struct kunit_case kunit_status_test_cases[] = { + KUNIT_CASE(kunit_status_set_failure_test), + KUNIT_CASE(kunit_status_mark_skipped_test), + {} +}; + +static struct kunit_suite kunit_status_test_suite = { + .name = "kunit_status", + .test_cases = kunit_status_test_cases, +}; + +static void kunit_current_test(struct kunit *test) +{ + /* Check results of both current->kunit_test and + * kunit_get_current_test() are equivalent to current test. + */ + KUNIT_EXPECT_PTR_EQ(test, test, current->kunit_test); + KUNIT_EXPECT_PTR_EQ(test, test, kunit_get_current_test()); +} + +static void kunit_current_fail_test(struct kunit *test) +{ + struct kunit fake; + + kunit_init_test(&fake, "fake test", NULL); + KUNIT_EXPECT_EQ(test, fake.status, KUNIT_SUCCESS); + + /* Set current->kunit_test to fake test. */ + current->kunit_test = &fake; + + kunit_fail_current_test("This should make `fake` test fail."); + KUNIT_EXPECT_EQ(test, fake.status, (enum kunit_status)KUNIT_FAILURE); + kunit_cleanup(&fake); + + /* Reset current->kunit_test to current test. */ + current->kunit_test = test; +} + +static struct kunit_case kunit_current_test_cases[] = { + KUNIT_CASE(kunit_current_test), + KUNIT_CASE(kunit_current_fail_test), + {} +}; + +static void test_dev_action(void *priv) +{ + *(long *)priv = 1; +} + +static void kunit_device_test(struct kunit *test) +{ + struct device *test_device; + long action_was_run = 0; + + test_device = kunit_device_register(test, "my_device"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_device); + + // Add an action to verify cleanup. + devm_add_action(test_device, test_dev_action, &action_was_run); + + KUNIT_EXPECT_EQ(test, action_was_run, 0); + + kunit_device_unregister(test, test_device); + + KUNIT_EXPECT_EQ(test, action_was_run, 1); +} + +static void kunit_device_cleanup_test(struct kunit *test) +{ + struct device *test_device; + long action_was_run = 0; + + test_device = kunit_device_register(test, "my_device"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_device); + + /* Add an action to verify cleanup. */ + devm_add_action(test_device, test_dev_action, &action_was_run); + + KUNIT_EXPECT_EQ(test, action_was_run, 0); + + /* Force KUnit to run cleanup early. */ + kunit_cleanup(test); + + KUNIT_EXPECT_EQ(test, action_was_run, 1); +} + +struct driver_test_state { + bool driver_device_probed; + bool driver_device_removed; + long action_was_run; +}; + +static int driver_probe_hook(struct device *dev) +{ + struct kunit *test = kunit_get_current_test(); + struct driver_test_state *state = (struct driver_test_state *)test->priv; + + state->driver_device_probed = true; + return 0; +} + +static int driver_remove_hook(struct device *dev) +{ + struct kunit *test = kunit_get_current_test(); + struct driver_test_state *state = (struct driver_test_state *)test->priv; + + state->driver_device_removed = true; + return 0; +} + +static void kunit_device_driver_test(struct kunit *test) +{ + struct device_driver *test_driver; + struct device *test_device; + struct driver_test_state *test_state = kunit_kzalloc(test, sizeof(*test_state), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_state); + + test->priv = test_state; + test_driver = kunit_driver_create(test, "my_driver"); + + // This can fail with an error pointer. + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_driver); + + test_driver->probe = driver_probe_hook; + test_driver->remove = driver_remove_hook; + + test_device = kunit_device_register_with_driver(test, "my_device", test_driver); + + // This can fail with an error pointer. + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_device); + + // Make sure the probe function was called. + KUNIT_ASSERT_TRUE(test, test_state->driver_device_probed); + + // Add an action to verify cleanup. + devm_add_action(test_device, test_dev_action, &test_state->action_was_run); + + KUNIT_EXPECT_EQ(test, test_state->action_was_run, 0); + + kunit_device_unregister(test, test_device); + test_device = NULL; + + // Make sure the remove hook was called. + KUNIT_ASSERT_TRUE(test, test_state->driver_device_removed); + + // We're going to test this again. + test_state->driver_device_probed = false; + + // The driver should not automatically be destroyed by + // kunit_device_unregister, so we can re-use it. + test_device = kunit_device_register_with_driver(test, "my_device", test_driver); + + // This can fail with an error pointer. + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_device); + + // Probe was called again. + KUNIT_ASSERT_TRUE(test, test_state->driver_device_probed); + + // Everything is automatically freed here. +} + +static struct kunit_case kunit_device_test_cases[] = { + KUNIT_CASE(kunit_device_test), + KUNIT_CASE(kunit_device_cleanup_test), + KUNIT_CASE(kunit_device_driver_test), + {} +}; + +static struct kunit_suite kunit_device_test_suite = { + .name = "kunit_device", + .test_cases = kunit_device_test_cases, +}; + +static struct kunit_suite kunit_current_test_suite = { + .name = "kunit_current", + .test_cases = kunit_current_test_cases, +}; + +static void kunit_stub_test(struct kunit *test) +{ + struct kunit fake_test; + const unsigned long fake_real_fn_addr = 0x1234; + const unsigned long fake_replacement_addr = 0x5678; + struct kunit_resource *res; + struct { + void *real_fn_addr; + void *replacement_addr; + } *stub_ctx; + + kunit_init_test(&fake_test, "kunit_stub_fake_test", NULL); + KUNIT_ASSERT_EQ(test, fake_test.status, KUNIT_SUCCESS); + KUNIT_ASSERT_EQ(test, list_count_nodes(&fake_test.resources), 0); + + __kunit_activate_static_stub(&fake_test, (void *)fake_real_fn_addr, + (void *)fake_replacement_addr); + KUNIT_ASSERT_EQ(test, fake_test.status, KUNIT_SUCCESS); + KUNIT_ASSERT_EQ(test, list_count_nodes(&fake_test.resources), 1); + + res = list_first_entry(&fake_test.resources, struct kunit_resource, node); + KUNIT_EXPECT_NOT_NULL(test, res); + + stub_ctx = res->data; + KUNIT_EXPECT_NOT_NULL(test, stub_ctx); + KUNIT_EXPECT_EQ(test, (unsigned long)stub_ctx->real_fn_addr, fake_real_fn_addr); + KUNIT_EXPECT_EQ(test, (unsigned long)stub_ctx->replacement_addr, fake_replacement_addr); + + __kunit_activate_static_stub(&fake_test, (void *)fake_real_fn_addr, NULL); + KUNIT_ASSERT_EQ(test, fake_test.status, KUNIT_SUCCESS); + KUNIT_ASSERT_EQ(test, list_count_nodes(&fake_test.resources), 0); +} + +static struct kunit_case kunit_stub_test_cases[] = { + KUNIT_CASE(kunit_stub_test), + {} +}; + +static struct kunit_suite kunit_stub_test_suite = { + .name = "kunit_stub", + .test_cases = kunit_stub_test_cases, +}; + +kunit_test_suites(&kunit_try_catch_test_suite, &kunit_resource_test_suite, + &kunit_log_test_suite, &kunit_status_test_suite, + &kunit_current_test_suite, &kunit_device_test_suite, + &kunit_fault_test_suite, &kunit_stub_test_suite); + +MODULE_DESCRIPTION("KUnit test for core test infrastructure"); +MODULE_LICENSE("GPL v2"); diff --git a/lib/kunit/platform-test.c b/lib/kunit/platform-test.c new file mode 100644 index 000000000000..e3debb8fbcef --- /dev/null +++ b/lib/kunit/platform-test.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for KUnit platform driver infrastructure. + */ + +#include <linux/platform_device.h> + +#include <kunit/platform_device.h> +#include <kunit/test.h> + +/* + * Test that kunit_platform_device_alloc() creates a platform device. + */ +static void kunit_platform_device_alloc_test(struct kunit *test) +{ + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, + kunit_platform_device_alloc(test, "kunit-platform", 1)); +} + +/* + * Test that kunit_platform_device_add() registers a platform device on the + * platform bus with the proper name and id. + */ +static void kunit_platform_device_add_test(struct kunit *test) +{ + struct platform_device *pdev; + const char *name = "kunit-platform-add"; + const int id = -1; + + pdev = kunit_platform_device_alloc(test, name, id); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + KUNIT_EXPECT_EQ(test, 0, kunit_platform_device_add(test, pdev)); + KUNIT_EXPECT_TRUE(test, dev_is_platform(&pdev->dev)); + KUNIT_EXPECT_STREQ(test, pdev->name, name); + KUNIT_EXPECT_EQ(test, pdev->id, id); +} + +/* + * Test that kunit_platform_device_add() called twice with the same device name + * and id fails the second time and properly cleans up. + */ +static void kunit_platform_device_add_twice_fails_test(struct kunit *test) +{ + struct platform_device *pdev; + const char *name = "kunit-platform-add-2"; + const int id = -1; + + pdev = kunit_platform_device_alloc(test, name, id); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev)); + + pdev = kunit_platform_device_alloc(test, name, id); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + KUNIT_EXPECT_NE(test, 0, kunit_platform_device_add(test, pdev)); +} + +static int kunit_platform_device_find_by_name(struct device *dev, const void *data) +{ + return strcmp(dev_name(dev), data) == 0; +} + +/* + * Test that kunit_platform_device_add() cleans up by removing the platform + * device when the test finishes. */ +static void kunit_platform_device_add_cleans_up(struct kunit *test) +{ + struct platform_device *pdev; + const char *name = "kunit-platform-clean"; + const int id = -1; + struct kunit fake; + struct device *dev; + + kunit_init_test(&fake, "kunit_platform_device_add_fake_test", NULL); + KUNIT_ASSERT_EQ(test, fake.status, KUNIT_SUCCESS); + + pdev = kunit_platform_device_alloc(&fake, name, id); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(&fake, pdev)); + dev = bus_find_device(&platform_bus_type, NULL, name, + kunit_platform_device_find_by_name); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + put_device(dev); + + /* Remove pdev */ + kunit_cleanup(&fake); + + /* + * Failing to migrate the kunit_resource would lead to an extra + * put_device() call on the platform device. The best we can do here is + * make sure the device no longer exists on the bus, but if something + * is wrong we'll see a refcount underflow here. We can't test for a + * refcount underflow because the kref matches the lifetime of the + * device which should already be freed and could be used by something + * else. + */ + dev = bus_find_device(&platform_bus_type, NULL, name, + kunit_platform_device_find_by_name); + KUNIT_EXPECT_PTR_EQ(test, NULL, dev); + put_device(dev); +} + +/* + * Test suite for struct platform_device kunit APIs + */ +static struct kunit_case kunit_platform_device_test_cases[] = { + KUNIT_CASE(kunit_platform_device_alloc_test), + KUNIT_CASE(kunit_platform_device_add_test), + KUNIT_CASE(kunit_platform_device_add_twice_fails_test), + KUNIT_CASE(kunit_platform_device_add_cleans_up), + {} +}; + +static struct kunit_suite kunit_platform_device_suite = { + .name = "kunit_platform_device", + .test_cases = kunit_platform_device_test_cases, +}; + +struct kunit_platform_driver_test_context { + struct platform_driver pdrv; + const char *data; +}; + +static const char * const test_data = "test data"; + +static inline struct kunit_platform_driver_test_context * +to_test_context(struct platform_device *pdev) +{ + return container_of(to_platform_driver(pdev->dev.driver), + struct kunit_platform_driver_test_context, + pdrv); +} + +static int kunit_platform_driver_probe(struct platform_device *pdev) +{ + struct kunit_platform_driver_test_context *ctx; + + ctx = to_test_context(pdev); + ctx->data = test_data; + + return 0; +} + +/* Test that kunit_platform_driver_register() registers a driver that probes. */ +static void kunit_platform_driver_register_test(struct kunit *test) +{ + struct platform_device *pdev; + struct kunit_platform_driver_test_context *ctx; + DECLARE_COMPLETION_ONSTACK(comp); + const char *name = "kunit-platform-register"; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + pdev = kunit_platform_device_alloc(test, name, -1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev)); + + ctx->pdrv.probe = kunit_platform_driver_probe; + ctx->pdrv.driver.name = name; + ctx->pdrv.driver.owner = THIS_MODULE; + + KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, &comp)); + + KUNIT_EXPECT_EQ(test, 0, kunit_platform_driver_register(test, &ctx->pdrv)); + KUNIT_EXPECT_NE(test, 0, wait_for_completion_timeout(&comp, 3 * HZ)); + KUNIT_EXPECT_STREQ(test, ctx->data, test_data); +} + +/* + * Test that kunit_platform_device_prepare_wait_for_probe() completes the completion + * when the device is already probed. + */ +static void kunit_platform_device_prepare_wait_for_probe_completes_when_already_probed(struct kunit *test) +{ + struct platform_device *pdev; + struct kunit_platform_driver_test_context *ctx; + DECLARE_COMPLETION_ONSTACK(comp); + const char *name = "kunit-platform-wait"; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + pdev = kunit_platform_device_alloc(test, name, -1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev)); + + ctx->pdrv.probe = kunit_platform_driver_probe; + ctx->pdrv.driver.name = name; + ctx->pdrv.driver.owner = THIS_MODULE; + + /* Make sure driver has actually probed */ + KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, &comp)); + KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, &ctx->pdrv)); + KUNIT_ASSERT_NE(test, 0, wait_for_completion_timeout(&comp, 3 * HZ)); + + reinit_completion(&comp); + KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, &comp)); + + KUNIT_EXPECT_NE(test, 0, wait_for_completion_timeout(&comp, HZ)); +} + +static struct kunit_case kunit_platform_driver_test_cases[] = { + KUNIT_CASE(kunit_platform_driver_register_test), + KUNIT_CASE(kunit_platform_device_prepare_wait_for_probe_completes_when_already_probed), + {} +}; + +/* + * Test suite for struct platform_driver kunit APIs + */ +static struct kunit_suite kunit_platform_driver_suite = { + .name = "kunit_platform_driver", + .test_cases = kunit_platform_driver_test_cases, +}; + +kunit_test_suites( + &kunit_platform_device_suite, + &kunit_platform_driver_suite, +); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("KUnit test for KUnit platform driver infrastructure"); diff --git a/lib/kunit/platform.c b/lib/kunit/platform.c new file mode 100644 index 000000000000..0b518de26065 --- /dev/null +++ b/lib/kunit/platform.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test managed platform driver + */ + +#include <linux/completion.h> +#include <linux/device/bus.h> +#include <linux/device/driver.h> +#include <linux/platform_device.h> + +#include <kunit/platform_device.h> +#include <kunit/resource.h> + +struct kunit_platform_device_alloc_params { + const char *name; + int id; +}; + +static int kunit_platform_device_alloc_init(struct kunit_resource *res, void *context) +{ + struct kunit_platform_device_alloc_params *params = context; + struct platform_device *pdev; + + pdev = platform_device_alloc(params->name, params->id); + if (!pdev) + return -ENOMEM; + + res->data = pdev; + + return 0; +} + +static void kunit_platform_device_alloc_exit(struct kunit_resource *res) +{ + struct platform_device *pdev = res->data; + + platform_device_put(pdev); +} + +/** + * kunit_platform_device_alloc() - Allocate a KUnit test managed platform device + * @test: test context + * @name: device name of platform device to alloc + * @id: identifier of platform device to alloc. + * + * Allocate a test managed platform device. The device is put when the test completes. + * + * Return: Allocated platform device on success, NULL on failure. + */ +struct platform_device * +kunit_platform_device_alloc(struct kunit *test, const char *name, int id) +{ + struct kunit_platform_device_alloc_params params = { + .name = name, + .id = id, + }; + + return kunit_alloc_resource(test, + kunit_platform_device_alloc_init, + kunit_platform_device_alloc_exit, + GFP_KERNEL, ¶ms); +} +EXPORT_SYMBOL_GPL(kunit_platform_device_alloc); + +static void kunit_platform_device_add_exit(struct kunit_resource *res) +{ + struct platform_device *pdev = res->data; + + platform_device_unregister(pdev); +} + +static bool +kunit_platform_device_alloc_match(struct kunit *test, + struct kunit_resource *res, void *match_data) +{ + struct platform_device *pdev = match_data; + + return res->data == pdev && res->free == kunit_platform_device_alloc_exit; +} + +KUNIT_DEFINE_ACTION_WRAPPER(platform_device_unregister_wrapper, + platform_device_unregister, struct platform_device *); +/** + * kunit_platform_device_add() - Register a KUnit test managed platform device + * @test: test context + * @pdev: platform device to add + * + * Register a test managed platform device. The device is unregistered when the + * test completes. + * + * Return: 0 on success, negative errno on failure. + */ +int kunit_platform_device_add(struct kunit *test, struct platform_device *pdev) +{ + struct kunit_resource *res; + int ret; + + ret = platform_device_add(pdev); + if (ret) + return ret; + + res = kunit_find_resource(test, kunit_platform_device_alloc_match, pdev); + if (res) { + /* + * Transfer the reference count of the platform device if it + * was allocated with kunit_platform_device_alloc(). In this + * case, calling platform_device_put() when the test exits from + * kunit_platform_device_alloc_exit() would lead to reference + * count underflow because platform_device_unregister_wrapper() + * calls platform_device_unregister() which also calls + * platform_device_put(). + * + * Usually callers transfer the refcount initialized in + * platform_device_alloc() to platform_device_add() by calling + * platform_device_unregister() when platform_device_add() + * succeeds or platform_device_put() when it fails. KUnit has to + * keep this straight by redirecting the free routine for the + * resource to the right function. Luckily this only has to + * account for the success scenario. + */ + res->free = kunit_platform_device_add_exit; + kunit_put_resource(res); + } else { + ret = kunit_add_action_or_reset(test, platform_device_unregister_wrapper, pdev); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(kunit_platform_device_add); + +struct kunit_platform_device_probe_nb { + struct completion *x; + struct device *dev; + struct notifier_block nb; +}; + +static int kunit_platform_device_probe_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct kunit_platform_device_probe_nb *knb; + struct device *dev = data; + + knb = container_of(nb, struct kunit_platform_device_probe_nb, nb); + if (event != BUS_NOTIFY_BOUND_DRIVER || knb->dev != dev) + return NOTIFY_DONE; + + complete(knb->x); + + return NOTIFY_OK; +} + +static void kunit_platform_device_probe_nb_remove(void *nb) +{ + bus_unregister_notifier(&platform_bus_type, nb); +} + +/** + * kunit_platform_device_prepare_wait_for_probe() - Prepare a completion + * variable to wait for a platform device to probe + * @test: test context + * @pdev: platform device to prepare to wait for probe of + * @x: completion variable completed when @dev has probed + * + * Prepare a completion variable @x to wait for @pdev to probe. Waiting on the + * completion forces a preemption, allowing the platform driver to probe. + * + * Example + * + * .. code-block:: c + * + * static int kunit_platform_driver_probe(struct platform_device *pdev) + * { + * return 0; + * } + * + * static void kunit_platform_driver_test(struct kunit *test) + * { + * struct platform_device *pdev; + * struct platform_driver *pdrv; + * DECLARE_COMPLETION_ONSTACK(comp); + * + * pdev = kunit_platform_device_alloc(test, "kunit-platform", -1); + * KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + * KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev)); + * + * pdrv = kunit_kzalloc(test, sizeof(*pdrv), GFP_KERNEL); + * KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdrv); + * + * pdrv->probe = kunit_platform_driver_probe; + * pdrv->driver.name = "kunit-platform"; + * pdrv->driver.owner = THIS_MODULE; + * + * KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, &comp)); + * KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, pdrv)); + * + * KUNIT_EXPECT_NE(test, 0, wait_for_completion_timeout(&comp, 3 * HZ)); + * } + * + * Return: 0 on success, negative errno on failure. + */ +int kunit_platform_device_prepare_wait_for_probe(struct kunit *test, + struct platform_device *pdev, + struct completion *x) +{ + struct device *dev = &pdev->dev; + struct kunit_platform_device_probe_nb *knb; + bool bound; + + knb = kunit_kzalloc(test, sizeof(*knb), GFP_KERNEL); + if (!knb) + return -ENOMEM; + + knb->nb.notifier_call = kunit_platform_device_probe_notify; + knb->dev = dev; + knb->x = x; + + device_lock(dev); + bound = device_is_bound(dev); + if (bound) { + device_unlock(dev); + complete(x); + kunit_kfree(test, knb); + return 0; + } + + bus_register_notifier(&platform_bus_type, &knb->nb); + device_unlock(&pdev->dev); + + return kunit_add_action_or_reset(test, kunit_platform_device_probe_nb_remove, &knb->nb); +} +EXPORT_SYMBOL_GPL(kunit_platform_device_prepare_wait_for_probe); + +KUNIT_DEFINE_ACTION_WRAPPER(platform_driver_unregister_wrapper, + platform_driver_unregister, struct platform_driver *); +/** + * kunit_platform_driver_register() - Register a KUnit test managed platform driver + * @test: test context + * @drv: platform driver to register + * + * Register a test managed platform driver. This allows callers to embed the + * @drv in a container structure and use container_of() in the probe function + * to pass information to KUnit tests. + * + * Example + * + * .. code-block:: c + * + * struct kunit_test_context { + * struct platform_driver pdrv; + * const char *data; + * }; + * + * static inline struct kunit_test_context * + * to_test_context(struct platform_device *pdev) + * { + * return container_of(to_platform_driver(pdev->dev.driver), + * struct kunit_test_context, + * pdrv); + * } + * + * static int kunit_platform_driver_probe(struct platform_device *pdev) + * { + * struct kunit_test_context *ctx; + * + * ctx = to_test_context(pdev); + * ctx->data = "test data"; + * + * return 0; + * } + * + * static void kunit_platform_driver_test(struct kunit *test) + * { + * struct kunit_test_context *ctx; + * + * ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + * KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + * + * ctx->pdrv.probe = kunit_platform_driver_probe; + * ctx->pdrv.driver.name = "kunit-platform"; + * ctx->pdrv.driver.owner = THIS_MODULE; + * + * KUNIT_EXPECT_EQ(test, 0, kunit_platform_driver_register(test, &ctx->pdrv)); + * <... wait for driver to probe ...> + * KUNIT_EXPECT_STREQ(test, ctx->data, "test data"); + * } + * + * Return: 0 on success, negative errno on failure. + */ +int kunit_platform_driver_register(struct kunit *test, + struct platform_driver *drv) +{ + int ret; + + ret = platform_driver_register(drv); + if (ret) + return ret; + + return kunit_add_action_or_reset(test, platform_driver_unregister_wrapper, drv); +} +EXPORT_SYMBOL_GPL(kunit_platform_driver_register); diff --git a/lib/kunit/resource.c b/lib/kunit/resource.c new file mode 100644 index 000000000000..f0209252b179 --- /dev/null +++ b/lib/kunit/resource.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit resource API for test managed resources (allocations, etc.). + * + * Copyright (C) 2022, Google LLC. + * Author: Daniel Latypov <dlatypov@google.com> + */ + +#include <kunit/resource.h> +#include <kunit/test.h> +#include <linux/kref.h> + +/* + * Used for static resources and when a kunit_resource * has been created by + * kunit_alloc_resource(). When an init function is supplied, @data is passed + * into the init function; otherwise, we simply set the resource data field to + * the data value passed in. Doesn't initialize res->should_kfree. + */ +int __kunit_add_resource(struct kunit *test, + kunit_resource_init_t init, + kunit_resource_free_t free, + struct kunit_resource *res, + void *data) +{ + int ret = 0; + unsigned long flags; + + res->free = free; + kref_init(&res->refcount); + + if (init) { + ret = init(res, data); + if (ret) + return ret; + } else { + res->data = data; + } + + spin_lock_irqsave(&test->lock, flags); + list_add_tail(&res->node, &test->resources); + /* refcount for list is established by kref_init() */ + spin_unlock_irqrestore(&test->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(__kunit_add_resource); + +void kunit_remove_resource(struct kunit *test, struct kunit_resource *res) +{ + unsigned long flags; + bool was_linked; + + spin_lock_irqsave(&test->lock, flags); + was_linked = !list_empty(&res->node); + list_del_init(&res->node); + spin_unlock_irqrestore(&test->lock, flags); + + if (was_linked) + kunit_put_resource(res); +} +EXPORT_SYMBOL_GPL(kunit_remove_resource); + +int kunit_destroy_resource(struct kunit *test, kunit_resource_match_t match, + void *match_data) +{ + struct kunit_resource *res = kunit_find_resource(test, match, + match_data); + + if (!res) + return -ENOENT; + + kunit_remove_resource(test, res); + + /* We have a reference also via _find(); drop it. */ + kunit_put_resource(res); + + return 0; +} +EXPORT_SYMBOL_GPL(kunit_destroy_resource); + +struct kunit_action_ctx { + struct kunit_resource res; + kunit_action_t *func; + void *ctx; +}; + +static void __kunit_action_free(struct kunit_resource *res) +{ + struct kunit_action_ctx *action_ctx = container_of(res, struct kunit_action_ctx, res); + + action_ctx->func(action_ctx->ctx); +} + + +int kunit_add_action(struct kunit *test, void (*action)(void *), void *ctx) +{ + struct kunit_action_ctx *action_ctx; + + KUNIT_ASSERT_NOT_NULL_MSG(test, action, "Tried to action a NULL function!"); + + action_ctx = kzalloc(sizeof(*action_ctx), GFP_KERNEL); + if (!action_ctx) + return -ENOMEM; + + action_ctx->func = action; + action_ctx->ctx = ctx; + + action_ctx->res.should_kfree = true; + /* As init is NULL, this cannot fail. */ + __kunit_add_resource(test, NULL, __kunit_action_free, &action_ctx->res, action_ctx); + + return 0; +} +EXPORT_SYMBOL_GPL(kunit_add_action); + +int kunit_add_action_or_reset(struct kunit *test, void (*action)(void *), + void *ctx) +{ + int res = kunit_add_action(test, action, ctx); + + if (res) + action(ctx); + return res; +} +EXPORT_SYMBOL_GPL(kunit_add_action_or_reset); + +static bool __kunit_action_match(struct kunit *test, + struct kunit_resource *res, void *match_data) +{ + struct kunit_action_ctx *match_ctx = (struct kunit_action_ctx *)match_data; + struct kunit_action_ctx *res_ctx = container_of(res, struct kunit_action_ctx, res); + + /* Make sure this is a free function. */ + if (res->free != __kunit_action_free) + return false; + + /* Both the function and context data should match. */ + return (match_ctx->func == res_ctx->func) && (match_ctx->ctx == res_ctx->ctx); +} + +void kunit_remove_action(struct kunit *test, + kunit_action_t *action, + void *ctx) +{ + struct kunit_action_ctx match_ctx; + struct kunit_resource *res; + + match_ctx.func = action; + match_ctx.ctx = ctx; + + res = kunit_find_resource(test, __kunit_action_match, &match_ctx); + if (res) { + /* Remove the free function so we don't run the action. */ + res->free = NULL; + kunit_remove_resource(test, res); + kunit_put_resource(res); + } +} +EXPORT_SYMBOL_GPL(kunit_remove_action); + +void kunit_release_action(struct kunit *test, + kunit_action_t *action, + void *ctx) +{ + struct kunit_action_ctx match_ctx; + struct kunit_resource *res; + + match_ctx.func = action; + match_ctx.ctx = ctx; + + res = kunit_find_resource(test, __kunit_action_match, &match_ctx); + if (res) { + kunit_remove_resource(test, res); + /* We have to put() this here, else free won't be called. */ + kunit_put_resource(res); + } +} +EXPORT_SYMBOL_GPL(kunit_release_action); diff --git a/lib/kunit/static_stub.c b/lib/kunit/static_stub.c new file mode 100644 index 000000000000..484fd85251b4 --- /dev/null +++ b/lib/kunit/static_stub.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit function redirection (static stubbing) API. + * + * Copyright (C) 2022, Google LLC. + * Author: David Gow <davidgow@google.com> + */ + +#include <kunit/test.h> +#include <kunit/static_stub.h> +#include "hooks-impl.h" + + +/* Context for a static stub. This is stored in the resource data. */ +struct kunit_static_stub_ctx { + void *real_fn_addr; + void *replacement_addr; +}; + +static void __kunit_static_stub_resource_free(struct kunit_resource *res) +{ + kfree(res->data); +} + +/* Matching function for kunit_find_resource(). match_data is real_fn_addr. */ +static bool __kunit_static_stub_resource_match(struct kunit *test, + struct kunit_resource *res, + void *match_real_fn_addr) +{ + /* This pointer is only valid if res is a static stub resource. */ + struct kunit_static_stub_ctx *ctx = res->data; + + /* Make sure the resource is a static stub resource. */ + if (res->free != &__kunit_static_stub_resource_free) + return false; + + return ctx->real_fn_addr == match_real_fn_addr; +} + +/* Hook to return the address of the replacement function. */ +void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr) +{ + struct kunit_resource *res; + struct kunit_static_stub_ctx *ctx; + void *replacement_addr; + + res = kunit_find_resource(test, + __kunit_static_stub_resource_match, + real_fn_addr); + + if (!res) + return NULL; + + ctx = res->data; + replacement_addr = ctx->replacement_addr; + kunit_put_resource(res); + return replacement_addr; +} + +void kunit_deactivate_static_stub(struct kunit *test, void *real_fn_addr) +{ + struct kunit_resource *res; + + KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL, + "Tried to deactivate a NULL stub."); + + /* Look up the existing stub for this function. */ + res = kunit_find_resource(test, + __kunit_static_stub_resource_match, + real_fn_addr); + + /* Error out if the stub doesn't exist. */ + KUNIT_ASSERT_PTR_NE_MSG(test, res, NULL, + "Tried to deactivate a nonexistent stub."); + + /* Free the stub. We 'put' twice, as we got a reference + * from kunit_find_resource() + */ + kunit_remove_resource(test, res); + kunit_put_resource(res); +} +EXPORT_SYMBOL_GPL(kunit_deactivate_static_stub); + +/* Helper function for kunit_activate_static_stub(). The macro does + * typechecking, so use it instead. + */ +void __kunit_activate_static_stub(struct kunit *test, + void *real_fn_addr, + void *replacement_addr) +{ + struct kunit_static_stub_ctx *ctx; + struct kunit_resource *res; + + KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL, + "Tried to activate a stub for function NULL"); + + /* If the replacement address is NULL, deactivate the stub. */ + if (!replacement_addr) { + kunit_deactivate_static_stub(test, real_fn_addr); + return; + } + + /* Look up any existing stubs for this function, and replace them. */ + res = kunit_find_resource(test, + __kunit_static_stub_resource_match, + real_fn_addr); + if (res) { + ctx = res->data; + ctx->replacement_addr = replacement_addr; + + /* We got an extra reference from find_resource(), so put it. */ + kunit_put_resource(res); + } else { + ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + ctx->real_fn_addr = real_fn_addr; + ctx->replacement_addr = replacement_addr; + res = kunit_alloc_resource(test, NULL, + &__kunit_static_stub_resource_free, + GFP_KERNEL, ctx); + } +} +EXPORT_SYMBOL_GPL(__kunit_activate_static_stub); diff --git a/lib/kunit/string-stream-test.c b/lib/kunit/string-stream-test.c new file mode 100644 index 000000000000..7734e33156f9 --- /dev/null +++ b/lib/kunit/string-stream-test.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for struct string_stream. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ + +#include <kunit/static_stub.h> +#include <kunit/test.h> +#include <linux/ktime.h> +#include <linux/prandom.h> +#include <linux/slab.h> +#include <linux/timekeeping.h> + +#include "string-stream.h" + +struct string_stream_test_priv { + /* For testing resource-managed free. */ + struct string_stream *expected_free_stream; + bool stream_was_freed; + bool stream_free_again; +}; + +/* Avoids a cast warning if kfree() is passed direct to kunit_add_action(). */ +KUNIT_DEFINE_ACTION_WRAPPER(kfree_wrapper, kfree, const void *); + +/* Avoids a cast warning if string_stream_destroy() is passed direct to kunit_add_action(). */ +KUNIT_DEFINE_ACTION_WRAPPER(cleanup_raw_stream, string_stream_destroy, struct string_stream *); + +static char *get_concatenated_string(struct kunit *test, struct string_stream *stream) +{ + char *str = string_stream_get_string(stream); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, str); + kunit_add_action(test, kfree_wrapper, (void *)str); + + return str; +} + +/* Managed string_stream object is initialized correctly. */ +static void string_stream_managed_init_test(struct kunit *test) +{ + struct string_stream *stream; + + /* Resource-managed initialization. */ + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + KUNIT_EXPECT_EQ(test, stream->length, 0); + KUNIT_EXPECT_TRUE(test, list_empty(&stream->fragments)); + KUNIT_EXPECT_TRUE(test, (stream->gfp == GFP_KERNEL)); + KUNIT_EXPECT_FALSE(test, stream->append_newlines); + KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream)); +} + +/* Unmanaged string_stream object is initialized correctly. */ +static void string_stream_unmanaged_init_test(struct kunit *test) +{ + struct string_stream *stream; + + stream = alloc_string_stream(GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + kunit_add_action(test, cleanup_raw_stream, stream); + + KUNIT_EXPECT_EQ(test, stream->length, 0); + KUNIT_EXPECT_TRUE(test, list_empty(&stream->fragments)); + KUNIT_EXPECT_TRUE(test, (stream->gfp == GFP_KERNEL)); + KUNIT_EXPECT_FALSE(test, stream->append_newlines); + + KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream)); +} + +static void string_stream_destroy_stub(struct string_stream *stream) +{ + struct kunit *fake_test = kunit_get_current_test(); + struct string_stream_test_priv *priv = fake_test->priv; + + /* The kunit could own string_streams other than the one we are testing. */ + if (stream == priv->expected_free_stream) { + if (priv->stream_was_freed) + priv->stream_free_again = true; + else + priv->stream_was_freed = true; + } + + /* + * Calling string_stream_destroy() will only call this function again + * because the redirection stub is still active. + * Avoid calling deactivate_static_stub() or changing current->kunit_test + * during cleanup. + */ + string_stream_clear(stream); + kfree(stream); +} + +/* kunit_free_string_stream() calls string_stream_desrtoy() */ +static void string_stream_managed_free_test(struct kunit *test) +{ + struct string_stream_test_priv *priv = test->priv; + + priv->expected_free_stream = NULL; + priv->stream_was_freed = false; + priv->stream_free_again = false; + + kunit_activate_static_stub(test, + string_stream_destroy, + string_stream_destroy_stub); + + priv->expected_free_stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->expected_free_stream); + + /* This should call the stub function. */ + kunit_free_string_stream(test, priv->expected_free_stream); + + KUNIT_EXPECT_TRUE(test, priv->stream_was_freed); + KUNIT_EXPECT_FALSE(test, priv->stream_free_again); +} + +/* string_stream object is freed when test is cleaned up. */ +static void string_stream_resource_free_test(struct kunit *test) +{ + struct string_stream_test_priv *priv = test->priv; + struct kunit *fake_test; + + fake_test = kunit_kzalloc(test, sizeof(*fake_test), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_test); + + kunit_init_test(fake_test, "string_stream_fake_test", NULL); + fake_test->priv = priv; + + /* + * Activate stub before creating string_stream so the + * string_stream will be cleaned up first. + */ + priv->expected_free_stream = NULL; + priv->stream_was_freed = false; + priv->stream_free_again = false; + + kunit_activate_static_stub(fake_test, + string_stream_destroy, + string_stream_destroy_stub); + + priv->expected_free_stream = kunit_alloc_string_stream(fake_test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->expected_free_stream); + + /* Set current->kunit_test to fake_test so the static stub will be called. */ + current->kunit_test = fake_test; + + /* Cleanup test - the stub function should be called */ + kunit_cleanup(fake_test); + + /* Set current->kunit_test back to current test. */ + current->kunit_test = test; + + KUNIT_EXPECT_TRUE(test, priv->stream_was_freed); + KUNIT_EXPECT_FALSE(test, priv->stream_free_again); +} + +/* + * Add a series of lines to a string_stream. Check that all lines + * appear in the correct order and no characters are dropped. + */ +static void string_stream_line_add_test(struct kunit *test) +{ + struct string_stream *stream; + char line[60]; + char *concat_string, *pos, *string_end; + size_t len, total_len; + int num_lines, i; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + /* Add series of sequence numbered lines */ + total_len = 0; + for (i = 0; i < 100; ++i) { + len = snprintf(line, sizeof(line), + "The quick brown fox jumps over the lazy penguin %d\n", i); + + /* Sanity-check that our test string isn't truncated */ + KUNIT_ASSERT_LT(test, len, sizeof(line)); + + string_stream_add(stream, line); + total_len += len; + } + num_lines = i; + + concat_string = get_concatenated_string(test, stream); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, concat_string); + KUNIT_EXPECT_EQ(test, strlen(concat_string), total_len); + + /* + * Split the concatenated string at the newlines and check that + * all the original added strings are present. + */ + pos = concat_string; + for (i = 0; i < num_lines; ++i) { + string_end = strchr(pos, '\n'); + KUNIT_EXPECT_NOT_NULL(test, string_end); + + /* Convert to NULL-terminated string */ + *string_end = '\0'; + + snprintf(line, sizeof(line), + "The quick brown fox jumps over the lazy penguin %d", i); + KUNIT_EXPECT_STREQ(test, pos, line); + + pos = string_end + 1; + } + + /* There shouldn't be any more data after this */ + KUNIT_EXPECT_EQ(test, strlen(pos), 0); +} + +/* Add a series of lines of variable length to a string_stream. */ +static void string_stream_variable_length_line_test(struct kunit *test) +{ + static const char line[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + " 0123456789!$%^&*()_-+={}[]:;@'~#<>,.?/|"; + struct string_stream *stream; + struct rnd_state rnd; + char *concat_string, *pos, *string_end; + size_t offset, total_len; + int num_lines, i; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + /* + * Log many lines of varying lengths until we have created + * many fragments. + * The "randomness" must be repeatable. + */ + prandom_seed_state(&rnd, 3141592653589793238ULL); + total_len = 0; + for (i = 0; i < 100; ++i) { + offset = prandom_u32_state(&rnd) % (sizeof(line) - 1); + string_stream_add(stream, "%s\n", &line[offset]); + total_len += sizeof(line) - offset; + } + num_lines = i; + + concat_string = get_concatenated_string(test, stream); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, concat_string); + KUNIT_EXPECT_EQ(test, strlen(concat_string), total_len); + + /* + * Split the concatenated string at the newlines and check that + * all the original added strings are present. + */ + prandom_seed_state(&rnd, 3141592653589793238ULL); + pos = concat_string; + for (i = 0; i < num_lines; ++i) { + string_end = strchr(pos, '\n'); + KUNIT_EXPECT_NOT_NULL(test, string_end); + + /* Convert to NULL-terminated string */ + *string_end = '\0'; + + offset = prandom_u32_state(&rnd) % (sizeof(line) - 1); + KUNIT_EXPECT_STREQ(test, pos, &line[offset]); + + pos = string_end + 1; + } + + /* There shouldn't be any more data after this */ + KUNIT_EXPECT_EQ(test, strlen(pos), 0); +} + +/* Appending the content of one string stream to another. */ +static void string_stream_append_test(struct kunit *test) +{ + static const char * const strings_1[] = { + "one", "two", "three", "four", "five", "six", + "seven", "eight", "nine", "ten", + }; + static const char * const strings_2[] = { + "Apple", "Pear", "Orange", "Banana", "Grape", "Apricot", + }; + struct string_stream *stream_1, *stream_2; + const char *stream1_content_before_append, *stream_2_content; + char *combined_content; + size_t combined_length; + int i; + + stream_1 = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_1); + + stream_2 = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_2); + + /* Append content of empty stream to empty stream */ + string_stream_append(stream_1, stream_2); + KUNIT_EXPECT_EQ(test, strlen(get_concatenated_string(test, stream_1)), 0); + + /* Add some data to stream_1 */ + for (i = 0; i < ARRAY_SIZE(strings_1); ++i) + string_stream_add(stream_1, "%s\n", strings_1[i]); + + stream1_content_before_append = get_concatenated_string(test, stream_1); + + /* Append content of empty stream to non-empty stream */ + string_stream_append(stream_1, stream_2); + KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1), + stream1_content_before_append); + + /* Add some data to stream_2 */ + for (i = 0; i < ARRAY_SIZE(strings_2); ++i) + string_stream_add(stream_2, "%s\n", strings_2[i]); + + /* Append content of non-empty stream to non-empty stream */ + string_stream_append(stream_1, stream_2); + + /* + * End result should be the original content of stream_1 plus + * the content of stream_2. + */ + stream_2_content = get_concatenated_string(test, stream_2); + combined_length = strlen(stream1_content_before_append) + strlen(stream_2_content); + combined_length++; /* for terminating \0 */ + combined_content = kunit_kmalloc(test, combined_length, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, combined_content); + snprintf(combined_content, combined_length, "%s%s", + stream1_content_before_append, stream_2_content); + + KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1), combined_content); + + /* Append content of non-empty stream to empty stream */ + kunit_free_string_stream(test, stream_1); + + stream_1 = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_1); + + string_stream_append(stream_1, stream_2); + KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1), stream_2_content); +} + +/* Appending the content of one string stream to one with auto-newlining. */ +static void string_stream_append_auto_newline_test(struct kunit *test) +{ + struct string_stream *stream_1, *stream_2; + + /* Stream 1 has newline appending enabled */ + stream_1 = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_1); + string_stream_set_append_newlines(stream_1, true); + KUNIT_EXPECT_TRUE(test, stream_1->append_newlines); + + /* Stream 2 does not append newlines */ + stream_2 = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_2); + + /* Appending a stream with a newline should not add another newline */ + string_stream_add(stream_1, "Original string\n"); + string_stream_add(stream_2, "Appended content\n"); + string_stream_add(stream_2, "More stuff\n"); + string_stream_append(stream_1, stream_2); + KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1), + "Original string\nAppended content\nMore stuff\n"); + + kunit_free_string_stream(test, stream_2); + stream_2 = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream_2); + + /* + * Appending a stream without newline should add a final newline. + * The appended string_stream is treated as a single string so newlines + * should not be inserted between fragments. + */ + string_stream_add(stream_2, "Another"); + string_stream_add(stream_2, "And again"); + string_stream_append(stream_1, stream_2); + KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream_1), + "Original string\nAppended content\nMore stuff\nAnotherAnd again\n"); +} + +/* Adding an empty string should not create a fragment. */ +static void string_stream_append_empty_string_test(struct kunit *test) +{ + struct string_stream *stream; + int original_frag_count; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + /* Formatted empty string */ + string_stream_add(stream, "%s", ""); + KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream)); + KUNIT_EXPECT_TRUE(test, list_empty(&stream->fragments)); + + /* Adding an empty string to a non-empty stream */ + string_stream_add(stream, "Add this line"); + original_frag_count = list_count_nodes(&stream->fragments); + + string_stream_add(stream, "%s", ""); + KUNIT_EXPECT_EQ(test, list_count_nodes(&stream->fragments), original_frag_count); + KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream), "Add this line"); +} + +/* Adding strings without automatic newline appending */ +static void string_stream_no_auto_newline_test(struct kunit *test) +{ + struct string_stream *stream; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + /* + * Add some strings with and without newlines. All formatted newlines + * should be preserved. It should not add any extra newlines. + */ + string_stream_add(stream, "One"); + string_stream_add(stream, "Two\n"); + string_stream_add(stream, "%s\n", "Three"); + string_stream_add(stream, "%s", "Four\n"); + string_stream_add(stream, "Five\n%s", "Six"); + string_stream_add(stream, "Seven\n\n"); + string_stream_add(stream, "Eight"); + KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream), + "OneTwo\nThree\nFour\nFive\nSixSeven\n\nEight"); +} + +/* Adding strings with automatic newline appending */ +static void string_stream_auto_newline_test(struct kunit *test) +{ + struct string_stream *stream; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + string_stream_set_append_newlines(stream, true); + KUNIT_EXPECT_TRUE(test, stream->append_newlines); + + /* + * Add some strings with and without newlines. Newlines should + * be appended to lines that do not end with \n, but newlines + * resulting from the formatting should not be changed. + */ + string_stream_add(stream, "One"); + string_stream_add(stream, "Two\n"); + string_stream_add(stream, "%s\n", "Three"); + string_stream_add(stream, "%s", "Four\n"); + string_stream_add(stream, "Five\n%s", "Six"); + string_stream_add(stream, "Seven\n\n"); + string_stream_add(stream, "Eight"); + KUNIT_EXPECT_STREQ(test, get_concatenated_string(test, stream), + "One\nTwo\nThree\nFour\nFive\nSix\nSeven\n\nEight\n"); +} + +/* + * This doesn't actually "test" anything. It reports time taken + * and memory used for logging a large number of lines. + */ +static void string_stream_performance_test(struct kunit *test) +{ + struct string_stream_fragment *frag_container; + struct string_stream *stream; + char test_line[101]; + ktime_t start_time, end_time; + size_t len, bytes_requested, actual_bytes_used, total_string_length; + int offset, i; + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + memset(test_line, 'x', sizeof(test_line) - 1); + test_line[sizeof(test_line) - 1] = '\0'; + + start_time = ktime_get(); + for (i = 0; i < 10000; i++) { + offset = i % (sizeof(test_line) - 1); + string_stream_add(stream, "%s: %d\n", &test_line[offset], i); + } + end_time = ktime_get(); + + /* + * Calculate memory used. This doesn't include invisible + * overhead due to kernel allocator fragment size rounding. + */ + bytes_requested = sizeof(*stream); + actual_bytes_used = ksize(stream); + total_string_length = 0; + + list_for_each_entry(frag_container, &stream->fragments, node) { + bytes_requested += sizeof(*frag_container); + actual_bytes_used += ksize(frag_container); + + len = strlen(frag_container->fragment); + total_string_length += len; + bytes_requested += len + 1; /* +1 for '\0' */ + actual_bytes_used += ksize(frag_container->fragment); + } + + kunit_info(test, "Time elapsed: %lld us\n", + ktime_us_delta(end_time, start_time)); + kunit_info(test, "Total string length: %zu\n", total_string_length); + kunit_info(test, "Bytes requested: %zu\n", bytes_requested); + kunit_info(test, "Actual bytes allocated: %zu\n", actual_bytes_used); +} + +static int string_stream_test_init(struct kunit *test) +{ + struct string_stream_test_priv *priv; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + test->priv = priv; + + return 0; +} + +static struct kunit_case string_stream_test_cases[] = { + KUNIT_CASE(string_stream_managed_init_test), + KUNIT_CASE(string_stream_unmanaged_init_test), + KUNIT_CASE(string_stream_managed_free_test), + KUNIT_CASE(string_stream_resource_free_test), + KUNIT_CASE(string_stream_line_add_test), + KUNIT_CASE(string_stream_variable_length_line_test), + KUNIT_CASE(string_stream_append_test), + KUNIT_CASE(string_stream_append_auto_newline_test), + KUNIT_CASE(string_stream_append_empty_string_test), + KUNIT_CASE(string_stream_no_auto_newline_test), + KUNIT_CASE(string_stream_auto_newline_test), + KUNIT_CASE(string_stream_performance_test), + {} +}; + +static struct kunit_suite string_stream_test_suite = { + .name = "string-stream-test", + .test_cases = string_stream_test_cases, + .init = string_stream_test_init, +}; +kunit_test_suites(&string_stream_test_suite); diff --git a/lib/kunit/string-stream.c b/lib/kunit/string-stream.c new file mode 100644 index 000000000000..54f4fdcbfac8 --- /dev/null +++ b/lib/kunit/string-stream.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * C++ stream style string builder used in KUnit for building messages. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ + +#include <kunit/static_stub.h> +#include <kunit/test.h> +#include <linux/list.h> +#include <linux/slab.h> + +#include "string-stream.h" + + +static struct string_stream_fragment *alloc_string_stream_fragment(int len, gfp_t gfp) +{ + struct string_stream_fragment *frag; + + frag = kzalloc(sizeof(*frag), gfp); + if (!frag) + return ERR_PTR(-ENOMEM); + + frag->fragment = kmalloc(len, gfp); + if (!frag->fragment) { + kfree(frag); + return ERR_PTR(-ENOMEM); + } + + return frag; +} + +static void string_stream_fragment_destroy(struct string_stream_fragment *frag) +{ + list_del(&frag->node); + kfree(frag->fragment); + kfree(frag); +} + +int string_stream_vadd(struct string_stream *stream, + const char *fmt, + va_list args) +{ + struct string_stream_fragment *frag_container; + int buf_len, result_len; + va_list args_for_counting; + + /* Make a copy because `vsnprintf` could change it */ + va_copy(args_for_counting, args); + + /* Evaluate length of formatted string */ + buf_len = vsnprintf(NULL, 0, fmt, args_for_counting); + + va_end(args_for_counting); + + if (buf_len == 0) + return 0; + + /* Reserve one extra for possible appended newline. */ + if (stream->append_newlines) + buf_len++; + + /* Need space for null byte. */ + buf_len++; + + frag_container = alloc_string_stream_fragment(buf_len, stream->gfp); + if (IS_ERR(frag_container)) + return PTR_ERR(frag_container); + + if (stream->append_newlines) { + /* Don't include reserved newline byte in writeable length. */ + result_len = vsnprintf(frag_container->fragment, buf_len - 1, fmt, args); + + /* Append newline if necessary. */ + if (frag_container->fragment[result_len - 1] != '\n') + result_len = strlcat(frag_container->fragment, "\n", buf_len); + } else { + result_len = vsnprintf(frag_container->fragment, buf_len, fmt, args); + } + + spin_lock(&stream->lock); + stream->length += result_len; + list_add_tail(&frag_container->node, &stream->fragments); + spin_unlock(&stream->lock); + + return 0; +} + +int string_stream_add(struct string_stream *stream, const char *fmt, ...) +{ + va_list args; + int result; + + va_start(args, fmt); + result = string_stream_vadd(stream, fmt, args); + va_end(args); + + return result; +} + +void string_stream_clear(struct string_stream *stream) +{ + struct string_stream_fragment *frag_container, *frag_container_safe; + + spin_lock(&stream->lock); + list_for_each_entry_safe(frag_container, + frag_container_safe, + &stream->fragments, + node) { + string_stream_fragment_destroy(frag_container); + } + stream->length = 0; + spin_unlock(&stream->lock); +} + +char *string_stream_get_string(struct string_stream *stream) +{ + struct string_stream_fragment *frag_container; + size_t buf_len = stream->length + 1; /* +1 for null byte. */ + char *buf; + + buf = kzalloc(buf_len, stream->gfp); + if (!buf) + return NULL; + + spin_lock(&stream->lock); + list_for_each_entry(frag_container, &stream->fragments, node) + strlcat(buf, frag_container->fragment, buf_len); + spin_unlock(&stream->lock); + + return buf; +} + +int string_stream_append(struct string_stream *stream, + struct string_stream *other) +{ + const char *other_content; + int ret; + + other_content = string_stream_get_string(other); + + if (!other_content) + return -ENOMEM; + + ret = string_stream_add(stream, other_content); + kfree(other_content); + + return ret; +} + +bool string_stream_is_empty(struct string_stream *stream) +{ + return list_empty(&stream->fragments); +} + +struct string_stream *alloc_string_stream(gfp_t gfp) +{ + struct string_stream *stream; + + stream = kzalloc(sizeof(*stream), gfp); + if (!stream) + return ERR_PTR(-ENOMEM); + + stream->gfp = gfp; + INIT_LIST_HEAD(&stream->fragments); + spin_lock_init(&stream->lock); + + return stream; +} + +void string_stream_destroy(struct string_stream *stream) +{ + KUNIT_STATIC_STUB_REDIRECT(string_stream_destroy, stream); + + if (IS_ERR_OR_NULL(stream)) + return; + + string_stream_clear(stream); + kfree(stream); +} + +static void resource_free_string_stream(void *p) +{ + struct string_stream *stream = p; + + string_stream_destroy(stream); +} + +struct string_stream *kunit_alloc_string_stream(struct kunit *test, gfp_t gfp) +{ + struct string_stream *stream; + + stream = alloc_string_stream(gfp); + if (IS_ERR(stream)) + return stream; + + if (kunit_add_action_or_reset(test, resource_free_string_stream, stream) != 0) + return ERR_PTR(-ENOMEM); + + return stream; +} + +void kunit_free_string_stream(struct kunit *test, struct string_stream *stream) +{ + kunit_release_action(test, resource_free_string_stream, (void *)stream); +} diff --git a/lib/kunit/string-stream.h b/lib/kunit/string-stream.h new file mode 100644 index 000000000000..7be2450c7079 --- /dev/null +++ b/lib/kunit/string-stream.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * C++ stream style string builder used in KUnit for building messages. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ + +#ifndef _KUNIT_STRING_STREAM_H +#define _KUNIT_STRING_STREAM_H + +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/stdarg.h> + +struct string_stream_fragment { + struct list_head node; + char *fragment; +}; + +struct string_stream { + size_t length; + struct list_head fragments; + /* length and fragments are protected by this lock */ + spinlock_t lock; + gfp_t gfp; + bool append_newlines; +}; + +struct kunit; + +struct string_stream *kunit_alloc_string_stream(struct kunit *test, gfp_t gfp); +void kunit_free_string_stream(struct kunit *test, struct string_stream *stream); + +struct string_stream *alloc_string_stream(gfp_t gfp); +void free_string_stream(struct string_stream *stream); + +int __printf(2, 3) string_stream_add(struct string_stream *stream, + const char *fmt, ...); + +int __printf(2, 0) string_stream_vadd(struct string_stream *stream, + const char *fmt, + va_list args); + +void string_stream_clear(struct string_stream *stream); + +char *string_stream_get_string(struct string_stream *stream); + +int string_stream_append(struct string_stream *stream, + struct string_stream *other); + +bool string_stream_is_empty(struct string_stream *stream); + +void string_stream_destroy(struct string_stream *stream); + +static inline void string_stream_set_append_newlines(struct string_stream *stream, + bool append_newlines) +{ + stream->append_newlines = append_newlines; +} + +#endif /* _KUNIT_STRING_STREAM_H */ diff --git a/lib/kunit/test.c b/lib/kunit/test.c new file mode 100644 index 000000000000..62eb529824c6 --- /dev/null +++ b/lib/kunit/test.c @@ -0,0 +1,1063 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Base unit test (KUnit) API. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ + +#include <kunit/resource.h> +#include <kunit/test.h> +#include <kunit/test-bug.h> +#include <kunit/attributes.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/panic.h> +#include <linux/sched/debug.h> +#include <linux/sched.h> +#include <linux/mm.h> + +#include "debugfs.h" +#include "device-impl.h" +#include "hooks-impl.h" +#include "string-stream.h" +#include "try-catch-impl.h" + +static DEFINE_MUTEX(kunit_run_lock); + +/* + * Hook to fail the current test and print an error message to the log. + */ +void __printf(3, 4) __kunit_fail_current_test_impl(const char *file, int line, const char *fmt, ...) +{ + va_list args; + int len; + char *buffer; + + if (!current->kunit_test) + return; + + kunit_set_failure(current->kunit_test); + + /* kunit_err() only accepts literals, so evaluate the args first. */ + va_start(args, fmt); + len = vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + + buffer = kunit_kmalloc(current->kunit_test, len, GFP_KERNEL); + if (!buffer) + return; + + va_start(args, fmt); + vsnprintf(buffer, len, fmt, args); + va_end(args); + + kunit_err(current->kunit_test, "%s:%d: %s", file, line, buffer); + kunit_kfree(current->kunit_test, buffer); +} + +/* + * Enable KUnit tests to run. + */ +#ifdef CONFIG_KUNIT_DEFAULT_ENABLED +static bool enable_param = true; +#else +static bool enable_param; +#endif +module_param_named(enable, enable_param, bool, 0); +MODULE_PARM_DESC(enable, "Enable KUnit tests"); + +/* + * Configure the base timeout. + */ +static unsigned long kunit_base_timeout = CONFIG_KUNIT_DEFAULT_TIMEOUT; +module_param_named(timeout, kunit_base_timeout, ulong, 0644); +MODULE_PARM_DESC(timeout, "Set the base timeout for Kunit test cases"); + +/* + * KUnit statistic mode: + * 0 - disabled + * 1 - only when there is more than one subtest + * 2 - enabled + */ +static int kunit_stats_enabled = 1; +module_param_named(stats_enabled, kunit_stats_enabled, int, 0644); +MODULE_PARM_DESC(stats_enabled, + "Print test stats: never (0), only for multiple subtests (1), or always (2)"); + +struct kunit_result_stats { + unsigned long passed; + unsigned long skipped; + unsigned long failed; + unsigned long total; +}; + +static bool kunit_should_print_stats(struct kunit_result_stats stats) +{ + if (kunit_stats_enabled == 0) + return false; + + if (kunit_stats_enabled == 2) + return true; + + return (stats.total > 1); +} + +static void kunit_print_test_stats(struct kunit *test, + struct kunit_result_stats stats) +{ + if (!kunit_should_print_stats(stats)) + return; + + kunit_log(KERN_INFO, test, + KUNIT_SUBTEST_INDENT + "# %s: pass:%lu fail:%lu skip:%lu total:%lu", + test->name, + stats.passed, + stats.failed, + stats.skipped, + stats.total); +} + +/* Append formatted message to log. */ +void kunit_log_append(struct string_stream *log, const char *fmt, ...) +{ + va_list args; + + if (!log) + return; + + va_start(args, fmt); + string_stream_vadd(log, fmt, args); + va_end(args); +} +EXPORT_SYMBOL_GPL(kunit_log_append); + +size_t kunit_suite_num_test_cases(struct kunit_suite *suite) +{ + struct kunit_case *test_case; + size_t len = 0; + + kunit_suite_for_each_test_case(suite, test_case) + len++; + + return len; +} +EXPORT_SYMBOL_GPL(kunit_suite_num_test_cases); + +/* Currently supported test levels */ +enum { + KUNIT_LEVEL_SUITE = 0, + KUNIT_LEVEL_CASE, + KUNIT_LEVEL_CASE_PARAM, +}; + +static void kunit_print_suite_start(struct kunit_suite *suite) +{ + /* + * We do not log the test suite header as doing so would + * mean debugfs display would consist of the test suite + * header prior to individual test results. + * Hence directly printk the suite status, and we will + * separately seq_printf() the suite header for the debugfs + * representation. + */ + pr_info(KUNIT_SUBTEST_INDENT "KTAP version 1\n"); + pr_info(KUNIT_SUBTEST_INDENT "# Subtest: %s\n", + suite->name); + kunit_print_attr((void *)suite, false, KUNIT_LEVEL_CASE); + pr_info(KUNIT_SUBTEST_INDENT "1..%zd\n", + kunit_suite_num_test_cases(suite)); +} + +static void kunit_print_ok_not_ok(struct kunit *test, + unsigned int test_level, + enum kunit_status status, + size_t test_number, + const char *description, + const char *directive) +{ + const char *directive_header = (status == KUNIT_SKIPPED) ? " # SKIP " : ""; + const char *directive_body = (status == KUNIT_SKIPPED) ? directive : ""; + + /* + * When test is NULL assume that results are from the suite + * and today suite results are expected at level 0 only. + */ + WARN(!test && test_level, "suite test level can't be %u!\n", test_level); + + /* + * We do not log the test suite results as doing so would + * mean debugfs display would consist of an incorrect test + * number. Hence directly printk the suite result, and we will + * separately seq_printf() the suite results for the debugfs + * representation. + */ + if (!test) + pr_info("%s %zd %s%s%s\n", + kunit_status_to_ok_not_ok(status), + test_number, description, directive_header, + directive_body); + else + kunit_log(KERN_INFO, test, + "%*s%s %zd %s%s%s", + KUNIT_INDENT_LEN * test_level, "", + kunit_status_to_ok_not_ok(status), + test_number, description, directive_header, + directive_body); +} + +enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite) +{ + const struct kunit_case *test_case; + enum kunit_status status = KUNIT_SKIPPED; + + if (suite->suite_init_err) + return KUNIT_FAILURE; + + kunit_suite_for_each_test_case(suite, test_case) { + if (test_case->status == KUNIT_FAILURE) + return KUNIT_FAILURE; + else if (test_case->status == KUNIT_SUCCESS) + status = KUNIT_SUCCESS; + } + + return status; +} +EXPORT_SYMBOL_GPL(kunit_suite_has_succeeded); + +static size_t kunit_suite_counter = 1; + +static void kunit_print_suite_end(struct kunit_suite *suite) +{ + kunit_print_ok_not_ok(NULL, KUNIT_LEVEL_SUITE, + kunit_suite_has_succeeded(suite), + kunit_suite_counter++, + suite->name, + suite->status_comment); +} + +unsigned int kunit_test_case_num(struct kunit_suite *suite, + struct kunit_case *test_case) +{ + struct kunit_case *tc; + unsigned int i = 1; + + kunit_suite_for_each_test_case(suite, tc) { + if (tc == test_case) + return i; + i++; + } + + return 0; +} +EXPORT_SYMBOL_GPL(kunit_test_case_num); + +static void kunit_print_string_stream(struct kunit *test, + struct string_stream *stream) +{ + struct string_stream_fragment *fragment; + char *buf; + + if (string_stream_is_empty(stream)) + return; + + buf = string_stream_get_string(stream); + if (!buf) { + kunit_err(test, + "Could not allocate buffer, dumping stream:\n"); + list_for_each_entry(fragment, &stream->fragments, node) { + kunit_err(test, "%s", fragment->fragment); + } + kunit_err(test, "\n"); + } else { + kunit_err(test, "%s", buf); + kfree(buf); + } +} + +static void kunit_fail(struct kunit *test, const struct kunit_loc *loc, + enum kunit_assert_type type, const struct kunit_assert *assert, + assert_format_t assert_format, const struct va_format *message) +{ + struct string_stream *stream; + + kunit_set_failure(test); + + stream = kunit_alloc_string_stream(test, GFP_KERNEL); + if (IS_ERR(stream)) { + WARN(true, + "Could not allocate stream to print failed assertion in %s:%d\n", + loc->file, + loc->line); + return; + } + + kunit_assert_prologue(loc, type, stream); + assert_format(assert, message, stream); + + kunit_print_string_stream(test, stream); + + kunit_free_string_stream(test, stream); +} + +void __noreturn __kunit_abort(struct kunit *test) +{ + kunit_try_catch_throw(&test->try_catch); /* Does not return. */ + + /* + * Throw could not abort from test. + * + * XXX: we should never reach this line! As kunit_try_catch_throw is + * marked __noreturn. + */ + WARN_ONCE(true, "Throw could not abort from test!\n"); +} +EXPORT_SYMBOL_GPL(__kunit_abort); + +void __kunit_do_failed_assertion(struct kunit *test, + const struct kunit_loc *loc, + enum kunit_assert_type type, + const struct kunit_assert *assert, + assert_format_t assert_format, + const char *fmt, ...) +{ + va_list args; + struct va_format message; + va_start(args, fmt); + + message.fmt = fmt; + message.va = &args; + + kunit_fail(test, loc, type, assert, assert_format, &message); + + va_end(args); +} +EXPORT_SYMBOL_GPL(__kunit_do_failed_assertion); + +static void kunit_init_params(struct kunit *test) +{ + test->params_array.params = NULL; + test->params_array.get_description = NULL; + test->params_array.num_params = 0; + test->params_array.elem_size = 0; +} + +void kunit_init_test(struct kunit *test, const char *name, struct string_stream *log) +{ + spin_lock_init(&test->lock); + INIT_LIST_HEAD(&test->resources); + test->name = name; + test->log = log; + if (test->log) + string_stream_clear(log); + test->status = KUNIT_SUCCESS; + test->status_comment[0] = '\0'; + kunit_init_params(test); +} +EXPORT_SYMBOL_GPL(kunit_init_test); + +/* Only warn when a test takes more than twice the threshold */ +#define KUNIT_SPEED_WARNING_MULTIPLIER 2 + +/* Slow tests are defined as taking more than 1s */ +#define KUNIT_SPEED_SLOW_THRESHOLD_S 1 + +#define KUNIT_SPEED_SLOW_WARNING_THRESHOLD_S \ + (KUNIT_SPEED_WARNING_MULTIPLIER * KUNIT_SPEED_SLOW_THRESHOLD_S) + +#define s_to_timespec64(s) ns_to_timespec64((s) * NSEC_PER_SEC) + +static void kunit_run_case_check_speed(struct kunit *test, + struct kunit_case *test_case, + struct timespec64 duration) +{ + struct timespec64 slow_thr = + s_to_timespec64(KUNIT_SPEED_SLOW_WARNING_THRESHOLD_S); + enum kunit_speed speed = test_case->attr.speed; + + if (timespec64_compare(&duration, &slow_thr) < 0) + return; + + if (speed == KUNIT_SPEED_VERY_SLOW || speed == KUNIT_SPEED_SLOW) + return; + + kunit_warn(test, + "Test should be marked slow (runtime: %lld.%09lds)", + duration.tv_sec, duration.tv_nsec); +} + +/* Returns timeout multiplier based on speed. + * DEFAULT: 1 + * KUNIT_SPEED_SLOW: 3 + * KUNIT_SPEED_VERY_SLOW: 12 + */ +static int kunit_timeout_mult(enum kunit_speed speed) +{ + switch (speed) { + case KUNIT_SPEED_SLOW: + return 3; + case KUNIT_SPEED_VERY_SLOW: + return 12; + default: + return 1; + } +} + +static unsigned long kunit_test_timeout(struct kunit_suite *suite, struct kunit_case *test_case) +{ + int mult = 1; + + /* + * The default test timeout is 300 seconds and will be adjusted by mult + * based on the test speed. The test speed will be overridden by the + * innermost test component. + */ + if (suite->attr.speed != KUNIT_SPEED_UNSET) + mult = kunit_timeout_mult(suite->attr.speed); + if (test_case->attr.speed != KUNIT_SPEED_UNSET) + mult = kunit_timeout_mult(test_case->attr.speed); + return mult * kunit_base_timeout * msecs_to_jiffies(MSEC_PER_SEC); +} + + +/* + * Initializes and runs test case. Does not clean up or do post validations. + */ +static void kunit_run_case_internal(struct kunit *test, + struct kunit_suite *suite, + struct kunit_case *test_case) +{ + struct timespec64 start, end; + + if (suite->init) { + int ret; + + ret = suite->init(test); + if (ret) { + kunit_err(test, "failed to initialize: %d\n", ret); + kunit_set_failure(test); + return; + } + } + + ktime_get_ts64(&start); + + test_case->run_case(test); + + ktime_get_ts64(&end); + + kunit_run_case_check_speed(test, test_case, timespec64_sub(end, start)); +} + +static void kunit_case_internal_cleanup(struct kunit *test) +{ + kunit_cleanup(test); +} + +/* + * Performs post validations and cleanup after a test case was run. + * XXX: Should ONLY BE CALLED AFTER kunit_run_case_internal! + */ +static void kunit_run_case_cleanup(struct kunit *test, + struct kunit_suite *suite) +{ + if (suite->exit) + suite->exit(test); + + kunit_case_internal_cleanup(test); +} + +struct kunit_try_catch_context { + struct kunit *test; + struct kunit_suite *suite; + struct kunit_case *test_case; +}; + +static void kunit_try_run_case(void *data) +{ + struct kunit_try_catch_context *ctx = data; + struct kunit *test = ctx->test; + struct kunit_suite *suite = ctx->suite; + struct kunit_case *test_case = ctx->test_case; + + current->kunit_test = test; + + /* + * kunit_run_case_internal may encounter a fatal error; if it does, + * abort will be called, this thread will exit, and finally the parent + * thread will resume control and handle any necessary clean up. + */ + kunit_run_case_internal(test, suite, test_case); +} + +static void kunit_try_run_case_cleanup(void *data) +{ + struct kunit_try_catch_context *ctx = data; + struct kunit *test = ctx->test; + struct kunit_suite *suite = ctx->suite; + + current->kunit_test = test; + + kunit_run_case_cleanup(test, suite); +} + +static void kunit_catch_run_case_cleanup(void *data) +{ + struct kunit_try_catch_context *ctx = data; + struct kunit *test = ctx->test; + int try_exit_code = kunit_try_catch_get_result(&test->try_catch); + + /* It is always a failure if cleanup aborts. */ + kunit_set_failure(test); + + if (try_exit_code) { + /* + * Test case could not finish, we have no idea what state it is + * in, so don't do clean up. + */ + if (try_exit_code == -ETIMEDOUT) { + kunit_err(test, "test case cleanup timed out\n"); + /* + * Unknown internal error occurred preventing test case from + * running, so there is nothing to clean up. + */ + } else { + kunit_err(test, "internal error occurred during test case cleanup: %d\n", + try_exit_code); + } + return; + } + + kunit_err(test, "test aborted during cleanup. continuing without cleaning up\n"); +} + + +static void kunit_catch_run_case(void *data) +{ + struct kunit_try_catch_context *ctx = data; + struct kunit *test = ctx->test; + int try_exit_code = kunit_try_catch_get_result(&test->try_catch); + + if (try_exit_code) { + kunit_set_failure(test); + /* + * Test case could not finish, we have no idea what state it is + * in, so don't do clean up. + */ + if (try_exit_code == -ETIMEDOUT) { + kunit_err(test, "test case timed out\n"); + /* + * Unknown internal error occurred preventing test case from + * running, so there is nothing to clean up. + */ + } else { + kunit_err(test, "internal error occurred preventing test case from running: %d\n", + try_exit_code); + } + return; + } +} + +/* + * Performs all logic to run a test case. It also catches most errors that + * occur in a test case and reports them as failures. + */ +static void kunit_run_case_catch_errors(struct kunit_suite *suite, + struct kunit_case *test_case, + struct kunit *test) +{ + struct kunit_try_catch_context context; + struct kunit_try_catch *try_catch; + + try_catch = &test->try_catch; + + kunit_try_catch_init(try_catch, + test, + kunit_try_run_case, + kunit_catch_run_case, + kunit_test_timeout(suite, test_case)); + context.test = test; + context.suite = suite; + context.test_case = test_case; + kunit_try_catch_run(try_catch, &context); + + /* Now run the cleanup */ + kunit_try_catch_init(try_catch, + test, + kunit_try_run_case_cleanup, + kunit_catch_run_case_cleanup, + kunit_test_timeout(suite, test_case)); + kunit_try_catch_run(try_catch, &context); + + /* Propagate the parameter result to the test case. */ + if (test->status == KUNIT_FAILURE) + test_case->status = KUNIT_FAILURE; + else if (test_case->status != KUNIT_FAILURE && test->status == KUNIT_SUCCESS) + test_case->status = KUNIT_SUCCESS; +} + +static void kunit_print_suite_stats(struct kunit_suite *suite, + struct kunit_result_stats suite_stats, + struct kunit_result_stats param_stats) +{ + if (kunit_should_print_stats(suite_stats)) { + kunit_log(KERN_INFO, suite, + "# %s: pass:%lu fail:%lu skip:%lu total:%lu", + suite->name, + suite_stats.passed, + suite_stats.failed, + suite_stats.skipped, + suite_stats.total); + } + + if (kunit_should_print_stats(param_stats)) { + kunit_log(KERN_INFO, suite, + "# Totals: pass:%lu fail:%lu skip:%lu total:%lu", + param_stats.passed, + param_stats.failed, + param_stats.skipped, + param_stats.total); + } +} + +static void kunit_update_stats(struct kunit_result_stats *stats, + enum kunit_status status) +{ + switch (status) { + case KUNIT_SUCCESS: + stats->passed++; + break; + case KUNIT_SKIPPED: + stats->skipped++; + break; + case KUNIT_FAILURE: + stats->failed++; + break; + } + + stats->total++; +} + +static void kunit_accumulate_stats(struct kunit_result_stats *total, + struct kunit_result_stats add) +{ + total->passed += add.passed; + total->skipped += add.skipped; + total->failed += add.failed; + total->total += add.total; +} + +const void *kunit_array_gen_params(struct kunit *test, const void *prev, char *desc) +{ + struct kunit_params *params_arr = &test->params_array; + const void *param; + + if (test->param_index < params_arr->num_params) { + param = (char *)params_arr->params + + test->param_index * params_arr->elem_size; + + if (params_arr->get_description) + params_arr->get_description(test, param, desc); + return param; + } + return NULL; +} +EXPORT_SYMBOL_GPL(kunit_array_gen_params); + +static void kunit_init_parent_param_test(struct kunit_case *test_case, struct kunit *test) +{ + if (test_case->param_init) { + int err = test_case->param_init(test); + + if (err) { + kunit_err(test_case, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT + "# failed to initialize parent parameter test (%d)", err); + test->status = KUNIT_FAILURE; + test_case->status = KUNIT_FAILURE; + } + } +} + +int kunit_run_tests(struct kunit_suite *suite) +{ + char param_desc[KUNIT_PARAM_DESC_SIZE]; + struct kunit_case *test_case; + struct kunit_result_stats suite_stats = { 0 }; + struct kunit_result_stats total_stats = { 0 }; + const void *curr_param; + + /* Taint the kernel so we know we've run tests. */ + add_taint(TAINT_TEST, LOCKDEP_STILL_OK); + + if (suite->suite_init) { + suite->suite_init_err = suite->suite_init(suite); + if (suite->suite_init_err) { + kunit_err(suite, KUNIT_SUBTEST_INDENT + "# failed to initialize (%d)", suite->suite_init_err); + goto suite_end; + } + } + + kunit_print_suite_start(suite); + + kunit_suite_for_each_test_case(suite, test_case) { + struct kunit test = { .param_value = NULL, .param_index = 0 }; + struct kunit_result_stats param_stats = { 0 }; + + kunit_init_test(&test, test_case->name, test_case->log); + if (test_case->status == KUNIT_SKIPPED) { + /* Test marked as skip */ + test.status = KUNIT_SKIPPED; + kunit_update_stats(¶m_stats, test.status); + } else if (!test_case->generate_params) { + /* Non-parameterised test. */ + test_case->status = KUNIT_SKIPPED; + kunit_run_case_catch_errors(suite, test_case, &test); + kunit_update_stats(¶m_stats, test.status); + } else { + kunit_init_parent_param_test(test_case, &test); + if (test_case->status == KUNIT_FAILURE) { + kunit_update_stats(¶m_stats, test.status); + goto test_case_end; + } + /* Get initial param. */ + param_desc[0] = '\0'; + /* TODO: Make generate_params try-catch */ + curr_param = test_case->generate_params(&test, NULL, param_desc); + test_case->status = KUNIT_SKIPPED; + kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT + "KTAP version 1\n"); + kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT + "# Subtest: %s", test_case->name); + if (test.params_array.params && + test_case->generate_params == kunit_array_gen_params) { + kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT + KUNIT_SUBTEST_INDENT "1..%zd\n", + test.params_array.num_params); + } + + while (curr_param) { + struct kunit param_test = { + .param_value = curr_param, + .param_index = ++test.param_index, + .parent = &test, + }; + kunit_init_test(¶m_test, test_case->name, NULL); + param_test.log = test_case->log; + kunit_run_case_catch_errors(suite, test_case, ¶m_test); + + if (param_desc[0] == '\0') { + snprintf(param_desc, sizeof(param_desc), + "param-%d", param_test.param_index); + } + + kunit_print_ok_not_ok(¶m_test, KUNIT_LEVEL_CASE_PARAM, + param_test.status, + param_test.param_index, + param_desc, + param_test.status_comment); + + kunit_update_stats(¶m_stats, param_test.status); + + /* Get next param. */ + param_desc[0] = '\0'; + curr_param = test_case->generate_params(&test, curr_param, + param_desc); + } + /* + * TODO: Put into a try catch. Since we don't need suite->exit + * for it we can't reuse kunit_try_run_cleanup for this yet. + */ + if (test_case->param_exit) + test_case->param_exit(&test); + /* TODO: Put this kunit_cleanup into a try-catch. */ + kunit_cleanup(&test); + } +test_case_end: + kunit_print_attr((void *)test_case, true, KUNIT_LEVEL_CASE); + + kunit_print_test_stats(&test, param_stats); + + kunit_print_ok_not_ok(&test, KUNIT_LEVEL_CASE, test_case->status, + kunit_test_case_num(suite, test_case), + test_case->name, + test.status_comment); + + kunit_update_stats(&suite_stats, test_case->status); + kunit_accumulate_stats(&total_stats, param_stats); + } + + if (suite->suite_exit) + suite->suite_exit(suite); + + kunit_print_suite_stats(suite, suite_stats, total_stats); +suite_end: + kunit_print_suite_end(suite); + + return 0; +} +EXPORT_SYMBOL_GPL(kunit_run_tests); + +static void kunit_init_suite(struct kunit_suite *suite) +{ + kunit_debugfs_create_suite(suite); + suite->status_comment[0] = '\0'; + suite->suite_init_err = 0; + + if (suite->log) + string_stream_clear(suite->log); +} + +bool kunit_enabled(void) +{ + return enable_param; +} + +int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_suites, + bool run_tests) +{ + unsigned int i; + + if (num_suites == 0) + return 0; + + if (!kunit_enabled() && num_suites > 0) { + pr_info("kunit: disabled\n"); + return 0; + } + + kunit_suite_counter = 1; + + /* Use mutex lock to guard against running tests concurrently. */ + if (mutex_lock_interruptible(&kunit_run_lock)) { + pr_err("kunit: test interrupted\n"); + return -EINTR; + } + static_branch_inc(&kunit_running); + + for (i = 0; i < num_suites; i++) { + kunit_init_suite(suites[i]); + if (run_tests) + kunit_run_tests(suites[i]); + } + + static_branch_dec(&kunit_running); + mutex_unlock(&kunit_run_lock); + return 0; +} +EXPORT_SYMBOL_GPL(__kunit_test_suites_init); + +static void kunit_exit_suite(struct kunit_suite *suite) +{ + kunit_debugfs_destroy_suite(suite); +} + +void __kunit_test_suites_exit(struct kunit_suite **suites, int num_suites) +{ + unsigned int i; + + if (!kunit_enabled()) + return; + + for (i = 0; i < num_suites; i++) + kunit_exit_suite(suites[i]); +} +EXPORT_SYMBOL_GPL(__kunit_test_suites_exit); + +static void kunit_module_init(struct module *mod) +{ + struct kunit_suite_set suite_set, filtered_set; + struct kunit_suite_set normal_suite_set = { + mod->kunit_suites, mod->kunit_suites + mod->num_kunit_suites, + }; + struct kunit_suite_set init_suite_set = { + mod->kunit_init_suites, mod->kunit_init_suites + mod->num_kunit_init_suites, + }; + const char *action = kunit_action(); + int err = 0; + + if (mod->num_kunit_init_suites > 0) + suite_set = kunit_merge_suite_sets(init_suite_set, normal_suite_set); + else + suite_set = normal_suite_set; + + filtered_set = kunit_filter_suites(&suite_set, + kunit_filter_glob() ?: "*.*", + kunit_filter(), kunit_filter_action(), + &err); + if (err) + pr_err("kunit module: error filtering suites: %d\n", err); + + mod->kunit_suites = (struct kunit_suite **)filtered_set.start; + mod->num_kunit_suites = filtered_set.end - filtered_set.start; + + if (mod->num_kunit_init_suites > 0) + kfree(suite_set.start); + + if (!action) + kunit_exec_run_tests(&filtered_set, false); + else if (!strcmp(action, "list")) + kunit_exec_list_tests(&filtered_set, false); + else if (!strcmp(action, "list_attr")) + kunit_exec_list_tests(&filtered_set, true); + else + pr_err("kunit: unknown action '%s'\n", action); +} + +static void kunit_module_exit(struct module *mod) +{ + struct kunit_suite_set suite_set = { + mod->kunit_suites, mod->kunit_suites + mod->num_kunit_suites, + }; + const char *action = kunit_action(); + + /* + * Check if the start address is a valid virtual address to detect + * if the module load sequence has failed and the suite set has not + * been initialized and filtered. + */ + if (!suite_set.start || !virt_addr_valid(suite_set.start)) + return; + + if (!action) + __kunit_test_suites_exit(mod->kunit_suites, + mod->num_kunit_suites); + + kunit_free_suite_set(suite_set); +} + +static int kunit_module_notify(struct notifier_block *nb, unsigned long val, + void *data) +{ + struct module *mod = data; + + switch (val) { + case MODULE_STATE_LIVE: + kunit_module_init(mod); + break; + case MODULE_STATE_GOING: + kunit_module_exit(mod); + break; + case MODULE_STATE_COMING: + break; + case MODULE_STATE_UNFORMED: + break; + } + + return 0; +} + +static struct notifier_block kunit_mod_nb = { + .notifier_call = kunit_module_notify, + .priority = 0, +}; + +KUNIT_DEFINE_ACTION_WRAPPER(kfree_action_wrapper, kfree, const void *) + +void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp) +{ + void *data; + + data = kmalloc_array(n, size, gfp); + + if (!data) + return NULL; + + if (kunit_add_action_or_reset(test, kfree_action_wrapper, data) != 0) + return NULL; + + return data; +} +EXPORT_SYMBOL_GPL(kunit_kmalloc_array); + +void kunit_kfree(struct kunit *test, const void *ptr) +{ + if (!ptr) + return; + + kunit_release_action(test, kfree_action_wrapper, (void *)ptr); +} +EXPORT_SYMBOL_GPL(kunit_kfree); + +void kunit_kfree_const(struct kunit *test, const void *x) +{ +#if !IS_MODULE(CONFIG_KUNIT) + if (!is_kernel_rodata((unsigned long)x)) +#endif + kunit_kfree(test, x); +} +EXPORT_SYMBOL_GPL(kunit_kfree_const); + +const char *kunit_kstrdup_const(struct kunit *test, const char *str, gfp_t gfp) +{ +#if !IS_MODULE(CONFIG_KUNIT) + if (is_kernel_rodata((unsigned long)str)) + return str; +#endif + return kunit_kstrdup(test, str, gfp); +} +EXPORT_SYMBOL_GPL(kunit_kstrdup_const); + +void kunit_cleanup(struct kunit *test) +{ + struct kunit_resource *res; + unsigned long flags; + + /* + * test->resources is a stack - each allocation must be freed in the + * reverse order from which it was added since one resource may depend + * on another for its entire lifetime. + * Also, we cannot use the normal list_for_each constructs, even the + * safe ones because *arbitrary* nodes may be deleted when + * kunit_resource_free is called; the list_for_each_safe variants only + * protect against the current node being deleted, not the next. + */ + while (true) { + spin_lock_irqsave(&test->lock, flags); + if (list_empty(&test->resources)) { + spin_unlock_irqrestore(&test->lock, flags); + break; + } + res = list_last_entry(&test->resources, + struct kunit_resource, + node); + /* + * Need to unlock here as a resource may remove another + * resource, and this can't happen if the test->lock + * is held. + */ + spin_unlock_irqrestore(&test->lock, flags); + kunit_remove_resource(test, res); + } + current->kunit_test = NULL; +} +EXPORT_SYMBOL_GPL(kunit_cleanup); + +static int __init kunit_init(void) +{ + /* Install the KUnit hook functions. */ + kunit_install_hooks(); + + kunit_debugfs_init(); + + kunit_bus_init(); + return register_module_notifier(&kunit_mod_nb); +} +late_initcall(kunit_init); + +static void __exit kunit_exit(void) +{ + memset(&kunit_hooks, 0, sizeof(kunit_hooks)); + unregister_module_notifier(&kunit_mod_nb); + + kunit_bus_shutdown(); + + kunit_debugfs_cleanup(); +} +module_exit(kunit_exit); + +MODULE_DESCRIPTION("Base unit test (KUnit) API"); +MODULE_LICENSE("GPL v2"); diff --git a/lib/kunit/try-catch-impl.h b/lib/kunit/try-catch-impl.h new file mode 100644 index 000000000000..6f401b97cd0b --- /dev/null +++ b/lib/kunit/try-catch-impl.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Internal kunit try catch implementation to be shared with tests. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ + +#ifndef _KUNIT_TRY_CATCH_IMPL_H +#define _KUNIT_TRY_CATCH_IMPL_H + +#include <kunit/try-catch.h> +#include <linux/types.h> + +struct kunit; + +static inline void kunit_try_catch_init(struct kunit_try_catch *try_catch, + struct kunit *test, + kunit_try_catch_func_t try, + kunit_try_catch_func_t catch, + unsigned long timeout) +{ + try_catch->test = test; + try_catch->try = try; + try_catch->catch = catch; + try_catch->timeout = timeout; +} + +#endif /* _KUNIT_TRY_CATCH_IMPL_H */ diff --git a/lib/kunit/try-catch.c b/lib/kunit/try-catch.c new file mode 100644 index 000000000000..d84a879f0a78 --- /dev/null +++ b/lib/kunit/try-catch.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An API to allow a function, that may fail, to be executed, and recover in a + * controlled manner. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <brendanhiggins@google.com> + */ + +#include <kunit/test.h> +#include <linux/completion.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/sched/task.h> + +#include "try-catch-impl.h" + +void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch) +{ + try_catch->try_result = -EFAULT; + kthread_exit(0); +} +EXPORT_SYMBOL_GPL(kunit_try_catch_throw); + +static int kunit_generic_run_threadfn_adapter(void *data) +{ + struct kunit_try_catch *try_catch = data; + + try_catch->try_result = -EINTR; + try_catch->try(try_catch->context); + if (try_catch->try_result == -EINTR) + try_catch->try_result = 0; + + return 0; +} + +void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context) +{ + struct kunit *test = try_catch->test; + struct task_struct *task_struct; + struct completion *task_done; + int exit_code, time_remaining; + + try_catch->context = context; + try_catch->try_result = 0; + task_struct = kthread_create(kunit_generic_run_threadfn_adapter, + try_catch, "kunit_try_catch_thread"); + if (IS_ERR(task_struct)) { + try_catch->try_result = PTR_ERR(task_struct); + try_catch->catch(try_catch->context); + return; + } + get_task_struct(task_struct); + /* + * As for a vfork(2), task_struct->vfork_done (pointing to the + * underlying kthread->exited) can be used to wait for the end of a + * kernel thread. It is set to NULL when the thread exits, so we + * keep a copy here. + */ + task_done = task_struct->vfork_done; + wake_up_process(task_struct); + + time_remaining = wait_for_completion_timeout( + task_done, try_catch->timeout); + if (time_remaining == 0) { + try_catch->try_result = -ETIMEDOUT; + kthread_stop(task_struct); + } + + put_task_struct(task_struct); + exit_code = try_catch->try_result; + + if (!exit_code) + return; + + if (exit_code == -EFAULT) + try_catch->try_result = 0; + else if (exit_code == -EINTR) { + if (test->last_seen.file) + kunit_err(test, "try faulted: last line seen %s:%d\n", + test->last_seen.file, test->last_seen.line); + else + kunit_err(test, "try faulted\n"); + } else if (exit_code == -ETIMEDOUT) + kunit_err(test, "try timed out\n"); + else if (exit_code) + kunit_err(test, "Unknown error: %d\n", exit_code); + + try_catch->catch(try_catch->context); +} +EXPORT_SYMBOL_GPL(kunit_try_catch_run); diff --git a/lib/kunit/user_alloc.c b/lib/kunit/user_alloc.c new file mode 100644 index 000000000000..b8cac765e620 --- /dev/null +++ b/lib/kunit/user_alloc.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit userspace memory allocation resource management. + */ +#include <kunit/resource.h> +#include <kunit/test.h> +#include <linux/kthread.h> +#include <linux/mm.h> + +struct kunit_vm_mmap_resource { + unsigned long addr; + size_t size; +}; + +/* vm_mmap() arguments */ +struct kunit_vm_mmap_params { + struct file *file; + unsigned long addr; + unsigned long len; + unsigned long prot; + unsigned long flag; + unsigned long offset; +}; + +int kunit_attach_mm(void) +{ + struct mm_struct *mm; + + if (current->mm) + return 0; + + /* arch_pick_mmap_layout() is only sane with MMU systems. */ + if (!IS_ENABLED(CONFIG_MMU)) + return -EINVAL; + + mm = mm_alloc(); + if (!mm) + return -ENOMEM; + + /* Define the task size. */ + mm->task_size = TASK_SIZE; + + /* Make sure we can allocate new VMAs. */ + arch_pick_mmap_layout(mm, ¤t->signal->rlim[RLIMIT_STACK]); + + /* Attach the mm. It will be cleaned up when the process dies. */ + kthread_use_mm(mm); + + return 0; +} +EXPORT_SYMBOL_GPL(kunit_attach_mm); + +static int kunit_vm_mmap_init(struct kunit_resource *res, void *context) +{ + struct kunit_vm_mmap_params *p = context; + struct kunit_vm_mmap_resource vres; + int ret; + + ret = kunit_attach_mm(); + if (ret) + return ret; + + vres.size = p->len; + vres.addr = vm_mmap(p->file, p->addr, p->len, p->prot, p->flag, p->offset); + if (!vres.addr) + return -ENOMEM; + res->data = kmemdup(&vres, sizeof(vres), GFP_KERNEL); + if (!res->data) { + vm_munmap(vres.addr, vres.size); + return -ENOMEM; + } + + return 0; +} + +static void kunit_vm_mmap_free(struct kunit_resource *res) +{ + struct kunit_vm_mmap_resource *vres = res->data; + + /* + * Since this is executed from the test monitoring process, + * the test's mm has already been torn down. We don't need + * to run vm_munmap(vres->addr, vres->size), only clean up + * the vres. + */ + + kfree(vres); + res->data = NULL; +} + +unsigned long kunit_vm_mmap(struct kunit *test, struct file *file, + unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flag, + unsigned long offset) +{ + struct kunit_vm_mmap_params params = { + .file = file, + .addr = addr, + .len = len, + .prot = prot, + .flag = flag, + .offset = offset, + }; + struct kunit_vm_mmap_resource *vres; + + vres = kunit_alloc_resource(test, + kunit_vm_mmap_init, + kunit_vm_mmap_free, + GFP_KERNEL, + ¶ms); + if (vres) + return vres->addr; + return 0; +} +EXPORT_SYMBOL_GPL(kunit_vm_mmap); + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); |
