Skip to content

Commit 929b979

Browse files
committed
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
2 parents fe75a21 + 2b8fdbb commit 929b979

6 files changed

Lines changed: 141 additions & 94 deletions

File tree

Documentation/dev-tools/kunit/usage.rst

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,63 @@ There's more boilerplate involved, but it can:
522522
* E.g. if we wanted to also test ``sha256sum``, we could add a ``sha256``
523523
field and reuse ``cases``.
524524

525+
* be converted to a "parameterized test", see below.
526+
527+
Parameterized Testing
528+
~~~~~~~~~~~~~~~~~~~~~
529+
530+
The table-driven testing pattern is common enough that KUnit has special
531+
support for it.
532+
533+
Reusing the same ``cases`` array from above, we can write the test as a
534+
"parameterized test" with the following.
535+
536+
.. code-block:: c
537+
538+
// This is copy-pasted from above.
539+
struct sha1_test_case {
540+
const char *str;
541+
const char *sha1;
542+
};
543+
struct sha1_test_case cases[] = {
544+
{
545+
.str = "hello world",
546+
.sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
547+
},
548+
{
549+
.str = "hello world!",
550+
.sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
551+
},
552+
};
553+
554+
// Need a helper function to generate a name for each test case.
555+
static void case_to_desc(const struct sha1_test_case *t, char *desc)
556+
{
557+
strcpy(desc, t->str);
558+
}
559+
// Creates `sha1_gen_params()` to iterate over `cases`.
560+
KUNIT_ARRAY_PARAM(sha1, cases, case_to_desc);
561+
562+
// Looks no different from a normal test.
563+
static void sha1_test(struct kunit *test)
564+
{
565+
// This function can just contain the body of the for-loop.
566+
// The former `cases[i]` is accessible under test->param_value.
567+
char out[40];
568+
struct sha1_test_case *test_param = (struct sha1_test_case *)(test->param_value);
569+
570+
sha1sum(test_param->str, out);
571+
KUNIT_EXPECT_STREQ_MSG(test, (char *)out, test_param->sha1,
572+
"sha1sum(%s)", test_param->str);
573+
}
574+
575+
// Instead of KUNIT_CASE, we use KUNIT_CASE_PARAM and pass in the
576+
// function declared by KUNIT_ARRAY_PARAM.
577+
static struct kunit_case sha1_test_cases[] = {
578+
KUNIT_CASE_PARAM(sha1_test, sha1_gen_params),
579+
{}
580+
};
581+
525582
.. _kunit-on-non-uml:
526583

527584
KUnit on non-UML architectures

tools/testing/kunit/kunit.py

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ class KunitStatus(Enum):
4343
BUILD_FAILURE = auto()
4444
TEST_FAILURE = auto()
4545

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

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

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

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

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

258258
if not linux:
259-
linux = kunit_kernel.LinuxSourceTree()
260-
261-
linux.create_kunitconfig(cli_args.build_dir)
262-
linux.read_kunitconfig(cli_args.build_dir)
259+
linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir)
263260

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

279276
if not linux:
280-
linux = kunit_kernel.LinuxSourceTree()
281-
282-
linux.create_kunitconfig(cli_args.build_dir)
283-
linux.read_kunitconfig(cli_args.build_dir)
277+
linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir)
284278

285279
request = KunitConfigRequest(cli_args.build_dir,
286280
cli_args.make_options)
@@ -292,10 +286,7 @@ def main(argv, linux=None):
292286
sys.exit(1)
293287
elif cli_args.subcommand == 'build':
294288
if not linux:
295-
linux = kunit_kernel.LinuxSourceTree()
296-
297-
linux.create_kunitconfig(cli_args.build_dir)
298-
linux.read_kunitconfig(cli_args.build_dir)
289+
linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir)
299290

300291
request = KunitBuildRequest(cli_args.jobs,
301292
cli_args.build_dir,
@@ -309,10 +300,7 @@ def main(argv, linux=None):
309300
sys.exit(1)
310301
elif cli_args.subcommand == 'exec':
311302
if not linux:
312-
linux = kunit_kernel.LinuxSourceTree()
313-
314-
linux.create_kunitconfig(cli_args.build_dir)
315-
linux.read_kunitconfig(cli_args.build_dir)
303+
linux = kunit_kernel.LinuxSourceTree(cli_args.build_dir)
316304

317305
exec_request = KunitExecRequest(cli_args.timeout,
318306
cli_args.build_dir,

tools/testing/kunit/kunit_config.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import collections
1010
import re
11+
from typing import List, Set
1112

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

33-
def __init__(self):
34-
self._entries = []
34+
def __init__(self) -> None:
35+
self._entries = [] # type: List[KconfigEntry]
3536

36-
def entries(self):
37+
def entries(self) -> Set[KconfigEntry]:
3738
return set(self._entries)
3839

3940
def add_entry(self, entry: KconfigEntry) -> None:

tools/testing/kunit/kunit_json.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from kunit_parser import TestStatus
1515

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

1919
# Each test suite is mapped to a KernelCI sub_group

tools/testing/kunit/kunit_kernel.py

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import os
1212
import shutil
1313
import signal
14+
from typing import Iterator
1415

1516
from contextlib import ExitStack
1617

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

42-
def make_mrproper(self):
43+
def make_mrproper(self) -> None:
4344
try:
4445
subprocess.check_output(['make', 'mrproper'], stderr=subprocess.STDOUT)
4546
except OSError as e:
4647
raise ConfigError('Could not call make command: ' + str(e))
4748
except subprocess.CalledProcessError as e:
4849
raise ConfigError(e.output.decode())
4950

50-
def make_olddefconfig(self, build_dir, make_options):
51+
def make_olddefconfig(self, build_dir, make_options) -> None:
5152
command = ['make', 'ARCH=um', 'olddefconfig']
5253
if make_options:
5354
command.extend(make_options)
@@ -60,7 +61,7 @@ def make_olddefconfig(self, build_dir, make_options):
6061
except subprocess.CalledProcessError as e:
6162
raise ConfigError(e.output.decode())
6263

63-
def make_allyesconfig(self, build_dir, make_options):
64+
def make_allyesconfig(self, build_dir, make_options) -> None:
6465
kunit_parser.print_with_timestamp(
6566
'Enabling all CONFIGs for UML...')
6667
command = ['make', 'ARCH=um', 'allyesconfig']
@@ -82,7 +83,7 @@ def make_allyesconfig(self, build_dir, make_options):
8283
kunit_parser.print_with_timestamp(
8384
'Starting Kernel with all configs takes a few minutes...')
8485

85-
def make(self, jobs, build_dir, make_options):
86+
def make(self, jobs, build_dir, make_options) -> None:
8687
command = ['make', 'ARCH=um', '--jobs=' + str(jobs)]
8788
if make_options:
8889
command.extend(make_options)
@@ -100,7 +101,7 @@ def make(self, jobs, build_dir, make_options):
100101
if stderr: # likely only due to build warnings
101102
print(stderr.decode())
102103

103-
def linux_bin(self, params, timeout, build_dir):
104+
def linux_bin(self, params, timeout, build_dir) -> None:
104105
"""Runs the Linux UML binary. Must be named 'linux'."""
105106
linux_bin = get_file_path(build_dir, 'linux')
106107
outfile = get_outfile_path(build_dir)
@@ -110,41 +111,42 @@ def linux_bin(self, params, timeout, build_dir):
110111
stderr=subprocess.STDOUT)
111112
process.wait(timeout)
112113

113-
def get_kconfig_path(build_dir):
114+
def get_kconfig_path(build_dir) -> str:
114115
return get_file_path(build_dir, KCONFIG_PATH)
115116

116-
def get_kunitconfig_path(build_dir):
117+
def get_kunitconfig_path(build_dir) -> str:
117118
return get_file_path(build_dir, KUNITCONFIG_PATH)
118119

119-
def get_outfile_path(build_dir):
120+
def get_outfile_path(build_dir) -> str:
120121
return get_file_path(build_dir, OUTFILE_PATH)
121122

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

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

129-
def clean(self):
130-
try:
131-
self._ops.make_mrproper()
132-
except ConfigError as e:
133-
logging.error(e)
134-
return False
135-
return True
129+
self._ops = LinuxSourceTreeOperations()
130+
131+
if not load_config:
132+
return
136133

137-
def create_kunitconfig(self, build_dir, defconfig=DEFAULT_KUNITCONFIG_PATH):
138134
kunitconfig_path = get_kunitconfig_path(build_dir)
139135
if not os.path.exists(kunitconfig_path):
140136
shutil.copyfile(defconfig, kunitconfig_path)
141137

142-
def read_kunitconfig(self, build_dir):
143-
kunitconfig_path = get_kunitconfig_path(build_dir)
144138
self._kconfig = kunit_config.Kconfig()
145139
self._kconfig.read_from_file(kunitconfig_path)
146140

147-
def validate_config(self, build_dir):
141+
def clean(self) -> bool:
142+
try:
143+
self._ops.make_mrproper()
144+
except ConfigError as e:
145+
logging.error(e)
146+
return False
147+
return True
148+
149+
def validate_config(self, build_dir) -> bool:
148150
kconfig_path = get_kconfig_path(build_dir)
149151
validated_kconfig = kunit_config.Kconfig()
150152
validated_kconfig.read_from_file(kconfig_path)
@@ -158,7 +160,7 @@ def validate_config(self, build_dir):
158160
return False
159161
return True
160162

161-
def build_config(self, build_dir, make_options):
163+
def build_config(self, build_dir, make_options) -> bool:
162164
kconfig_path = get_kconfig_path(build_dir)
163165
if build_dir and not os.path.exists(build_dir):
164166
os.mkdir(build_dir)
@@ -170,7 +172,7 @@ def build_config(self, build_dir, make_options):
170172
return False
171173
return self.validate_config(build_dir)
172174

173-
def build_reconfig(self, build_dir, make_options):
175+
def build_reconfig(self, build_dir, make_options) -> bool:
174176
"""Creates a new .config if it is not a subset of the .kunitconfig."""
175177
kconfig_path = get_kconfig_path(build_dir)
176178
if os.path.exists(kconfig_path):
@@ -186,7 +188,7 @@ def build_reconfig(self, build_dir, make_options):
186188
print('Generating .config ...')
187189
return self.build_config(build_dir, make_options)
188190

189-
def build_um_kernel(self, alltests, jobs, build_dir, make_options):
191+
def build_um_kernel(self, alltests, jobs, build_dir, make_options) -> bool:
190192
try:
191193
if alltests:
192194
self._ops.make_allyesconfig(build_dir, make_options)
@@ -197,7 +199,7 @@ def build_um_kernel(self, alltests, jobs, build_dir, make_options):
197199
return False
198200
return self.validate_config(build_dir)
199201

200-
def run_kernel(self, args=[], build_dir='', timeout=None):
202+
def run_kernel(self, args=[], build_dir='', timeout=None) -> Iterator[str]:
201203
args.extend(['mem=1G', 'console=tty'])
202204
self._ops.linux_bin(args, timeout, build_dir)
203205
outfile = get_outfile_path(build_dir)
@@ -206,6 +208,6 @@ def run_kernel(self, args=[], build_dir='', timeout=None):
206208
for line in file:
207209
yield line
208210

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

0 commit comments

Comments
 (0)