kunit_tool: added option for creating KUnit test skeletons
Adds command to kunit.py which allows a user to specify a file to test,
which will then print out the boilerplate needed to create a KUnit test.
Change-Id: I2505462e9f60d5d6975da422b9c0bed367791d5f
Google-Bug-Id: 117125357
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py
index 938b0da..34c0eb5d 100755
--- a/tools/testing/kunit/kunit.py
+++ b/tools/testing/kunit/kunit.py
@@ -10,6 +10,7 @@
import kunit_config
import kunit_kernel
+import kunit_new_template
import kunit_parser
def run_tests(cli_args, linux):
@@ -50,6 +51,11 @@
build_end - build_start,
test_end - test_start)))
+def print_test_skeletons(cli_args):
+ kunit_new_template.create_skeletons_from_path(
+ cli_args.path,
+ namespace_prefix=cli_args.namespace_prefix)
+
def main(argv, linux=kunit_kernel.LinuxSourceTree()):
parser = argparse.ArgumentParser(
description='Helps writing and running KUnit tests.')
@@ -67,9 +73,22 @@
default=300,
metavar='timeout')
+ new_parser = subparser.add_parser(
+ 'new',
+ help='Prints out boilerplate for writing new tests.')
+ new_parser.add_argument('--path',
+ help='Path of source file to be tested.',
+ type=str,
+ required=True)
+ new_parser.add_argument('--namespace_prefix',
+ help='Namespace of the code to be tested.',
+ type=str)
+
cli_args = parser.parse_args(argv)
- if cli_args.subcommand == 'run':
+ if cli_args.subcommand == 'new':
+ print_test_skeletons(cli_args)
+ elif cli_args.subcommand == 'run':
run_tests(cli_args, linux)
else:
parser.print_help()
diff --git a/tools/testing/kunit/kunit_new_template.py b/tools/testing/kunit/kunit_new_template.py
new file mode 100644
index 0000000..1b4516d
--- /dev/null
+++ b/tools/testing/kunit/kunit_new_template.py
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: GPL-2.0
+
+import os
+import string
+
+TEMPLATE_DIR = os.path.dirname(os.path.abspath(__file__))
+TEST_TEMPLATE_PATH = os.path.join(TEMPLATE_DIR, 'test_template.c')
+KCONFIG_TEMPLATE_PATH = os.path.join(TEMPLATE_DIR, 'test_template.Kconfig')
+MAKEFILE_TEMPLATE_PATH = os.path.join(TEMPLATE_DIR, 'test_template.Makefile')
+
+def create_skeleton_from_template(template_path, test_prefix, test_object_file):
+ with open(template_path, 'r') as f:
+ return string.Template(f.read()).safe_substitute(
+ test_prefix=test_prefix,
+ caps_test_prefix=test_prefix.upper(),
+ test_object_file=test_object_file)
+
+
+class Skeletons(object):
+ """
+ Represents the KUnit skeletons for a test, Kconfig entry, and Makefile
+ entry.
+ """
+ def __init__(self, test_skeleton, kconfig_skeleton, makefile_skeleton):
+ self.test_skeleton = test_skeleton
+ self.kconfig_skeleton = kconfig_skeleton
+ self.makefile_skeleton = makefile_skeleton
+
+
+def create_skeletons(namespace_prefix, test_object_file):
+ test_prefix = namespace_prefix + '_test'
+ return Skeletons(
+ test_skeleton=create_skeleton_from_template(
+ TEST_TEMPLATE_PATH,
+ test_prefix,
+ test_object_file),
+ kconfig_skeleton=create_skeleton_from_template(
+ KCONFIG_TEMPLATE_PATH,
+ test_prefix,
+ test_object_file),
+ makefile_skeleton=create_skeleton_from_template(
+ MAKEFILE_TEMPLATE_PATH,
+ test_prefix,
+ test_object_file)
+ )
+
+def namespace_prefix_from_path(path):
+ file_name = os.path.basename(path)
+ return os.path.splitext(file_name)
+
+def create_skeletons_from_path(path, namespace_prefix=None):
+ dir_name, file_name = os.path.split(path)
+ file_prefix, _ = os.path.splitext(file_name)
+ test_path = os.path.join(dir_name, file_prefix + '-test.c')
+ test_object_file = file_prefix + '-test.o'
+ if not namespace_prefix:
+ namespace_prefix = file_prefix.replace('-', '_')
+ skeletons = create_skeletons(namespace_prefix, test_object_file)
+ print('### In ' + test_path)
+ print(skeletons.test_skeleton)
+ print('### In Kconfig')
+ print(skeletons.kconfig_skeleton)
+ print('### In Makefile')
+ print(skeletons.makefile_skeleton)
+
diff --git a/tools/testing/kunit/kunit_test.py b/tools/testing/kunit/kunit_test.py
index 816ba07..e7baf2868 100755
--- a/tools/testing/kunit/kunit_test.py
+++ b/tools/testing/kunit/kunit_test.py
@@ -207,5 +207,25 @@
self.linux_source_mock.run_kernel.assert_called_once_with(timeout=timeout)
self.print_mock.assert_any_call(StrContains('Testing complete.'))
+ def test_new_no_namespace(self):
+ kunit.main(['new', '--path', 'drivers/i2c/busses/i2c-aspeed.c'], self.linux_source_mock)
+ assert self.linux_source_mock.build_reconfig.call_count == 0
+ assert self.linux_source_mock.run_kernel.call_count == 0
+ self.print_mock.assert_any_call(StrContains('i2c_aspeed'))
+ for kall in self.print_mock.call_args_list:
+ assert kall != mock.call(StrContains('aspeed_i2c'))
+
+ def test_new_with_namespace(self):
+ namespace_prefix = 'aspeed_i2c'
+ kunit.main([
+ 'new',
+ '--path', 'drivers/i2c/busses/i2c-aspeed.c',
+ '--namespace_prefix', namespace_prefix],
+ self.linux_source_mock)
+ assert self.linux_source_mock.build_reconfig.call_count == 0
+ assert self.linux_source_mock.run_kernel.call_count == 0
+ self.print_mock.assert_any_call(StrContains('i2c-aspeed'))
+ self.print_mock.assert_any_call(StrContains('aspeed_i2c'))
+
if __name__ == '__main__':
unittest.main()
diff --git a/tools/testing/kunit/test_template.Kconfig b/tools/testing/kunit/test_template.Kconfig
new file mode 100644
index 0000000..1bdafb3
--- /dev/null
+++ b/tools/testing/kunit/test_template.Kconfig
@@ -0,0 +1,5 @@
+config ${caps_test_prefix}
+ bool "KUnit test for ${test_prefix}"
+ depends on TEST
+ help
+ TODO: Describe config fully.
diff --git a/tools/testing/kunit/test_template.Makefile b/tools/testing/kunit/test_template.Makefile
new file mode 100644
index 0000000..b32a538
--- /dev/null
+++ b/tools/testing/kunit/test_template.Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_${caps_test_prefix}) += ${test_object_file}
diff --git a/tools/testing/kunit/test_template.c b/tools/testing/kunit/test_template.c
new file mode 100644
index 0000000..b2169cc
--- /dev/null
+++ b/tools/testing/kunit/test_template.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TODO: Add test description.
+ */
+
+#include <test/test.h>
+#include <test/mock.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 test *)`. `struct test` is a context object that stores
+ * information about the current test.
+ */
+static void ${test_prefix}_foo(struct test *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.
+ */
+ EXPECT_EQ(test, 1, 2); // Obvious failure.
+}
+
+/*
+ * This is run once before each test case, see the comment on
+ * example_test_module for more information.
+ */
+static int ${test_prefix}_init(struct test *test)
+{
+ return 0;
+}
+
+/*
+ * This is run once after each test case, see the comment on example_test_module
+ * for more information.
+ */
+static void ${test_prefix}_exit(struct test *test)
+{
+}
+
+/*
+ * Here we make a list of all the test cases we want to add to the test module
+ * below.
+ */
+static struct test_case ${test_prefix}_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 module.
+ */
+ TEST_CASE(${test_prefix}_foo),
+ {},
+};
+
+/*
+ * This defines a suite or grouping of tests.
+ *
+ * Test cases are defined as belonging to the suite by adding them to
+ * `test_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 module would behave as follows:
+ *
+ * module.init(test);
+ * module.test_case[0](test);
+ * module.exit(test);
+ * module.init(test);
+ * module.test_case[1](test);
+ * module.exit(test);
+ * ...;
+ */
+static struct test_module ${test_prefix}_module = {
+ .name = "${test_prefix}",
+ .init = ${test_prefix}_init,
+ .exit = ${test_prefix}_exit,
+ .test_cases = ${test_prefix}_cases,
+};
+
+/*
+ * This registers the above test module telling KUnit that this is a suite of
+ * tests that need to be run.
+ */
+module_test(${test_prefix}_module);