1a69ecb24Sjimingham""" 2a69ecb24SjiminghamTest option and argument definitions in parsed script commands 3a69ecb24Sjimingham""" 4a69ecb24Sjimingham 5a69ecb24Sjimingham 6a69ecb24Sjiminghamimport sys 7a69ecb24Sjiminghamimport os 8a69ecb24Sjiminghamimport lldb 9a69ecb24Sjiminghamfrom lldbsuite.test.decorators import * 10a69ecb24Sjiminghamfrom lldbsuite.test.lldbtest import * 11a69ecb24Sjimingham 12a69ecb24Sjimingham 13a69ecb24Sjiminghamclass ParsedCommandTestCase(TestBase): 14a69ecb24Sjimingham NO_DEBUG_INFO_TESTCASE = True 15a69ecb24Sjimingham 16a69ecb24Sjimingham def test(self): 17a69ecb24Sjimingham self.pycmd_tests() 18a69ecb24Sjimingham 1977d131edSjimingham def setUp(self): 2077d131edSjimingham TestBase.setUp(self) 2177d131edSjimingham self.stdin_path = self.getBuildArtifact("stdin.txt") 2277d131edSjimingham self.stdout_path = self.getBuildArtifact("stdout.txt") 2377d131edSjimingham 24a69ecb24Sjimingham def check_help_options(self, cmd_name, opt_list, substrs=[]): 25a69ecb24Sjimingham """ 26a69ecb24Sjimingham Pass the command name in cmd_name and a vector of the short option, type & long option. 27a69ecb24Sjimingham This will append the checks for all the options and test "help command". 28a69ecb24Sjimingham Any strings already in substrs will also be checked. 29a69ecb24Sjimingham Any element in opt list that begin with "+" will be added to the checked strings as is. 30a69ecb24Sjimingham """ 31a69ecb24Sjimingham for elem in opt_list: 32a69ecb24Sjimingham if elem[0] == "+": 33a69ecb24Sjimingham substrs.append(elem[1:]) 34a69ecb24Sjimingham else: 35a69ecb24Sjimingham (short_opt, type, long_opt) = elem 36a69ecb24Sjimingham substrs.append(f"-{short_opt} <{type}> ( --{long_opt} <{type}> )") 37a69ecb24Sjimingham self.expect("help " + cmd_name, substrs=substrs) 38a69ecb24Sjimingham 3977d131edSjimingham def run_one_repeat(self, commands, expected_num_errors): 4077d131edSjimingham with open(self.stdin_path, "w") as input_handle: 4177d131edSjimingham input_handle.write(commands) 4277d131edSjimingham 4377d131edSjimingham in_fileH = open(self.stdin_path, "r") 4477d131edSjimingham self.dbg.SetInputFileHandle(in_fileH, False) 4577d131edSjimingham 4677d131edSjimingham out_fileH = open(self.stdout_path, "w") 4777d131edSjimingham self.dbg.SetOutputFileHandle(out_fileH, False) 4877d131edSjimingham self.dbg.SetErrorFileHandle(out_fileH, False) 4977d131edSjimingham 5077d131edSjimingham options = lldb.SBCommandInterpreterRunOptions() 5177d131edSjimingham options.SetEchoCommands(False) 5277d131edSjimingham options.SetPrintResults(True) 5377d131edSjimingham options.SetPrintErrors(True) 5477d131edSjimingham options.SetAllowRepeats(True) 5577d131edSjimingham 5677d131edSjimingham n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter( 5777d131edSjimingham True, False, options, 0, False, False 5877d131edSjimingham ) 5977d131edSjimingham 6077d131edSjimingham in_fileH.close() 6177d131edSjimingham out_fileH.close() 6277d131edSjimingham 6377d131edSjimingham results = None 6477d131edSjimingham with open(self.stdout_path, "r") as out_fileH: 6577d131edSjimingham results = out_fileH.read() 6677d131edSjimingham 6777d131edSjimingham self.assertEqual(n_errors, expected_num_errors) 6877d131edSjimingham 6977d131edSjimingham return results 7077d131edSjimingham 71*04b443e7Sjimingham def handle_completion( 72*04b443e7Sjimingham self, 73*04b443e7Sjimingham cmd_str, 74*04b443e7Sjimingham exp_num_completions, 75*04b443e7Sjimingham exp_matches, 76*04b443e7Sjimingham exp_descriptions, 77*04b443e7Sjimingham match_description, 78*04b443e7Sjimingham ): 79*04b443e7Sjimingham matches = lldb.SBStringList() 80*04b443e7Sjimingham descriptions = lldb.SBStringList() 81*04b443e7Sjimingham 82*04b443e7Sjimingham interp = self.dbg.GetCommandInterpreter() 83*04b443e7Sjimingham num_completions = interp.HandleCompletionWithDescriptions( 84*04b443e7Sjimingham cmd_str, len(cmd_str), 0, 1000, matches, descriptions 85*04b443e7Sjimingham ) 86*04b443e7Sjimingham self.assertEqual( 87*04b443e7Sjimingham num_completions, exp_num_completions, "Number of completions is right." 88*04b443e7Sjimingham ) 89*04b443e7Sjimingham num_matches = matches.GetSize() 90*04b443e7Sjimingham self.assertEqual( 91*04b443e7Sjimingham num_matches, 92*04b443e7Sjimingham exp_matches.GetSize(), 93*04b443e7Sjimingham "matches and expected matches of different lengths", 94*04b443e7Sjimingham ) 95*04b443e7Sjimingham num_descriptions = descriptions.GetSize() 96*04b443e7Sjimingham if match_description: 97*04b443e7Sjimingham self.assertEqual( 98*04b443e7Sjimingham num_descriptions, 99*04b443e7Sjimingham exp_descriptions.GetSize(), 100*04b443e7Sjimingham "descriptions and expected of different lengths", 101*04b443e7Sjimingham ) 102*04b443e7Sjimingham 103*04b443e7Sjimingham self.assertEqual( 104*04b443e7Sjimingham matches.GetSize(), 105*04b443e7Sjimingham num_completions + 1, 106*04b443e7Sjimingham "The first element is the complete additional text", 107*04b443e7Sjimingham ) 108*04b443e7Sjimingham 109*04b443e7Sjimingham for idx in range(0, num_matches): 110*04b443e7Sjimingham match = matches.GetStringAtIndex(idx) 111*04b443e7Sjimingham exp_match = exp_matches.GetStringAtIndex(idx) 112*04b443e7Sjimingham self.assertEqual( 113*04b443e7Sjimingham match, exp_match, f"{match} did not match expectation: {exp_match}" 114*04b443e7Sjimingham ) 115*04b443e7Sjimingham if match_description: 116*04b443e7Sjimingham desc = descriptions.GetStringAtIndex(idx) 117*04b443e7Sjimingham exp_desc = exp_descriptions.GetStringAtIndex(idx) 118*04b443e7Sjimingham self.assertEqual( 119*04b443e7Sjimingham desc, exp_desc, f"{desc} didn't match expectation: {exp_desc}" 120*04b443e7Sjimingham ) 121*04b443e7Sjimingham 122a69ecb24Sjimingham def pycmd_tests(self): 123a69ecb24Sjimingham source_dir = self.getSourceDir() 124a69ecb24Sjimingham test_file_path = os.path.join(source_dir, "test_commands.py") 125a69ecb24Sjimingham self.runCmd("command script import " + test_file_path) 126a69ecb24Sjimingham self.expect("help", substrs=["no-args", "one-arg-no-opt", "two-args"]) 127a69ecb24Sjimingham 128a69ecb24Sjimingham # Test that we did indeed add these commands as user commands: 129a69ecb24Sjimingham 130a69ecb24Sjimingham # This is the function to remove the custom commands in order to have a 131a69ecb24Sjimingham # clean slate for the next test case. 132a69ecb24Sjimingham def cleanup(): 133096c530aSJonas Devlieghere self.runCmd( 134096c530aSJonas Devlieghere "command script delete no-args one-arg-no-opt two-args", check=False 135096c530aSJonas Devlieghere ) 136a69ecb24Sjimingham 137a69ecb24Sjimingham # Execute the cleanup function during test case tear down. 138a69ecb24Sjimingham self.addTearDownHook(cleanup) 139a69ecb24Sjimingham 140a69ecb24Sjimingham # First test the no arguments command. Make sure the help is right: 141096c530aSJonas Devlieghere no_arg_opts = [ 142096c530aSJonas Devlieghere ["b", "boolean", "bool-arg"], 143a69ecb24Sjimingham "+a boolean arg, defaults to True", 144a69ecb24Sjimingham ["d", "filename", "disk-file-name"], 145a69ecb24Sjimingham "+An on disk filename", 146a69ecb24Sjimingham ["e", "none", "enum-option"], 147a69ecb24Sjimingham "+An enum, doesn't actually do anything", 148a69ecb24Sjimingham "+Values: foo | bar | baz", 149a69ecb24Sjimingham ["l", "linenum", "line-num"], 150a69ecb24Sjimingham "+A line number", 151a69ecb24Sjimingham ["s", "shlib-name", "shlib-name"], 152096c530aSJonas Devlieghere "+A shared library name", 153096c530aSJonas Devlieghere ] 154096c530aSJonas Devlieghere substrs = [ 155096c530aSJonas Devlieghere "Example command for use in debugging", 156096c530aSJonas Devlieghere "Syntax: no-args <cmd-options>", 157096c530aSJonas Devlieghere ] 158a69ecb24Sjimingham 159a69ecb24Sjimingham self.check_help_options("no-args", no_arg_opts, substrs) 160a69ecb24Sjimingham 161a69ecb24Sjimingham # Make sure the command doesn't accept arguments: 162096c530aSJonas Devlieghere self.expect( 163096c530aSJonas Devlieghere "no-args an-arg", 164096c530aSJonas Devlieghere substrs=["'no-args' doesn't take any arguments."], 165096c530aSJonas Devlieghere error=True, 166096c530aSJonas Devlieghere ) 167a69ecb24Sjimingham 168a69ecb24Sjimingham # Try setting the bool with the wrong value: 169096c530aSJonas Devlieghere self.expect( 170096c530aSJonas Devlieghere "no-args -b Something", 171a69ecb24Sjimingham substrs=["Error setting option: bool-arg to Something"], 172096c530aSJonas Devlieghere error=True, 173096c530aSJonas Devlieghere ) 174a69ecb24Sjimingham # Try setting the enum to an illegal value as well: 175096c530aSJonas Devlieghere self.expect( 176096c530aSJonas Devlieghere "no-args --enum-option Something", 177a69ecb24Sjimingham substrs=["error: Error setting option: enum-option to Something"], 178096c530aSJonas Devlieghere error=True, 179096c530aSJonas Devlieghere ) 180a69ecb24Sjimingham 181a69ecb24Sjimingham # Check some of the command groups: 182096c530aSJonas Devlieghere self.expect( 183096c530aSJonas Devlieghere "no-args -b true -s Something -l 10", 184a69ecb24Sjimingham substrs=["error: invalid combination of options for the given command"], 185096c530aSJonas Devlieghere error=True, 186096c530aSJonas Devlieghere ) 187a69ecb24Sjimingham 188a69ecb24Sjimingham # Now set the bool arg correctly, note only the first option was set: 189096c530aSJonas Devlieghere self.expect( 190096c530aSJonas Devlieghere "no-args -b true", 191096c530aSJonas Devlieghere substrs=[ 192096c530aSJonas Devlieghere "bool-arg (set: True): True", 193a69ecb24Sjimingham "shlib-name (set: False):", 194a69ecb24Sjimingham "disk-file-name (set: False):", 195a69ecb24Sjimingham "line-num (set: False):", 196096c530aSJonas Devlieghere "enum-option (set: False):", 197096c530aSJonas Devlieghere ], 198096c530aSJonas Devlieghere ) 199a69ecb24Sjimingham 200a69ecb24Sjimingham # Now set the enum arg correctly, note only the first option was set: 201096c530aSJonas Devlieghere self.expect( 202096c530aSJonas Devlieghere "no-args -e foo", 203096c530aSJonas Devlieghere substrs=[ 204096c530aSJonas Devlieghere "bool-arg (set: False):", 205a69ecb24Sjimingham "shlib-name (set: False):", 206a69ecb24Sjimingham "disk-file-name (set: False):", 207a69ecb24Sjimingham "line-num (set: False):", 208096c530aSJonas Devlieghere "enum-option (set: True): foo", 209096c530aSJonas Devlieghere ], 210096c530aSJonas Devlieghere ) 211a69ecb24Sjimingham # Try a pair together: 212096c530aSJonas Devlieghere self.expect( 213096c530aSJonas Devlieghere "no-args -b false -s Something", 214096c530aSJonas Devlieghere substrs=[ 215096c530aSJonas Devlieghere "bool-arg (set: True): False", 216a69ecb24Sjimingham "shlib-name (set: True): Something", 217a69ecb24Sjimingham "disk-file-name (set: False):", 218a69ecb24Sjimingham "line-num (set: False):", 219096c530aSJonas Devlieghere "enum-option (set: False):", 220096c530aSJonas Devlieghere ], 221096c530aSJonas Devlieghere ) 222a69ecb24Sjimingham 223a69ecb24Sjimingham # Next try some completion tests: 224a69ecb24Sjimingham 225a69ecb24Sjimingham interp = self.dbg.GetCommandInterpreter() 226a69ecb24Sjimingham matches = lldb.SBStringList() 227a69ecb24Sjimingham descriptions = lldb.SBStringList() 228a69ecb24Sjimingham 229a69ecb24Sjimingham # First try an enum completion: 230*04b443e7Sjimingham # Note - this is an enum so all the values are returned: 231*04b443e7Sjimingham matches.AppendList(["oo ", "foo"], 2) 232*04b443e7Sjimingham 233*04b443e7Sjimingham self.handle_completion("no-args -e f", 1, matches, descriptions, False) 234a69ecb24Sjimingham 235a69ecb24Sjimingham # Now try an internal completer, the on disk file one is handy: 236a69ecb24Sjimingham partial_name = os.path.join(source_dir, "test_") 237a69ecb24Sjimingham cmd_str = f"no-args -d '{partial_name}'" 238a69ecb24Sjimingham 239a69ecb24Sjimingham matches.Clear() 240a69ecb24Sjimingham descriptions.Clear() 241*04b443e7Sjimingham matches.AppendList(["commands.py' ", test_file_path], 2) 242*04b443e7Sjimingham # We don't have descriptions for the file path completer: 243*04b443e7Sjimingham self.handle_completion(cmd_str, 1, matches, descriptions, False) 244a69ecb24Sjimingham 245a69ecb24Sjimingham # Try a command with arguments. 246a69ecb24Sjimingham # FIXME: It should be enough to define an argument and it's type to get the completer 247a69ecb24Sjimingham # wired up for that argument type if it is a known type. But that isn't wired up in the 248a69ecb24Sjimingham # command parser yet, so I don't have any tests for that. We also don't currently check 249a69ecb24Sjimingham # that the arguments passed match the argument specifications, so here I just pass a couple 250a69ecb24Sjimingham # sets of arguments and make sure we get back what we put in: 251096c530aSJonas Devlieghere self.expect( 252096c530aSJonas Devlieghere "two-args 'First Argument' 'Second Argument'", 253096c530aSJonas Devlieghere substrs=["0: First Argument", "1: Second Argument"], 254096c530aSJonas Devlieghere ) 25577d131edSjimingham 256*04b443e7Sjimingham # Now test custom completions - two-args has both option and arg completers. In both 257*04b443e7Sjimingham # completers we return different values if the -p option is set, so we can test that too: 258*04b443e7Sjimingham matches.Clear() 259*04b443e7Sjimingham descriptions.Clear() 260*04b443e7Sjimingham cmd_str = "two-args -p something -c other_" 261*04b443e7Sjimingham matches.AppendString("something ") 262*04b443e7Sjimingham matches.AppendString("other_something") 263*04b443e7Sjimingham # This is a full match so no descriptions: 264*04b443e7Sjimingham self.handle_completion(cmd_str, 1, matches, descriptions, False) 265*04b443e7Sjimingham 266*04b443e7Sjimingham matches.Clear() 267*04b443e7Sjimingham descriptions.Clear() 268*04b443e7Sjimingham cmd_str = "two-args -c other_" 269*04b443e7Sjimingham matches.AppendList(["", "other_nice", "other_not_nice", "other_mediocre"], 4) 270*04b443e7Sjimingham # The option doesn't return descriptions either: 271*04b443e7Sjimingham self.handle_completion(cmd_str, 3, matches, descriptions, False) 272*04b443e7Sjimingham 273*04b443e7Sjimingham # Now try the argument - it says "no completions" if the proc_name was set: 274*04b443e7Sjimingham matches.Clear() 275*04b443e7Sjimingham descriptions.Clear() 276*04b443e7Sjimingham cmd_str = "two-args -p something arg" 277*04b443e7Sjimingham matches.AppendString("") 278*04b443e7Sjimingham self.handle_completion(cmd_str, 0, matches, descriptions, False) 279*04b443e7Sjimingham 280*04b443e7Sjimingham cmd_str = "two-args arg_" 281*04b443e7Sjimingham matches.Clear() 282*04b443e7Sjimingham descriptions.Clear() 283*04b443e7Sjimingham matches.AppendList(["", "arg_cool", "arg_yuck"], 3) 284*04b443e7Sjimingham descriptions.AppendList(["", "good idea", "bad idea"], 3) 285*04b443e7Sjimingham self.handle_completion(cmd_str, 2, matches, descriptions, True) 286*04b443e7Sjimingham 287*04b443e7Sjimingham # This one gets a single unique match: 288*04b443e7Sjimingham cmd_str = "two-args correct_" 289*04b443e7Sjimingham matches.Clear() 290*04b443e7Sjimingham descriptions.Clear() 291*04b443e7Sjimingham matches.AppendList(["answer ", "correct_answer"], 2) 292*04b443e7Sjimingham self.handle_completion(cmd_str, 1, matches, descriptions, False) 293*04b443e7Sjimingham 29477d131edSjimingham # Now make sure get_repeat_command works properly: 29577d131edSjimingham 29677d131edSjimingham # no-args turns off auto-repeat 29777d131edSjimingham results = self.run_one_repeat("no-args\n\n", 1) 29877d131edSjimingham self.assertIn("No auto repeat", results, "Got auto-repeat error") 29977d131edSjimingham 30077d131edSjimingham # one-args does the normal repeat 30177d131edSjimingham results = self.run_one_repeat("one-arg-no-opt ONE_ARG\n\n", 0) 30277d131edSjimingham self.assertEqual(results.count("ONE_ARG"), 2, "We did a normal repeat") 30377d131edSjimingham 30477d131edSjimingham # two-args adds an argument: 30577d131edSjimingham results = self.run_one_repeat("two-args FIRST_ARG SECOND_ARG\n\n", 0) 30677d131edSjimingham self.assertEqual( 30777d131edSjimingham results.count("FIRST_ARG"), 2, "Passed first arg to both commands" 30877d131edSjimingham ) 30977d131edSjimingham self.assertEqual( 31077d131edSjimingham results.count("SECOND_ARG"), 2, "Passed second arg to both commands" 31177d131edSjimingham ) 31277d131edSjimingham self.assertEqual(results.count("THIRD_ARG"), 1, "Passed third arg in repeat") 313