diff options
| author | Ruben Herold <ruben@puettmann.net> | 2026-08-09 23:06:47 +0200 |
|---|---|---|
| committer | Ruben Herold <ruben@puettmann.net> | 2026-08-09 23:06:47 +0200 |
| commit | b069ded58275ccb19d01b7dfa9944f27f0b4bb4d (patch) | |
| tree | 0e5e208b1a0c8046b9929976d10d4139f1c5b3ec /src | |
| parent | f6a1ff94f02282bd018b9b49ae1a9812619ec7ba (diff) | |
| download | vyos-1x-b069ded58275ccb19d01b7dfa9944f27f0b4bb4d.tar.gz vyos-1x-b069ded58275ccb19d01b7dfa9944f27f0b4bb4d.zip | |
utils: T9185: fix ask_yes_no() busy-looping forever on non-tty stdin
commit-confirm (and ~40 other call sites) hang and burn a full CPU core
indefinitely when invoked with stdin that isn't a TTY, e.g. a
non-interactive vbash session (vbash -c "... commit-confirm 10; exit").
input() raises EOFError immediately and repeatedly in that case, and the
except handler in ask_yes_no() looped straight back to input() with no
backoff or exit condition.
Check stdin.isatty() up front and return the default immediately when
it's not a TTY, matching what a user pressing Enter (accepting the
default) would already do interactively.
Diffstat (limited to 'src')
| -rw-r--r-- | src/tests/test_utils_io.py | 46 |
1 files changed, 46 insertions, 0 deletions
diff --git a/src/tests/test_utils_io.py b/src/tests/test_utils_io.py new file mode 100644 index 000000000..7adb0dc61 --- /dev/null +++ b/src/tests/test_utils_io.py @@ -0,0 +1,46 @@ +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest +from unittest.mock import patch + +from vyos.utils.io import ask_yes_no + + +class TestVyOSUtilsIO(unittest.TestCase): + def test_ask_yes_no_non_interactive_returns_default_without_reading_stdin(self): + # T9185: on a non-TTY stdin (e.g. a non-interactive vbash session), + # input() raises EOFError immediately and forever; ask_yes_no() must + # return the default straight away instead of ever looping on input(). + with patch('sys.stdin') as mock_stdin: + mock_stdin.isatty.return_value = False + with patch('builtins.input') as mock_input: + self.assertTrue(ask_yes_no('Proceed?', default=True)) + self.assertFalse(ask_yes_no('Proceed?', default=False)) + mock_input.assert_not_called() + + def test_ask_yes_no_interactive_reads_input(self): + with patch('sys.stdin') as mock_stdin: + mock_stdin.isatty.return_value = True + with patch('builtins.input', return_value='y'): + self.assertTrue(ask_yes_no('Proceed?', default=False)) + with patch('builtins.input', return_value='n'): + self.assertFalse(ask_yes_no('Proceed?', default=True)) + with patch('builtins.input', return_value=''): + self.assertTrue(ask_yes_no('Proceed?', default=True)) + self.assertFalse(ask_yes_no('Proceed?', default=False)) + + +if __name__ == '__main__': + unittest.main() |
