Skip to content

Commit 142189f

Browse files
dlatypovshuahkh
authored andcommitted
kunit: tool: print parsed test results fully incrementally
With the parser rework [1] and run_kernel() rework [2], this allows the parser to print out test results incrementally. Currently, that's held up by the fact that the LineStream eagerly pre-fetches the next line when you call pop(). This blocks parse_test_result() from returning until the line *after* the "ok 1 - test name" line is also printed. One can see this with the following example: $ (echo -e 'TAP version 14\n1..3\nok 1 - fake test'; sleep 2; echo -e 'ok 2 - fake test 2'; sleep 3; echo -e 'ok 3 - fake test 3') | ./tools/testing/kunit/kunit.py parse Before this patch [1]: there's a pause before 'fake test' is printed. After this patch: 'fake test' is printed out immediately. This patch also adds * a unit test to verify LineStream's behavior directly * a test case to ensure that it's lazily calling the generator * an explicit exception for when users go beyond EOF [1] https://lore.kernel.org/linux-kselftest/[email protected]/ [2] https://lore.kernel.org/linux-kselftest/[email protected]/ Signed-off-by: Daniel Latypov <[email protected]> Reviewed-by: David Gow <[email protected]> Reviewed-by: Brendan Higgins <[email protected]> Signed-off-by: Shuah Khan <[email protected]>
1 parent 44b7da5 commit 142189f

File tree

2 files changed

+57
-7
lines changed

2 files changed

+57
-7
lines changed

tools/testing/kunit/kunit_parser.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,42 +168,51 @@ def add_status(self, status: TestStatus) -> None:
168168
class LineStream:
169169
"""
170170
A class to represent the lines of kernel output.
171-
Provides a peek()/pop() interface over an iterator of
171+
Provides a lazy peek()/pop() interface over an iterator of
172172
(line#, text).
173173
"""
174174
_lines: Iterator[Tuple[int, str]]
175175
_next: Tuple[int, str]
176+
_need_next: bool
176177
_done: bool
177178

178179
def __init__(self, lines: Iterator[Tuple[int, str]]):
179180
"""Creates a new LineStream that wraps the given iterator."""
180181
self._lines = lines
181182
self._done = False
183+
self._need_next = True
182184
self._next = (0, '')
183-
self._get_next()
184185

185186
def _get_next(self) -> None:
186-
"""Advances the LineSteam to the next line."""
187+
"""Advances the LineSteam to the next line, if necessary."""
188+
if not self._need_next:
189+
return
187190
try:
188191
self._next = next(self._lines)
189192
except StopIteration:
190193
self._done = True
194+
finally:
195+
self._need_next = False
191196

192197
def peek(self) -> str:
193198
"""Returns the current line, without advancing the LineStream.
194199
"""
200+
self._get_next()
195201
return self._next[1]
196202

197203
def pop(self) -> str:
198204
"""Returns the current line and advances the LineStream to
199205
the next line.
200206
"""
201-
n = self._next
202-
self._get_next()
203-
return n[1]
207+
s = self.peek()
208+
if self._done:
209+
raise ValueError(f'LineStream: going past EOF, last line was {s}')
210+
self._need_next = True
211+
return s
204212

205213
def __bool__(self) -> bool:
206214
"""Returns True if stream has more lines."""
215+
self._get_next()
207216
return not self._done
208217

209218
# Only used by kunit_tool_test.py.
@@ -216,6 +225,7 @@ def __iter__(self) -> Iterator[str]:
216225

217226
def line_number(self) -> int:
218227
"""Returns the line number of the current line."""
228+
self._get_next()
219229
return self._next[0]
220230

221231
# Parsing helper methods:

tools/testing/kunit/kunit_tool_test.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313

1414
import itertools
1515
import json
16+
import os
1617
import signal
1718
import subprocess
18-
import os
19+
from typing import Iterable
1920

2021
import kunit_config
2122
import kunit_parser
@@ -331,6 +332,45 @@ def test_pound_no_prefix(self):
331332
result.status)
332333
self.assertEqual('kunit-resource-test', result.test.subtests[0].name)
333334

335+
def line_stream_from_strs(strs: Iterable[str]) -> kunit_parser.LineStream:
336+
return kunit_parser.LineStream(enumerate(strs, start=1))
337+
338+
class LineStreamTest(unittest.TestCase):
339+
340+
def test_basic(self):
341+
stream = line_stream_from_strs(['hello', 'world'])
342+
343+
self.assertTrue(stream, msg='Should be more input')
344+
self.assertEqual(stream.line_number(), 1)
345+
self.assertEqual(stream.peek(), 'hello')
346+
self.assertEqual(stream.pop(), 'hello')
347+
348+
self.assertTrue(stream, msg='Should be more input')
349+
self.assertEqual(stream.line_number(), 2)
350+
self.assertEqual(stream.peek(), 'world')
351+
self.assertEqual(stream.pop(), 'world')
352+
353+
self.assertFalse(stream, msg='Should be no more input')
354+
with self.assertRaisesRegex(ValueError, 'LineStream: going past EOF'):
355+
stream.pop()
356+
357+
def test_is_lazy(self):
358+
called_times = 0
359+
def generator():
360+
nonlocal called_times
361+
for i in range(1,5):
362+
called_times += 1
363+
yield called_times, str(called_times)
364+
365+
stream = kunit_parser.LineStream(generator())
366+
self.assertEqual(called_times, 0)
367+
368+
self.assertEqual(stream.pop(), '1')
369+
self.assertEqual(called_times, 1)
370+
371+
self.assertEqual(stream.pop(), '2')
372+
self.assertEqual(called_times, 2)
373+
334374
class LinuxSourceTreeTest(unittest.TestCase):
335375

336376
def setUp(self):

0 commit comments

Comments
 (0)