xref: /llvm-project/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py (revision 04b443e77845cd20ab5acc4356cee509316135dd)
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