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