Depends on: * https://github.com/llvm/llvm-project/pull/162050 Since it's a 'Note' diagnostic it would only show up when expression evaluation actually failed. This helps with expression evaluation failure reports in mixed language environments where it's not quite clear what language the expression ran as. It may also reduce confusion around why the expression evaluator ran an expression in a language it wasn't asked to run (a softer alternative to what I attempted in https://github.com/llvm/llvm-project/pull/156648). Here are some example outputs: ``` # Without target (lldb) expr blah note: Falling back to default language. Ran expression as 'Objective C++'. # Stopped in target (lldb) expr blah note: Ran expression as 'C++14'. (lldb) expr -l objc -- blah note: Expression evaluation in pure Objective-C not supported. Ran expression as 'Objective C++'. (lldb) expr -l c -- blah note: Expression evaluation in pure C not supported. Ran expression as 'ISO C++'. (lldb) expr -l c++14 -- blah note: Ran expression as 'C++14' (lldb) expr -l c++20 -- blah note: Ran expression as 'C++20' (lldb) expr -l objective-c++ -- blah note: Ran expression as 'Objective C++' (lldb) expr -l D -- blah note: Expression evaluation in D not supported. Falling back to default language. Ran expression as 'Objective C++'. ``` I didn't put the diagnostic on the same line as the inline diagnostic for now because of implementation convenience, but if reviewers deem that a blocker I can take a stab at that again. Also, other language plugins (namely Swift), won't immediately benefit from this and will have to emit their own diagnistc. I played around with having a virtual API on `UserExpression` or `ExpressionParser` that will be called consistently, but by the time we're about to parse the expression we are already several frames deep into the plugin. Before (and at the beginning of) the generic `UserExpression::Parse` call we don't have enough information to notify which language we're going to parse in (at least for the C++ plugin). rdar://160297649 rdar://159669244
295 lines
11 KiB
Python
295 lines
11 KiB
Python
"""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",
|
|
)
|