1""" 2Test option and argument definitions in parsed script commands 3""" 4 5 6import sys 7import os 8import lldb 9from lldbsuite.test.decorators import * 10from lldbsuite.test.lldbtest import * 11 12 13class ParsedCommandTestCase(TestBase): 14 NO_DEBUG_INFO_TESTCASE = True 15 16 def test(self): 17 self.pycmd_tests() 18 19 def setUp(self): 20 TestBase.setUp(self) 21 self.stdin_path = self.getBuildArtifact("stdin.txt") 22 self.stdout_path = self.getBuildArtifact("stdout.txt") 23 24 def check_help_options(self, cmd_name, opt_list, substrs=[]): 25 """ 26 Pass the command name in cmd_name and a vector of the short option, type & long option. 27 This will append the checks for all the options and test "help command". 28 Any strings already in substrs will also be checked. 29 Any element in opt list that begin with "+" will be added to the checked strings as is. 30 """ 31 for elem in opt_list: 32 if elem[0] == "+": 33 substrs.append(elem[1:]) 34 else: 35 (short_opt, type, long_opt) = elem 36 substrs.append(f"-{short_opt} <{type}> ( --{long_opt} <{type}> )") 37 self.expect("help " + cmd_name, substrs=substrs) 38 39 def run_one_repeat(self, commands, expected_num_errors): 40 with open(self.stdin_path, "w") as input_handle: 41 input_handle.write(commands) 42 43 in_fileH = open(self.stdin_path, "r") 44 self.dbg.SetInputFileHandle(in_fileH, False) 45 46 out_fileH = open(self.stdout_path, "w") 47 self.dbg.SetOutputFileHandle(out_fileH, False) 48 self.dbg.SetErrorFileHandle(out_fileH, False) 49 50 options = lldb.SBCommandInterpreterRunOptions() 51 options.SetEchoCommands(False) 52 options.SetPrintResults(True) 53 options.SetPrintErrors(True) 54 options.SetAllowRepeats(True) 55 56 n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter( 57 True, False, options, 0, False, False 58 ) 59 60 in_fileH.close() 61 out_fileH.close() 62 63 results = None 64 with open(self.stdout_path, "r") as out_fileH: 65 results = out_fileH.read() 66 67 self.assertEqual(n_errors, expected_num_errors) 68 69 return results 70 71 def handle_completion( 72 self, 73 cmd_str, 74 exp_num_completions, 75 exp_matches, 76 exp_descriptions, 77 match_description, 78 ): 79 matches = lldb.SBStringList() 80 descriptions = lldb.SBStringList() 81 82 interp = self.dbg.GetCommandInterpreter() 83 num_completions = interp.HandleCompletionWithDescriptions( 84 cmd_str, len(cmd_str), 0, 1000, matches, descriptions 85 ) 86 self.assertEqual( 87 num_completions, exp_num_completions, "Number of completions is right." 88 ) 89 num_matches = matches.GetSize() 90 self.assertEqual( 91 num_matches, 92 exp_matches.GetSize(), 93 "matches and expected matches of different lengths", 94 ) 95 num_descriptions = descriptions.GetSize() 96 if match_description: 97 self.assertEqual( 98 num_descriptions, 99 exp_descriptions.GetSize(), 100 "descriptions and expected of different lengths", 101 ) 102 103 self.assertEqual( 104 matches.GetSize(), 105 num_completions + 1, 106 "The first element is the complete additional text", 107 ) 108 109 for idx in range(0, num_matches): 110 match = matches.GetStringAtIndex(idx) 111 exp_match = exp_matches.GetStringAtIndex(idx) 112 self.assertEqual( 113 match, exp_match, f"{match} did not match expectation: {exp_match}" 114 ) 115 if match_description: 116 desc = descriptions.GetStringAtIndex(idx) 117 exp_desc = exp_descriptions.GetStringAtIndex(idx) 118 self.assertEqual( 119 desc, exp_desc, f"{desc} didn't match expectation: {exp_desc}" 120 ) 121 122 def pycmd_tests(self): 123 source_dir = self.getSourceDir() 124 test_file_path = os.path.join(source_dir, "test_commands.py") 125 self.runCmd("command script import " + test_file_path) 126 self.expect("help", substrs=["no-args", "one-arg-no-opt", "two-args"]) 127 128 # Test that we did indeed add these commands as user commands: 129 130 # This is the function to remove the custom commands in order to have a 131 # clean slate for the next test case. 132 def cleanup(): 133 self.runCmd( 134 "command script delete no-args one-arg-no-opt two-args", check=False 135 ) 136 137 # Execute the cleanup function during test case tear down. 138 self.addTearDownHook(cleanup) 139 140 # First test the no arguments command. Make sure the help is right: 141 no_arg_opts = [ 142 ["b", "boolean", "bool-arg"], 143 "+a boolean arg, defaults to True", 144 ["d", "filename", "disk-file-name"], 145 "+An on disk filename", 146 ["e", "none", "enum-option"], 147 "+An enum, doesn't actually do anything", 148 "+Values: foo | bar | baz", 149 ["l", "linenum", "line-num"], 150 "+A line number", 151 ["s", "shlib-name", "shlib-name"], 152 "+A shared library name", 153 ] 154 substrs = [ 155 "Example command for use in debugging", 156 "Syntax: no-args <cmd-options>", 157 ] 158 159 self.check_help_options("no-args", no_arg_opts, substrs) 160 161 # Make sure the command doesn't accept arguments: 162 self.expect( 163 "no-args an-arg", 164 substrs=["'no-args' doesn't take any arguments."], 165 error=True, 166 ) 167 168 # Try setting the bool with the wrong value: 169 self.expect( 170 "no-args -b Something", 171 substrs=["Error setting option: bool-arg to Something"], 172 error=True, 173 ) 174 # Try setting the enum to an illegal value as well: 175 self.expect( 176 "no-args --enum-option Something", 177 substrs=["error: Error setting option: enum-option to Something"], 178 error=True, 179 ) 180 181 # Check some of the command groups: 182 self.expect( 183 "no-args -b true -s Something -l 10", 184 substrs=["error: invalid combination of options for the given command"], 185 error=True, 186 ) 187 188 # Now set the bool arg correctly, note only the first option was set: 189 self.expect( 190 "no-args -b true", 191 substrs=[ 192 "bool-arg (set: True): True", 193 "shlib-name (set: False):", 194 "disk-file-name (set: False):", 195 "line-num (set: False):", 196 "enum-option (set: False):", 197 ], 198 ) 199 200 # Now set the enum arg correctly, note only the first option was set: 201 self.expect( 202 "no-args -e foo", 203 substrs=[ 204 "bool-arg (set: False):", 205 "shlib-name (set: False):", 206 "disk-file-name (set: False):", 207 "line-num (set: False):", 208 "enum-option (set: True): foo", 209 ], 210 ) 211 # Try a pair together: 212 self.expect( 213 "no-args -b false -s Something", 214 substrs=[ 215 "bool-arg (set: True): False", 216 "shlib-name (set: True): Something", 217 "disk-file-name (set: False):", 218 "line-num (set: False):", 219 "enum-option (set: False):", 220 ], 221 ) 222 223 # Next try some completion tests: 224 225 interp = self.dbg.GetCommandInterpreter() 226 matches = lldb.SBStringList() 227 descriptions = lldb.SBStringList() 228 229 # First try an enum completion: 230 # Note - this is an enum so all the values are returned: 231 matches.AppendList(["oo ", "foo"], 2) 232 233 self.handle_completion("no-args -e f", 1, matches, descriptions, False) 234 235 # Now try an internal completer, the on disk file one is handy: 236 partial_name = os.path.join(source_dir, "test_") 237 cmd_str = f"no-args -d '{partial_name}'" 238 239 matches.Clear() 240 descriptions.Clear() 241 matches.AppendList(["commands.py' ", test_file_path], 2) 242 # We don't have descriptions for the file path completer: 243 self.handle_completion(cmd_str, 1, matches, descriptions, False) 244 245 # Try a command with arguments. 246 # FIXME: It should be enough to define an argument and it's type to get the completer 247 # wired up for that argument type if it is a known type. But that isn't wired up in the 248 # command parser yet, so I don't have any tests for that. We also don't currently check 249 # that the arguments passed match the argument specifications, so here I just pass a couple 250 # sets of arguments and make sure we get back what we put in: 251 self.expect( 252 "two-args 'First Argument' 'Second Argument'", 253 substrs=["0: First Argument", "1: Second Argument"], 254 ) 255 256 # Now test custom completions - two-args has both option and arg completers. In both 257 # completers we return different values if the -p option is set, so we can test that too: 258 matches.Clear() 259 descriptions.Clear() 260 cmd_str = "two-args -p something -c other_" 261 matches.AppendString("something ") 262 matches.AppendString("other_something") 263 # This is a full match so no descriptions: 264 self.handle_completion(cmd_str, 1, matches, descriptions, False) 265 266 matches.Clear() 267 descriptions.Clear() 268 cmd_str = "two-args -c other_" 269 matches.AppendList(["", "other_nice", "other_not_nice", "other_mediocre"], 4) 270 # The option doesn't return descriptions either: 271 self.handle_completion(cmd_str, 3, matches, descriptions, False) 272 273 # Now try the argument - it says "no completions" if the proc_name was set: 274 matches.Clear() 275 descriptions.Clear() 276 cmd_str = "two-args -p something arg" 277 matches.AppendString("") 278 self.handle_completion(cmd_str, 0, matches, descriptions, False) 279 280 cmd_str = "two-args arg_" 281 matches.Clear() 282 descriptions.Clear() 283 matches.AppendList(["", "arg_cool", "arg_yuck"], 3) 284 descriptions.AppendList(["", "good idea", "bad idea"], 3) 285 self.handle_completion(cmd_str, 2, matches, descriptions, True) 286 287 # This one gets a single unique match: 288 cmd_str = "two-args correct_" 289 matches.Clear() 290 descriptions.Clear() 291 matches.AppendList(["answer ", "correct_answer"], 2) 292 self.handle_completion(cmd_str, 1, matches, descriptions, False) 293 294 # Now make sure get_repeat_command works properly: 295 296 # no-args turns off auto-repeat 297 results = self.run_one_repeat("no-args\n\n", 1) 298 self.assertIn("No auto repeat", results, "Got auto-repeat error") 299 300 # one-args does the normal repeat 301 results = self.run_one_repeat("one-arg-no-opt ONE_ARG\n\n", 0) 302 self.assertEqual(results.count("ONE_ARG"), 2, "We did a normal repeat") 303 304 # two-args adds an argument: 305 results = self.run_one_repeat("two-args FIRST_ARG SECOND_ARG\n\n", 0) 306 self.assertEqual( 307 results.count("FIRST_ARG"), 2, "Passed first arg to both commands" 308 ) 309 self.assertEqual( 310 results.count("SECOND_ARG"), 2, "Passed second arg to both commands" 311 ) 312 self.assertEqual(results.count("THIRD_ARG"), 1, "Passed third arg in repeat") 313