1313import sys
1414
1515import cmd2
16- import mock
1716import pytest
18-
19- # Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit)
20- try :
21- import gnureadline as readline
22- except ImportError :
23- # Try to import readline, but allow failure for convenience in Windows unit testing
24- # Note: If this actually fails, you should install readline on Linux or Mac or pyreadline on Windows
25- try :
26- # noinspection PyUnresolvedReferences
27- import readline
28- except ImportError :
29- pass
30-
17+ from conftest import complete_tester
3118
3219# List of strings used with completion functions
3320food_item_strs = ['Pizza' , 'Ham' , 'Ham Sandwich' , 'Potato' ]
@@ -87,41 +74,6 @@ def cmd2_app():
8774 return c
8875
8976
90- def complete_tester (text , line , begidx , endidx , app ):
91- """
92- This is a convenience function to test cmd2.complete() since
93- in a unit test environment there is no actual console readline
94- is monitoring. Therefore we use mock to provide readline data
95- to complete().
96-
97- :param text: str - the string prefix we are attempting to match
98- :param line: str - the current input line with leading whitespace removed
99- :param begidx: int - the beginning index of the prefix text
100- :param endidx: int - the ending index of the prefix text
101- :param app: the cmd2 app that will run completions
102- :return: The first matched string or None if there are no matches
103- Matches are stored in app.completion_matches
104- These matches also have been sorted by complete()
105- """
106- def get_line ():
107- return line
108-
109- def get_begidx ():
110- return begidx
111-
112- def get_endidx ():
113- return endidx
114-
115- first_match = None
116- with mock .patch .object (readline , 'get_line_buffer' , get_line ):
117- with mock .patch .object (readline , 'get_begidx' , get_begidx ):
118- with mock .patch .object (readline , 'get_endidx' , get_endidx ):
119- # Run the readline tab-completion function with readline mocks in place
120- first_match = app .complete (text , 0 )
121-
122- return first_match
123-
124-
12577def test_cmd2_command_completion_single (cmd2_app ):
12678 text = 'he'
12779 line = text
@@ -911,6 +863,7 @@ def test_subcommand_tab_completion(sc_app):
911863 # It is at end of line, so extra space is present
912864 assert first_match is not None and sc_app .completion_matches == ['Football ' ]
913865
866+
914867def test_subcommand_tab_completion_with_no_completer (sc_app ):
915868 # This tests what happens when a subcommand has no completer
916869 # In this case, the foo subcommand has no completer defined
@@ -922,6 +875,7 @@ def test_subcommand_tab_completion_with_no_completer(sc_app):
922875 first_match = complete_tester (text , line , begidx , endidx , sc_app )
923876 assert first_match is None
924877
878+
925879def test_subcommand_tab_completion_space_in_text (sc_app ):
926880 text = 'B'
927881 line = 'base sport "Space {}' .format (text )
@@ -934,6 +888,179 @@ def test_subcommand_tab_completion_space_in_text(sc_app):
934888 sc_app .completion_matches == ['Ball" ' ] and \
935889 sc_app .display_matches == ['Space Ball' ]
936890
891+ ####################################################
892+
893+
894+ class SubcommandsWithUnknownExample (cmd2 .Cmd ):
895+ """
896+ Example cmd2 application where we a base command which has a couple subcommands
897+ and the "sport" subcommand has tab completion enabled.
898+ """
899+
900+ def __init__ (self ):
901+ cmd2 .Cmd .__init__ (self )
902+
903+ # subcommand functions for the base command
904+ def base_foo (self , args ):
905+ """foo subcommand of base command"""
906+ self .poutput (args .x * args .y )
907+
908+ def base_bar (self , args ):
909+ """bar subcommand of base command"""
910+ self .poutput ('((%s))' % args .z )
911+
912+ def base_sport (self , args ):
913+ """sport subcommand of base command"""
914+ self .poutput ('Sport is {}' .format (args .sport ))
915+
916+ # noinspection PyUnusedLocal
917+ def complete_base_sport (self , text , line , begidx , endidx ):
918+ """ Adds tab completion to base sport subcommand """
919+ index_dict = {1 : sport_item_strs }
920+ return self .index_based_complete (text , line , begidx , endidx , index_dict )
921+
922+ # create the top-level parser for the base command
923+ base_parser = argparse .ArgumentParser (prog = 'base' )
924+ base_subparsers = base_parser .add_subparsers (title = 'subcommands' , help = 'subcommand help' )
925+
926+ # create the parser for the "foo" subcommand
927+ parser_foo = base_subparsers .add_parser ('foo' , help = 'foo help' )
928+ parser_foo .add_argument ('-x' , type = int , default = 1 , help = 'integer' )
929+ parser_foo .add_argument ('y' , type = float , help = 'float' )
930+ parser_foo .set_defaults (func = base_foo )
931+
932+ # create the parser for the "bar" subcommand
933+ parser_bar = base_subparsers .add_parser ('bar' , help = 'bar help' )
934+ parser_bar .add_argument ('z' , help = 'string' )
935+ parser_bar .set_defaults (func = base_bar )
936+
937+ # create the parser for the "sport" subcommand
938+ parser_sport = base_subparsers .add_parser ('sport' , help = 'sport help' )
939+ parser_sport .add_argument ('sport' , help = 'Enter name of a sport' )
940+
941+ # Set both a function and tab completer for the "sport" subcommand
942+ parser_sport .set_defaults (func = base_sport , completer = complete_base_sport )
943+
944+ @cmd2 .with_argparser_and_unknown_args (base_parser )
945+ def do_base (self , args ):
946+ """Base command help"""
947+ func = getattr (args , 'func' , None )
948+ if func is not None :
949+ # Call whatever subcommand function was selected
950+ func (self , args )
951+ else :
952+ # No subcommand was provided, so call help
953+ self .do_help ('base' )
954+
955+ # Enable tab completion of base to make sure the subcommands' completers get called.
956+ complete_base = cmd2 .Cmd .cmd_with_subs_completer
957+
958+
959+ @pytest .fixture
960+ def scu_app ():
961+ """Declare test fixture for with_argparser_and_unknown_args"""
962+ app = SubcommandsWithUnknownExample ()
963+ return app
964+
965+
966+ def test_cmd2_subcmd_with_unknown_completion_single_end (scu_app ):
967+ text = 'f'
968+ line = 'base {}' .format (text )
969+ endidx = len (line )
970+ begidx = endidx - len (text )
971+
972+ first_match = complete_tester (text , line , begidx , endidx , scu_app )
973+
974+ # It is at end of line, so extra space is present
975+ assert first_match is not None and scu_app .completion_matches == ['foo ' ]
976+
977+
978+ def test_cmd2_subcmd_with_unknown_completion_multiple (scu_app ):
979+ text = ''
980+ line = 'base {}' .format (text )
981+ endidx = len (line )
982+ begidx = endidx - len (text )
983+
984+ first_match = complete_tester (text , line , begidx , endidx , scu_app )
985+ assert first_match is not None and scu_app .completion_matches == ['bar' , 'foo' , 'sport' ]
986+
987+
988+ def test_cmd2_subcmd_with_unknown_completion_nomatch (scu_app ):
989+ text = 'z'
990+ line = 'base {}' .format (text )
991+ endidx = len (line )
992+ begidx = endidx - len (text )
993+
994+ first_match = complete_tester (text , line , begidx , endidx , scu_app )
995+ assert first_match is None
996+
997+
998+ def test_cmd2_help_subcommand_completion_single (scu_app ):
999+ text = 'base'
1000+ line = 'help {}' .format (text )
1001+ endidx = len (line )
1002+ begidx = endidx - len (text )
1003+ assert scu_app .complete_help (text , line , begidx , endidx ) == ['base' ]
1004+
1005+
1006+ def test_cmd2_help_subcommand_completion_multiple (scu_app ):
1007+ text = ''
1008+ line = 'help base {}' .format (text )
1009+ endidx = len (line )
1010+ begidx = endidx - len (text )
1011+
1012+ matches = sorted (scu_app .complete_help (text , line , begidx , endidx ))
1013+ assert matches == ['bar' , 'foo' , 'sport' ]
1014+
1015+
1016+ def test_cmd2_help_subcommand_completion_nomatch (scu_app ):
1017+ text = 'z'
1018+ line = 'help base {}' .format (text )
1019+ endidx = len (line )
1020+ begidx = endidx - len (text )
1021+ assert scu_app .complete_help (text , line , begidx , endidx ) == []
1022+
1023+
1024+ def test_subcommand_tab_completion (scu_app ):
1025+ # This makes sure the correct completer for the sport subcommand is called
1026+ text = 'Foot'
1027+ line = 'base sport {}' .format (text )
1028+ endidx = len (line )
1029+ begidx = endidx - len (text )
1030+
1031+ first_match = complete_tester (text , line , begidx , endidx , scu_app )
1032+
1033+ # It is at end of line, so extra space is present
1034+ assert first_match is not None and scu_app .completion_matches == ['Football ' ]
1035+
1036+
1037+ def test_subcommand_tab_completion_with_no_completer (scu_app ):
1038+ # This tests what happens when a subcommand has no completer
1039+ # In this case, the foo subcommand has no completer defined
1040+ text = 'Foot'
1041+ line = 'base foo {}' .format (text )
1042+ endidx = len (line )
1043+ begidx = endidx - len (text )
1044+
1045+ first_match = complete_tester (text , line , begidx , endidx , scu_app )
1046+ assert first_match is None
1047+
1048+
1049+ def test_subcommand_tab_completion_space_in_text (scu_app ):
1050+ text = 'B'
1051+ line = 'base sport "Space {}' .format (text )
1052+ endidx = len (line )
1053+ begidx = endidx - len (text )
1054+
1055+ first_match = complete_tester (text , line , begidx , endidx , scu_app )
1056+
1057+ assert first_match is not None and \
1058+ scu_app .completion_matches == ['Ball" ' ] and \
1059+ scu_app .display_matches == ['Space Ball' ]
1060+
1061+ ####################################################
1062+
1063+
9371064class SecondLevel (cmd2 .Cmd ):
9381065 """To be used as a second level command class. """
9391066
0 commit comments