@@ -3244,3 +3244,100 @@ class SynonymApp(cmd2.cmd2.Cmd):
32443244
32453245 assert synonym_parser is not None
32463246 assert synonym_parser is help_parser
3247+
3248+
3249+ def test_custom_completekey ():
3250+ # Test setting a custom completekey
3251+ app = cmd2 .Cmd (completekey = '?' )
3252+ assert app .completekey == '?'
3253+
3254+
3255+ def test_prompt_session_init_exception (monkeypatch ):
3256+ from prompt_toolkit .shortcuts import PromptSession
3257+
3258+ # Mock PromptSession to raise ValueError on first call, then succeed
3259+ valid_session_mock = mock .MagicMock (spec = PromptSession )
3260+ mock_session = mock .MagicMock (side_effect = [ValueError , valid_session_mock ])
3261+ monkeypatch .setattr ("cmd2.cmd2.PromptSession" , mock_session )
3262+
3263+ cmd2 .Cmd ()
3264+ # Check that fallback to DummyInput/Output happened
3265+ from prompt_toolkit .input import DummyInput
3266+ from prompt_toolkit .output import DummyOutput
3267+
3268+ assert mock_session .call_count == 2
3269+ # Check args of second call
3270+ call_args = mock_session .call_args_list [1 ]
3271+ kwargs = call_args [1 ]
3272+ assert isinstance (kwargs ['input' ], DummyInput )
3273+ assert isinstance (kwargs ['output' ], DummyOutput )
3274+
3275+
3276+ def test_pager_on_windows (monkeypatch ):
3277+ monkeypatch .setattr ("sys.platform" , "win32" )
3278+ app = cmd2 .Cmd ()
3279+ assert app .pager == 'more'
3280+ assert app .pager_chop == 'more'
3281+
3282+
3283+ def test_path_complete_users_windows (monkeypatch , base_app ):
3284+ monkeypatch .setattr ("sys.platform" , "win32" )
3285+
3286+ # Mock os.path.expanduser and isdir
3287+ monkeypatch .setattr ("os.path.expanduser" , lambda p : '/home/user' if p == '~user' else p )
3288+ monkeypatch .setattr ("os.path.isdir" , lambda p : p == '/home/user' )
3289+
3290+ matches = base_app .path_complete ('~user' , 'cmd ~user' , 0 , 9 )
3291+ # Should contain ~user/ (or ~user\ depending on sep)
3292+ # Since we didn't mock os.path.sep, it will use system separator.
3293+ expected = '~user' + os .path .sep
3294+ assert expected in matches
3295+
3296+
3297+ def test_async_alert_success (base_app ):
3298+ import threading
3299+
3300+ success = []
3301+
3302+ def run_alert ():
3303+ base_app .async_alert ("Alert Message" , new_prompt = "(New) " )
3304+ success .append (True )
3305+
3306+ t = threading .Thread (target = run_alert )
3307+ t .start ()
3308+ t .join ()
3309+
3310+ assert success
3311+ assert base_app .prompt == "(New) "
3312+
3313+
3314+ def test_async_alert_main_thread_error (base_app ):
3315+ with pytest .raises (RuntimeError , match = "main thread" ):
3316+ base_app .async_alert ("fail" )
3317+
3318+
3319+ def test_async_alert_lock_held (base_app ):
3320+ import threading
3321+
3322+ # Acquire lock in main thread
3323+ base_app .terminal_lock .acquire ()
3324+
3325+ exceptions = []
3326+
3327+ def run_alert ():
3328+ try :
3329+ base_app .async_alert ("fail" )
3330+ except RuntimeError as e :
3331+ exceptions .append (e )
3332+ finally :
3333+ pass
3334+
3335+ try :
3336+ t = threading .Thread (target = run_alert )
3337+ t .start ()
3338+ t .join ()
3339+ finally :
3340+ base_app .terminal_lock .release ()
3341+
3342+ assert len (exceptions ) == 1
3343+ assert "another thread holds terminal_lock" in str (exceptions [0 ])
0 commit comments