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