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: Shuvam Pandey <shuvampandey1@gmail.com>
Reviewed-by: David Gow <david@davidgow.net>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
master
Shuvam Pandey 2026-02-27 18:16:36 +05:45 committed by Shuah Khan
parent b73f50ffd4
commit e42c349f4c
2 changed files with 50 additions and 2 deletions

View File

@ -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()

View File

@ -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: