Index: test/tools/lldb-mi/interpreter/TestMiCliSupport.py =================================================================== --- /dev/null +++ test/tools/lldb-mi/interpreter/TestMiCliSupport.py @@ -0,0 +1,211 @@ +""" +Test lldb-mi can interpret CLI commands directly. +""" + +import lldbmi_testcase +from lldbtest import * +import unittest2 + +class MiCliSupportTestCase(lldbmi_testcase.MiTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + def test_lldbmi_target_create(self): + """Test that 'lldb-mi --interpreter' can create target by 'target create' command.""" + + self.spawnLldbMi(args = None) + + # Test that "target create" loads executable + self.runCmd("target create \"%s\"" % self.myexe) + self.expect("\^done") + + # Test that executable was loaded properly + self.runCmd("-break-insert -f main") + self.expect("\^done,bkpt={number=\"1\"") + self.runCmd("-exec-run") + self.expect("\^running") + self.expect("\*stopped,reason=\"breakpoint-hit\"") + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + @skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races + @skipIfLinux # llvm.org/pr22841: lldb-mi tests fail on all Linux buildbots + def test_lldbmi_breakpoint_set(self): + """Test that 'lldb-mi --interpreter' can set breakpoint by 'breakpoint set' command.""" + + self.spawnLldbMi(args = None) + + # Load executable + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + + # Test that "breakpoint set" sets a breakpoint + self.runCmd("breakpoint set --name main") + self.expect("\^done") + + # Test that breakpoint was set properly + self.runCmd("-exec-run") + self.expect("\^running") + self.expect("\*stopped,reason=\"breakpoint-hit\"") + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + @skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races + @skipIfLinux # llvm.org/pr22841: lldb-mi tests fail on all Linux buildbots + def test_lldbmi_settings_set_target_run_args_before(self): + """Test that 'lldb-mi --interpreter' can set target arguments by 'setting set target.run-args' command before than target was created.""" + + self.spawnLldbMi(args = None) + + # Test that "settings set target.run-args" passes arguments to executable + #FIXME: --arg1 causes an error + self.runCmd("setting set target.run-args arg1 \"2nd arg\" third_arg fourth=\"4th arg\"") + self.expect("\^done") + + # Load executable + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + + # Run + self.runCmd("-exec-run") + self.expect("\^running") + + # Test that arguments were passed properly + self.expect("~\"argc=5\\\\r\\\\n\"") + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + @skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races + @skipIfLinux # llvm.org/pr22841: lldb-mi tests fail on all Linux buildbots + def test_lldbmi_settings_set_target_run_args_after(self): + """Test that 'lldb-mi --interpreter' can set target arguments by 'setting set target.run-args' command after than target was created.""" + + self.spawnLldbMi(args = None) + + # Load executable + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + + # Test that "settings set target.run-args" passes arguments to executable + #FIXME: --arg1 causes an error + self.runCmd("setting set target.run-args arg1 \"2nd arg\" third_arg fourth=\"4th arg\"") + self.expect("\^done") + + # Run + self.runCmd("-exec-run") + self.expect("\^running") + + # Test that arguments were passed properly + self.expect("~\"argc=5\\\\r\\\\n\"") + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + @skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races + @skipIfLinux # llvm.org/pr22841: lldb-mi tests fail on all Linux buildbots + def test_lldbmi_process_launch(self): + """Test that 'lldb-mi --interpreter' can launch process by "process launch" command.""" + + self.spawnLldbMi(args = None) + + # Load executable + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + + # Set breakpoint + self.runCmd("-break-insert -f main") + self.expect("\^done,bkpt={number=\"1\"") + + # Test that "process launch" launches executable + self.runCmd("process launch") + self.expect("\^done") + + # Test that breakpoint hit + self.expect("\*stopped,reason=\"breakpoint-hit\"") + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + @skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races + @skipIfLinux # llvm.org/pr22841: lldb-mi tests fail on all Linux buildbots + def test_lldbmi_thread_step_in(self): + """Test that 'lldb-mi --interpreter' can step in by "thread step-in" command.""" + + self.spawnLldbMi(args = None) + + # Load executable + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + + # Run to main + self.runCmd("-break-insert -f main") + self.expect("\^done,bkpt={number=\"1\"") + self.runCmd("-exec-run") + self.expect("\^running") + self.expect("\*stopped,reason=\"breakpoint-hit\"") + + # Test that "thread step-in" steps into (or not) printf depending on debug info + # Note that message is different in Darwin and Linux: + # Darwin: "*stopped,reason=\"end-stepping-range\",frame={addr=\"0x[0-9a-f]+\",func=\"main\",args=[{name=\"argc\",value=\"1\"},{name=\"argv\",value="0x[0-9a-f]+\"}],file=\"main.cpp\",fullname=\".+main.cpp\",line=\"\d\"},thread-id=\"1\",stopped-threads=\"all\" + # Linux: "*stopped,reason=\"end-stepping-range\",frame={addr="0x[0-9a-f]+\",func=\"__printf\",args=[{name=\"format\",value=\"0x[0-9a-f]+\"}],file=\"printf.c\",fullname=\".+printf.c\",line="\d+"},thread-id=\"1\",stopped-threads=\"all\" + self.runCmd("thread step-in") + self.expect("\^done") + it = self.expect([ "~\"argc=1\\\\r\\\\n\"", + "\*stopped,reason=\"end-stepping-range\".+func=\"((?!main).)+\"" ]) + if it == 0: + self.expect("\*stopped,reason=\"end-stepping-range\".+func=\"main\"") + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + @skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races + @skipIfLinux # llvm.org/pr22841: lldb-mi tests fail on all Linux buildbots + def test_lldbmi_thread_step_over(self): + """Test that 'lldb-mi --interpreter' can step over by "thread step-over" command.""" + + self.spawnLldbMi(args = None) + + # Load executable + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + + # Run to main + self.runCmd("-break-insert -f main") + self.expect("\^done,bkpt={number=\"1\"") + self.runCmd("-exec-run") + self.expect("\^running") + self.expect("\*stopped,reason=\"breakpoint-hit\"") + + # Test that "thread step-over" steps over + self.runCmd("thread step-over") + self.expect("\^done") + self.expect("~\"argc=1\\\\r\\\\n\"") + self.expect("\*stopped,reason=\"end-stepping-range\"") + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + @skipIfFreeBSD # llvm.org/pr22411: Failure presumably due to known thread races + @skipIfLinux # llvm.org/pr22841: lldb-mi tests fail on all Linux buildbots + def test_lldbmi_thread_continue(self): + """Test that 'lldb-mi --interpreter' can continue execution by "thread continue" command.""" + + self.spawnLldbMi(args = None) + + # Load executable + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + + # Run to main + self.runCmd("-break-insert -f main") + self.expect("\^done,bkpt={number=\"1\"") + self.runCmd("-exec-run") + self.expect("\^running") + self.expect("\*stopped,reason=\"breakpoint-hit\"") + + # Test that "thread continue" continues execution + self.runCmd("thread continue") + self.expect("\^done") + self.expect("~\"argc=1\\\\r\\\\n") + self.expect("\*stopped,reason=\"exited-normally\"") + +if __name__ == '__main__': + unittest2.main() Index: tools/lldb-mi/MIDriver.h =================================================================== --- tools/lldb-mi/MIDriver.h +++ tools/lldb-mi/MIDriver.h @@ -130,6 +130,7 @@ bool DoAppQuit(void); bool InterpretCommand(const CMIUtilString &vTextLine); bool InterpretCommandThisDriver(const CMIUtilString &vTextLine, bool &vwbCmdYesValid); + CMIUtilString WrapCLICommandIntoMICommand(const CMIUtilString &vTextLine) const; bool InterpretCommandFallThruDriver(const CMIUtilString &vTextLine, bool &vwbCmdYesValid); bool ExecuteCommand(const SMICmdData &vCmdData); bool StartWorkerThreads(void); Index: tools/lldb-mi/MIDriver.cpp =================================================================== --- tools/lldb-mi/MIDriver.cpp +++ tools/lldb-mi/MIDriver.cpp @@ -821,6 +821,79 @@ } //++ ------------------------------------------------------------------------------------ +// Details: Helper function for CMIDriver::InterpretCommandThisDriver. +// Convert a CLI command to MI command (just wrap any CLI command +// into "-interpreter-exec command \"\""). +// Type: Method. +// Args: vTextLine - (R) Text data representing a possible command. +// Return: CMIUtilString - The original MI command or converted CLI command. +// MIstatus::failure - Functional failed. +// Throws: None. +//-- +CMIUtilString +CMIDriver::WrapCLICommandIntoMICommand(const CMIUtilString &vTextLine) const +{ + // Tokens contain following digits + static const CMIUtilString digits("0123456789"); + + // Consider an algorithm on the following example: + // 001-file-exec-and-symbols "/path/to/file" + // + // 1. Skip a command token + // For example: + // 001-file-exec-and-symbols "/path/to/file" + // 001target create "/path/to/file" + // ^ -- command starts here (in both cases) + // Also possible case when command not found: + // 001 + // ^ -- i.e. only tokens are present (or empty string at all) + const MIuint nCommandOffset = vTextLine.find_first_not_of(digits); + + // 2. Check if command is empty + // For example: + // 001-file-exec-and-symbols "/path/to/file" + // 001target create "/path/to/file" + // ^ -- command not empty (in both cases) + // or: + // 001 + // ^ -- command wasn't found + const bool bIsEmptyCommand = (nCommandOffset == (MIuint)CMIUtilString::npos); + + // 3. Check and exit if it isn't a CLI command + // For example: + // 001-file-exec-and-symbols "/path/to/file" + // 001 + // ^ -- it isn't CLI command (in both cases) + // or: + // 001target create "/path/to/file" + // ^ -- it's CLI command + const bool bIsCliCommand = !bIsEmptyCommand && (vTextLine.at(nCommandOffset) != '-'); + if (!bIsCliCommand) + return vTextLine; + + // 4. Wrap CLI command to make it MI-compatible + // + // 001target create "/path/to/file" + // ^^^ -- token + const std::string vToken(vTextLine.begin(), vTextLine.begin() + nCommandOffset); + // 001target create "/path/to/file" + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- CLI command + const CMIUtilString vCliCommand(std::string(vTextLine, nCommandOffset).c_str()); + + // 5. Escape special characters and embed the command in a string + // Result: it looks like -- target create \"/path/to/file\". + const std::string vShieldedCliCommand(vCliCommand.AddSlashes()); + + // 6. Turn the CLI command into an MI command, as in: + // 001-interpreter-exec command "target create \"/path/to/file\"" + // ^^^ -- token + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ -- wrapper + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- shielded CLI command + return CMIUtilString::Format("%s-interpreter-exec command \"%s\"", + vToken.c_str(), vShieldedCliCommand.c_str()); +} + +//++ ------------------------------------------------------------------------------------ // Details: Interpret the text data and match against current commands to see if there // is a match. If a match then the command is issued and actioned on. If a // command cannot be found to match then vwbCmdYesValid is set to false and @@ -828,7 +901,7 @@ // This function is used by the application's main thread. // Type: Method. // Args: vTextLine - (R) Text data representing a possible command. -// vwbCmdYesValid - (W) True = Command invalid, false = command acted on. +// vwbCmdYesValid - (W) True = Command valid, false = command not handled. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. @@ -836,12 +909,14 @@ bool CMIDriver::InterpretCommandThisDriver(const CMIUtilString &vTextLine, bool &vwbCmdYesValid) { - vwbCmdYesValid = false; + // Convert any CLI commands into MI commands + CMIUtilString vMITextLine(WrapCLICommandIntoMICommand(vTextLine)); + vwbCmdYesValid = false; bool bCmdNotInCmdFactor = false; SMICmdData cmdData; CMICmdMgr &rCmdMgr = CMICmdMgr::Instance(); - if (!rCmdMgr.CmdInterpret(vTextLine, vwbCmdYesValid, bCmdNotInCmdFactor, cmdData)) + if (!rCmdMgr.CmdInterpret(vMITextLine, vwbCmdYesValid, bCmdNotInCmdFactor, cmdData)) return MIstatus::failure; if (vwbCmdYesValid) @@ -855,12 +930,12 @@ // Check for escape character, may be cursor control characters // This code is not necessary for application operation, just want to keep tabs on what // is been given to the driver to try and intepret. - if (vTextLine.at(0) == 27) + if (vMITextLine.at(0) == 27) { CMIUtilString logInput(MIRSRC(IDS_STDIN_INPUT_CTRL_CHARS)); - for (MIuint i = 0; i < vTextLine.length(); i++) + for (MIuint i = 0; i < vMITextLine.length(); i++) { - logInput += CMIUtilString::Format("%d ", vTextLine.at(i)); + logInput += CMIUtilString::Format("%d ", vMITextLine.at(i)); } m_pLog->WriteLog(logInput); return MIstatus::success; @@ -873,7 +948,7 @@ strNotInCmdFactory = CMIUtilString::Format(MIRSRC(IDS_DRIVER_CMD_NOT_IN_FACTORY), cmdData.strMiCmd.c_str()); const CMIUtilString strNot(CMIUtilString::Format("%s ", MIRSRC(IDS_WORD_NOT))); const CMIUtilString msg( - CMIUtilString::Format(MIRSRC(IDS_DRIVER_CMD_RECEIVED), vTextLine.c_str(), strNot.c_str(), strNotInCmdFactory.c_str())); + CMIUtilString::Format(MIRSRC(IDS_DRIVER_CMD_RECEIVED), vMITextLine.c_str(), strNot.c_str(), strNotInCmdFactory.c_str())); const CMICmnMIValueConst vconst = CMICmnMIValueConst(msg); const CMICmnMIValueResult valueResult("msg", vconst); const CMICmnMIResultRecord miResultRecord(cmdData.strMiCmdToken, CMICmnMIResultRecord::eResultClass_Error, valueResult);