diff options
Diffstat (limited to 'lib/kunit')
| -rw-r--r-- | lib/kunit/Kconfig | 73 | ||||
| -rw-r--r-- | lib/kunit/Makefile | 12 | ||||
| -rw-r--r-- | lib/kunit/assert.c | 71 | ||||
| -rw-r--r-- | lib/kunit/assert_test.c | 388 | ||||
| -rw-r--r-- | lib/kunit/attributes.c | 474 | ||||
| -rw-r--r-- | lib/kunit/debugfs.c | 144 | ||||
| -rw-r--r-- | lib/kunit/device-impl.h | 19 | ||||
| -rw-r--r-- | lib/kunit/device.c | 202 | ||||
| -rw-r--r-- | lib/kunit/executor.c | 357 | ||||
| -rw-r--r-- | lib/kunit/executor_test.c | 186 | ||||
| -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 | 409 | ||||
| -rw-r--r-- | lib/kunit/kunit-test.c | 437 | ||||
| -rw-r--r-- | lib/kunit/platform-test.c | 224 | ||||
| -rw-r--r-- | lib/kunit/platform.c | 302 | ||||
| -rw-r--r-- | lib/kunit/resource.c | 99 | ||||
| -rw-r--r-- | lib/kunit/static_stub.c | 123 | ||||
| -rw-r--r-- | lib/kunit/string-stream-test.c | 518 | ||||
| -rw-r--r-- | lib/kunit/string-stream.c | 100 | ||||
| -rw-r--r-- | lib/kunit/string-stream.h | 16 | ||||
| -rw-r--r-- | lib/kunit/test.c | 515 | ||||
| -rw-r--r-- | lib/kunit/try-catch-impl.h | 4 | ||||
| -rw-r--r-- | lib/kunit/try-catch.c | 67 | ||||
| -rw-r--r-- | lib/kunit/user_alloc.c | 117 |
25 files changed, 4534 insertions, 375 deletions
diff --git a/lib/kunit/Kconfig b/lib/kunit/Kconfig index 626719b95bad..50ecf55d2b9c 100644 --- a/lib/kunit/Kconfig +++ b/lib/kunit/Kconfig @@ -4,7 +4,7 @@ menuconfig KUNIT tristate "KUnit - Enable support for unit tests" - select GLOB if KUNIT=y + select GLOB help Enables support for kernel unit tests (KUnit), a lightweight unit testing and mocking framework for the Linux kernel. These tests are @@ -24,6 +24,17 @@ config KUNIT_DEBUGFS 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 @@ -70,4 +81,64 @@ config KUNIT_DEFAULT_ENABLED 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 index 29aff6562b42..656f1fa35abc 100644 --- a/lib/kunit/Makefile +++ b/lib/kunit/Makefile @@ -2,20 +2,30 @@ 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 + 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 index f5b50babe38d..867aa5c4bccf 100644 --- a/lib/kunit/assert.c +++ b/lib/kunit/assert.c @@ -7,6 +7,7 @@ */ #include <kunit/assert.h> #include <kunit/test.h> +#include <kunit/visibility.h> #include "string-stream.h" @@ -30,8 +31,9 @@ void kunit_assert_prologue(const struct kunit_loc *loc, } EXPORT_SYMBOL_GPL(kunit_assert_prologue); -static void kunit_assert_print_msg(const struct va_format *message, - struct string_stream *stream) +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); @@ -89,8 +91,7 @@ void kunit_ptr_not_err_assert_format(const struct kunit_assert *assert, EXPORT_SYMBOL_GPL(kunit_ptr_not_err_assert_format); /* Checks if `text` is a literal representing `value`, e.g. "5" and 5 */ -static bool is_literal(struct kunit *test, const char *text, long long value, - gfp_t gfp) +VISIBLE_IF_KUNIT bool is_literal(const char *text, long long value) { char *buffer; int len; @@ -100,14 +101,15 @@ static bool is_literal(struct kunit *test, const char *text, long long value, if (strlen(text) != len) return false; - buffer = kunit_kmalloc(test, len+1, gfp); + buffer = kmalloc(len+1, GFP_KERNEL); if (!buffer) return false; snprintf(buffer, len+1, "%lld", value); ret = strncmp(buffer, text, len) == 0; - kunit_kfree(test, buffer); + kfree(buffer); + return ret; } @@ -125,14 +127,12 @@ void kunit_binary_assert_format(const struct kunit_assert *assert, binary_assert->text->left_text, binary_assert->text->operation, binary_assert->text->right_text); - if (!is_literal(stream->test, binary_assert->text->left_text, - binary_assert->left_value, stream->gfp)) + 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(stream->test, binary_assert->text->right_text, - binary_assert->right_value, stream->gfp)) + 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, @@ -168,7 +168,7 @@ 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. */ -static bool is_str_literal(const char *text, const char *value) +VISIBLE_IF_KUNIT bool is_str_literal(const char *text, const char *value) { int len; @@ -210,10 +210,11 @@ 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 <>. */ -static void kunit_assert_hexdump(struct string_stream *stream, - const void *buf, - const void *compared_buf, - const size_t len) +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; @@ -241,24 +242,34 @@ void kunit_mem_assert_format(const struct kunit_assert *assert, mem_assert = container_of(assert, struct kunit_mem_assert, assert); - 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); + 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, 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, "\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); + 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); + 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 index de0ee2e03ed6..9c326f1837bd 100644 --- a/lib/kunit/debugfs.c +++ b/lib/kunit/debugfs.c @@ -8,11 +8,14 @@ #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: @@ -20,6 +23,8 @@ * 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 * */ @@ -36,14 +41,21 @@ void kunit_debugfs_init(void) debugfs_rootdir = debugfs_create_dir(KUNIT_DEBUGFS_ROOT, NULL); } -static void debugfs_print_result(struct seq_file *seq, - struct kunit_suite *suite, - struct kunit_case *test_case) +static void debugfs_print_result(struct seq_file *seq, struct string_stream *log) { - if (!test_case || !test_case->log) + struct string_stream_fragment *frag_container; + + if (!log) return; - seq_printf(seq, "%s", test_case->log); + /* + * 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); } /* @@ -52,16 +64,27 @@ static void debugfs_print_result(struct seq_file *seq, static int debugfs_print_results(struct seq_file *seq, void *v) { struct kunit_suite *suite = (struct kunit_suite *)seq->private; - enum kunit_status success = kunit_suite_has_succeeded(suite); + enum kunit_status success; struct kunit_case *test_case; - if (!suite || !suite->log) + if (!suite) return 0; - seq_printf(seq, "%s", suite->log); + 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, 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); @@ -82,6 +105,51 @@ static int debugfs_results_open(struct inode *inode, struct file *file) 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, @@ -89,20 +157,66 @@ static const struct file_operations debugfs_results_fops = { .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; - /* Allocate logs before creating debugfs representation. */ - suite->log = kzalloc(KUNIT_LOG_SIZE, GFP_KERNEL); - kunit_suite_for_each_test_case(suite, test_case) - test_case->log = kzalloc(KUNIT_LOG_SIZE, GFP_KERNEL); + /* 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) @@ -110,7 +224,7 @@ void kunit_debugfs_destroy_suite(struct kunit_suite *suite) struct kunit_case *test_case; debugfs_remove_recursive(suite->debugfs); - kfree(suite->log); + string_stream_destroy(suite->log); kunit_suite_for_each_test_case(suite, test_case) - kfree(test_case->log); + string_stream_destroy(test_case->log); } 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 index 74982b83707c..02ff380ab793 100644 --- a/lib/kunit/executor.c +++ b/lib/kunit/executor.c @@ -2,6 +2,7 @@ #include <linux/reboot.h> #include <kunit/test.h> +#include <kunit/attributes.h> #include <linux/glob.h> #include <linux/moduleparam.h> @@ -11,51 +12,108 @@ */ 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[]; -#if IS_BUILTIN(CONFIG_KUNIT) - -static char *filter_glob_param; static char *action_param; -module_param_named(filter_glob, filter_glob_param, charp, 0); -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(action, action_param, charp, 0); +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' 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_test_filter { +struct kunit_glob_filter { char *suite_glob; char *test_glob; }; /* Split "suite_glob.test_glob" into two. Assumes filter_glob is not empty. */ -static void kunit_parse_filter_glob(struct kunit_test_filter *parsed, +static int kunit_parse_glob_filter(struct kunit_glob_filter *parsed, const char *filter_glob) { - const int len = strlen(filter_glob); const char *period = strchr(filter_glob, '.'); if (!period) { - parsed->suite_glob = kzalloc(len + 1, GFP_KERNEL); + parsed->suite_glob = kstrdup(filter_glob, GFP_KERNEL); + if (!parsed->suite_glob) + return -ENOMEM; parsed->test_glob = NULL; - strcpy(parsed->suite_glob, filter_glob); - return; + return 0; } - parsed->suite_glob = kzalloc(period - filter_glob + 1, GFP_KERNEL); - parsed->test_glob = kzalloc(len - (period - filter_glob) + 1, GFP_KERNEL); + 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; + } - strncpy(parsed->suite_glob, filter_glob, period - filter_glob); - strncpy(parsed->test_glob, period + 1, len - (period - filter_glob)); + return 0; } /* Create a copy of suite with only tests that match test_glob. */ static struct kunit_suite * -kunit_filter_tests(const struct kunit_suite *const suite, const char *test_glob) +kunit_filter_glob_tests(const struct kunit_suite *const suite, const char *test_glob) { int n = 0; struct kunit_case *filtered, *test_case; @@ -89,90 +147,148 @@ kunit_filter_tests(const struct kunit_suite *const suite, const char *test_glob) return copy; } -static char *kunit_shutdown; -core_param(kunit_shutdown, kunit_shutdown, charp, 0644); - -/* Stores an array of suites, end points one past the end */ -struct suite_set { - struct kunit_suite * const *start; - struct kunit_suite * const *end; -}; - -static void kunit_free_suite_set(struct suite_set suite_set) +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++) + for (suites = suite_set.start; suites < suite_set.end; suites++) { + kfree((*suites)->test_cases); kfree(*suites); + } kfree(suite_set.start); } -static struct suite_set kunit_filter_suites(const struct suite_set *suite_set, - const char *filter_glob, - int *err) +/* + * 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; - struct kunit_suite **copy, *filtered_suite; - struct suite_set filtered; - struct kunit_test_filter filter; + 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 = kmalloc_array(max, sizeof(*filtered.start), GFP_KERNEL); - filtered.start = copy; + copy = kcalloc(max, sizeof(*copy), GFP_KERNEL); if (!copy) { /* won't be able to run anything, return an empty set */ - filtered.end = copy; return filtered; } + copy_start = copy; - kunit_parse_filter_glob(&filter, filter_glob); + if (filter_glob) { + *err = kunit_parse_glob_filter(&parsed_glob, filter_glob); + if (*err) + goto free_copy; + } - for (i = 0; &suite_set->start[i] != suite_set->end; i++) { - if (!glob_match(filter.suite_glob, suite_set->start[i]->name)) - continue; + /* 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; + } - filtered_suite = kunit_filter_tests(suite_set->start[i], filter.test_glob); - if (IS_ERR(filtered_suite)) { - *err = PTR_ERR(filtered_suite); - return filtered; + 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; - kfree(filter.suite_glob); - kfree(filter.test_glob); - return filtered; -} +free_filtered_suite: + if (*err) { + for (suites = copy_start; suites < copy; suites++) { + kfree((*suites)->test_cases); + kfree(*suites); + } + } -static void kunit_handle_shutdown(void) -{ - if (!kunit_shutdown) - return; +free_parsed_filters: + if (filter_count) + kfree(parsed_filters); - 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); +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; } -static void kunit_exec_run_tests(struct suite_set *suite_set) +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(); - pr_info("KTAP version 1\n"); - pr_info("1..%zu\n", num_suites); + 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); + __kunit_test_suites_init(suite_set->start, num_suites, autorun); } -static void kunit_exec_list_tests(struct suite_set *suite_set) +void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr) { struct kunit_suite * const *suites; struct kunit_case *test_case; @@ -180,39 +296,126 @@ static void kunit_exec_list_tests(struct suite_set *suite_set) /* 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++) + 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 suite_set suite_set = {__kunit_suites_start, __kunit_suites_end}; + 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 out; + goto free_out; } - if (filter_glob_param) { - suite_set = kunit_filter_suites(&suite_set, filter_glob_param, &err); + 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 out; + goto free_out; } } if (!action_param) - kunit_exec_run_tests(&suite_set); + kunit_exec_run_tests(&suite_set, true); else if (strcmp(action_param, "list") == 0) - kunit_exec_list_tests(&suite_set); + 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); - if (filter_glob_param) { /* a copy was made of each suite */ +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(); diff --git a/lib/kunit/executor_test.c b/lib/kunit/executor_test.c index 0cea31c27b23..f0090c2729cd 100644 --- a/lib/kunit/executor_test.c +++ b/lib/kunit/executor_test.c @@ -7,8 +7,9 @@ */ #include <kunit/test.h> +#include <kunit/attributes.h> -static void kfree_at_end(struct kunit *test, const void *to_free); +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); @@ -24,15 +25,15 @@ static struct kunit_case dummy_test_cases[] = { static void parse_filter_test(struct kunit *test) { - struct kunit_test_filter filter = {NULL, NULL}; + struct kunit_glob_filter filter = {NULL, NULL}; - kunit_parse_filter_glob(&filter, "suite"); + 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_filter_glob(&filter, "suite.test"); + 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); @@ -42,18 +43,20 @@ static void parse_filter_test(struct kunit *test) static void filter_suites_test(struct kunit *test) { struct kunit_suite *subsuite[3] = {NULL, NULL}; - struct suite_set suite_set = {.start = subsuite, .end = &subsuite[2]}; - struct suite_set got; + 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", &err); + 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); - kfree_at_end(test, got.start); + free_suite_set_at_end(test, &got); /* Validate we just have suite2 */ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]); @@ -66,18 +69,20 @@ static void filter_suites_test(struct kunit *test) static void filter_suites_test_glob_test(struct kunit *test) { struct kunit_suite *subsuite[3] = {NULL, NULL}; - struct suite_set suite_set = {.start = subsuite, .end = &subsuite[2]}; - struct suite_set got; + 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", &err); + 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); - kfree_at_end(test, got.start); + free_suite_set_at_end(test, &got); /* Validate we just have suite2 */ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]); @@ -93,26 +98,152 @@ static void filter_suites_test_glob_test(struct kunit *test) static void filter_suites_to_empty_test(struct kunit *test) { struct kunit_suite *subsuite[3] = {NULL, NULL}; - struct suite_set suite_set = {.start = subsuite, .end = &subsuite[2]}; - struct suite_set got; + 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", &err); + got = kunit_filter_suites(&suite_set, "not_found", NULL, NULL, &err); KUNIT_ASSERT_EQ(test, err, 0); - kfree_at_end(test, got.start); /* just in case */ + 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), {} }; @@ -125,21 +256,26 @@ kunit_test_suites(&executor_test_suite); /* Test helpers */ -static void kfree_res_free(struct kunit_resource *res) +static void free_suite_set(void *suite_set) { - kfree(res->data); + kunit_free_suite_set(*(struct kunit_suite_set *)suite_set); + kfree(suite_set); } -/* Use the resource API to register a call to kfree(to_free). +/* 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 kfree_at_end(struct kunit *test, const void *to_free) +static void free_suite_set_at_end(struct kunit *test, const void *to_free) { - /* kfree() handles NULL already, but avoid allocating a no-op cleanup. */ - if (IS_ERR_OR_NULL(to_free)) + struct kunit_suite_set *free; + + if (!((struct kunit_suite_set *)to_free)->start) return; - kunit_alloc_resource(test, NULL, kfree_res_free, GFP_KERNEL, - (void *)to_free); + + 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, @@ -150,7 +286,7 @@ static struct kunit_suite *alloc_fake_suite(struct kunit *test, /* We normally never expect to allocate suites, hence the non-const cast. */ suite = kunit_kzalloc(test, sizeof(*suite), GFP_KERNEL); - strncpy((char *)suite->name, suite_name, sizeof(suite->name) - 1); + 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 index 66cc4e2365ec..9452b163956f 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -7,6 +7,7 @@ */ #include <kunit/test.h> +#include <kunit/static_stub.h> /* * This is the most fundamental element of KUnit, the test case. A test case @@ -41,6 +42,16 @@ static int example_test_init(struct kunit *test) } /* + * 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. */ @@ -52,6 +63,16 @@ static int example_test_init_suite(struct kunit_suite *suite) } /* + * 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) @@ -130,6 +151,344 @@ static void example_all_expect_macros_test(struct kunit *test) 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. @@ -145,6 +504,16 @@ static struct kunit_case example_test_cases[] = { 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), {} }; @@ -173,7 +542,9 @@ static struct kunit_case example_test_cases[] = { 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, }; @@ -183,4 +554,42 @@ static struct kunit_suite example_test_suite = { */ 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 index 4df0335d0d06..63130a48e237 100644 --- a/lib/kunit/kunit-test.c +++ b/lib/kunit/kunit-test.c @@ -5,8 +5,15 @@ * 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 { @@ -37,7 +44,8 @@ static void kunit_test_try_catch_successful_try_no_catch(struct kunit *test) kunit_try_catch_init(try_catch, test, kunit_test_successful_try, - kunit_test_no_catch); + 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); @@ -69,7 +77,8 @@ static void kunit_test_try_catch_unsuccessful_try_does_catch(struct kunit *test) kunit_try_catch_init(try_catch, test, kunit_test_unsuccessful_try, - kunit_test_catch); + kunit_test_catch, + 300 * msecs_to_jiffies(MSEC_PER_SEC)); kunit_try_catch_run(try_catch, test); KUNIT_EXPECT_TRUE(test, ctx->function_called); @@ -103,6 +112,49 @@ static struct kunit_suite kunit_try_catch_test_suite = { .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 @@ -111,7 +163,7 @@ struct kunit_test_resource_context { struct kunit test; bool is_resource_initialized; int allocate_order[2]; - int free_order[2]; + int free_order[4]; }; static int fake_resource_init(struct kunit_resource *res, void *context) @@ -402,6 +454,88 @@ static void kunit_resource_test_named(struct kunit *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 = @@ -433,6 +567,10 @@ static struct kunit_case kunit_resource_test_cases[] = { 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), {} }; @@ -443,24 +581,24 @@ static struct kunit_suite kunit_resource_test_suite = { .test_cases = kunit_resource_test_cases, }; -static void kunit_log_test(struct kunit *test); - -static struct kunit_case kunit_log_test_cases[] = { - KUNIT_CASE(kunit_log_test), - {} -}; +/* + * 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) -static struct kunit_suite kunit_log_test_suite = { - .name = "kunit-log-test", - .test_cases = kunit_log_test_cases, -}; +/* 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; - - suite.log = kunit_kzalloc(test, KUNIT_LOG_SIZE, GFP_KERNEL); +#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."); @@ -468,19 +606,64 @@ static void kunit_log_test(struct kunit *test) 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(test->log, "put this in log.")); + strstr(full_log, "put this in log.")); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, - strstr(test->log, "this too.")); + 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(suite.log, "add to suite log.")); + strstr(full_log, "add to suite log.")); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, - strstr(suite.log, "along with this.")); + 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; @@ -521,7 +704,221 @@ static struct kunit_suite kunit_status_test_suite = { .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_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 index c414df922f34..f0209252b179 100644 --- a/lib/kunit/resource.c +++ b/lib/kunit/resource.c @@ -77,3 +77,102 @@ int kunit_destroy_resource(struct kunit *test, kunit_resource_match_t match, 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 index 110f3a993250..7734e33156f9 100644 --- a/lib/kunit/string-stream-test.c +++ b/lib/kunit/string-stream-test.c @@ -6,48 +6,532 @@ * 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" -static void string_stream_test_empty_on_creation(struct kunit *test) +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 = alloc_string_stream(test, GFP_KERNEL); + 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_test_not_empty_after_add(struct kunit *test) +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 *stream = alloc_string_stream(test, GFP_KERNEL); + struct string_stream_test_priv *priv = test->priv; + struct kunit *fake_test; - string_stream_add(stream, "Foo"); + fake_test = kunit_kzalloc(test, sizeof(*fake_test), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_test); - KUNIT_EXPECT_FALSE(test, string_stream_is_empty(stream)); + 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); } -static void string_stream_test_get_string(struct kunit *test) +/* Add a series of lines of variable length to a string_stream. */ +static void string_stream_variable_length_line_test(struct kunit *test) { - struct string_stream *stream = alloc_string_stream(test, GFP_KERNEL); - char *output; + 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; - string_stream_add(stream, "Foo"); - string_stream_add(stream, " %s", "bar"); + test->priv = priv; - output = string_stream_get_string(stream); - KUNIT_ASSERT_STREQ(test, output, "Foo bar"); + return 0; } static struct kunit_case string_stream_test_cases[] = { - KUNIT_CASE(string_stream_test_empty_on_creation), - KUNIT_CASE(string_stream_test_not_empty_after_add), - KUNIT_CASE(string_stream_test_get_string), + 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 + .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 index cc32743c1171..54f4fdcbfac8 100644 --- a/lib/kunit/string-stream.c +++ b/lib/kunit/string-stream.c @@ -6,6 +6,7 @@ * Author: Brendan Higgins <brendanhiggins@google.com> */ +#include <kunit/static_stub.h> #include <kunit/test.h> #include <linux/list.h> #include <linux/slab.h> @@ -13,30 +14,28 @@ #include "string-stream.h" -static struct string_stream_fragment *alloc_string_stream_fragment( - struct kunit *test, int len, gfp_t gfp) +static struct string_stream_fragment *alloc_string_stream_fragment(int len, gfp_t gfp) { struct string_stream_fragment *frag; - frag = kunit_kzalloc(test, sizeof(*frag), gfp); + frag = kzalloc(sizeof(*frag), gfp); if (!frag) return ERR_PTR(-ENOMEM); - frag->fragment = kunit_kmalloc(test, len, gfp); + frag->fragment = kmalloc(len, gfp); if (!frag->fragment) { - kunit_kfree(test, frag); + kfree(frag); return ERR_PTR(-ENOMEM); } return frag; } -static void string_stream_fragment_destroy(struct kunit *test, - struct string_stream_fragment *frag) +static void string_stream_fragment_destroy(struct string_stream_fragment *frag) { list_del(&frag->node); - kunit_kfree(test, frag->fragment); - kunit_kfree(test, frag); + kfree(frag->fragment); + kfree(frag); } int string_stream_vadd(struct string_stream *stream, @@ -44,26 +43,44 @@ int string_stream_vadd(struct string_stream *stream, va_list args) { struct string_stream_fragment *frag_container; - int len; + int buf_len, result_len; va_list args_for_counting; /* Make a copy because `vsnprintf` could change it */ va_copy(args_for_counting, args); - /* Need space for null byte. */ - len = vsnprintf(NULL, 0, fmt, args_for_counting) + 1; + /* Evaluate length of formatted string */ + buf_len = vsnprintf(NULL, 0, fmt, args_for_counting); va_end(args_for_counting); - frag_container = alloc_string_stream_fragment(stream->test, - len, - stream->gfp); + 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); - len = vsnprintf(frag_container->fragment, len, fmt, args); + 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 += len; + stream->length += result_len; list_add_tail(&frag_container->node, &stream->fragments); spin_unlock(&stream->lock); @@ -82,7 +99,7 @@ int string_stream_add(struct string_stream *stream, const char *fmt, ...) return result; } -static void string_stream_clear(struct string_stream *stream) +void string_stream_clear(struct string_stream *stream) { struct string_stream_fragment *frag_container, *frag_container_safe; @@ -91,7 +108,7 @@ static void string_stream_clear(struct string_stream *stream) frag_container_safe, &stream->fragments, node) { - string_stream_fragment_destroy(stream->test, frag_container); + string_stream_fragment_destroy(frag_container); } stream->length = 0; spin_unlock(&stream->lock); @@ -103,7 +120,7 @@ char *string_stream_get_string(struct string_stream *stream) size_t buf_len = stream->length + 1; /* +1 for null byte. */ char *buf; - buf = kunit_kzalloc(stream->test, buf_len, stream->gfp); + buf = kzalloc(buf_len, stream->gfp); if (!buf) return NULL; @@ -119,13 +136,17 @@ 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; - return string_stream_add(stream, other_content); + ret = string_stream_add(stream, other_content); + kfree(other_content); + + return ret; } bool string_stream_is_empty(struct string_stream *stream) @@ -133,16 +154,15 @@ bool string_stream_is_empty(struct string_stream *stream) return list_empty(&stream->fragments); } -struct string_stream *alloc_string_stream(struct kunit *test, gfp_t gfp) +struct string_stream *alloc_string_stream(gfp_t gfp) { struct string_stream *stream; - stream = kunit_kzalloc(test, sizeof(*stream), gfp); + stream = kzalloc(sizeof(*stream), gfp); if (!stream) return ERR_PTR(-ENOMEM); stream->gfp = gfp; - stream->test = test; INIT_LIST_HEAD(&stream->fragments); spin_lock_init(&stream->lock); @@ -151,5 +171,37 @@ struct string_stream *alloc_string_stream(struct kunit *test, gfp_t gfp) 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 index b669f9a75a94..7be2450c7079 100644 --- a/lib/kunit/string-stream.h +++ b/lib/kunit/string-stream.h @@ -23,13 +23,17 @@ struct string_stream { struct list_head fragments; /* length and fragments are protected by this lock */ spinlock_t lock; - struct kunit *test; gfp_t gfp; + bool append_newlines; }; struct kunit; -struct string_stream *alloc_string_stream(struct kunit *test, gfp_t gfp); +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, ...); @@ -38,6 +42,8 @@ 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, @@ -47,4 +53,10 @@ 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 index c9ebf975e56b..62eb529824c6 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -9,24 +9,28 @@ #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" -DEFINE_STATIC_KEY_FALSE(kunit_running); +static DEFINE_MUTEX(kunit_run_lock); -#if IS_BUILTIN(CONFIG_KUNIT) /* - * Fail the current test and print an error message to the log. + * Hook to fail the current test and print an error message to the log. */ -void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...) +void __printf(3, 4) __kunit_fail_current_test_impl(const char *file, int line, const char *fmt, ...) { va_list args; int len; @@ -53,8 +57,6 @@ void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...) kunit_err(current->kunit_test, "%s:%d: %s", file, line, buffer); kunit_kfree(current->kunit_test, buffer); } -EXPORT_SYMBOL_GPL(__kunit_fail_current_test); -#endif /* * Enable KUnit tests to run. @@ -68,6 +70,13 @@ 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 @@ -112,28 +121,17 @@ static void kunit_print_test_stats(struct kunit *test, stats.total); } -/* - * Append formatted message to log, size of which is limited to - * KUNIT_LOG_SIZE bytes (including null terminating byte). - */ -void kunit_log_append(char *log, const char *fmt, ...) +/* Append formatted message to log. */ +void kunit_log_append(struct string_stream *log, const char *fmt, ...) { - char line[KUNIT_LOG_SIZE]; va_list args; - int len_left; if (!log) return; - len_left = KUNIT_LOG_SIZE - strlen(log) - 1; - if (len_left <= 0) - return; - va_start(args, fmt); - vsnprintf(line, sizeof(line), fmt, args); + string_stream_vadd(log, fmt, args); va_end(args); - - strncat(log, line, len_left); } EXPORT_SYMBOL_GPL(kunit_log_append); @@ -149,45 +147,66 @@ size_t kunit_suite_num_test_cases(struct kunit_suite *suite) } 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) { - kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "KTAP version 1\n"); - kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "# Subtest: %s", + /* + * 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_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "1..%zd", + 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(void *test_or_suite, - bool is_test, +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) { - struct kunit_suite *suite = is_test ? NULL : test_or_suite; - struct kunit *test = is_test ? test_or_suite : NULL; 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 the test suite - * description and status prior to individual test results. - * Hence directly printk the suite status, and we will - * separately seq_printf() the suite status for the debugfs + * 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 (suite) + if (!test) pr_info("%s %zd %s%s%s\n", kunit_status_to_ok_not_ok(status), test_number, description, directive_header, - (status == KUNIT_SKIPPED) ? directive : ""); + directive_body); else kunit_log(KERN_INFO, test, - KUNIT_SUBTEST_INDENT "%s %zd %s%s%s", + "%*s%s %zd %s%s%s", + KUNIT_INDENT_LEN * test_level, "", kunit_status_to_ok_not_ok(status), test_number, description, directive_header, - (status == KUNIT_SKIPPED) ? directive : ""); + directive_body); } enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite) @@ -213,7 +232,7 @@ static size_t kunit_suite_counter = 1; static void kunit_print_suite_end(struct kunit_suite *suite) { - kunit_print_ok_not_ok((void *)suite, false, + kunit_print_ok_not_ok(NULL, KUNIT_LEVEL_SUITE, kunit_suite_has_succeeded(suite), kunit_suite_counter++, suite->name, @@ -255,7 +274,7 @@ static void kunit_print_string_stream(struct kunit *test, kunit_err(test, "\n"); } else { kunit_err(test, "%s", buf); - kunit_kfree(test, buf); + kfree(buf); } } @@ -267,7 +286,7 @@ static void kunit_fail(struct kunit *test, const struct kunit_loc *loc, kunit_set_failure(test); - stream = alloc_string_stream(test, GFP_KERNEL); + 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", @@ -281,10 +300,10 @@ static void kunit_fail(struct kunit *test, const struct kunit_loc *loc, kunit_print_string_stream(test, stream); - string_stream_destroy(stream); + kunit_free_string_stream(test, stream); } -static void __noreturn kunit_abort(struct kunit *test) +void __noreturn __kunit_abort(struct kunit *test) { kunit_try_catch_throw(&test->try_catch); /* Does not return. */ @@ -296,8 +315,9 @@ static void __noreturn kunit_abort(struct kunit *test) */ WARN_ONCE(true, "Throw could not abort from test!\n"); } +EXPORT_SYMBOL_GPL(__kunit_abort); -void kunit_do_failed_assertion(struct kunit *test, +void __kunit_do_failed_assertion(struct kunit *test, const struct kunit_loc *loc, enum kunit_assert_type type, const struct kunit_assert *assert, @@ -314,25 +334,95 @@ void kunit_do_failed_assertion(struct kunit *test, kunit_fail(test, loc, type, assert, assert_format, &message); va_end(args); +} +EXPORT_SYMBOL_GPL(__kunit_do_failed_assertion); - if (type == KUNIT_ASSERTION) - kunit_abort(test); +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; } -EXPORT_SYMBOL_GPL(kunit_do_failed_assertion); -void kunit_init_test(struct kunit *test, const char *name, char *log) +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) - test->log[0] = '\0'; + 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. */ @@ -340,6 +430,8 @@ 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; @@ -351,7 +443,13 @@ static void kunit_run_case_internal(struct kunit *test, } } + 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) @@ -393,15 +491,54 @@ static void kunit_try_run_case(void *data) * thread will resume control and handle any necessary clean up. */ kunit_run_case_internal(test, suite, test_case); - /* This line may never be reached. */ +} + +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; - struct kunit_suite *suite = ctx->suite; int try_exit_code = kunit_try_catch_get_result(&test->try_catch); if (try_exit_code) { @@ -422,12 +559,6 @@ static void kunit_catch_run_case(void *data) } return; } - - /* - * Test case was run, but aborted. It is the test case's business as to - * whether it failed or not, we just need to clean up. - */ - kunit_run_case_cleanup(test, suite); } /* @@ -441,18 +572,26 @@ static void kunit_run_case_catch_errors(struct kunit_suite *suite, struct kunit_try_catch_context context; struct kunit_try_catch *try_catch; - kunit_init_test(test, test_case->name, test_case->log); try_catch = &test->try_catch; kunit_try_catch_init(try_catch, test, kunit_try_run_case, - kunit_catch_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; @@ -511,12 +650,44 @@ static void kunit_accumulate_stats(struct kunit_result_stats *total, 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); @@ -535,48 +706,82 @@ int kunit_run_tests(struct kunit_suite *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 }; - test_case->status = KUNIT_SKIPPED; - if (!test_case->generate_params) { + 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'; - test.param_value = test_case->generate_params(NULL, param_desc); + /* 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 (test.param_value) { - kunit_run_case_catch_errors(suite, test_case, &test); + 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", test.param_index); + "param-%d", param_test.param_index); } - kunit_log(KERN_INFO, &test, - KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT - "%s %d %s", - kunit_status_to_ok_not_ok(test.status), - test.param_index + 1, param_desc); + 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'; - test.param_value = test_case->generate_params(test.param_value, param_desc); - test.param_index++; - - kunit_update_stats(¶m_stats, test.status); + 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, true, test_case->status, + 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); @@ -601,6 +806,9 @@ 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) @@ -608,23 +816,36 @@ bool kunit_enabled(void) return enable_param; } -int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_suites) +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]); - kunit_run_tests(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); @@ -643,20 +864,69 @@ void __kunit_test_suites_exit(struct kunit_suite **suites, int num_suites) for (i = 0; i < num_suites; i++) kunit_exit_suite(suites[i]); - - kunit_suite_counter = 1; } EXPORT_SYMBOL_GPL(__kunit_test_suites_exit); -#ifdef CONFIG_MODULES static void kunit_module_init(struct module *mod) { - __kunit_test_suites_init(mod->kunit_suites, mod->num_kunit_suites); + 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) { - __kunit_test_suites_exit(mod->kunit_suites, mod->num_kunit_suites); + 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, @@ -672,6 +942,7 @@ static int kunit_module_notify(struct notifier_block *nb, unsigned long val, kunit_module_exit(mod); break; case MODULE_STATE_COMING: + break; case MODULE_STATE_UNFORMED: break; } @@ -683,63 +954,53 @@ static struct notifier_block kunit_mod_nb = { .notifier_call = kunit_module_notify, .priority = 0, }; -#endif -struct kunit_kmalloc_array_params { - size_t n; - size_t size; - gfp_t gfp; -}; +KUNIT_DEFINE_ACTION_WRAPPER(kfree_action_wrapper, kfree, const void *) -static int kunit_kmalloc_array_init(struct kunit_resource *res, void *context) +void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp) { - struct kunit_kmalloc_array_params *params = context; + void *data; - res->data = kmalloc_array(params->n, params->size, params->gfp); - if (!res->data) - return -ENOMEM; + data = kmalloc_array(n, size, gfp); - return 0; -} + if (!data) + return NULL; -static void kunit_kmalloc_array_free(struct kunit_resource *res) -{ - kfree(res->data); -} + if (kunit_add_action_or_reset(test, kfree_action_wrapper, data) != 0) + return NULL; -void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp) -{ - struct kunit_kmalloc_array_params params = { - .size = size, - .n = n, - .gfp = gfp - }; - - return kunit_alloc_resource(test, - kunit_kmalloc_array_init, - kunit_kmalloc_array_free, - gfp, - ¶ms); + return data; } EXPORT_SYMBOL_GPL(kunit_kmalloc_array); -static inline bool kunit_kfree_match(struct kunit *test, - struct kunit_resource *res, void *match_data) -{ - /* Only match resources allocated with kunit_kmalloc() and friends. */ - return res->free == kunit_kmalloc_array_free && res->data == match_data; -} - void kunit_kfree(struct kunit *test, const void *ptr) { if (!ptr) return; - if (kunit_destroy_resource(test, kunit_kfree_match, (void *)ptr)) - KUNIT_FAIL(test, "kunit_kfree: %px already freed or not allocated by kunit", ptr); + 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; @@ -777,22 +1038,26 @@ EXPORT_SYMBOL_GPL(kunit_cleanup); static int __init kunit_init(void) { + /* Install the KUnit hook functions. */ + kunit_install_hooks(); + kunit_debugfs_init(); -#ifdef CONFIG_MODULES + + kunit_bus_init(); return register_module_notifier(&kunit_mod_nb); -#else - return 0; -#endif } late_initcall(kunit_init); static void __exit kunit_exit(void) { -#ifdef CONFIG_MODULES + memset(&kunit_hooks, 0, sizeof(kunit_hooks)); unregister_module_notifier(&kunit_mod_nb); -#endif + + 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 index 203ba6a5e740..6f401b97cd0b 100644 --- a/lib/kunit/try-catch-impl.h +++ b/lib/kunit/try-catch-impl.h @@ -17,11 +17,13 @@ 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) + 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 index f7825991d576..d84a879f0a78 100644 --- a/lib/kunit/try-catch.c +++ b/lib/kunit/try-catch.c @@ -11,13 +11,14 @@ #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_complete_and_exit(try_catch->try_completion, -EFAULT); + kthread_exit(0); } EXPORT_SYMBOL_GPL(kunit_try_catch_throw); @@ -25,62 +26,48 @@ 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; - kthread_complete_and_exit(try_catch->try_completion, 0); -} - -static unsigned long kunit_test_timeout(void) -{ - /* - * TODO(brendanhiggins@google.com): We should probably have some type of - * variable timeout here. The only question is what that timeout value - * should be. - * - * The intention has always been, at some point, to be able to label - * tests with some type of size bucket (unit/small, integration/medium, - * large/system/end-to-end, etc), where each size bucket would get a - * default timeout value kind of like what Bazel does: - * https://docs.bazel.build/versions/master/be/common-definitions.html#test.size - * There is still some debate to be had on exactly how we do this. (For - * one, we probably want to have some sort of test runner level - * timeout.) - * - * For more background on this topic, see: - * https://mike-bland.com/2011/11/01/small-medium-large.html - * - * If tests timeout due to exceeding sysctl_hung_task_timeout_secs, - * the task will be killed and an oops generated. - */ - return 300 * msecs_to_jiffies(MSEC_PER_SEC); /* 5 min */ + return 0; } void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context) { - DECLARE_COMPLETION_ONSTACK(try_completion); 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_completion = &try_completion; try_catch->try_result = 0; - task_struct = kthread_run(kunit_generic_run_threadfn_adapter, - try_catch, - "kunit_try_catch_thread"); + 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(&try_completion, - kunit_test_timeout()); + time_remaining = wait_for_completion_timeout( + task_done, try_catch->timeout); if (time_remaining == 0) { - kunit_err(test, "try timed out\n"); try_catch->try_result = -ETIMEDOUT; kthread_stop(task_struct); } + put_task_struct(task_struct); exit_code = try_catch->try_result; if (!exit_code) @@ -88,8 +75,14 @@ void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context) if (exit_code == -EFAULT) try_catch->try_result = 0; - else if (exit_code == -EINTR) - kunit_err(test, "wake_up_process() was never called\n"); + 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); 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"); |
