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