295 lines · python
1"""tESt the SBCommandInterpreter APIs."""2 3import json4import lldb5from lldbsuite.test.decorators import *6from lldbsuite.test.lldbtest import *7from lldbsuite.test import lldbutil8 9 10class CommandInterpreterAPICase(TestBase):11 NO_DEBUG_INFO_TESTCASE = True12 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 ci31 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 lldbutil68 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 of85 # 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_get_transcript(self):108 """Test structured transcript generation and retrieval."""109 ci = self.buildAndCreateTarget()110 self.assertTrue(ci, VALID_COMMAND_INTERPRETER)111 112 # Make sure the "save-transcript" setting is on113 self.runCmd("settings set interpreter.save-transcript true")114 115 # Send a few commands through the command interpreter.116 #117 # Using `ci.HandleCommand` because some commands will fail so that we118 # can test the "error" field in the saved transcript.119 res = lldb.SBCommandReturnObject()120 ci.HandleCommand("version", res)121 ci.HandleCommand("an-unknown-command", res)122 ci.HandleCommand("br s -f main.c -l %d" % self.line, res)123 ci.HandleCommand("p a", res)124 ci.HandleCommand("statistics dump", res)125 total_number_of_commands = 6126 127 # Get transcript as python object128 transcript = self.getTranscriptAsPythonObject(ci)129 130 # All commands should have expected fields.131 for command in transcript:132 self.assertIn("command", command)133 # Unresolved commands don't have "commandName"/"commandArguments".134 # We will validate these fields below, instead of here.135 self.assertIn("output", command)136 self.assertIn("error", command)137 self.assertIn("durationInSeconds", command)138 self.assertIn("timestampInEpochSeconds", command)139 140 # The following validates individual commands in the transcript.141 #142 # Notes:143 # 1. Some of the asserts rely on the exact output format of the144 # commands. Hopefully we are not changing them any time soon.145 # 2. We are removing the time-related fields from each command, so146 # that some of the validations below can be easier / more readable.147 for command in transcript:148 del command["durationInSeconds"]149 del command["timestampInEpochSeconds"]150 151 # (lldb) version152 self.assertEqual(transcript[0]["command"], "version")153 self.assertEqual(transcript[0]["commandName"], "version")154 self.assertEqual(transcript[0]["commandArguments"], "")155 self.assertIn("lldb version", transcript[0]["output"])156 self.assertEqual(transcript[0]["error"], "")157 158 # (lldb) an-unknown-command159 self.assertEqual(160 transcript[1],161 {162 "command": "an-unknown-command",163 # Unresolved commands don't have "commandName"/"commandArguments"164 "output": "",165 "error": "error: 'an-unknown-command' is not a valid command.\n",166 },167 )168 169 # (lldb) br s -f main.c -l <line>170 self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)171 self.assertEqual(transcript[2]["commandName"], "breakpoint set")172 self.assertEqual(173 transcript[2]["commandArguments"], "-f main.c -l %d" % self.line174 )175 # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d176 self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"])177 self.assertEqual(transcript[2]["error"], "")178 179 # (lldb) p a180 self.assertEqual(181 transcript[3],182 {183 "command": "p a",184 "commandName": "dwim-print",185 "commandArguments": "-- a",186 "output": "",187 "error": "note: Falling back to default language. Ran expression as 'Objective C++'.\n"188 "error: <user expression 0>:1:1: use of undeclared identifier 'a'\n 1 | a\n | ^\n",189 },190 )191 192 # (lldb) statistics dump193 self.assertEqual(transcript[4]["command"], "statistics dump")194 self.assertEqual(transcript[4]["commandName"], "statistics dump")195 self.assertEqual(transcript[4]["commandArguments"], "")196 self.assertEqual(transcript[4]["error"], "")197 statistics_dump = json.loads(transcript[4]["output"])198 # Dump result should be valid JSON199 self.assertTrue(statistics_dump is not json.JSONDecodeError)200 # Dump result should contain expected fields201 self.assertIn("commands", statistics_dump)202 self.assertIn("memory", statistics_dump)203 self.assertIn("modules", statistics_dump)204 self.assertIn("targets", statistics_dump)205 206 def test_save_transcript_setting_default(self):207 ci = self.dbg.GetCommandInterpreter()208 self.assertTrue(ci, VALID_COMMAND_INTERPRETER)209 210 # The setting's default value should be "false"211 self.runCmd(212 "settings show interpreter.save-transcript",213 "interpreter.save-transcript (boolean) = false\n",214 )215 216 def test_save_transcript_setting_off(self):217 ci = self.dbg.GetCommandInterpreter()218 self.assertTrue(ci, VALID_COMMAND_INTERPRETER)219 220 # Make sure the setting is off221 self.runCmd("settings set interpreter.save-transcript false")222 223 # The transcript should be empty after running a command224 self.runCmd("version")225 transcript = self.getTranscriptAsPythonObject(ci)226 self.assertEqual(transcript, [])227 228 def test_save_transcript_setting_on(self):229 ci = self.dbg.GetCommandInterpreter()230 self.assertTrue(ci, VALID_COMMAND_INTERPRETER)231 232 # Make sure the setting is on233 self.runCmd("settings set interpreter.save-transcript true")234 235 # The transcript should contain one item after running a command236 self.runCmd("version")237 transcript = self.getTranscriptAsPythonObject(ci)238 self.assertEqual(len(transcript), 1)239 self.assertEqual(transcript[0]["command"], "version")240 241 def test_get_transcript_returns_copy(self):242 """243 Test that the returned structured data is *at least* a shallow copy.244 245 We believe that a deep copy *is* performed in `SBCommandInterpreter::GetTranscript`.246 However, the deep copy cannot be tested and doesn't need to be tested,247 because there is no logic in the command interpreter to modify a248 transcript item (representing a command) after it has been returned.249 """250 ci = self.dbg.GetCommandInterpreter()251 self.assertTrue(ci, VALID_COMMAND_INTERPRETER)252 253 # Make sure the setting is on254 self.runCmd("settings set interpreter.save-transcript true")255 256 # Run commands and get the transcript as structured data257 self.runCmd("version")258 structured_data_1 = ci.GetTranscript()259 self.assertTrue(structured_data_1.IsValid())260 self.assertEqual(structured_data_1.GetSize(), 1)261 self.assertEqual(262 structured_data_1.GetItemAtIndex(0)263 .GetValueForKey("command")264 .GetStringValue(100),265 "version",266 )267 268 # Run some more commands and get the transcript as structured data again269 self.runCmd("help")270 structured_data_2 = ci.GetTranscript()271 self.assertTrue(structured_data_2.IsValid())272 self.assertEqual(structured_data_2.GetSize(), 2)273 self.assertEqual(274 structured_data_2.GetItemAtIndex(0)275 .GetValueForKey("command")276 .GetStringValue(100),277 "version",278 )279 self.assertEqual(280 structured_data_2.GetItemAtIndex(1)281 .GetValueForKey("command")282 .GetStringValue(100),283 "help",284 )285 286 # Now, the first structured data should remain unchanged287 self.assertTrue(structured_data_1.IsValid())288 self.assertEqual(structured_data_1.GetSize(), 1)289 self.assertEqual(290 structured_data_1.GetItemAtIndex(0)291 .GetValueForKey("command")292 .GetStringValue(100),293 "version",294 )295