diff options
Diffstat (limited to 'lib/kunit')
| -rw-r--r-- | lib/kunit/Kconfig | 36 | ||||
| -rw-r--r-- | lib/kunit/Makefile | 9 | ||||
| -rw-r--r-- | lib/kunit/assert.c | 141 | ||||
| -rw-r--r-- | lib/kunit/example-test.c | 88 | ||||
| -rw-r--r-- | lib/kunit/string-stream-test.c | 52 | ||||
| -rw-r--r-- | lib/kunit/string-stream.c | 217 | ||||
| -rw-r--r-- | lib/kunit/test-test.c | 331 | ||||
| -rw-r--r-- | lib/kunit/test.c | 478 | ||||
| -rw-r--r-- | lib/kunit/try-catch.c | 118 | 
9 files changed, 1470 insertions, 0 deletions
diff --git a/lib/kunit/Kconfig b/lib/kunit/Kconfig new file mode 100644 index 000000000000..af37016bfdd4 --- /dev/null +++ b/lib/kunit/Kconfig @@ -0,0 +1,36 @@ +# +# KUnit base configuration +# + +menuconfig KUNIT +	bool "KUnit - Enable support for unit tests" +	help +	  Enables support for kernel unit tests (KUnit), a lightweight unit +	  testing and mocking framework for the Linux kernel. These tests are +	  able to be run locally on a developer's workstation without a VM or +	  special hardware when using UML. Can also be used on most other +	  architectures. For more information, please see +	  Documentation/dev-tools/kunit/. + +if KUNIT + +config KUNIT_TEST +	bool "KUnit test for KUnit" +	help +	  Enables the unit tests for the KUnit test framework. These tests test +	  the KUnit test framework itself; the tests are both written using +	  KUnit and test KUnit. This option should only be enabled for testing +	  purposes by developers interested in testing that KUnit works as +	  expected. + +config KUNIT_EXAMPLE_TEST +	bool "Example test for KUnit" +	help +	  Enables an example unit test that illustrates some of the basic +	  features of KUnit. This test only exists to help new users understand +	  what KUnit is and how it is used. Please refer to the example test +	  itself, lib/kunit/example-test.c, for more information. This option +	  is intended for curious hackers who would like to understand how to +	  use KUnit for kernel development. + +endif # KUNIT diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile new file mode 100644 index 000000000000..769d9402b5d3 --- /dev/null +++ b/lib/kunit/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_KUNIT) +=			test.o \ +					string-stream.o \ +					assert.o \ +					try-catch.o + +obj-$(CONFIG_KUNIT_TEST) +=		test-test.o \ +					string-stream-test.o + +obj-$(CONFIG_KUNIT_EXAMPLE_TEST) +=	example-test.o diff --git a/lib/kunit/assert.c b/lib/kunit/assert.c new file mode 100644 index 000000000000..86013d4cf891 --- /dev/null +++ b/lib/kunit/assert.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Assertion and expectation serialization API. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <[email protected]> + */ +#include <kunit/assert.h> + +void kunit_base_assert_format(const struct kunit_assert *assert, +			      struct string_stream *stream) +{ +	const char *expect_or_assert = NULL; + +	switch (assert->type) { +	case KUNIT_EXPECTATION: +		expect_or_assert = "EXPECTATION"; +		break; +	case KUNIT_ASSERTION: +		expect_or_assert = "ASSERTION"; +		break; +	} + +	string_stream_add(stream, "%s FAILED at %s:%d\n", +			 expect_or_assert, assert->file, assert->line); +} + +void kunit_assert_print_msg(const struct kunit_assert *assert, +			    struct string_stream *stream) +{ +	if (assert->message.fmt) +		string_stream_add(stream, "\n%pV", &assert->message); +} + +void kunit_fail_assert_format(const struct kunit_assert *assert, +			      struct string_stream *stream) +{ +	kunit_base_assert_format(assert, stream); +	string_stream_add(stream, "%pV", &assert->message); +} + +void kunit_unary_assert_format(const struct kunit_assert *assert, +			       struct string_stream *stream) +{ +	struct kunit_unary_assert *unary_assert = container_of( +			assert, struct kunit_unary_assert, assert); + +	kunit_base_assert_format(assert, stream); +	if (unary_assert->expected_true) +		string_stream_add(stream, +				 "\tExpected %s to be true, but is false\n", +				 unary_assert->condition); +	else +		string_stream_add(stream, +				 "\tExpected %s to be false, but is true\n", +				 unary_assert->condition); +	kunit_assert_print_msg(assert, stream); +} + +void kunit_ptr_not_err_assert_format(const struct kunit_assert *assert, +				     struct string_stream *stream) +{ +	struct kunit_ptr_not_err_assert *ptr_assert = container_of( +			assert, struct kunit_ptr_not_err_assert, assert); + +	kunit_base_assert_format(assert, stream); +	if (!ptr_assert->value) { +		string_stream_add(stream, +				 "\tExpected %s is not null, but is\n", +				 ptr_assert->text); +	} else if (IS_ERR(ptr_assert->value)) { +		string_stream_add(stream, +				 "\tExpected %s is not error, but is: %ld\n", +				 ptr_assert->text, +				 PTR_ERR(ptr_assert->value)); +	} +	kunit_assert_print_msg(assert, stream); +} + +void kunit_binary_assert_format(const struct kunit_assert *assert, +				struct string_stream *stream) +{ +	struct kunit_binary_assert *binary_assert = container_of( +			assert, struct kunit_binary_assert, assert); + +	kunit_base_assert_format(assert, stream); +	string_stream_add(stream, +			 "\tExpected %s %s %s, but\n", +			 binary_assert->left_text, +			 binary_assert->operation, +			 binary_assert->right_text); +	string_stream_add(stream, "\t\t%s == %lld\n", +			 binary_assert->left_text, +			 binary_assert->left_value); +	string_stream_add(stream, "\t\t%s == %lld", +			 binary_assert->right_text, +			 binary_assert->right_value); +	kunit_assert_print_msg(assert, stream); +} + +void kunit_binary_ptr_assert_format(const struct kunit_assert *assert, +				    struct string_stream *stream) +{ +	struct kunit_binary_ptr_assert *binary_assert = container_of( +			assert, struct kunit_binary_ptr_assert, assert); + +	kunit_base_assert_format(assert, stream); +	string_stream_add(stream, +			 "\tExpected %s %s %s, but\n", +			 binary_assert->left_text, +			 binary_assert->operation, +			 binary_assert->right_text); +	string_stream_add(stream, "\t\t%s == %pK\n", +			 binary_assert->left_text, +			 binary_assert->left_value); +	string_stream_add(stream, "\t\t%s == %pK", +			 binary_assert->right_text, +			 binary_assert->right_value); +	kunit_assert_print_msg(assert, stream); +} + +void kunit_binary_str_assert_format(const struct kunit_assert *assert, +				    struct string_stream *stream) +{ +	struct kunit_binary_str_assert *binary_assert = container_of( +			assert, struct kunit_binary_str_assert, assert); + +	kunit_base_assert_format(assert, stream); +	string_stream_add(stream, +			 "\tExpected %s %s %s, but\n", +			 binary_assert->left_text, +			 binary_assert->operation, +			 binary_assert->right_text); +	string_stream_add(stream, "\t\t%s == %s\n", +			 binary_assert->left_text, +			 binary_assert->left_value); +	string_stream_add(stream, "\t\t%s == %s", +			 binary_assert->right_text, +			 binary_assert->right_value); +	kunit_assert_print_msg(assert, stream); +} diff --git a/lib/kunit/example-test.c b/lib/kunit/example-test.c new file mode 100644 index 000000000000..f64a829aa441 --- /dev/null +++ b/lib/kunit/example-test.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Example KUnit test to show how to use KUnit. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <[email protected]> + */ + +#include <kunit/test.h> + +/* + * This is the most fundamental element of KUnit, the test case. A test case + * makes a set EXPECTATIONs and ASSERTIONs about the behavior of some code; if + * any expectations or assertions are not met, the test fails; otherwise, the + * test passes. + * + * In KUnit, a test case is just a function with the signature + * `void (*)(struct kunit *)`. `struct kunit` is a context object that stores + * information about the current test. + */ +static void example_simple_test(struct kunit *test) +{ +	/* +	 * This is an EXPECTATION; it is how KUnit tests things. When you want +	 * to test a piece of code, you set some expectations about what the +	 * code should do. KUnit then runs the test and verifies that the code's +	 * behavior matched what was expected. +	 */ +	KUNIT_EXPECT_EQ(test, 1 + 1, 2); +} + +/* + * This is run once before each test case, see the comment on + * example_test_suite for more information. + */ +static int example_test_init(struct kunit *test) +{ +	kunit_info(test, "initializing\n"); + +	return 0; +} + +/* + * Here we make a list of all the test cases we want to add to the test suite + * below. + */ +static struct kunit_case example_test_cases[] = { +	/* +	 * This is a helper to create a test case object from a test case +	 * function; its exact function is not important to understand how to +	 * use KUnit, just know that this is how you associate test cases with a +	 * test suite. +	 */ +	KUNIT_CASE(example_simple_test), +	{} +}; + +/* + * This defines a suite or grouping of tests. + * + * Test cases are defined as belonging to the suite by adding them to + * `kunit_cases`. + * + * Often it is desirable to run some function which will set up things which + * will be used by every test; this is accomplished with an `init` function + * which runs before each test case is invoked. Similarly, an `exit` function + * may be specified which runs after every test case and can be used to for + * cleanup. For clarity, running tests in a test suite would behave as follows: + * + * suite.init(test); + * suite.test_case[0](test); + * suite.exit(test); + * suite.init(test); + * suite.test_case[1](test); + * suite.exit(test); + * ...; + */ +static struct kunit_suite example_test_suite = { +	.name = "example", +	.init = example_test_init, +	.test_cases = example_test_cases, +}; + +/* + * This registers the above test suite telling KUnit that this is a suite of + * tests that need to be run. + */ +kunit_test_suite(example_test_suite); diff --git a/lib/kunit/string-stream-test.c b/lib/kunit/string-stream-test.c new file mode 100644 index 000000000000..76cc05eb00ed --- /dev/null +++ b/lib/kunit/string-stream-test.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for struct string_stream. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <[email protected]> + */ + +#include <kunit/string-stream.h> +#include <kunit/test.h> +#include <linux/slab.h> + +static void string_stream_test_empty_on_creation(struct kunit *test) +{ +	struct string_stream *stream = alloc_string_stream(test, GFP_KERNEL); + +	KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream)); +} + +static void string_stream_test_not_empty_after_add(struct kunit *test) +{ +	struct string_stream *stream = alloc_string_stream(test, GFP_KERNEL); + +	string_stream_add(stream, "Foo"); + +	KUNIT_EXPECT_FALSE(test, string_stream_is_empty(stream)); +} + +static void string_stream_test_get_string(struct kunit *test) +{ +	struct string_stream *stream = alloc_string_stream(test, GFP_KERNEL); +	char *output; + +	string_stream_add(stream, "Foo"); +	string_stream_add(stream, " %s", "bar"); + +	output = string_stream_get_string(stream); +	KUNIT_ASSERT_STREQ(test, output, "Foo bar"); +} + +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), +	{} +}; + +static struct kunit_suite string_stream_test_suite = { +	.name = "string-stream-test", +	.test_cases = string_stream_test_cases +}; +kunit_test_suite(string_stream_test_suite); diff --git a/lib/kunit/string-stream.c b/lib/kunit/string-stream.c new file mode 100644 index 000000000000..e6d17aacca30 --- /dev/null +++ b/lib/kunit/string-stream.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * C++ stream style string builder used in KUnit for building messages. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <[email protected]> + */ + +#include <kunit/string-stream.h> +#include <kunit/test.h> +#include <linux/list.h> +#include <linux/slab.h> + +struct string_stream_fragment_alloc_context { +	struct kunit *test; +	int len; +	gfp_t gfp; +}; + +static int string_stream_fragment_init(struct kunit_resource *res, +				       void *context) +{ +	struct string_stream_fragment_alloc_context *ctx = context; +	struct string_stream_fragment *frag; + +	frag = kunit_kzalloc(ctx->test, sizeof(*frag), ctx->gfp); +	if (!frag) +		return -ENOMEM; + +	frag->test = ctx->test; +	frag->fragment = kunit_kmalloc(ctx->test, ctx->len, ctx->gfp); +	if (!frag->fragment) +		return -ENOMEM; + +	res->allocation = frag; + +	return 0; +} + +static void string_stream_fragment_free(struct kunit_resource *res) +{ +	struct string_stream_fragment *frag = res->allocation; + +	list_del(&frag->node); +	kunit_kfree(frag->test, frag->fragment); +	kunit_kfree(frag->test, frag); +} + +static struct string_stream_fragment *alloc_string_stream_fragment( +		struct kunit *test, int len, gfp_t gfp) +{ +	struct string_stream_fragment_alloc_context context = { +		.test = test, +		.len = len, +		.gfp = gfp +	}; + +	return kunit_alloc_resource(test, +				    string_stream_fragment_init, +				    string_stream_fragment_free, +				    gfp, +				    &context); +} + +static int string_stream_fragment_destroy(struct string_stream_fragment *frag) +{ +	return kunit_resource_destroy(frag->test, +				      kunit_resource_instance_match, +				      string_stream_fragment_free, +				      frag); +} + +int string_stream_vadd(struct string_stream *stream, +		       const char *fmt, +		       va_list args) +{ +	struct string_stream_fragment *frag_container; +	int 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; + +	va_end(args_for_counting); + +	frag_container = alloc_string_stream_fragment(stream->test, +						      len, +						      stream->gfp); +	if (!frag_container) +		return -ENOMEM; + +	len = vsnprintf(frag_container->fragment, len, fmt, args); +	spin_lock(&stream->lock); +	stream->length += len; +	list_add_tail(&frag_container->node, &stream->fragments); +	spin_unlock(&stream->lock); + +	return 0; +} + +int string_stream_add(struct string_stream *stream, const char *fmt, ...) +{ +	va_list args; +	int result; + +	va_start(args, fmt); +	result = string_stream_vadd(stream, fmt, args); +	va_end(args); + +	return result; +} + +static void string_stream_clear(struct string_stream *stream) +{ +	struct string_stream_fragment *frag_container, *frag_container_safe; + +	spin_lock(&stream->lock); +	list_for_each_entry_safe(frag_container, +				 frag_container_safe, +				 &stream->fragments, +				 node) { +		string_stream_fragment_destroy(frag_container); +	} +	stream->length = 0; +	spin_unlock(&stream->lock); +} + +char *string_stream_get_string(struct string_stream *stream) +{ +	struct string_stream_fragment *frag_container; +	size_t buf_len = stream->length + 1; /* +1 for null byte. */ +	char *buf; + +	buf = kunit_kzalloc(stream->test, buf_len, stream->gfp); +	if (!buf) +		return NULL; + +	spin_lock(&stream->lock); +	list_for_each_entry(frag_container, &stream->fragments, node) +		strlcat(buf, frag_container->fragment, buf_len); +	spin_unlock(&stream->lock); + +	return buf; +} + +int string_stream_append(struct string_stream *stream, +			 struct string_stream *other) +{ +	const char *other_content; + +	other_content = string_stream_get_string(other); + +	if (!other_content) +		return -ENOMEM; + +	return string_stream_add(stream, other_content); +} + +bool string_stream_is_empty(struct string_stream *stream) +{ +	return list_empty(&stream->fragments); +} + +struct string_stream_alloc_context { +	struct kunit *test; +	gfp_t gfp; +}; + +static int string_stream_init(struct kunit_resource *res, void *context) +{ +	struct string_stream *stream; +	struct string_stream_alloc_context *ctx = context; + +	stream = kunit_kzalloc(ctx->test, sizeof(*stream), ctx->gfp); +	if (!stream) +		return -ENOMEM; + +	res->allocation = stream; +	stream->gfp = ctx->gfp; +	stream->test = ctx->test; +	INIT_LIST_HEAD(&stream->fragments); +	spin_lock_init(&stream->lock); + +	return 0; +} + +static void string_stream_free(struct kunit_resource *res) +{ +	struct string_stream *stream = res->allocation; + +	string_stream_clear(stream); +} + +struct string_stream *alloc_string_stream(struct kunit *test, gfp_t gfp) +{ +	struct string_stream_alloc_context context = { +		.test = test, +		.gfp = gfp +	}; + +	return kunit_alloc_resource(test, +				    string_stream_init, +				    string_stream_free, +				    gfp, +				    &context); +} + +int string_stream_destroy(struct string_stream *stream) +{ +	return kunit_resource_destroy(stream->test, +				      kunit_resource_instance_match, +				      string_stream_free, +				      stream); +} diff --git a/lib/kunit/test-test.c b/lib/kunit/test-test.c new file mode 100644 index 000000000000..5ebe059d16e2 --- /dev/null +++ b/lib/kunit/test-test.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for core test infrastructure. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <[email protected]> + */ +#include <kunit/test.h> + +struct kunit_try_catch_test_context { +	struct kunit_try_catch *try_catch; +	bool function_called; +}; + +static void kunit_test_successful_try(void *data) +{ +	struct kunit *test = data; +	struct kunit_try_catch_test_context *ctx = test->priv; + +	ctx->function_called = true; +} + +static void kunit_test_no_catch(void *data) +{ +	struct kunit *test = data; + +	KUNIT_FAIL(test, "Catch should not be called\n"); +} + +static void kunit_test_try_catch_successful_try_no_catch(struct kunit *test) +{ +	struct kunit_try_catch_test_context *ctx = test->priv; +	struct kunit_try_catch *try_catch = ctx->try_catch; + +	kunit_try_catch_init(try_catch, +			     test, +			     kunit_test_successful_try, +			     kunit_test_no_catch); +	kunit_try_catch_run(try_catch, test); + +	KUNIT_EXPECT_TRUE(test, ctx->function_called); +} + +static void kunit_test_unsuccessful_try(void *data) +{ +	struct kunit *test = data; +	struct kunit_try_catch_test_context *ctx = test->priv; +	struct kunit_try_catch *try_catch = ctx->try_catch; + +	kunit_try_catch_throw(try_catch); +	KUNIT_FAIL(test, "This line should never be reached\n"); +} + +static void kunit_test_catch(void *data) +{ +	struct kunit *test = data; +	struct kunit_try_catch_test_context *ctx = test->priv; + +	ctx->function_called = true; +} + +static void kunit_test_try_catch_unsuccessful_try_does_catch(struct kunit *test) +{ +	struct kunit_try_catch_test_context *ctx = test->priv; +	struct kunit_try_catch *try_catch = ctx->try_catch; + +	kunit_try_catch_init(try_catch, +			     test, +			     kunit_test_unsuccessful_try, +			     kunit_test_catch); +	kunit_try_catch_run(try_catch, test); + +	KUNIT_EXPECT_TRUE(test, ctx->function_called); +} + +static int kunit_try_catch_test_init(struct kunit *test) +{ +	struct kunit_try_catch_test_context *ctx; + +	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); +	test->priv = ctx; + +	ctx->try_catch = kunit_kmalloc(test, +				       sizeof(*ctx->try_catch), +				       GFP_KERNEL); +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->try_catch); + +	return 0; +} + +static struct kunit_case kunit_try_catch_test_cases[] = { +	KUNIT_CASE(kunit_test_try_catch_successful_try_no_catch), +	KUNIT_CASE(kunit_test_try_catch_unsuccessful_try_does_catch), +	{} +}; + +static struct kunit_suite kunit_try_catch_test_suite = { +	.name = "kunit-try-catch-test", +	.init = kunit_try_catch_test_init, +	.test_cases = kunit_try_catch_test_cases, +}; +kunit_test_suite(kunit_try_catch_test_suite); + +/* + * Context for testing test managed resources + * is_resource_initialized is used to test arbitrary resources + */ +struct kunit_test_resource_context { +	struct kunit test; +	bool is_resource_initialized; +	int allocate_order[2]; +	int free_order[2]; +}; + +static int fake_resource_init(struct kunit_resource *res, void *context) +{ +	struct kunit_test_resource_context *ctx = context; + +	res->allocation = &ctx->is_resource_initialized; +	ctx->is_resource_initialized = true; +	return 0; +} + +static void fake_resource_free(struct kunit_resource *res) +{ +	bool *is_resource_initialized = res->allocation; + +	*is_resource_initialized = false; +} + +static void kunit_resource_test_init_resources(struct kunit *test) +{ +	struct kunit_test_resource_context *ctx = test->priv; + +	kunit_init_test(&ctx->test, "testing_test_init_test"); + +	KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_alloc_resource(struct kunit *test) +{ +	struct kunit_test_resource_context *ctx = test->priv; +	struct kunit_resource *res; +	kunit_resource_free_t free = fake_resource_free; + +	res = kunit_alloc_and_get_resource(&ctx->test, +					   fake_resource_init, +					   fake_resource_free, +					   GFP_KERNEL, +					   ctx); + +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res); +	KUNIT_EXPECT_PTR_EQ(test, +			    &ctx->is_resource_initialized, +			    (bool *) res->allocation); +	KUNIT_EXPECT_TRUE(test, list_is_last(&res->node, &ctx->test.resources)); +	KUNIT_EXPECT_PTR_EQ(test, free, res->free); +} + +static void kunit_resource_test_destroy_resource(struct kunit *test) +{ +	struct kunit_test_resource_context *ctx = test->priv; +	struct kunit_resource *res = kunit_alloc_and_get_resource( +			&ctx->test, +			fake_resource_init, +			fake_resource_free, +			GFP_KERNEL, +			ctx); + +	KUNIT_ASSERT_FALSE(test, +			   kunit_resource_destroy(&ctx->test, +						  kunit_resource_instance_match, +						  res->free, +						  res->allocation)); + +	KUNIT_EXPECT_FALSE(test, ctx->is_resource_initialized); +	KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_cleanup_resources(struct kunit *test) +{ +	int i; +	struct kunit_test_resource_context *ctx = test->priv; +	struct kunit_resource *resources[5]; + +	for (i = 0; i < ARRAY_SIZE(resources); i++) { +		resources[i] = kunit_alloc_and_get_resource(&ctx->test, +							    fake_resource_init, +							    fake_resource_free, +							    GFP_KERNEL, +							    ctx); +	} + +	kunit_cleanup(&ctx->test); + +	KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_mark_order(int order_array[], +					   size_t order_size, +					   int key) +{ +	int i; + +	for (i = 0; i < order_size && order_array[i]; i++) +		; + +	order_array[i] = key; +} + +#define KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, order_field, key)		       \ +		kunit_resource_test_mark_order(ctx->order_field,	       \ +					       ARRAY_SIZE(ctx->order_field),   \ +					       key) + +static int fake_resource_2_init(struct kunit_resource *res, void *context) +{ +	struct kunit_test_resource_context *ctx = context; + +	KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, allocate_order, 2); + +	res->allocation = ctx; + +	return 0; +} + +static void fake_resource_2_free(struct kunit_resource *res) +{ +	struct kunit_test_resource_context *ctx = res->allocation; + +	KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, free_order, 2); +} + +static int fake_resource_1_init(struct kunit_resource *res, void *context) +{ +	struct kunit_test_resource_context *ctx = context; + +	kunit_alloc_and_get_resource(&ctx->test, +				     fake_resource_2_init, +				     fake_resource_2_free, +				     GFP_KERNEL, +				     ctx); + +	KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, allocate_order, 1); + +	res->allocation = ctx; + +	return 0; +} + +static void fake_resource_1_free(struct kunit_resource *res) +{ +	struct kunit_test_resource_context *ctx = res->allocation; + +	KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, free_order, 1); +} + +/* + * TODO([email protected]): replace the arrays that keep track of the + * order of allocation and freeing with strict mocks using the IN_SEQUENCE macro + * to assert allocation and freeing order when the feature becomes available. + */ +static void kunit_resource_test_proper_free_ordering(struct kunit *test) +{ +	struct kunit_test_resource_context *ctx = test->priv; + +	/* fake_resource_1 allocates a fake_resource_2 in its init. */ +	kunit_alloc_and_get_resource(&ctx->test, +				     fake_resource_1_init, +				     fake_resource_1_free, +				     GFP_KERNEL, +				     ctx); + +	/* +	 * Since fake_resource_2_init calls KUNIT_RESOURCE_TEST_MARK_ORDER +	 * before returning to fake_resource_1_init, it should be the first to +	 * put its key in the allocate_order array. +	 */ +	KUNIT_EXPECT_EQ(test, ctx->allocate_order[0], 2); +	KUNIT_EXPECT_EQ(test, ctx->allocate_order[1], 1); + +	kunit_cleanup(&ctx->test); + +	/* +	 * Because fake_resource_2 finishes allocation before fake_resource_1, +	 * fake_resource_1 should be freed first since it could depend on +	 * fake_resource_2. +	 */ +	KUNIT_EXPECT_EQ(test, ctx->free_order[0], 1); +	KUNIT_EXPECT_EQ(test, ctx->free_order[1], 2); +} + +static int kunit_resource_test_init(struct kunit *test) +{ +	struct kunit_test_resource_context *ctx = +			kzalloc(sizeof(*ctx), GFP_KERNEL); + +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + +	test->priv = ctx; + +	kunit_init_test(&ctx->test, "test_test_context"); + +	return 0; +} + +static void kunit_resource_test_exit(struct kunit *test) +{ +	struct kunit_test_resource_context *ctx = test->priv; + +	kunit_cleanup(&ctx->test); +	kfree(ctx); +} + +static struct kunit_case kunit_resource_test_cases[] = { +	KUNIT_CASE(kunit_resource_test_init_resources), +	KUNIT_CASE(kunit_resource_test_alloc_resource), +	KUNIT_CASE(kunit_resource_test_destroy_resource), +	KUNIT_CASE(kunit_resource_test_cleanup_resources), +	KUNIT_CASE(kunit_resource_test_proper_free_ordering), +	{} +}; + +static struct kunit_suite kunit_resource_test_suite = { +	.name = "kunit-resource-test", +	.init = kunit_resource_test_init, +	.exit = kunit_resource_test_exit, +	.test_cases = kunit_resource_test_cases, +}; +kunit_test_suite(kunit_resource_test_suite); diff --git a/lib/kunit/test.c b/lib/kunit/test.c new file mode 100644 index 000000000000..c83c0fa59cbd --- /dev/null +++ b/lib/kunit/test.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Base unit test (KUnit) API. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <[email protected]> + */ + +#include <kunit/test.h> +#include <kunit/try-catch.h> +#include <linux/kernel.h> +#include <linux/sched/debug.h> + +static void kunit_set_failure(struct kunit *test) +{ +	WRITE_ONCE(test->success, false); +} + +static void kunit_print_tap_version(void) +{ +	static bool kunit_has_printed_tap_version; + +	if (!kunit_has_printed_tap_version) { +		pr_info("TAP version 14\n"); +		kunit_has_printed_tap_version = true; +	} +} + +static size_t kunit_test_cases_len(struct kunit_case *test_cases) +{ +	struct kunit_case *test_case; +	size_t len = 0; + +	for (test_case = test_cases; test_case->run_case; test_case++) +		len++; + +	return len; +} + +static void kunit_print_subtest_start(struct kunit_suite *suite) +{ +	kunit_print_tap_version(); +	pr_info("\t# Subtest: %s\n", suite->name); +	pr_info("\t1..%zd\n", kunit_test_cases_len(suite->test_cases)); +} + +static void kunit_print_ok_not_ok(bool should_indent, +				  bool is_ok, +				  size_t test_number, +				  const char *description) +{ +	const char *indent, *ok_not_ok; + +	if (should_indent) +		indent = "\t"; +	else +		indent = ""; + +	if (is_ok) +		ok_not_ok = "ok"; +	else +		ok_not_ok = "not ok"; + +	pr_info("%s%s %zd - %s\n", indent, ok_not_ok, test_number, description); +} + +static bool kunit_suite_has_succeeded(struct kunit_suite *suite) +{ +	const struct kunit_case *test_case; + +	for (test_case = suite->test_cases; test_case->run_case; test_case++) +		if (!test_case->success) +			return false; + +	return true; +} + +static void kunit_print_subtest_end(struct kunit_suite *suite) +{ +	static size_t kunit_suite_counter = 1; + +	kunit_print_ok_not_ok(false, +			      kunit_suite_has_succeeded(suite), +			      kunit_suite_counter++, +			      suite->name); +} + +static void kunit_print_test_case_ok_not_ok(struct kunit_case *test_case, +					    size_t test_number) +{ +	kunit_print_ok_not_ok(true, +			      test_case->success, +			      test_number, +			      test_case->name); +} + +static void kunit_print_string_stream(struct kunit *test, +				      struct string_stream *stream) +{ +	struct string_stream_fragment *fragment; +	char *buf; + +	buf = string_stream_get_string(stream); +	if (!buf) { +		kunit_err(test, +			  "Could not allocate buffer, dumping stream:\n"); +		list_for_each_entry(fragment, &stream->fragments, node) { +			kunit_err(test, "%s", fragment->fragment); +		} +		kunit_err(test, "\n"); +	} else { +		kunit_err(test, "%s", buf); +		kunit_kfree(test, buf); +	} +} + +static void kunit_fail(struct kunit *test, struct kunit_assert *assert) +{ +	struct string_stream *stream; + +	kunit_set_failure(test); + +	stream = alloc_string_stream(test, GFP_KERNEL); +	if (!stream) { +		WARN(true, +		     "Could not allocate stream to print failed assertion in %s:%d\n", +		     assert->file, +		     assert->line); +		return; +	} + +	assert->format(assert, stream); + +	kunit_print_string_stream(test, stream); + +	WARN_ON(string_stream_destroy(stream)); +} + +static void __noreturn kunit_abort(struct kunit *test) +{ +	kunit_try_catch_throw(&test->try_catch); /* Does not return. */ + +	/* +	 * Throw could not abort from test. +	 * +	 * XXX: we should never reach this line! As kunit_try_catch_throw is +	 * marked __noreturn. +	 */ +	WARN_ONCE(true, "Throw could not abort from test!\n"); +} + +void kunit_do_assertion(struct kunit *test, +			struct kunit_assert *assert, +			bool pass, +			const char *fmt, ...) +{ +	va_list args; + +	if (pass) +		return; + +	va_start(args, fmt); + +	assert->message.fmt = fmt; +	assert->message.va = &args; + +	kunit_fail(test, assert); + +	va_end(args); + +	if (assert->type == KUNIT_ASSERTION) +		kunit_abort(test); +} + +void kunit_init_test(struct kunit *test, const char *name) +{ +	spin_lock_init(&test->lock); +	INIT_LIST_HEAD(&test->resources); +	test->name = name; +	test->success = true; +} + +/* + * Initializes and runs test case. Does not clean up or do post validations. + */ +static void kunit_run_case_internal(struct kunit *test, +				    struct kunit_suite *suite, +				    struct kunit_case *test_case) +{ +	if (suite->init) { +		int ret; + +		ret = suite->init(test); +		if (ret) { +			kunit_err(test, "failed to initialize: %d\n", ret); +			kunit_set_failure(test); +			return; +		} +	} + +	test_case->run_case(test); +} + +static void kunit_case_internal_cleanup(struct kunit *test) +{ +	kunit_cleanup(test); +} + +/* + * Performs post validations and cleanup after a test case was run. + * XXX: Should ONLY BE CALLED AFTER kunit_run_case_internal! + */ +static void kunit_run_case_cleanup(struct kunit *test, +				   struct kunit_suite *suite) +{ +	if (suite->exit) +		suite->exit(test); + +	kunit_case_internal_cleanup(test); +} + +struct kunit_try_catch_context { +	struct kunit *test; +	struct kunit_suite *suite; +	struct kunit_case *test_case; +}; + +static void kunit_try_run_case(void *data) +{ +	struct kunit_try_catch_context *ctx = data; +	struct kunit *test = ctx->test; +	struct kunit_suite *suite = ctx->suite; +	struct kunit_case *test_case = ctx->test_case; + +	/* +	 * kunit_run_case_internal may encounter a fatal error; if it does, +	 * abort will be called, this thread will exit, and finally the parent +	 * thread will resume control and handle any necessary clean up. +	 */ +	kunit_run_case_internal(test, suite, test_case); +	/* This line may never be reached. */ +	kunit_run_case_cleanup(test, suite); +} + +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) { +		kunit_set_failure(test); +		/* +		 * Test case could not finish, we have no idea what state it is +		 * in, so don't do clean up. +		 */ +		if (try_exit_code == -ETIMEDOUT) { +			kunit_err(test, "test case timed out\n"); +		/* +		 * Unknown internal error occurred preventing test case from +		 * running, so there is nothing to clean up. +		 */ +		} else { +			kunit_err(test, "internal error occurred preventing test case from running: %d\n", +				  try_exit_code); +		} +		return; +	} + +	/* +	 * 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); +} + +/* + * Performs all logic to run a test case. It also catches most errors that + * occur in a test case and reports them as failures. + */ +static void kunit_run_case_catch_errors(struct kunit_suite *suite, +					struct kunit_case *test_case) +{ +	struct kunit_try_catch_context context; +	struct kunit_try_catch *try_catch; +	struct kunit test; + +	kunit_init_test(&test, test_case->name); +	try_catch = &test.try_catch; + +	kunit_try_catch_init(try_catch, +			     &test, +			     kunit_try_run_case, +			     kunit_catch_run_case); +	context.test = &test; +	context.suite = suite; +	context.test_case = test_case; +	kunit_try_catch_run(try_catch, &context); + +	test_case->success = test.success; +} + +int kunit_run_tests(struct kunit_suite *suite) +{ +	struct kunit_case *test_case; +	size_t test_case_count = 1; + +	kunit_print_subtest_start(suite); + +	for (test_case = suite->test_cases; test_case->run_case; test_case++) { +		kunit_run_case_catch_errors(suite, test_case); +		kunit_print_test_case_ok_not_ok(test_case, test_case_count++); +	} + +	kunit_print_subtest_end(suite); + +	return 0; +} + +struct kunit_resource *kunit_alloc_and_get_resource(struct kunit *test, +						    kunit_resource_init_t init, +						    kunit_resource_free_t free, +						    gfp_t internal_gfp, +						    void *context) +{ +	struct kunit_resource *res; +	int ret; + +	res = kzalloc(sizeof(*res), internal_gfp); +	if (!res) +		return NULL; + +	ret = init(res, context); +	if (ret) +		return NULL; + +	res->free = free; +	spin_lock(&test->lock); +	list_add_tail(&res->node, &test->resources); +	spin_unlock(&test->lock); + +	return res; +} + +static void kunit_resource_free(struct kunit *test, struct kunit_resource *res) +{ +	res->free(res); +	kfree(res); +} + +static struct kunit_resource *kunit_resource_find(struct kunit *test, +						  kunit_resource_match_t match, +						  kunit_resource_free_t free, +						  void *match_data) +{ +	struct kunit_resource *resource; + +	lockdep_assert_held(&test->lock); + +	list_for_each_entry_reverse(resource, &test->resources, node) { +		if (resource->free != free) +			continue; +		if (match(test, resource->allocation, match_data)) +			return resource; +	} + +	return NULL; +} + +static struct kunit_resource *kunit_resource_remove( +		struct kunit *test, +		kunit_resource_match_t match, +		kunit_resource_free_t free, +		void *match_data) +{ +	struct kunit_resource *resource; + +	spin_lock(&test->lock); +	resource = kunit_resource_find(test, match, free, match_data); +	if (resource) +		list_del(&resource->node); +	spin_unlock(&test->lock); + +	return resource; +} + +int kunit_resource_destroy(struct kunit *test, +			   kunit_resource_match_t match, +			   kunit_resource_free_t free, +			   void *match_data) +{ +	struct kunit_resource *resource; + +	resource = kunit_resource_remove(test, match, free, match_data); + +	if (!resource) +		return -ENOENT; + +	kunit_resource_free(test, resource); +	return 0; +} + +struct kunit_kmalloc_params { +	size_t size; +	gfp_t gfp; +}; + +static int kunit_kmalloc_init(struct kunit_resource *res, void *context) +{ +	struct kunit_kmalloc_params *params = context; + +	res->allocation = kmalloc(params->size, params->gfp); +	if (!res->allocation) +		return -ENOMEM; + +	return 0; +} + +static void kunit_kmalloc_free(struct kunit_resource *res) +{ +	kfree(res->allocation); +} + +void *kunit_kmalloc(struct kunit *test, size_t size, gfp_t gfp) +{ +	struct kunit_kmalloc_params params = { +		.size = size, +		.gfp = gfp +	}; + +	return kunit_alloc_resource(test, +				    kunit_kmalloc_init, +				    kunit_kmalloc_free, +				    gfp, +				    ¶ms); +} + +void kunit_kfree(struct kunit *test, const void *ptr) +{ +	int rc; + +	rc = kunit_resource_destroy(test, +				    kunit_resource_instance_match, +				    kunit_kmalloc_free, +				    (void *)ptr); + +	WARN_ON(rc); +} + +void kunit_cleanup(struct kunit *test) +{ +	struct kunit_resource *resource; + +	/* +	 * test->resources is a stack - each allocation must be freed in the +	 * reverse order from which it was added since one resource may depend +	 * on another for its entire lifetime. +	 * Also, we cannot use the normal list_for_each constructs, even the +	 * safe ones because *arbitrary* nodes may be deleted when +	 * kunit_resource_free is called; the list_for_each_safe variants only +	 * protect against the current node being deleted, not the next. +	 */ +	while (true) { +		spin_lock(&test->lock); +		if (list_empty(&test->resources)) { +			spin_unlock(&test->lock); +			break; +		} +		resource = list_last_entry(&test->resources, +					   struct kunit_resource, +					   node); +		list_del(&resource->node); +		spin_unlock(&test->lock); + +		kunit_resource_free(test, resource); +	} +} diff --git a/lib/kunit/try-catch.c b/lib/kunit/try-catch.c new file mode 100644 index 000000000000..55686839eb61 --- /dev/null +++ b/lib/kunit/try-catch.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An API to allow a function, that may fail, to be executed, and recover in a + * controlled manner. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins <[email protected]> + */ + +#include <kunit/test.h> +#include <kunit/try-catch.h> +#include <linux/completion.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/sched/sysctl.h> + +void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch) +{ +	try_catch->try_result = -EFAULT; +	complete_and_exit(try_catch->try_completion, -EFAULT); +} + +static int kunit_generic_run_threadfn_adapter(void *data) +{ +	struct kunit_try_catch *try_catch = data; + +	try_catch->try(try_catch->context); + +	complete_and_exit(try_catch->try_completion, 0); +} + +static unsigned long kunit_test_timeout(void) +{ +	unsigned long timeout_msecs; + +	/* +	 * TODO([email protected]): 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 (sysctl_hung_task_timeout_secs) { +		/* +		 * If sysctl_hung_task is active, just set the timeout to some +		 * value less than that. +		 * +		 * In regards to the above TODO, if we decide on variable +		 * timeouts, this logic will likely need to change. +		 */ +		timeout_msecs = (sysctl_hung_task_timeout_secs - 1) * +				MSEC_PER_SEC; +	} else { +		timeout_msecs = 300 * MSEC_PER_SEC; /* 5 min */ +	} + +	return timeout_msecs; +} + +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; +	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"); +	if (IS_ERR(task_struct)) { +		try_catch->catch(try_catch->context); +		return; +	} + +	time_remaining = wait_for_completion_timeout(&try_completion, +						     kunit_test_timeout()); +	if (time_remaining == 0) { +		kunit_err(test, "try timed out\n"); +		try_catch->try_result = -ETIMEDOUT; +	} + +	exit_code = try_catch->try_result; + +	if (!exit_code) +		return; + +	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) +		kunit_err(test, "Unknown error: %d\n", exit_code); + +	try_catch->catch(try_catch->context); +} + +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) +{ +	try_catch->test = test; +	try_catch->try = try; +	try_catch->catch = catch; +}  |