Commit e42c349f authored by Shuvam Pandey's avatar Shuvam Pandey Committed by Shuah Khan
Browse files

kunit: tool: skip stty when stdin is not a tty



run_kernel() cleanup and signal_handler() invoke stty unconditionally.
When stdin is not a tty (for example in CI or unit tests), this writes
noise to stderr.

Call stty only when stdin is a tty.

Add regression tests for these paths:
- run_kernel() with non-tty stdin
- signal_handler() with non-tty stdin
- signal_handler() with tty stdin

Signed-off-by: default avatarShuvam Pandey <shuvampandey1@gmail.com>
Reviewed-by: default avatarDavid Gow <david@davidgow.net>
Signed-off-by: default avatarShuah Khan <skhan@linuxfoundation.org>
parent b73f50ff
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -345,6 +345,12 @@ class LinuxSourceTree:
			return False
		return self.validate_config(build_dir)

	def _restore_terminal_if_tty(self) -> None:
		# stty requires a controlling terminal; skip headless runs.
		if sys.stdin is None or not sys.stdin.isatty():
			return
		subprocess.call(['stty', 'sane'])

	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]:
		# Copy to avoid mutating the caller-supplied list. exec_tests() reuses
		# the same args across repeated run_kernel() calls (e.g. --run_isolated),
@@ -386,8 +392,8 @@ class LinuxSourceTree:
			process.stdout.close()

			waiter.join()
			subprocess.call(['stty', 'sane'])
			self._restore_terminal_if_tty()

	def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None:
		logging.error('Build interruption occurred. Cleaning console.')
		subprocess.call(['stty', 'sane'])
		self._restore_terminal_if_tty()
+42 −0
Original line number Diff line number Diff line
@@ -529,6 +529,48 @@ class LinuxSourceTreeTest(unittest.TestCase):
				self.assertIn('kunit.filter_glob=suite.test1', start_calls[0])
				self.assertIn('kunit.filter_glob=suite.test2', start_calls[1])

	def test_run_kernel_skips_terminal_reset_without_tty(self):
		def fake_start(unused_args, unused_build_dir):
			return subprocess.Popen(['printf', 'KTAP version 1\n'],
						text=True, stdout=subprocess.PIPE)

		non_tty_stdin = mock.Mock()
		non_tty_stdin.isatty.return_value = False

		with tempfile.TemporaryDirectory('') as build_dir:
			tree = kunit_kernel.LinuxSourceTree(build_dir, kunitconfig_paths=[os.devnull])
			with mock.patch.object(tree._ops, 'start', side_effect=fake_start), \
			     mock.patch.object(kunit_kernel.sys, 'stdin', non_tty_stdin), \
			     mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call:
				for _ in tree.run_kernel(build_dir=build_dir):
					pass

				mock_call.assert_not_called()

	def test_signal_handler_skips_terminal_reset_without_tty(self):
		non_tty_stdin = mock.Mock()
		non_tty_stdin.isatty.return_value = False
		tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull])

		with mock.patch.object(kunit_kernel.sys, 'stdin', non_tty_stdin), \
		     mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call, \
		     mock.patch.object(kunit_kernel.logging, 'error') as mock_error:
			tree.signal_handler(signal.SIGINT, None)
			mock_error.assert_called_once()
			mock_call.assert_not_called()

	def test_signal_handler_resets_terminal_with_tty(self):
		tty_stdin = mock.Mock()
		tty_stdin.isatty.return_value = True
		tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull])

		with mock.patch.object(kunit_kernel.sys, 'stdin', tty_stdin), \
		     mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call, \
		     mock.patch.object(kunit_kernel.logging, 'error') as mock_error:
			tree.signal_handler(signal.SIGINT, None)
			mock_error.assert_called_once()
			mock_call.assert_called_once_with(['stty', 'sane'])

	def test_build_reconfig_no_config(self):
		with tempfile.TemporaryDirectory('') as build_dir:
			with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f: