Skip to content

Commit 723c825

Browse files
rmr167shuahkh
authored andcommitted
kunit: tool: Add command line interface to filter and report attributes
Add ability to kunit.py to filter attributes and report a list of tests including attributes without running tests. Add flag "--filter" to input filters on test attributes. Tests will be filtered out if they do not match all inputted filters. Example: --filter speed=slow (This filter would run only the tests that are marked as slow) Filters have operations: <, >, <=, >=, !=, and =. But note that the characters < and > are often interpreted by the shell, so they may need to be quoted or escaped. Example: --filter "speed>slow" or --filter speed\>slow (This filter would run only the tests that have the speed faster than slow. Additionally, multiple filters can be used. Example: --filter "speed=slow, module!=example" (This filter would run only the tests that have the speed slow and are not in the "example" module) Note if the user wants to skip filtered tests instead of not running/showing them use the "--filter_action=skip" flag instead. Expose the output of kunit.action=list option with flag "--list_tests" to output a list of tests. Additionally, add flag "--list_tests_attr" to output a list of tests and their attributes. These flags are useful to see tests and test attributes without needing to run tests. Example of the output of "--list_tests_attr": example example.test_1 example.test_2 # example.test_2.speed: slow This output includes a suite, example, with two test cases, test_1 and test_2. And in this instance test_2 has been marked as slow. Reviewed-by: David Gow <[email protected]> Signed-off-by: Rae Moar <[email protected]> Signed-off-by: Shuah Khan <[email protected]>
1 parent 529534e commit 723c825

File tree

4 files changed

+99
-29
lines changed

4 files changed

+99
-29
lines changed

tools/testing/kunit/kunit.py

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@ class KunitExecRequest(KunitParseRequest):
5555
build_dir: str
5656
timeout: int
5757
filter_glob: str
58+
filter: str
59+
filter_action: Optional[str]
5860
kernel_args: Optional[List[str]]
5961
run_isolated: Optional[str]
62+
list_tests: bool
63+
list_tests_attr: bool
6064

6165
@dataclass
6266
class KunitRequest(KunitExecRequest, KunitBuildRequest):
@@ -102,19 +106,41 @@ def config_and_build_tests(linux: kunit_kernel.LinuxSourceTree,
102106

103107
def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]:
104108
args = ['kunit.action=list']
109+
110+
if request.kernel_args:
111+
args.extend(request.kernel_args)
112+
113+
output = linux.run_kernel(args=args,
114+
timeout=request.timeout,
115+
filter_glob=request.filter_glob,
116+
filter=request.filter,
117+
filter_action=request.filter_action,
118+
build_dir=request.build_dir)
119+
lines = kunit_parser.extract_tap_lines(output)
120+
# Hack! Drop the dummy TAP version header that the executor prints out.
121+
lines.pop()
122+
123+
# Filter out any extraneous non-test output that might have gotten mixed in.
124+
return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
125+
126+
def _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]:
127+
args = ['kunit.action=list_attr']
128+
105129
if request.kernel_args:
106130
args.extend(request.kernel_args)
107131

108132
output = linux.run_kernel(args=args,
109133
timeout=request.timeout,
110134
filter_glob=request.filter_glob,
135+
filter=request.filter,
136+
filter_action=request.filter_action,
111137
build_dir=request.build_dir)
112138
lines = kunit_parser.extract_tap_lines(output)
113139
# Hack! Drop the dummy TAP version header that the executor prints out.
114140
lines.pop()
115141

116142
# Filter out any extraneous non-test output that might have gotten mixed in.
117-
return [l for l in lines if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
143+
return lines
118144

119145
def _suites_from_test_list(tests: List[str]) -> List[str]:
120146
"""Extracts all the suites from an ordered list of tests."""
@@ -128,10 +154,18 @@ def _suites_from_test_list(tests: List[str]) -> List[str]:
128154
suites.append(suite)
129155
return suites
130156

131-
132-
133157
def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult:
134158
filter_globs = [request.filter_glob]
159+
if request.list_tests:
160+
output = _list_tests(linux, request)
161+
for line in output:
162+
print(line.rstrip())
163+
return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
164+
if request.list_tests_attr:
165+
attr_output = _list_tests_attr(linux, request)
166+
for line in attr_output:
167+
print(line.rstrip())
168+
return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
135169
if request.run_isolated:
136170
tests = _list_tests(linux, request)
137171
if request.run_isolated == 'test':
@@ -155,6 +189,8 @@ def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -
155189
args=request.kernel_args,
156190
timeout=request.timeout,
157191
filter_glob=filter_glob,
192+
filter=request.filter,
193+
filter_action=request.filter_action,
158194
build_dir=request.build_dir)
159195

160196
_, test_result = parse_tests(request, metadata, run_result)
@@ -341,6 +377,16 @@ def add_exec_opts(parser: argparse.ArgumentParser) -> None:
341377
nargs='?',
342378
default='',
343379
metavar='filter_glob')
380+
parser.add_argument('--filter',
381+
help='Filter KUnit tests with attributes, '
382+
'e.g. module=example or speed>slow',
383+
type=str,
384+
default='')
385+
parser.add_argument('--filter_action',
386+
help='If set to skip, filtered tests will be skipped, '
387+
'e.g. --filter_action=skip. Otherwise they will not run.',
388+
type=str,
389+
choices=['skip'])
344390
parser.add_argument('--kernel_args',
345391
help='Kernel command-line parameters. Maybe be repeated',
346392
action='append', metavar='')
@@ -350,6 +396,12 @@ def add_exec_opts(parser: argparse.ArgumentParser) -> None:
350396
'what ran before it.',
351397
type=str,
352398
choices=['suite', 'test'])
399+
parser.add_argument('--list_tests', help='If set, list all tests that will be '
400+
'run.',
401+
action='store_true')
402+
parser.add_argument('--list_tests_attr', help='If set, list all tests and test '
403+
'attributes.',
404+
action='store_true')
353405

354406
def add_parse_opts(parser: argparse.ArgumentParser) -> None:
355407
parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. '
@@ -398,8 +450,12 @@ def run_handler(cli_args: argparse.Namespace) -> None:
398450
json=cli_args.json,
399451
timeout=cli_args.timeout,
400452
filter_glob=cli_args.filter_glob,
453+
filter=cli_args.filter,
454+
filter_action=cli_args.filter_action,
401455
kernel_args=cli_args.kernel_args,
402-
run_isolated=cli_args.run_isolated)
456+
run_isolated=cli_args.run_isolated,
457+
list_tests=cli_args.list_tests,
458+
list_tests_attr=cli_args.list_tests_attr)
403459
result = run_tests(linux, request)
404460
if result.status != KunitStatus.SUCCESS:
405461
sys.exit(1)
@@ -441,8 +497,12 @@ def exec_handler(cli_args: argparse.Namespace) -> None:
441497
json=cli_args.json,
442498
timeout=cli_args.timeout,
443499
filter_glob=cli_args.filter_glob,
500+
filter=cli_args.filter,
501+
filter_action=cli_args.filter_action,
444502
kernel_args=cli_args.kernel_args,
445-
run_isolated=cli_args.run_isolated)
503+
run_isolated=cli_args.run_isolated,
504+
list_tests=cli_args.list_tests,
505+
list_tests_attr=cli_args.list_tests_attr)
446506
result = exec_tests(linux, exec_request)
447507
stdout.print_with_timestamp((
448508
'Elapsed time: %.3fs\n') % (result.elapsed_time))

tools/testing/kunit/kunit_kernel.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,15 @@ def build_kernel(self, jobs: int, build_dir: str, make_options: Optional[List[st
330330
return False
331331
return self.validate_config(build_dir)
332332

333-
def run_kernel(self, args: Optional[List[str]]=None, build_dir: str='', filter_glob: str='', timeout: Optional[int]=None) -> Iterator[str]:
333+
def run_kernel(self, args: Optional[List[str]]=None, build_dir: str='', filter_glob: str='', filter: str='', filter_action: Optional[str]=None, timeout: Optional[int]=None) -> Iterator[str]:
334334
if not args:
335335
args = []
336336
if filter_glob:
337-
args.append('kunit.filter_glob='+filter_glob)
337+
args.append('kunit.filter_glob=' + filter_glob)
338+
if filter:
339+
args.append('kunit.filter="' + filter + '"')
340+
if filter_action:
341+
args.append('kunit.filter_action=' + filter_action)
338342
args.append('kunit.enable=1')
339343

340344
process = self._ops.start(args, build_dir)

tools/testing/kunit/kunit_parser.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def line_number(self) -> int:
212212
TAP_START = re.compile(r'\s*TAP version ([0-9]+)$')
213213
KTAP_END = re.compile(r'\s*(List of all partitions:|'
214214
'Kernel panic - not syncing: VFS:|reboot: System halted)')
215+
EXECUTOR_ERROR = re.compile(r'\s*kunit executor: (.*)$')
215216

216217
def extract_tap_lines(kernel_output: Iterable[str]) -> LineStream:
217218
"""Extracts KTAP lines from the kernel output."""
@@ -242,6 +243,8 @@ def isolate_ktap_output(kernel_output: Iterable[str]) \
242243
# remove the prefix, if any.
243244
line = line[prefix_len:]
244245
yield line_num, line
246+
elif EXECUTOR_ERROR.search(line):
247+
yield line_num, line
245248
return LineStream(lines=isolate_ktap_output(kernel_output))
246249

247250
KTAP_VERSIONS = [1]
@@ -447,7 +450,7 @@ def parse_diagnostic(lines: LineStream) -> List[str]:
447450
Log of diagnostic lines
448451
"""
449452
log = [] # type: List[str]
450-
non_diagnostic_lines = [TEST_RESULT, TEST_HEADER, KTAP_START]
453+
non_diagnostic_lines = [TEST_RESULT, TEST_HEADER, KTAP_START, TAP_START]
451454
while lines and not any(re.match(lines.peek())
452455
for re in non_diagnostic_lines):
453456
log.append(lines.pop())
@@ -713,6 +716,11 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest:
713716
"""
714717
test = Test()
715718
test.log.extend(log)
719+
720+
# Parse any errors prior to parsing tests
721+
err_log = parse_diagnostic(lines)
722+
test.log.extend(err_log)
723+
716724
if not is_subtest:
717725
# If parsing the main/top-level test, parse KTAP version line and
718726
# test plan
@@ -774,6 +782,7 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest:
774782
# Don't override a bad status if this test had one reported.
775783
# Assumption: no subtests means CRASHED is from Test.__init__()
776784
if test.status in (TestStatus.TEST_CRASHED, TestStatus.SUCCESS):
785+
print_log(test.log)
777786
test.status = TestStatus.NO_TESTS
778787
test.add_error('0 tests run!')
779788

tools/testing/kunit/kunit_tool_test.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -597,15 +597,15 @@ def test_exec_passes_args_pass(self):
597597
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 0)
598598
self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
599599
self.linux_source_mock.run_kernel.assert_called_once_with(
600-
args=None, build_dir='.kunit', filter_glob='', timeout=300)
600+
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
601601
self.print_mock.assert_any_call(StrContains('Testing complete.'))
602602

603603
def test_run_passes_args_pass(self):
604604
kunit.main(['run'])
605605
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
606606
self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
607607
self.linux_source_mock.run_kernel.assert_called_once_with(
608-
args=None, build_dir='.kunit', filter_glob='', timeout=300)
608+
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
609609
self.print_mock.assert_any_call(StrContains('Testing complete.'))
610610

611611
def test_exec_passes_args_fail(self):
@@ -629,7 +629,7 @@ def test_exec_no_tests(self):
629629
kunit.main(['run'])
630630
self.assertEqual(e.exception.code, 1)
631631
self.linux_source_mock.run_kernel.assert_called_once_with(
632-
args=None, build_dir='.kunit', filter_glob='', timeout=300)
632+
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
633633
self.print_mock.assert_any_call(StrContains(' 0 tests run!'))
634634

635635
def test_exec_raw_output(self):
@@ -670,29 +670,29 @@ def test_run_raw_output_does_not_take_positional_args(self):
670670
self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
671671
kunit.main(['run', '--raw_output', 'filter_glob'])
672672
self.linux_source_mock.run_kernel.assert_called_once_with(
673-
args=None, build_dir='.kunit', filter_glob='filter_glob', timeout=300)
673+
args=None, build_dir='.kunit', filter_glob='filter_glob', filter='', filter_action=None, timeout=300)
674674

675675
def test_exec_timeout(self):
676676
timeout = 3453
677677
kunit.main(['exec', '--timeout', str(timeout)])
678678
self.linux_source_mock.run_kernel.assert_called_once_with(
679-
args=None, build_dir='.kunit', filter_glob='', timeout=timeout)
679+
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=timeout)
680680
self.print_mock.assert_any_call(StrContains('Testing complete.'))
681681

682682
def test_run_timeout(self):
683683
timeout = 3453
684684
kunit.main(['run', '--timeout', str(timeout)])
685685
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
686686
self.linux_source_mock.run_kernel.assert_called_once_with(
687-
args=None, build_dir='.kunit', filter_glob='', timeout=timeout)
687+
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=timeout)
688688
self.print_mock.assert_any_call(StrContains('Testing complete.'))
689689

690690
def test_run_builddir(self):
691691
build_dir = '.kunit'
692692
kunit.main(['run', '--build_dir=.kunit'])
693693
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
694694
self.linux_source_mock.run_kernel.assert_called_once_with(
695-
args=None, build_dir=build_dir, filter_glob='', timeout=300)
695+
args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
696696
self.print_mock.assert_any_call(StrContains('Testing complete.'))
697697

698698
def test_config_builddir(self):
@@ -710,7 +710,7 @@ def test_exec_builddir(self):
710710
build_dir = '.kunit'
711711
kunit.main(['exec', '--build_dir', build_dir])
712712
self.linux_source_mock.run_kernel.assert_called_once_with(
713-
args=None, build_dir=build_dir, filter_glob='', timeout=300)
713+
args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
714714
self.print_mock.assert_any_call(StrContains('Testing complete.'))
715715

716716
def test_run_kunitconfig(self):
@@ -786,21 +786,19 @@ def test_run_kernel_args(self):
786786
kunit.main(['run', '--kernel_args=a=1', '--kernel_args=b=2'])
787787
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
788788
self.linux_source_mock.run_kernel.assert_called_once_with(
789-
args=['a=1','b=2'], build_dir='.kunit', filter_glob='', timeout=300)
789+
args=['a=1','b=2'], build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
790790
self.print_mock.assert_any_call(StrContains('Testing complete.'))
791791

792792
def test_list_tests(self):
793793
want = ['suite.test1', 'suite.test2', 'suite2.test1']
794794
self.linux_source_mock.run_kernel.return_value = ['TAP version 14', 'init: random output'] + want
795795

796796
got = kunit._list_tests(self.linux_source_mock,
797-
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', None, 'suite'))
798-
797+
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False))
799798
self.assertEqual(got, want)
800799
# Should respect the user's filter glob when listing tests.
801800
self.linux_source_mock.run_kernel.assert_called_once_with(
802-
args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', timeout=300)
803-
801+
args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', filter='', filter_action=None, timeout=300)
804802

805803
@mock.patch.object(kunit, '_list_tests')
806804
def test_run_isolated_by_suite(self, mock_tests):
@@ -809,10 +807,10 @@ def test_run_isolated_by_suite(self, mock_tests):
809807

810808
# Should respect the user's filter glob when listing tests.
811809
mock_tests.assert_called_once_with(mock.ANY,
812-
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*.test*', None, 'suite'))
810+
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False))
813811
self.linux_source_mock.run_kernel.assert_has_calls([
814-
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', timeout=300),
815-
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', timeout=300),
812+
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', filter='', filter_action=None, timeout=300),
813+
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', filter='', filter_action=None, timeout=300),
816814
])
817815

818816
@mock.patch.object(kunit, '_list_tests')
@@ -822,13 +820,12 @@ def test_run_isolated_by_test(self, mock_tests):
822820

823821
# Should respect the user's filter glob when listing tests.
824822
mock_tests.assert_called_once_with(mock.ANY,
825-
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', None, 'test'))
823+
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', '', None, None, 'test', False, False))
826824
self.linux_source_mock.run_kernel.assert_has_calls([
827-
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', timeout=300),
828-
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', timeout=300),
829-
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', timeout=300),
825+
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', filter='', filter_action=None, timeout=300),
826+
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', filter='', filter_action=None, timeout=300),
827+
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', filter='', filter_action=None, timeout=300),
830828
])
831829

832-
833830
if __name__ == '__main__':
834831
unittest.main()

0 commit comments

Comments
 (0)