Skip to content

Commit 435dd97

Browse files
authored
Add AllowRepeats to SBCommandInterpreterRunOptions. (#94786)
This is useful if you have a transcript of a user session and want to rerun those commands with RunCommandInterpreter. The same functionality is also useful in testing. I'm adding it primarily for the second reason. In a subsequent patch, I'm adding the ability to Python based commands to provide their "auto-repeat" command. Among other things, that will allow potentially state destroying user commands to prevent auto-repeat. Testing this with Shell or pexpect tests is not nearly as accurate or convenient as using RunCommandInterpreter, but to use that I need to allow auto-repeat. I think for consistency's sake, having interactive sessions always do auto-repeats is the right choice, though that's a lightly held opinion...
1 parent c8eff87 commit 435dd97

File tree

6 files changed

+98
-14
lines changed

6 files changed

+98
-14
lines changed

lldb/bindings/interface/SBCommandInterpreterRunOptionsDocstrings.i

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@ A default SBCommandInterpreterRunOptions object has:
1010
* PrintResults: true
1111
* PrintErrors: true
1212
* AddToHistory: true
13+
* AllowRepeats false
1314
15+
Interactive debug sessions always allow repeats, the AllowRepeats
16+
run option only affects non-interactive sessions.
1417
") lldb::SBCommandInterpreterRunOptions;

lldb/include/lldb/API/SBCommandInterpreterRunOptions.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ class LLDB_API SBCommandInterpreterRunOptions {
7272

7373
void SetSpawnThread(bool);
7474

75+
bool GetAllowRepeats() const;
76+
77+
/// By default, RunCommandInterpreter will discard repeats if the
78+
/// IOHandler being used is not interactive. Setting AllowRepeats to true
79+
/// will override this behavior and always process empty lines in the input
80+
/// as a repeat command.
81+
void SetAllowRepeats(bool);
82+
7583
private:
7684
lldb_private::CommandInterpreterRunOptions *get() const;
7785

lldb/include/lldb/Interpreter/CommandInterpreter.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,20 @@ class CommandInterpreterRunOptions {
9393
/// \param[in] add_to_history
9494
/// If \b true add the commands to the command history. If \b false, don't
9595
/// add them.
96+
/// \param[in] handle_repeats
97+
/// If \b true then treat empty lines as repeat commands even if the
98+
/// interpreter is non-interactive.
9699
CommandInterpreterRunOptions(LazyBool stop_on_continue,
97100
LazyBool stop_on_error, LazyBool stop_on_crash,
98101
LazyBool echo_commands, LazyBool echo_comments,
99102
LazyBool print_results, LazyBool print_errors,
100-
LazyBool add_to_history)
103+
LazyBool add_to_history,
104+
LazyBool handle_repeats)
101105
: m_stop_on_continue(stop_on_continue), m_stop_on_error(stop_on_error),
102106
m_stop_on_crash(stop_on_crash), m_echo_commands(echo_commands),
103107
m_echo_comment_commands(echo_comments), m_print_results(print_results),
104-
m_print_errors(print_errors), m_add_to_history(add_to_history) {}
108+
m_print_errors(print_errors), m_add_to_history(add_to_history),
109+
m_allow_repeats(handle_repeats) {}
105110

106111
CommandInterpreterRunOptions() = default;
107112

@@ -183,6 +188,12 @@ class CommandInterpreterRunOptions {
183188
m_spawn_thread = spawn_thread ? eLazyBoolYes : eLazyBoolNo;
184189
}
185190

191+
bool GetAllowRepeats() const { return DefaultToNo(m_allow_repeats); }
192+
193+
void SetAllowRepeats(bool allow_repeats) {
194+
m_allow_repeats = allow_repeats ? eLazyBoolYes : eLazyBoolNo;
195+
}
196+
186197
LazyBool m_stop_on_continue = eLazyBoolCalculate;
187198
LazyBool m_stop_on_error = eLazyBoolCalculate;
188199
LazyBool m_stop_on_crash = eLazyBoolCalculate;
@@ -193,6 +204,7 @@ class CommandInterpreterRunOptions {
193204
LazyBool m_add_to_history = eLazyBoolCalculate;
194205
LazyBool m_auto_handle_events;
195206
LazyBool m_spawn_thread;
207+
LazyBool m_allow_repeats = eLazyBoolCalculate;
196208

197209
private:
198210
static bool DefaultToYes(LazyBool flag) {

lldb/source/API/SBCommandInterpreterRunOptions.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,18 @@ void SBCommandInterpreterRunOptions::SetSpawnThread(bool spawn_thread) {
164164
m_opaque_up->SetSpawnThread(spawn_thread);
165165
}
166166

167+
bool SBCommandInterpreterRunOptions::GetAllowRepeats() const {
168+
LLDB_INSTRUMENT_VA(this);
169+
170+
return m_opaque_up->GetAllowRepeats();
171+
}
172+
173+
void SBCommandInterpreterRunOptions::SetAllowRepeats(bool allow_repeats) {
174+
LLDB_INSTRUMENT_VA(this, allow_repeats);
175+
176+
m_opaque_up->SetAllowRepeats(allow_repeats);
177+
}
178+
167179
lldb_private::CommandInterpreterRunOptions *
168180
SBCommandInterpreterRunOptions::get() const {
169181
return m_opaque_up.get();

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2707,7 +2707,8 @@ enum {
27072707
eHandleCommandFlagEchoCommentCommand = (1u << 3),
27082708
eHandleCommandFlagPrintResult = (1u << 4),
27092709
eHandleCommandFlagPrintErrors = (1u << 5),
2710-
eHandleCommandFlagStopOnCrash = (1u << 6)
2710+
eHandleCommandFlagStopOnCrash = (1u << 6),
2711+
eHandleCommandFlagAllowRepeats = (1u << 7)
27112712
};
27122713

27132714
void CommandInterpreter::HandleCommandsFromFile(
@@ -3129,14 +3130,19 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
31293130
return;
31303131

31313132
const bool is_interactive = io_handler.GetIsInteractive();
3132-
if (!is_interactive) {
3133+
const bool allow_repeats =
3134+
io_handler.GetFlags().Test(eHandleCommandFlagAllowRepeats);
3135+
3136+
if (!is_interactive && !allow_repeats) {
31333137
// When we are not interactive, don't execute blank lines. This will happen
31343138
// sourcing a commands file. We don't want blank lines to repeat the
31353139
// previous command and cause any errors to occur (like redefining an
31363140
// alias, get an error and stop parsing the commands file).
3141+
// But obey the AllowRepeats flag if the user has set it.
31373142
if (line.empty())
31383143
return;
3139-
3144+
}
3145+
if (!is_interactive) {
31403146
// When using a non-interactive file handle (like when sourcing commands
31413147
// from a file) we need to echo the command out so we don't just see the
31423148
// command output and no command...
@@ -3388,6 +3394,8 @@ CommandInterpreter::GetIOHandler(bool force_create,
33883394
flags |= eHandleCommandFlagPrintResult;
33893395
if (options->m_print_errors != eLazyBoolNo)
33903396
flags |= eHandleCommandFlagPrintErrors;
3397+
if (options->m_allow_repeats == eLazyBoolYes)
3398+
flags |= eHandleCommandFlagAllowRepeats;
33913399
} else {
33923400
flags = eHandleCommandFlagEchoCommand | eHandleCommandFlagPrintResult |
33933401
eHandleCommandFlagPrintErrors;

lldb/test/API/python_api/interpreter/TestRunCommandInterpreterAPI.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,28 +47,66 @@ def setUp(self):
4747
TestBase.setUp(self)
4848

4949
self.stdin_path = self.getBuildArtifact("stdin.txt")
50+
self.stdout_path = self.getBuildArtifact("stdout.txt")
51+
52+
def run_commands_string(
53+
self, command_string, options=lldb.SBCommandInterpreterRunOptions()
54+
):
55+
"""Run the commands in command_string through RunCommandInterpreter.
56+
Returns (n_errors, quit_requested, has_crashed, result_string)."""
5057

5158
with open(self.stdin_path, "w") as input_handle:
52-
input_handle.write("nonexistingcommand\nquit")
59+
input_handle.write(command_string)
5360

54-
self.dbg.SetInputFile(open(self.stdin_path, "r"))
61+
n_errors = 0
62+
quit_requested = False
63+
has_crashed = False
5564

56-
# No need to track the output
57-
devnull = open(os.devnull, "w")
58-
self.dbg.SetOutputFile(devnull)
59-
self.dbg.SetErrorFile(devnull)
65+
with open(self.stdin_path, "r") as in_fileH, open(
66+
self.stdout_path, "w"
67+
) as out_fileH:
68+
self.dbg.SetInputFile(in_fileH)
69+
70+
self.dbg.SetOutputFile(out_fileH)
71+
self.dbg.SetErrorFile(out_fileH)
72+
73+
n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
74+
True, False, options, 0, False, False
75+
)
76+
77+
result_string = None
78+
with open(self.stdout_path, "r") as out_fileH:
79+
result_string = out_fileH.read()
80+
81+
return (n_errors, quit_requested, has_crashed, result_string)
6082

6183
def test_run_session_with_error_and_quit(self):
6284
"""Run non-existing and quit command returns appropriate values"""
6385

64-
n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
65-
True, False, lldb.SBCommandInterpreterRunOptions(), 0, False, False
86+
n_errors, quit_requested, has_crashed, _ = self.run_commands_string(
87+
"nonexistingcommand\nquit\n"
6688
)
67-
6889
self.assertGreater(n_errors, 0)
6990
self.assertTrue(quit_requested)
7091
self.assertFalse(has_crashed)
7192

93+
def test_allow_repeat(self):
94+
"""Try auto-repeat of process launch - the command will fail and
95+
the auto-repeat will fail because of no auto-repeat."""
96+
options = lldb.SBCommandInterpreterRunOptions()
97+
options.SetEchoCommands(False)
98+
options.SetAllowRepeats(True)
99+
100+
n_errors, quit_requested, has_crashed, result_str = self.run_commands_string(
101+
"process launch\n\n", options
102+
)
103+
self.assertEqual(n_errors, 2)
104+
self.assertFalse(quit_requested)
105+
self.assertFalse(has_crashed)
106+
107+
self.assertIn("invalid target", result_str)
108+
self.assertIn("No auto repeat", result_str)
109+
72110

73111
class SBCommandInterpreterRunOptionsCase(TestBase):
74112
NO_DEBUG_INFO_TESTCASE = True
@@ -86,6 +124,7 @@ def test_command_interpreter_run_options(self):
86124
self.assertTrue(opts.GetPrintResults())
87125
self.assertTrue(opts.GetPrintErrors())
88126
self.assertTrue(opts.GetAddToHistory())
127+
self.assertFalse(opts.GetAllowRepeats())
89128

90129
# Invert values
91130
opts.SetStopOnContinue(not opts.GetStopOnContinue())
@@ -95,6 +134,7 @@ def test_command_interpreter_run_options(self):
95134
opts.SetPrintResults(not opts.GetPrintResults())
96135
opts.SetPrintErrors(not opts.GetPrintErrors())
97136
opts.SetAddToHistory(not opts.GetAddToHistory())
137+
opts.SetAllowRepeats(not opts.GetAllowRepeats())
98138

99139
# Check the value changed
100140
self.assertTrue(opts.GetStopOnContinue())
@@ -104,3 +144,4 @@ def test_command_interpreter_run_options(self):
104144
self.assertFalse(opts.GetPrintResults())
105145
self.assertFalse(opts.GetPrintErrors())
106146
self.assertFalse(opts.GetAddToHistory())
147+
self.assertTrue(opts.GetAllowRepeats())

0 commit comments

Comments
 (0)