@@ -1068,16 +1068,70 @@ def test_cmdloop_without_rawinput() -> None:
1068
1068
assert out == expected
1069
1069
1070
1070
1071
- @pytest .mark .skipif (sys .platform .startswith ('win' ), reason = "stty sane only run on Linux/Mac" )
1072
- def test_stty_sane (base_app , monkeypatch ) -> None :
1073
- """Make sure stty sane is run on Linux/Mac after each command if stdin is a terminal"""
1074
- with mock .patch ('sys.stdin.isatty' , mock .MagicMock (name = 'isatty' , return_value = True )):
1075
- # Mock out the subprocess.Popen call so we don't actually run stty sane
1076
- m = mock .MagicMock (name = 'Popen' )
1077
- monkeypatch .setattr ("subprocess.Popen" , m )
1071
+ def test_cmdfinalizations_runs (base_app , monkeypatch ) -> None :
1072
+ """Make sure _run_cmdfinalization_hooks is run after each command."""
1073
+ with (
1074
+ mock .patch ('sys.stdin.isatty' , mock .MagicMock (name = 'isatty' , return_value = True )),
1075
+ mock .patch ('sys.stdin.fileno' , mock .MagicMock (name = 'fileno' , return_value = 0 )),
1076
+ ):
1077
+ monkeypatch .setattr (base_app .stdin , "fileno" , lambda : 0 )
1078
+ monkeypatch .setattr (base_app .stdin , "isatty" , lambda : True )
1079
+
1080
+ cmd_fin = mock .MagicMock (name = 'cmdfinalization' )
1081
+ monkeypatch .setattr ("cmd2.Cmd._run_cmdfinalization_hooks" , cmd_fin )
1078
1082
1079
1083
base_app .onecmd_plus_hooks ('help' )
1080
- m .assert_called_once_with (['stty' , 'sane' ])
1084
+ cmd_fin .assert_called_once ()
1085
+
1086
+
1087
+ @pytest .mark .skipif (sys .platform .startswith ('win' ), reason = "termios is not available on Windows" )
1088
+ @pytest .mark .parametrize (
1089
+ ('is_tty' , 'settings_set' , 'raised_exception' , 'should_call' ),
1090
+ [
1091
+ (True , True , None , True ),
1092
+ (True , True , 'termios_error' , True ),
1093
+ (True , True , 'unsupported_operation' , True ),
1094
+ (False , True , None , False ),
1095
+ (True , False , None , False ),
1096
+ ],
1097
+ )
1098
+ def test_restore_termios_settings (base_app , monkeypatch , is_tty , settings_set , raised_exception , should_call ):
1099
+ """Test that terminal settings are restored after a command and that errors are suppressed."""
1100
+ import io
1101
+ import termios # Mock termios since it's imported within the method
1102
+
1103
+ termios_mock = mock .MagicMock ()
1104
+ # The error attribute needs to be the actual exception for isinstance checks
1105
+ termios_mock .error = termios .error
1106
+ monkeypatch .setitem (sys .modules , 'termios' , termios_mock )
1107
+
1108
+ # Set the exception to be raised by tcsetattr
1109
+ if raised_exception == 'termios_error' :
1110
+ termios_mock .tcsetattr .side_effect = termios .error ("test termios error" )
1111
+ elif raised_exception == 'unsupported_operation' :
1112
+ termios_mock .tcsetattr .side_effect = io .UnsupportedOperation ("test io error" )
1113
+
1114
+ # Set initial termios settings so the logic will run
1115
+ if settings_set :
1116
+ termios_settings = ["dummy settings" ]
1117
+ base_app ._initial_termios_settings = termios_settings
1118
+ else :
1119
+ base_app ._initial_termios_settings = None
1120
+ termios_settings = None # for the assert
1121
+
1122
+ # Mock stdin to make it look like a TTY
1123
+ monkeypatch .setattr (base_app .stdin , "isatty" , lambda : is_tty )
1124
+ monkeypatch .setattr (base_app .stdin , "fileno" , lambda : 0 )
1125
+
1126
+ # Run a command to trigger _run_cmdfinalization_hooks
1127
+ # This should not raise an exception
1128
+ base_app .onecmd_plus_hooks ('help' )
1129
+
1130
+ # Verify that tcsetattr was called with the correct arguments
1131
+ if should_call :
1132
+ termios_mock .tcsetattr .assert_called_once_with (0 , termios_mock .TCSANOW , termios_settings )
1133
+ else :
1134
+ termios_mock .tcsetattr .assert_not_called ()
1081
1135
1082
1136
1083
1137
def test_sigint_handler (base_app ) -> None :
0 commit comments