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);