Skip to content

Commit 45dcbb6

Browse files
bjh83shuahkh
authored andcommitted
kunit: test: add test plan to KUnit TAP format
TAP 14 allows an optional test plan to be emitted before the start of the start of testing[1]; this is valuable because it makes it possible for a test harness to detect whether the number of tests run matches the number of tests expected to be run, ensuring that no tests silently failed. Link[1]: https://github.com/isaacs/testanything.github.io/blob/tap14/tap-version-14-specification.md#the-plan Signed-off-by: Brendan Higgins <[email protected]> Reviewed-by: Stephen Boyd <[email protected]> Signed-off-by: Shuah Khan <[email protected]>
1 parent 8c0d884 commit 45dcbb6

File tree

6 files changed

+82
-25
lines changed

6 files changed

+82
-25
lines changed

lib/kunit/executor.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,27 @@ extern struct kunit_suite * const * const __kunit_suites_end[];
1111

1212
#if IS_BUILTIN(CONFIG_KUNIT)
1313

14+
static void kunit_print_tap_header(void)
15+
{
16+
struct kunit_suite * const * const *suites, * const *subsuite;
17+
int num_of_suites = 0;
18+
19+
for (suites = __kunit_suites_start;
20+
suites < __kunit_suites_end;
21+
suites++)
22+
for (subsuite = *suites; *subsuite != NULL; subsuite++)
23+
num_of_suites++;
24+
25+
pr_info("TAP version 14\n");
26+
pr_info("1..%d\n", num_of_suites);
27+
}
28+
1429
int kunit_run_all_tests(void)
1530
{
1631
struct kunit_suite * const * const *suites;
1732

33+
kunit_print_tap_header();
34+
1835
for (suites = __kunit_suites_start;
1936
suites < __kunit_suites_end;
2037
suites++)

lib/kunit/test.c

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,6 @@ static void kunit_set_failure(struct kunit *test)
2020
WRITE_ONCE(test->success, false);
2121
}
2222

23-
static void kunit_print_tap_version(void)
24-
{
25-
static bool kunit_has_printed_tap_version;
26-
27-
if (!kunit_has_printed_tap_version) {
28-
pr_info("TAP version 14\n");
29-
kunit_has_printed_tap_version = true;
30-
}
31-
}
32-
3323
/*
3424
* Append formatted message to log, size of which is limited to
3525
* KUNIT_LOG_SIZE bytes (including null terminating byte).
@@ -69,7 +59,6 @@ EXPORT_SYMBOL_GPL(kunit_suite_num_test_cases);
6959

7060
static void kunit_print_subtest_start(struct kunit_suite *suite)
7161
{
72-
kunit_print_tap_version();
7362
kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "# Subtest: %s",
7463
suite->name);
7564
kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "1..%zd",

tools/testing/kunit/kunit_parser.py

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ class TestStatus(Enum):
4545
FAILURE = auto()
4646
TEST_CRASHED = auto()
4747
NO_TESTS = auto()
48+
FAILURE_TO_PARSE_TESTS = auto()
4849

4950
kunit_start_re = re.compile(r'TAP version [0-9]+$')
5051
kunit_end_re = re.compile('(List of all partitions:|'
51-
'Kernel panic - not syncing: VFS:|reboot: System halted)')
52+
'Kernel panic - not syncing: VFS:)')
5253

5354
def isolate_kunit_output(kernel_output):
5455
started = False
@@ -109,7 +110,7 @@ def save_non_diagnositic(lines: List[str], test_case: TestCase) -> None:
109110

110111
OK_NOT_OK_SUBTEST = re.compile(r'^[\s]+(ok|not ok) [0-9]+ - (.*)$')
111112

112-
OK_NOT_OK_MODULE = re.compile(r'^(ok|not ok) [0-9]+ - (.*)$')
113+
OK_NOT_OK_MODULE = re.compile(r'^(ok|not ok) ([0-9]+) - (.*)$')
113114

114115
def parse_ok_not_ok_test_case(lines: List[str], test_case: TestCase) -> bool:
115116
save_non_diagnositic(lines, test_case)
@@ -197,7 +198,9 @@ def max_status(left: TestStatus, right: TestStatus) -> TestStatus:
197198
else:
198199
return TestStatus.SUCCESS
199200

200-
def parse_ok_not_ok_test_suite(lines: List[str], test_suite: TestSuite) -> bool:
201+
def parse_ok_not_ok_test_suite(lines: List[str],
202+
test_suite: TestSuite,
203+
expected_suite_index: int) -> bool:
201204
consume_non_diagnositic(lines)
202205
if not lines:
203206
test_suite.status = TestStatus.TEST_CRASHED
@@ -210,6 +213,12 @@ def parse_ok_not_ok_test_suite(lines: List[str], test_suite: TestSuite) -> bool:
210213
test_suite.status = TestStatus.SUCCESS
211214
else:
212215
test_suite.status = TestStatus.FAILURE
216+
suite_index = int(match.group(2))
217+
if suite_index != expected_suite_index:
218+
print_with_timestamp(
219+
red('[ERROR] ') + 'expected_suite_index ' +
220+
str(expected_suite_index) + ', but got ' +
221+
str(suite_index))
213222
return True
214223
else:
215224
return False
@@ -222,7 +231,7 @@ def bubble_up_test_case_errors(test_suite: TestSuite) -> TestStatus:
222231
max_test_case_status = bubble_up_errors(lambda x: x.status, test_suite.cases)
223232
return max_status(max_test_case_status, test_suite.status)
224233

225-
def parse_test_suite(lines: List[str]) -> TestSuite:
234+
def parse_test_suite(lines: List[str], expected_suite_index: int) -> TestSuite:
226235
if not lines:
227236
return None
228237
consume_non_diagnositic(lines)
@@ -241,7 +250,7 @@ def parse_test_suite(lines: List[str]) -> TestSuite:
241250
break
242251
test_suite.cases.append(test_case)
243252
expected_test_case_num -= 1
244-
if parse_ok_not_ok_test_suite(lines, test_suite):
253+
if parse_ok_not_ok_test_suite(lines, test_suite, expected_suite_index):
245254
test_suite.status = bubble_up_test_case_errors(test_suite)
246255
return test_suite
247256
elif not lines:
@@ -261,27 +270,51 @@ def parse_tap_header(lines: List[str]) -> bool:
261270
else:
262271
return False
263272

273+
TEST_PLAN = re.compile(r'[0-9]+\.\.([0-9]+)')
274+
275+
def parse_test_plan(lines: List[str]) -> int:
276+
consume_non_diagnositic(lines)
277+
match = TEST_PLAN.match(lines[0])
278+
if match:
279+
lines.pop(0)
280+
return int(match.group(1))
281+
else:
282+
return None
283+
264284
def bubble_up_suite_errors(test_suite_list: List[TestSuite]) -> TestStatus:
265285
return bubble_up_errors(lambda x: x.status, test_suite_list)
266286

267287
def parse_test_result(lines: List[str]) -> TestResult:
268288
consume_non_diagnositic(lines)
269289
if not lines or not parse_tap_header(lines):
270290
return TestResult(TestStatus.NO_TESTS, [], lines)
291+
expected_test_suite_num = parse_test_plan(lines)
292+
if not expected_test_suite_num:
293+
return TestResult(TestStatus.FAILURE_TO_PARSE_TESTS, [], lines)
271294
test_suites = []
272-
test_suite = parse_test_suite(lines)
273-
while test_suite:
274-
test_suites.append(test_suite)
275-
test_suite = parse_test_suite(lines)
276-
return TestResult(bubble_up_suite_errors(test_suites), test_suites, lines)
295+
for i in range(1, expected_test_suite_num + 1):
296+
test_suite = parse_test_suite(lines, i)
297+
if test_suite:
298+
test_suites.append(test_suite)
299+
else:
300+
print_with_timestamp(
301+
red('[ERROR] ') + ' expected ' +
302+
str(expected_test_suite_num) +
303+
' test suites, but got ' + str(i - 2))
304+
break
305+
test_suite = parse_test_suite(lines, -1)
306+
if test_suite:
307+
print_with_timestamp(red('[ERROR] ') +
308+
'got unexpected test suite: ' + test_suite.name)
309+
if test_suites:
310+
return TestResult(bubble_up_suite_errors(test_suites), test_suites, lines)
311+
else:
312+
return TestResult(TestStatus.NO_TESTS, [], lines)
277313

278-
def parse_run_tests(kernel_output) -> TestResult:
314+
def print_and_count_results(test_result: TestResult) -> None:
279315
total_tests = 0
280316
failed_tests = 0
281317
crashed_tests = 0
282-
test_result = parse_test_result(list(isolate_kunit_output(kernel_output)))
283-
if test_result.status == TestStatus.NO_TESTS:
284-
print_with_timestamp(red('[ERROR] ') + 'no kunit output detected')
285318
for test_suite in test_result.suites:
286319
if test_suite.status == TestStatus.SUCCESS:
287320
print_suite_divider(green('[PASSED] ') + test_suite.name)
@@ -303,6 +336,21 @@ def parse_run_tests(kernel_output) -> TestResult:
303336
print_with_timestamp(red('[FAILED] ') + test_case.name)
304337
print_log(map(yellow, test_case.log))
305338
print_with_timestamp('')
339+
return total_tests, failed_tests, crashed_tests
340+
341+
def parse_run_tests(kernel_output) -> TestResult:
342+
total_tests = 0
343+
failed_tests = 0
344+
crashed_tests = 0
345+
test_result = parse_test_result(list(isolate_kunit_output(kernel_output)))
346+
if test_result.status == TestStatus.NO_TESTS:
347+
print(red('[ERROR] ') + yellow('no tests run!'))
348+
elif test_result.status == TestStatus.FAILURE_TO_PARSE_TESTS:
349+
print(red('[ERROR] ') + yellow('could not parse test results!'))
350+
else:
351+
(total_tests,
352+
failed_tests,
353+
crashed_tests) = print_and_count_results(test_result)
306354
print_with_timestamp(DIVIDER)
307355
fmt = green if test_result.status == TestStatus.SUCCESS else red
308356
print_with_timestamp(

tools/testing/kunit/test_data/test_is_test_passed-all_passed.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
TAP version 14
2+
1..2
23
# Subtest: sysctl_test
34
1..8
45
# sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed

tools/testing/kunit/test_data/test_is_test_passed-crash.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
printk: console [tty0] enabled
22
printk: console [mc-1] enabled
33
TAP version 14
4+
1..2
45
# Subtest: sysctl_test
56
1..8
67
# sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed

tools/testing/kunit/test_data/test_is_test_passed-failure.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
TAP version 14
2+
1..2
23
# Subtest: sysctl_test
34
1..8
45
# sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed

0 commit comments

Comments
 (0)