xref: /llvm-project/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py (revision e8dc8d614ada201e250fbf075241b2b6180943b5)
1"""Test the SBCommandInterpreter APIs."""
2
3import json
4import lldb
5from lldbsuite.test.decorators import *
6from lldbsuite.test.lldbtest import *
7from lldbsuite.test import lldbutil
8
9
10class CommandInterpreterAPICase(TestBase):
11    NO_DEBUG_INFO_TESTCASE = True
12
13    def setUp(self):
14        # Call super's setUp().
15        TestBase.setUp(self)
16        # Find the line number to break on inside main.cpp.
17        self.line = line_number("main.c", "Hello world.")
18
19    def buildAndCreateTarget(self):
20        self.build()
21        exe = self.getBuildArtifact("a.out")
22
23        # Create a target by the debugger.
24        target = self.dbg.CreateTarget(exe)
25        self.assertTrue(target, VALID_TARGET)
26
27        # Retrieve the associated command interpreter from our debugger.
28        ci = self.dbg.GetCommandInterpreter()
29        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
30        return ci
31
32    def test_with_process_launch_api(self):
33        """Test the SBCommandInterpreter APIs."""
34        ci = self.buildAndCreateTarget()
35
36        # Exercise some APIs....
37
38        self.assertTrue(ci.HasCommands())
39        self.assertTrue(ci.HasAliases())
40        self.assertTrue(ci.HasAliasOptions())
41        self.assertTrue(ci.CommandExists("breakpoint"))
42        self.assertTrue(ci.CommandExists("target"))
43        self.assertTrue(ci.CommandExists("platform"))
44        self.assertTrue(ci.AliasExists("file"))
45        self.assertTrue(ci.AliasExists("run"))
46        self.assertTrue(ci.AliasExists("bt"))
47
48        res = lldb.SBCommandReturnObject()
49        ci.HandleCommand("breakpoint set -f main.c -l %d" % self.line, res)
50        self.assertTrue(res.Succeeded())
51        ci.HandleCommand("process launch", res)
52        self.assertTrue(res.Succeeded())
53
54        # Boundary conditions should not crash lldb!
55        self.assertFalse(ci.CommandExists(None))
56        self.assertFalse(ci.AliasExists(None))
57        ci.HandleCommand(None, res)
58        self.assertFalse(res.Succeeded())
59        res.AppendMessage("Just appended a message.")
60        res.AppendMessage(None)
61        if self.TraceOn():
62            print(res)
63
64        process = ci.GetProcess()
65        self.assertTrue(process)
66
67        import lldbsuite.test.lldbutil as lldbutil
68
69        if process.GetState() != lldb.eStateStopped:
70            self.fail(
71                "Process should be in the 'stopped' state, "
72                "instead the actual state is: '%s'"
73                % lldbutil.state_type_to_str(process.GetState())
74            )
75
76        if self.TraceOn():
77            lldbutil.print_stacktraces(process)
78
79    def test_command_output(self):
80        """Test command output handling."""
81        ci = self.dbg.GetCommandInterpreter()
82        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
83
84        # Test that a command which produces no output returns "" instead of
85        # None.
86        res = lldb.SBCommandReturnObject()
87        ci.HandleCommand("settings set use-color false", res)
88        self.assertTrue(res.Succeeded())
89        self.assertIsNotNone(res.GetOutput())
90        self.assertEqual(res.GetOutput(), "")
91        self.assertIsNotNone(res.GetError())
92        self.assertEqual(res.GetError(), "")
93
94    def getTranscriptAsPythonObject(self, ci):
95        """Retrieve the transcript and convert it into a Python object"""
96        structured_data = ci.GetTranscript()
97        self.assertTrue(structured_data.IsValid())
98
99        stream = lldb.SBStream()
100        self.assertTrue(stream)
101
102        error = structured_data.GetAsJSON(stream)
103        self.assertSuccess(error)
104
105        return json.loads(stream.GetData())
106
107    def test_structured_transcript(self):
108        """Test structured transcript generation and retrieval."""
109        ci = self.buildAndCreateTarget()
110
111        # Make sure the "save-transcript" setting is on
112        self.runCmd("settings set interpreter.save-transcript true")
113
114        # Send a few commands through the command interpreter.
115        #
116        # Using `ci.HandleCommand` because some commands will fail so that we
117        # can test the "error" field in the saved transcript.
118        res = lldb.SBCommandReturnObject()
119        ci.HandleCommand("version", res)
120        ci.HandleCommand("an-unknown-command", res)
121        ci.HandleCommand("breakpoint set -f main.c -l %d" % self.line, res)
122        ci.HandleCommand("r", res)
123        ci.HandleCommand("p a", res)
124        ci.HandleCommand("statistics dump", res)
125        total_number_of_commands = 6
126
127        # Get transcript as python object
128        transcript = self.getTranscriptAsPythonObject(ci)
129
130        # All commands should have expected fields.
131        for command in transcript:
132            self.assertIn("command", command)
133            self.assertIn("output", command)
134            self.assertIn("error", command)
135            self.assertIn("seconds", command)
136
137        # The following validates individual commands in the transcript.
138        #
139        # Notes:
140        # 1. Some of the asserts rely on the exact output format of the
141        #    commands. Hopefully we are not changing them any time soon.
142        # 2. We are removing the "seconds" field from each command, so that
143        #    some of the validations below can be easier / more readable.
144        for command in transcript:
145            del(command["seconds"])
146
147        # (lldb) version
148        self.assertEqual(transcript[0]["command"], "version")
149        self.assertIn("lldb version", transcript[0]["output"])
150        self.assertEqual(transcript[0]["error"], "")
151
152        # (lldb) an-unknown-command
153        self.assertEqual(transcript[1],
154            {
155                "command": "an-unknown-command",
156                "output": "",
157                "error": "error: 'an-unknown-command' is not a valid command.\n",
158            })
159
160        # (lldb) breakpoint set -f main.c -l <line>
161        self.assertEqual(transcript[2]["command"], "breakpoint set -f main.c -l %d" % self.line)
162        # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d
163        self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"])
164        self.assertEqual(transcript[2]["error"], "")
165
166        # (lldb) r
167        self.assertEqual(transcript[3]["command"], "r")
168        # Process 25494 launched: '<path>/TestCommandInterpreterAPI.test_structured_transcript/a.out' (x86_64)
169        self.assertIn("Process", transcript[3]["output"])
170        self.assertIn("launched", transcript[3]["output"])
171        self.assertEqual(transcript[3]["error"], "")
172
173        # (lldb) p a
174        self.assertEqual(transcript[4],
175            {
176                "command": "p a",
177                "output": "(int) 123\n",
178                "error": "",
179            })
180
181        # (lldb) statistics dump
182        statistics_dump = json.loads(transcript[5]["output"])
183        # Dump result should be valid JSON
184        self.assertTrue(statistics_dump is not json.JSONDecodeError)
185        # Dump result should contain expected fields
186        self.assertIn("commands", statistics_dump)
187        self.assertIn("memory", statistics_dump)
188        self.assertIn("modules", statistics_dump)
189        self.assertIn("targets", statistics_dump)
190
191    def test_save_transcript_setting_default(self):
192        ci = self.buildAndCreateTarget()
193        res = lldb.SBCommandReturnObject()
194
195        # The setting's default value should be "false"
196        self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n")
197        # self.assertEqual(res.GetOutput(), )
198
199    def test_save_transcript_setting_off(self):
200        ci = self.buildAndCreateTarget()
201
202        # Make sure the setting is off
203        self.runCmd("settings set interpreter.save-transcript false")
204
205        # The transcript should be empty after running a command
206        self.runCmd("version")
207        transcript = self.getTranscriptAsPythonObject(ci)
208        self.assertEqual(transcript, [])
209
210    def test_save_transcript_setting_on(self):
211        ci = self.buildAndCreateTarget()
212        res = lldb.SBCommandReturnObject()
213
214        # Make sure the setting is on
215        self.runCmd("settings set interpreter.save-transcript true")
216
217        # The transcript should contain one item after running a command
218        self.runCmd("version")
219        transcript = self.getTranscriptAsPythonObject(ci)
220        self.assertEqual(len(transcript), 1)
221        self.assertEqual(transcript[0]["command"], "version")
222
223    def test_save_transcript_returns_copy(self):
224        """
225        Test that the returned structured data is *at least* a shallow copy.
226
227        We believe that a deep copy *is* performed in `SBCommandInterpreter::GetTranscript`.
228        However, the deep copy cannot be tested and doesn't need to be tested,
229        because there is no logic in the command interpreter to modify a
230        transcript item (representing a command) after it has been returned.
231        """
232        ci = self.buildAndCreateTarget()
233
234        # Make sure the setting is on
235        self.runCmd("settings set interpreter.save-transcript true")
236
237        # Run commands and get the transcript as structured data
238        self.runCmd("version")
239        structured_data_1 = ci.GetTranscript()
240        self.assertTrue(structured_data_1.IsValid())
241        self.assertEqual(structured_data_1.GetSize(), 1)
242        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
243
244        # Run some more commands and get the transcript as structured data again
245        self.runCmd("help")
246        structured_data_2 = ci.GetTranscript()
247        self.assertTrue(structured_data_2.IsValid())
248        self.assertEqual(structured_data_2.GetSize(), 2)
249        self.assertEqual(structured_data_2.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
250        self.assertEqual(structured_data_2.GetItemAtIndex(1).GetValueForKey("command").GetStringValue(100), "help")
251
252        # Now, the first structured data should remain unchanged
253        self.assertTrue(structured_data_1.IsValid())
254        self.assertEqual(structured_data_1.GetSize(), 1)
255        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
256