Commit 929b9796 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'linux-kselftest-kunit-fixes-5.11-rc5' of...

Merge tag 'linux-kselftest-kunit-fixes-5.11-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest

Pull kunit fixes from Shuah :
 "Five fixes to the kunit tool and documentation from Daniel Latypov and
  David Gow"

* tag 'linux-kselftest-kunit-fixes-5.11-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest:
  kunit: tool: move kunitconfig parsing into __init__, make it optional
  kunit: tool: fix minor typing issue with None status
  kunit: tool: surface and address more typing issues
  Documentation: kunit: include example of a parameterized test
  kunit: tool: Fix spelling of "diagnostic" in kunit_parser
parents fe75a218 2b8fdbbf
Loading
Loading
Loading
Loading
+57 −0
Original line number Diff line number Diff line
@@ -522,6 +522,63 @@ There's more boilerplate involved, but it can:
  * E.g. if we wanted to also test ``sha256sum``, we could add a ``sha256``
    field and reuse ``cases``.

* be converted to a "parameterized test", see below.

Parameterized Testing
~~~~~~~~~~~~~~~~~~~~~

The table-driven testing pattern is common enough that KUnit has special
support for it.

Reusing the same ``cases`` array from above, we can write the test as a
"parameterized test" with the following.

.. code-block:: c

	// This is copy-pasted from above.
	struct sha1_test_case {
		const char *str;
		const char *sha1;
	};
	struct sha1_test_case cases[] = {
		{
			.str = "hello world",
			.sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
		},
		{
			.str = "hello world!",
			.sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
		},
	};

	// Need a helper function to generate a name for each test case.
	static void case_to_desc(const struct sha1_test_case *t, char *desc)
	{
		strcpy(desc, t->str);
	}
	// Creates `sha1_gen_params()` to iterate over `cases`.
	KUNIT_ARRAY_PARAM(sha1, cases, case_to_desc);

	// Looks no different from a normal test.
	static void sha1_test(struct kunit *test)
	{
		// This function can just contain the body of the for-loop.
		// The former `cases[i]` is accessible under test->param_value.
		char out[40];
		struct sha1_test_case *test_param = (struct sha1_test_case *)(test->param_value);

		sha1sum(test_param->str, out);
		KUNIT_EXPECT_STREQ_MSG(test, (char *)out, test_param->sha1,
				      "sha1sum(%s)", test_param->str);
	}

	// Instead of KUNIT_CASE, we use KUNIT_CASE_PARAM and pass in the
	// function declared by KUNIT_ARRAY_PARAM.
	static struct kunit_case sha1_test_cases[] = {
		KUNIT_CASE_PARAM(sha1_test, sha1_gen_params),
		{}
	};

.. _kunit-on-non-uml:

KUnit on non-UML architectures
+11 −23
Original line number Diff line number Diff line
@@ -43,9 +43,9 @@ class KunitStatus(Enum):
	BUILD_FAILURE = auto()
	TEST_FAILURE = auto()

def get_kernel_root_path():
	parts = sys.argv[0] if not __file__ else __file__
	parts = os.path.realpath(parts).split('tools/testing/kunit')
def get_kernel_root_path() -> str:
	path = sys.argv[0] if not __file__ else __file__
	parts = os.path.realpath(path).split('tools/testing/kunit')
	if len(parts) != 2:
		sys.exit(1)
	return parts[0]
@@ -171,7 +171,7 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree,
				exec_result.elapsed_time))
	return parse_result

def add_common_opts(parser):
def add_common_opts(parser) -> None:
	parser.add_argument('--build_dir',
			    help='As in the make command, it specifies the build '
			    'directory.',
@@ -183,13 +183,13 @@ def add_common_opts(parser):
			    help='Run all KUnit tests through allyesconfig',
			    action='store_true')

def add_build_opts(parser):
def add_build_opts(parser) -> None:
	parser.add_argument('--jobs',
			    help='As in the make command, "Specifies  the number of '
			    'jobs (commands) to run simultaneously."',
			    type=int, default=8, metavar='jobs')

def add_exec_opts(parser):
def add_exec_opts(parser) -> None:
	parser.add_argument('--timeout',
			    help='maximum number of seconds to allow for all tests '
			    'to run. This does not include time taken to build the '
@@ -198,7 +198,7 @@ def add_exec_opts(parser):
			    default=300,
			    metavar='timeout')

def add_parse_opts(parser):
def add_parse_opts(parser) -> None:
	parser.add_argument('--raw_output', help='don\'t format output from kernel',
			    action='store_true')
	parser.add_argument('--json',
@@ -256,10 +256,7 @@ def main(argv, linux=None):
			os.mkdir(cli_args.build_dir)

		if not linux:
			linux = kunit_kernel.LinuxSourceTree()

		linux.create_kunitconfig(cli_args.build_dir)
		linux.read_kunitconfig(cli_args.build_dir)
			linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir)

		request = KunitRequest(cli_args.raw_output,
				       cli_args.timeout,
@@ -277,10 +274,7 @@ def main(argv, linux=None):
			os.mkdir(cli_args.build_dir)

		if not linux:
			linux = kunit_kernel.LinuxSourceTree()

		linux.create_kunitconfig(cli_args.build_dir)
		linux.read_kunitconfig(cli_args.build_dir)
			linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir)

		request = KunitConfigRequest(cli_args.build_dir,
					     cli_args.make_options)
@@ -292,10 +286,7 @@ def main(argv, linux=None):
			sys.exit(1)
	elif cli_args.subcommand == 'build':
		if not linux:
			linux = kunit_kernel.LinuxSourceTree()

		linux.create_kunitconfig(cli_args.build_dir)
		linux.read_kunitconfig(cli_args.build_dir)
			linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir)

		request = KunitBuildRequest(cli_args.jobs,
					    cli_args.build_dir,
@@ -309,10 +300,7 @@ def main(argv, linux=None):
			sys.exit(1)
	elif cli_args.subcommand == 'exec':
		if not linux:
			linux = kunit_kernel.LinuxSourceTree()

		linux.create_kunitconfig(cli_args.build_dir)
		linux.read_kunitconfig(cli_args.build_dir)
			linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir)

		exec_request = KunitExecRequest(cli_args.timeout,
						cli_args.build_dir,
+4 −3
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@

import collections
import re
from typing import List, Set

CONFIG_IS_NOT_SET_PATTERN = r'^# CONFIG_(\w+) is not set$'
CONFIG_PATTERN = r'^CONFIG_(\w+)=(\S+|".*")$'
@@ -30,10 +31,10 @@ class KconfigParseError(Exception):
class Kconfig(object):
	"""Represents defconfig or .config specified using the Kconfig language."""

	def __init__(self):
		self._entries = []
	def __init__(self) -> None:
		self._entries = []  # type: List[KconfigEntry]

	def entries(self):
	def entries(self) -> Set[KconfigEntry]:
		return set(self._entries)

	def add_entry(self, entry: KconfigEntry) -> None:
+1 −1
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ import kunit_parser

from kunit_parser import TestStatus

def get_json_result(test_result, def_config, build_dir, json_path):
def get_json_result(test_result, def_config, build_dir, json_path) -> str:
	sub_groups = []

	# Each test suite is mapped to a KernelCI sub_group
+28 −26
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ import subprocess
import os
import shutil
import signal
from typing import Iterator

from contextlib import ExitStack

@@ -39,7 +40,7 @@ class BuildError(Exception):
class LinuxSourceTreeOperations(object):
	"""An abstraction over command line operations performed on a source tree."""

	def make_mrproper(self):
	def make_mrproper(self) -> None:
		try:
			subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT)
		except OSError as e:
@@ -47,7 +48,7 @@ class LinuxSourceTreeOperations(object):
		except subprocess.CalledProcessError as e:
			raise ConfigError(e.output.decode())

	def make_olddefconfig(self, build_dir, make_options):
	def make_olddefconfig(self, build_dir, make_options) -> None:
		command = ['make', 'ARCH=um', 'olddefconfig']
		if make_options:
			command.extend(make_options)
@@ -60,7 +61,7 @@ class LinuxSourceTreeOperations(object):
		except subprocess.CalledProcessError as e:
			raise ConfigError(e.output.decode())

	def make_allyesconfig(self, build_dir, make_options):
	def make_allyesconfig(self, build_dir, make_options) -> None:
		kunit_parser.print_with_timestamp(
			'Enabling all CONFIGs for UML...')
		command = ['make', 'ARCH=um', 'allyesconfig']
@@ -82,7 +83,7 @@ class LinuxSourceTreeOperations(object):
		kunit_parser.print_with_timestamp(
			'Starting Kernel with all configs takes a few minutes...')

	def make(self, jobs, build_dir, make_options):
	def make(self, jobs, build_dir, make_options) -> None:
		command = ['make', 'ARCH=um', '--jobs=' + str(jobs)]
		if make_options:
			command.extend(make_options)
@@ -100,7 +101,7 @@ class LinuxSourceTreeOperations(object):
		if stderr:  # likely only due to build warnings
			print(stderr.decode())

	def linux_bin(self, params, timeout, build_dir):
	def linux_bin(self, params, timeout, build_dir) -> None:
		"""Runs the Linux UML binary. Must be named 'linux'."""
		linux_bin = get_file_path(build_dir, 'linux')
		outfile = get_outfile_path(build_dir)
@@ -110,41 +111,42 @@ class LinuxSourceTreeOperations(object):
						   stderr=subprocess.STDOUT)
			process.wait(timeout)

def get_kconfig_path(build_dir):
def get_kconfig_path(build_dir) -> str:
	return get_file_path(build_dir, KCONFIG_PATH)

def get_kunitconfig_path(build_dir):
def get_kunitconfig_path(build_dir) -> str:
	return get_file_path(build_dir, KUNITCONFIG_PATH)

def get_outfile_path(build_dir):
def get_outfile_path(build_dir) -> str:
	return get_file_path(build_dir, OUTFILE_PATH)

class LinuxSourceTree(object):
	"""Represents a Linux kernel source tree with KUnit tests."""

	def __init__(self):
		self._ops = LinuxSourceTreeOperations()
	def __init__(self, build_dir: str, load_config=True, defconfig=DEFAULT_KUNITCONFIG_PATH) -> None:
		signal.signal(signal.SIGINT, self.signal_handler)

	def clean(self):
		try:
			self._ops.make_mrproper()
		except ConfigError as e:
			logging.error(e)
			return False
		return True
		self._ops = LinuxSourceTreeOperations()

		if not load_config:
			return

	def create_kunitconfig(self, build_dir, defconfig=DEFAULT_KUNITCONFIG_PATH):
		kunitconfig_path = get_kunitconfig_path(build_dir)
		if not os.path.exists(kunitconfig_path):
			shutil.copyfile(defconfig, kunitconfig_path)

	def read_kunitconfig(self, build_dir):
		kunitconfig_path = get_kunitconfig_path(build_dir)
		self._kconfig = kunit_config.Kconfig()
		self._kconfig.read_from_file(kunitconfig_path)

	def validate_config(self, build_dir):
	def clean(self) -> bool:
		try:
			self._ops.make_mrproper()
		except ConfigError as e:
			logging.error(e)
			return False
		return True

	def validate_config(self, build_dir) -> bool:
		kconfig_path = get_kconfig_path(build_dir)
		validated_kconfig = kunit_config.Kconfig()
		validated_kconfig.read_from_file(kconfig_path)
@@ -158,7 +160,7 @@ class LinuxSourceTree(object):
			return False
		return True

	def build_config(self, build_dir, make_options):
	def build_config(self, build_dir, make_options) -> bool:
		kconfig_path = get_kconfig_path(build_dir)
		if build_dir and not os.path.exists(build_dir):
			os.mkdir(build_dir)
@@ -170,7 +172,7 @@ class LinuxSourceTree(object):
			return False
		return self.validate_config(build_dir)

	def build_reconfig(self, build_dir, make_options):
	def build_reconfig(self, build_dir, make_options) -> bool:
		"""Creates a new .config if it is not a subset of the .kunitconfig."""
		kconfig_path = get_kconfig_path(build_dir)
		if os.path.exists(kconfig_path):
@@ -186,7 +188,7 @@ class LinuxSourceTree(object):
			print('Generating .config ...')
			return self.build_config(build_dir, make_options)

	def build_um_kernel(self, alltests, jobs, build_dir, make_options):
	def build_um_kernel(self, alltests, jobs, build_dir, make_options) -> bool:
		try:
			if alltests:
				self._ops.make_allyesconfig(build_dir, make_options)
@@ -197,7 +199,7 @@ class LinuxSourceTree(object):
			return False
		return self.validate_config(build_dir)

	def run_kernel(self, args=[], build_dir='', timeout=None):
	def run_kernel(self, args=[], build_dir='', timeout=None) -> Iterator[str]:
		args.extend(['mem=1G', 'console=tty'])
		self._ops.linux_bin(args, timeout, build_dir)
		outfile = get_outfile_path(build_dir)
@@ -206,6 +208,6 @@ class LinuxSourceTree(object):
			for line in file:
				yield line

	def signal_handler(self, sig, frame):
	def signal_handler(self, sig, frame) -> None:
		logging.error('Build interruption occurred. Cleaning console.')
		subprocess.call(['stty', 'sane'])
Loading