"""Test the SBCommandInterpreter APIs.""" import json import lldb from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil class CommandInterpreterAPICase(TestBase): NO_DEBUG_INFO_TESTCASE = True def setUp(self): # Call super's setUp(). TestBase.setUp(self) # Find the line number to break on inside main.cpp. self.line = line_number("main.c", "Hello world.") def buildAndCreateTarget(self): self.build() exe = self.getBuildArtifact("a.out") # Create a target by the debugger. target = self.dbg.CreateTarget(exe) self.assertTrue(target, VALID_TARGET) # Retrieve the associated command interpreter from our debugger. ci = self.dbg.GetCommandInterpreter() self.assertTrue(ci, VALID_COMMAND_INTERPRETER) return ci def test_with_process_launch_api(self): """Test the SBCommandInterpreter APIs.""" ci = self.buildAndCreateTarget() # Exercise some APIs.... self.assertTrue(ci.HasCommands()) self.assertTrue(ci.HasAliases()) self.assertTrue(ci.HasAliasOptions()) self.assertTrue(ci.CommandExists("breakpoint")) self.assertTrue(ci.CommandExists("target")) self.assertTrue(ci.CommandExists("platform")) self.assertTrue(ci.AliasExists("file")) self.assertTrue(ci.AliasExists("run")) self.assertTrue(ci.AliasExists("bt")) res = lldb.SBCommandReturnObject() ci.HandleCommand("breakpoint set -f main.c -l %d" % self.line, res) self.assertTrue(res.Succeeded()) ci.HandleCommand("process launch", res) self.assertTrue(res.Succeeded()) # Boundary conditions should not crash lldb! self.assertFalse(ci.CommandExists(None)) self.assertFalse(ci.AliasExists(None)) ci.HandleCommand(None, res) self.assertFalse(res.Succeeded()) res.AppendMessage("Just appended a message.") res.AppendMessage(None) if self.TraceOn(): print(res) process = ci.GetProcess() self.assertTrue(process) import lldbsuite.test.lldbutil as lldbutil if process.GetState() != lldb.eStateStopped: self.fail( "Process should be in the 'stopped' state, " "instead the actual state is: '%s'" % lldbutil.state_type_to_str(process.GetState()) ) if self.TraceOn(): lldbutil.print_stacktraces(process) def test_command_output(self): """Test command output handling.""" ci = self.dbg.GetCommandInterpreter() self.assertTrue(ci, VALID_COMMAND_INTERPRETER) # Test that a command which produces no output returns "" instead of # None. res = lldb.SBCommandReturnObject() ci.HandleCommand("settings set use-color false", res) self.assertTrue(res.Succeeded()) self.assertIsNotNone(res.GetOutput()) self.assertEqual(res.GetOutput(), "") self.assertIsNotNone(res.GetError()) self.assertEqual(res.GetError(), "") def getTranscriptAsPythonObject(self, ci): """Retrieve the transcript and convert it into a Python object""" structured_data = ci.GetTranscript() self.assertTrue(structured_data.IsValid()) stream = lldb.SBStream() self.assertTrue(stream) error = structured_data.GetAsJSON(stream) self.assertSuccess(error) return json.loads(stream.GetData()) def test_get_transcript(self): """Test structured transcript generation and retrieval.""" ci = self.buildAndCreateTarget() self.assertTrue(ci, VALID_COMMAND_INTERPRETER) # Make sure the "save-transcript" setting is on self.runCmd("settings set interpreter.save-transcript true") # Send a few commands through the command interpreter. # # Using `ci.HandleCommand` because some commands will fail so that we # can test the "error" field in the saved transcript. res = lldb.SBCommandReturnObject() ci.HandleCommand("version", res) ci.HandleCommand("an-unknown-command", res) ci.HandleCommand("br s -f main.c -l %d" % self.line, res) ci.HandleCommand("p a", res) ci.HandleCommand("statistics dump", res) total_number_of_commands = 6 # Get transcript as python object transcript = self.getTranscriptAsPythonObject(ci) # All commands should have expected fields. for command in transcript: self.assertIn("command", command) # Unresolved commands don't have "commandName"/"commandArguments". # We will validate these fields below, instead of here. self.assertIn("output", command) self.assertIn("error", command) self.assertIn("durationInSeconds", command) self.assertIn("timestampInEpochSeconds", command) # The following validates individual commands in the transcript. # # Notes: # 1. Some of the asserts rely on the exact output format of the # commands. Hopefully we are not changing them any time soon. # 2. We are removing the time-related fields from each command, so # that some of the validations below can be easier / more readable. for command in transcript: del command["durationInSeconds"] del command["timestampInEpochSeconds"] # (lldb) version self.assertEqual(transcript[0]["command"], "version") self.assertEqual(transcript[0]["commandName"], "version") self.assertEqual(transcript[0]["commandArguments"], "") self.assertIn("lldb version", transcript[0]["output"]) self.assertEqual(transcript[0]["error"], "") # (lldb) an-unknown-command self.assertEqual(transcript[1], { "command": "an-unknown-command", # Unresolved commands don't have "commandName"/"commandArguments" "output": "", "error": "error: 'an-unknown-command' is not a valid command.\n", }) # (lldb) br s -f main.c -l self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line) self.assertEqual(transcript[2]["commandName"], "breakpoint set") self.assertEqual( transcript[2]["commandArguments"], "-f main.c -l %d" % self.line ) # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"]) self.assertEqual(transcript[2]["error"], "") # (lldb) p a self.assertEqual(transcript[3], { "command": "p a", "commandName": "dwim-print", "commandArguments": "-- a", "output": "", "error": "error: :1:1: use of undeclared identifier 'a'\n 1 | a\n | ^\n", }) # (lldb) statistics dump self.assertEqual(transcript[4]["command"], "statistics dump") self.assertEqual(transcript[4]["commandName"], "statistics dump") self.assertEqual(transcript[4]["commandArguments"], "") self.assertEqual(transcript[4]["error"], "") statistics_dump = json.loads(transcript[4]["output"]) # Dump result should be valid JSON self.assertTrue(statistics_dump is not json.JSONDecodeError) # Dump result should contain expected fields self.assertIn("commands", statistics_dump) self.assertIn("memory", statistics_dump) self.assertIn("modules", statistics_dump) self.assertIn("targets", statistics_dump) def test_save_transcript_setting_default(self): ci = self.dbg.GetCommandInterpreter() self.assertTrue(ci, VALID_COMMAND_INTERPRETER) # The setting's default value should be "false" self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n") def test_save_transcript_setting_off(self): ci = self.dbg.GetCommandInterpreter() self.assertTrue(ci, VALID_COMMAND_INTERPRETER) # Make sure the setting is off self.runCmd("settings set interpreter.save-transcript false") # The transcript should be empty after running a command self.runCmd("version") transcript = self.getTranscriptAsPythonObject(ci) self.assertEqual(transcript, []) def test_save_transcript_setting_on(self): ci = self.dbg.GetCommandInterpreter() self.assertTrue(ci, VALID_COMMAND_INTERPRETER) # Make sure the setting is on self.runCmd("settings set interpreter.save-transcript true") # The transcript should contain one item after running a command self.runCmd("version") transcript = self.getTranscriptAsPythonObject(ci) self.assertEqual(len(transcript), 1) self.assertEqual(transcript[0]["command"], "version") def test_get_transcript_returns_copy(self): """ Test that the returned structured data is *at least* a shallow copy. We believe that a deep copy *is* performed in `SBCommandInterpreter::GetTranscript`. However, the deep copy cannot be tested and doesn't need to be tested, because there is no logic in the command interpreter to modify a transcript item (representing a command) after it has been returned. """ ci = self.dbg.GetCommandInterpreter() self.assertTrue(ci, VALID_COMMAND_INTERPRETER) # Make sure the setting is on self.runCmd("settings set interpreter.save-transcript true") # Run commands and get the transcript as structured data self.runCmd("version") structured_data_1 = ci.GetTranscript() self.assertTrue(structured_data_1.IsValid()) self.assertEqual(structured_data_1.GetSize(), 1) self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version") # Run some more commands and get the transcript as structured data again self.runCmd("help") structured_data_2 = ci.GetTranscript() self.assertTrue(structured_data_2.IsValid()) self.assertEqual(structured_data_2.GetSize(), 2) self.assertEqual(structured_data_2.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version") self.assertEqual(structured_data_2.GetItemAtIndex(1).GetValueForKey("command").GetStringValue(100), "help") # Now, the first structured data should remain unchanged self.assertTrue(structured_data_1.IsValid()) self.assertEqual(structured_data_1.GetSize(), 1) self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")