xref: /llvm-project/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py (revision c2d061da7e17e61d4a0efad261e5280793c1b7ce)
199451b44SJordan Rupprecht"""Test the SBCommandInterpreter APIs."""
299451b44SJordan Rupprecht
3e8dc8d61Sroyitaqiimport json
499451b44SJordan Rupprechtimport lldb
599451b44SJordan Rupprechtfrom lldbsuite.test.decorators import *
699451b44SJordan Rupprechtfrom lldbsuite.test.lldbtest import *
799451b44SJordan Rupprechtfrom lldbsuite.test import lldbutil
899451b44SJordan Rupprecht
999451b44SJordan Rupprecht
1099451b44SJordan Rupprechtclass CommandInterpreterAPICase(TestBase):
1199451b44SJordan Rupprecht    NO_DEBUG_INFO_TESTCASE = True
1299451b44SJordan Rupprecht
1399451b44SJordan Rupprecht    def setUp(self):
1499451b44SJordan Rupprecht        # Call super's setUp().
1599451b44SJordan Rupprecht        TestBase.setUp(self)
1699451b44SJordan Rupprecht        # Find the line number to break on inside main.cpp.
172238dcc3SJonas Devlieghere        self.line = line_number("main.c", "Hello world.")
1899451b44SJordan Rupprecht
19e8dc8d61Sroyitaqi    def buildAndCreateTarget(self):
2099451b44SJordan Rupprecht        self.build()
2199451b44SJordan Rupprecht        exe = self.getBuildArtifact("a.out")
2299451b44SJordan Rupprecht
2399451b44SJordan Rupprecht        # Create a target by the debugger.
2499451b44SJordan Rupprecht        target = self.dbg.CreateTarget(exe)
2599451b44SJordan Rupprecht        self.assertTrue(target, VALID_TARGET)
2699451b44SJordan Rupprecht
2799451b44SJordan Rupprecht        # Retrieve the associated command interpreter from our debugger.
2899451b44SJordan Rupprecht        ci = self.dbg.GetCommandInterpreter()
2999451b44SJordan Rupprecht        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
30e8dc8d61Sroyitaqi        return ci
31e8dc8d61Sroyitaqi
32e8dc8d61Sroyitaqi    def test_with_process_launch_api(self):
33e8dc8d61Sroyitaqi        """Test the SBCommandInterpreter APIs."""
34e8dc8d61Sroyitaqi        ci = self.buildAndCreateTarget()
3599451b44SJordan Rupprecht
3699451b44SJordan Rupprecht        # Exercise some APIs....
3799451b44SJordan Rupprecht
3899451b44SJordan Rupprecht        self.assertTrue(ci.HasCommands())
3999451b44SJordan Rupprecht        self.assertTrue(ci.HasAliases())
4099451b44SJordan Rupprecht        self.assertTrue(ci.HasAliasOptions())
4199451b44SJordan Rupprecht        self.assertTrue(ci.CommandExists("breakpoint"))
4299451b44SJordan Rupprecht        self.assertTrue(ci.CommandExists("target"))
4399451b44SJordan Rupprecht        self.assertTrue(ci.CommandExists("platform"))
4499451b44SJordan Rupprecht        self.assertTrue(ci.AliasExists("file"))
4599451b44SJordan Rupprecht        self.assertTrue(ci.AliasExists("run"))
4699451b44SJordan Rupprecht        self.assertTrue(ci.AliasExists("bt"))
4799451b44SJordan Rupprecht
4899451b44SJordan Rupprecht        res = lldb.SBCommandReturnObject()
4999451b44SJordan Rupprecht        ci.HandleCommand("breakpoint set -f main.c -l %d" % self.line, res)
5099451b44SJordan Rupprecht        self.assertTrue(res.Succeeded())
5199451b44SJordan Rupprecht        ci.HandleCommand("process launch", res)
5299451b44SJordan Rupprecht        self.assertTrue(res.Succeeded())
5399451b44SJordan Rupprecht
5499451b44SJordan Rupprecht        # Boundary conditions should not crash lldb!
5599451b44SJordan Rupprecht        self.assertFalse(ci.CommandExists(None))
5699451b44SJordan Rupprecht        self.assertFalse(ci.AliasExists(None))
5799451b44SJordan Rupprecht        ci.HandleCommand(None, res)
5899451b44SJordan Rupprecht        self.assertFalse(res.Succeeded())
5999451b44SJordan Rupprecht        res.AppendMessage("Just appended a message.")
6099451b44SJordan Rupprecht        res.AppendMessage(None)
6199451b44SJordan Rupprecht        if self.TraceOn():
6299451b44SJordan Rupprecht            print(res)
6399451b44SJordan Rupprecht
6499451b44SJordan Rupprecht        process = ci.GetProcess()
6599451b44SJordan Rupprecht        self.assertTrue(process)
6699451b44SJordan Rupprecht
6799451b44SJordan Rupprecht        import lldbsuite.test.lldbutil as lldbutil
682238dcc3SJonas Devlieghere
6999451b44SJordan Rupprecht        if process.GetState() != lldb.eStateStopped:
702238dcc3SJonas Devlieghere            self.fail(
712238dcc3SJonas Devlieghere                "Process should be in the 'stopped' state, "
722238dcc3SJonas Devlieghere                "instead the actual state is: '%s'"
732238dcc3SJonas Devlieghere                % lldbutil.state_type_to_str(process.GetState())
742238dcc3SJonas Devlieghere            )
7599451b44SJordan Rupprecht
7699451b44SJordan Rupprecht        if self.TraceOn():
7799451b44SJordan Rupprecht            lldbutil.print_stacktraces(process)
7899451b44SJordan Rupprecht
7999451b44SJordan Rupprecht    def test_command_output(self):
8099451b44SJordan Rupprecht        """Test command output handling."""
8199451b44SJordan Rupprecht        ci = self.dbg.GetCommandInterpreter()
8299451b44SJordan Rupprecht        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
8399451b44SJordan Rupprecht
8499451b44SJordan Rupprecht        # Test that a command which produces no output returns "" instead of
8599451b44SJordan Rupprecht        # None.
8699451b44SJordan Rupprecht        res = lldb.SBCommandReturnObject()
8799451b44SJordan Rupprecht        ci.HandleCommand("settings set use-color false", res)
8899451b44SJordan Rupprecht        self.assertTrue(res.Succeeded())
8999451b44SJordan Rupprecht        self.assertIsNotNone(res.GetOutput())
9080fcecb1SJonas Devlieghere        self.assertEqual(res.GetOutput(), "")
9199451b44SJordan Rupprecht        self.assertIsNotNone(res.GetError())
9280fcecb1SJonas Devlieghere        self.assertEqual(res.GetError(), "")
93e8dc8d61Sroyitaqi
94e8dc8d61Sroyitaqi    def getTranscriptAsPythonObject(self, ci):
95e8dc8d61Sroyitaqi        """Retrieve the transcript and convert it into a Python object"""
96e8dc8d61Sroyitaqi        structured_data = ci.GetTranscript()
97e8dc8d61Sroyitaqi        self.assertTrue(structured_data.IsValid())
98e8dc8d61Sroyitaqi
99e8dc8d61Sroyitaqi        stream = lldb.SBStream()
100e8dc8d61Sroyitaqi        self.assertTrue(stream)
101e8dc8d61Sroyitaqi
102e8dc8d61Sroyitaqi        error = structured_data.GetAsJSON(stream)
103e8dc8d61Sroyitaqi        self.assertSuccess(error)
104e8dc8d61Sroyitaqi
105e8dc8d61Sroyitaqi        return json.loads(stream.GetData())
106e8dc8d61Sroyitaqi
107*c2d061daSroyitaqi    def test_get_transcript(self):
108e8dc8d61Sroyitaqi        """Test structured transcript generation and retrieval."""
109e8dc8d61Sroyitaqi        ci = self.buildAndCreateTarget()
110*c2d061daSroyitaqi        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
111e8dc8d61Sroyitaqi
112e8dc8d61Sroyitaqi        # Make sure the "save-transcript" setting is on
113e8dc8d61Sroyitaqi        self.runCmd("settings set interpreter.save-transcript true")
114e8dc8d61Sroyitaqi
115e8dc8d61Sroyitaqi        # Send a few commands through the command interpreter.
116e8dc8d61Sroyitaqi        #
117e8dc8d61Sroyitaqi        # Using `ci.HandleCommand` because some commands will fail so that we
118e8dc8d61Sroyitaqi        # can test the "error" field in the saved transcript.
119e8dc8d61Sroyitaqi        res = lldb.SBCommandReturnObject()
120e8dc8d61Sroyitaqi        ci.HandleCommand("version", res)
121e8dc8d61Sroyitaqi        ci.HandleCommand("an-unknown-command", res)
122*c2d061daSroyitaqi        ci.HandleCommand("br s -f main.c -l %d" % self.line, res)
123e8dc8d61Sroyitaqi        ci.HandleCommand("p a", res)
124e8dc8d61Sroyitaqi        ci.HandleCommand("statistics dump", res)
125e8dc8d61Sroyitaqi        total_number_of_commands = 6
126e8dc8d61Sroyitaqi
127e8dc8d61Sroyitaqi        # Get transcript as python object
128e8dc8d61Sroyitaqi        transcript = self.getTranscriptAsPythonObject(ci)
129e8dc8d61Sroyitaqi
130e8dc8d61Sroyitaqi        # All commands should have expected fields.
131e8dc8d61Sroyitaqi        for command in transcript:
132e8dc8d61Sroyitaqi            self.assertIn("command", command)
133*c2d061daSroyitaqi            # Unresolved commands don't have "commandName"/"commandArguments".
134*c2d061daSroyitaqi            # We will validate these fields below, instead of here.
135e8dc8d61Sroyitaqi            self.assertIn("output", command)
136e8dc8d61Sroyitaqi            self.assertIn("error", command)
137*c2d061daSroyitaqi            self.assertIn("durationInSeconds", command)
138*c2d061daSroyitaqi            self.assertIn("timestampInEpochSeconds", command)
139e8dc8d61Sroyitaqi
140e8dc8d61Sroyitaqi        # The following validates individual commands in the transcript.
141e8dc8d61Sroyitaqi        #
142e8dc8d61Sroyitaqi        # Notes:
143e8dc8d61Sroyitaqi        # 1. Some of the asserts rely on the exact output format of the
144e8dc8d61Sroyitaqi        #    commands. Hopefully we are not changing them any time soon.
145*c2d061daSroyitaqi        # 2. We are removing the time-related fields from each command, so
146*c2d061daSroyitaqi        #    that some of the validations below can be easier / more readable.
147e8dc8d61Sroyitaqi        for command in transcript:
148*c2d061daSroyitaqi            del command["durationInSeconds"]
149*c2d061daSroyitaqi            del command["timestampInEpochSeconds"]
150e8dc8d61Sroyitaqi
151e8dc8d61Sroyitaqi        # (lldb) version
152e8dc8d61Sroyitaqi        self.assertEqual(transcript[0]["command"], "version")
153*c2d061daSroyitaqi        self.assertEqual(transcript[0]["commandName"], "version")
154*c2d061daSroyitaqi        self.assertEqual(transcript[0]["commandArguments"], "")
155e8dc8d61Sroyitaqi        self.assertIn("lldb version", transcript[0]["output"])
156e8dc8d61Sroyitaqi        self.assertEqual(transcript[0]["error"], "")
157e8dc8d61Sroyitaqi
158e8dc8d61Sroyitaqi        # (lldb) an-unknown-command
159e8dc8d61Sroyitaqi        self.assertEqual(transcript[1],
160e8dc8d61Sroyitaqi            {
161e8dc8d61Sroyitaqi                "command": "an-unknown-command",
162*c2d061daSroyitaqi                # Unresolved commands don't have "commandName"/"commandArguments"
163e8dc8d61Sroyitaqi                "output": "",
164e8dc8d61Sroyitaqi                "error": "error: 'an-unknown-command' is not a valid command.\n",
165e8dc8d61Sroyitaqi            })
166e8dc8d61Sroyitaqi
167*c2d061daSroyitaqi        # (lldb) br s -f main.c -l <line>
168*c2d061daSroyitaqi        self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
169*c2d061daSroyitaqi        self.assertEqual(transcript[2]["commandName"], "breakpoint set")
170*c2d061daSroyitaqi        self.assertEqual(
171*c2d061daSroyitaqi            transcript[2]["commandArguments"], "-f main.c -l %d" % self.line
172*c2d061daSroyitaqi        )
173e8dc8d61Sroyitaqi        # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d
174e8dc8d61Sroyitaqi        self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"])
175e8dc8d61Sroyitaqi        self.assertEqual(transcript[2]["error"], "")
176e8dc8d61Sroyitaqi
177e8dc8d61Sroyitaqi        # (lldb) p a
178*c2d061daSroyitaqi        self.assertEqual(transcript[3],
179e8dc8d61Sroyitaqi            {
180e8dc8d61Sroyitaqi                "command": "p a",
181*c2d061daSroyitaqi                "commandName": "dwim-print",
182*c2d061daSroyitaqi                "commandArguments": "-- a",
183*c2d061daSroyitaqi                "output": "",
184*c2d061daSroyitaqi                "error": "error: <user expression 0>:1:1: use of undeclared identifier 'a'\n    1 | a\n      | ^\n",
185e8dc8d61Sroyitaqi            })
186e8dc8d61Sroyitaqi
187e8dc8d61Sroyitaqi        # (lldb) statistics dump
188*c2d061daSroyitaqi        self.assertEqual(transcript[4]["command"], "statistics dump")
189*c2d061daSroyitaqi        self.assertEqual(transcript[4]["commandName"], "statistics dump")
190*c2d061daSroyitaqi        self.assertEqual(transcript[4]["commandArguments"], "")
191*c2d061daSroyitaqi        self.assertEqual(transcript[4]["error"], "")
192*c2d061daSroyitaqi        statistics_dump = json.loads(transcript[4]["output"])
193e8dc8d61Sroyitaqi        # Dump result should be valid JSON
194e8dc8d61Sroyitaqi        self.assertTrue(statistics_dump is not json.JSONDecodeError)
195e8dc8d61Sroyitaqi        # Dump result should contain expected fields
196e8dc8d61Sroyitaqi        self.assertIn("commands", statistics_dump)
197e8dc8d61Sroyitaqi        self.assertIn("memory", statistics_dump)
198e8dc8d61Sroyitaqi        self.assertIn("modules", statistics_dump)
199e8dc8d61Sroyitaqi        self.assertIn("targets", statistics_dump)
200e8dc8d61Sroyitaqi
201e8dc8d61Sroyitaqi    def test_save_transcript_setting_default(self):
202*c2d061daSroyitaqi        ci = self.dbg.GetCommandInterpreter()
203*c2d061daSroyitaqi        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
204e8dc8d61Sroyitaqi
205e8dc8d61Sroyitaqi        # The setting's default value should be "false"
206e8dc8d61Sroyitaqi        self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n")
207e8dc8d61Sroyitaqi
208e8dc8d61Sroyitaqi    def test_save_transcript_setting_off(self):
209*c2d061daSroyitaqi        ci = self.dbg.GetCommandInterpreter()
210*c2d061daSroyitaqi        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
211e8dc8d61Sroyitaqi
212e8dc8d61Sroyitaqi        # Make sure the setting is off
213e8dc8d61Sroyitaqi        self.runCmd("settings set interpreter.save-transcript false")
214e8dc8d61Sroyitaqi
215e8dc8d61Sroyitaqi        # The transcript should be empty after running a command
216e8dc8d61Sroyitaqi        self.runCmd("version")
217e8dc8d61Sroyitaqi        transcript = self.getTranscriptAsPythonObject(ci)
218e8dc8d61Sroyitaqi        self.assertEqual(transcript, [])
219e8dc8d61Sroyitaqi
220e8dc8d61Sroyitaqi    def test_save_transcript_setting_on(self):
221*c2d061daSroyitaqi        ci = self.dbg.GetCommandInterpreter()
222*c2d061daSroyitaqi        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
223e8dc8d61Sroyitaqi
224e8dc8d61Sroyitaqi        # Make sure the setting is on
225e8dc8d61Sroyitaqi        self.runCmd("settings set interpreter.save-transcript true")
226e8dc8d61Sroyitaqi
227e8dc8d61Sroyitaqi        # The transcript should contain one item after running a command
228e8dc8d61Sroyitaqi        self.runCmd("version")
229e8dc8d61Sroyitaqi        transcript = self.getTranscriptAsPythonObject(ci)
230e8dc8d61Sroyitaqi        self.assertEqual(len(transcript), 1)
231e8dc8d61Sroyitaqi        self.assertEqual(transcript[0]["command"], "version")
232e8dc8d61Sroyitaqi
233*c2d061daSroyitaqi    def test_get_transcript_returns_copy(self):
234e8dc8d61Sroyitaqi        """
235e8dc8d61Sroyitaqi        Test that the returned structured data is *at least* a shallow copy.
236e8dc8d61Sroyitaqi
237e8dc8d61Sroyitaqi        We believe that a deep copy *is* performed in `SBCommandInterpreter::GetTranscript`.
238e8dc8d61Sroyitaqi        However, the deep copy cannot be tested and doesn't need to be tested,
239e8dc8d61Sroyitaqi        because there is no logic in the command interpreter to modify a
240e8dc8d61Sroyitaqi        transcript item (representing a command) after it has been returned.
241e8dc8d61Sroyitaqi        """
242*c2d061daSroyitaqi        ci = self.dbg.GetCommandInterpreter()
243*c2d061daSroyitaqi        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
244e8dc8d61Sroyitaqi
245e8dc8d61Sroyitaqi        # Make sure the setting is on
246e8dc8d61Sroyitaqi        self.runCmd("settings set interpreter.save-transcript true")
247e8dc8d61Sroyitaqi
248e8dc8d61Sroyitaqi        # Run commands and get the transcript as structured data
249e8dc8d61Sroyitaqi        self.runCmd("version")
250e8dc8d61Sroyitaqi        structured_data_1 = ci.GetTranscript()
251e8dc8d61Sroyitaqi        self.assertTrue(structured_data_1.IsValid())
252e8dc8d61Sroyitaqi        self.assertEqual(structured_data_1.GetSize(), 1)
253e8dc8d61Sroyitaqi        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
254e8dc8d61Sroyitaqi
255e8dc8d61Sroyitaqi        # Run some more commands and get the transcript as structured data again
256e8dc8d61Sroyitaqi        self.runCmd("help")
257e8dc8d61Sroyitaqi        structured_data_2 = ci.GetTranscript()
258e8dc8d61Sroyitaqi        self.assertTrue(structured_data_2.IsValid())
259e8dc8d61Sroyitaqi        self.assertEqual(structured_data_2.GetSize(), 2)
260e8dc8d61Sroyitaqi        self.assertEqual(structured_data_2.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
261e8dc8d61Sroyitaqi        self.assertEqual(structured_data_2.GetItemAtIndex(1).GetValueForKey("command").GetStringValue(100), "help")
262e8dc8d61Sroyitaqi
263e8dc8d61Sroyitaqi        # Now, the first structured data should remain unchanged
264e8dc8d61Sroyitaqi        self.assertTrue(structured_data_1.IsValid())
265e8dc8d61Sroyitaqi        self.assertEqual(structured_data_1.GetSize(), 1)
266e8dc8d61Sroyitaqi        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
267