@@ -1068,16 +1068,70 @@ def test_cmdloop_without_rawinput() -> None:
10681068 assert out == expected
10691069
10701070
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 )
10781082
10791083 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 ()
10811135
10821136
10831137def test_sigint_handler (base_app ) -> None :
0 commit comments