| """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": "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") |