| """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 <line> | |
| 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": "note: Falling back to default language. Ran expression as 'Objective C++'.\n" | |
| "error: <user expression 0>: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", | |
| ) |