summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRuben Herold <ruben@puettmann.net>2026-08-09 23:06:47 +0200
committerRuben Herold <ruben@puettmann.net>2026-08-09 23:06:47 +0200
commitb069ded58275ccb19d01b7dfa9944f27f0b4bb4d (patch)
tree0e5e208b1a0c8046b9929976d10d4139f1c5b3ec /src
parentf6a1ff94f02282bd018b9b49ae1a9812619ec7ba (diff)
downloadvyos-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.py46
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()