Commit 142189f0 authored by Daniel Latypov's avatar Daniel Latypov Committed by Shuah Khan
Browse files

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/20211006170049.106852-1-dlatypov@google.com/
[2] https://lore.kernel.org/linux-kselftest/20211005011340.2826268-1-dlatypov@google.com/



Signed-off-by: default avatarDaniel Latypov <dlatypov@google.com>
Reviewed-by: default avatarDavid Gow <davidgow@google.com>
Reviewed-by: default avatarBrendan Higgins <brendanhiggins@google.com>
Signed-off-by: default avatarShuah Khan <skhan@linuxfoundation.org>
parent 44b7da5f
Loading
Loading
Loading
Loading
+16 −6
Original line number Diff line number Diff line
@@ -168,42 +168,51 @@ class TestCounts:
class LineStream:
	"""
	A class to represent the lines of kernel output.
	Provides a peek()/pop() interface over an iterator of
	Provides a lazy peek()/pop() interface over an iterator of
	(line#, text).
	"""
	_lines: Iterator[Tuple[int, str]]
	_next: Tuple[int, str]
	_need_next: bool
	_done: bool

	def __init__(self, lines: Iterator[Tuple[int, str]]):
		"""Creates a new LineStream that wraps the given iterator."""
		self._lines = lines
		self._done = False
		self._need_next = True
		self._next = (0, '')
		self._get_next()

	def _get_next(self) -> None:
		"""Advances the LineSteam to the next line."""
		"""Advances the LineSteam to the next line, if necessary."""
		if not self._need_next:
			return
		try:
			self._next = next(self._lines)
		except StopIteration:
			self._done = True
		finally:
			self._need_next = False

	def peek(self) -> str:
		"""Returns the current line, without advancing the LineStream.
		"""
		self._get_next()
		return self._next[1]

	def pop(self) -> str:
		"""Returns the current line and advances the LineStream to
		the next line.
		"""
		n = self._next
		self._get_next()
		return n[1]
		s = self.peek()
		if self._done:
			raise ValueError(f'LineStream: going past EOF, last line was {s}')
		self._need_next = True
		return s

	def __bool__(self) -> bool:
		"""Returns True if stream has more lines."""
		self._get_next()
		return not self._done

	# Only used by kunit_tool_test.py.
@@ -216,6 +225,7 @@ class LineStream:

	def line_number(self) -> int:
		"""Returns the line number of the current line."""
		self._get_next()
		return self._next[0]

# Parsing helper methods:
+41 −1
Original line number Diff line number Diff line
@@ -13,9 +13,10 @@ import tempfile, shutil # Handling test_tmpdir

import itertools
import json
import os
import signal
import subprocess
import os
from typing import Iterable

import kunit_config
import kunit_parser
@@ -331,6 +332,45 @@ class KUnitParserTest(unittest.TestCase):
				result.status)
			self.assertEqual('kunit-resource-test', result.test.subtests[0].name)

def line_stream_from_strs(strs: Iterable[str]) -> kunit_parser.LineStream:
	return kunit_parser.LineStream(enumerate(strs, start=1))

class LineStreamTest(unittest.TestCase):

	def test_basic(self):
		stream = line_stream_from_strs(['hello', 'world'])

		self.assertTrue(stream, msg='Should be more input')
		self.assertEqual(stream.line_number(), 1)
		self.assertEqual(stream.peek(), 'hello')
		self.assertEqual(stream.pop(), 'hello')

		self.assertTrue(stream, msg='Should be more input')
		self.assertEqual(stream.line_number(), 2)
		self.assertEqual(stream.peek(), 'world')
		self.assertEqual(stream.pop(), 'world')

		self.assertFalse(stream, msg='Should be no more input')
		with self.assertRaisesRegex(ValueError, 'LineStream: going past EOF'):
			stream.pop()

	def test_is_lazy(self):
		called_times = 0
		def generator():
			nonlocal called_times
			for i in range(1,5):
				called_times += 1
				yield called_times, str(called_times)

		stream = kunit_parser.LineStream(generator())
		self.assertEqual(called_times, 0)

		self.assertEqual(stream.pop(), '1')
		self.assertEqual(called_times, 1)

		self.assertEqual(stream.pop(), '2')
		self.assertEqual(called_times, 2)

class LinuxSourceTreeTest(unittest.TestCase):

	def setUp(self):