Fix a crash when tab-completing arguments for parsed commands that have arguments but no options. In `HandleArgumentCompletion`, `GetOptions()` returns `nullptr` when a command has no options defined. The code was dereferencing this pointer without a null check, causing a segfault when attempting tab completion.
334 lines
12 KiB
Python
334 lines
12 KiB
Python
"""
|
|
Test option and argument definitions in parsed script commands
|
|
"""
|
|
|
|
|
|
import sys
|
|
import os
|
|
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
|
|
|
|
class ParsedCommandTestCase(TestBase):
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
def test(self):
|
|
self.pycmd_tests()
|
|
|
|
def setUp(self):
|
|
TestBase.setUp(self)
|
|
self.stdin_path = self.getBuildArtifact("stdin.txt")
|
|
self.stdout_path = self.getBuildArtifact("stdout.txt")
|
|
|
|
def check_help_options(self, cmd_name, opt_list, substrs=[]):
|
|
"""
|
|
Pass the command name in cmd_name and a vector of the short option, type & long option.
|
|
This will append the checks for all the options and test "help command".
|
|
Any strings already in substrs will also be checked.
|
|
Any element in opt list that begin with "+" will be added to the checked strings as is.
|
|
"""
|
|
for elem in opt_list:
|
|
if elem[0] == "+":
|
|
substrs.append(elem[1:])
|
|
else:
|
|
(short_opt, type, long_opt) = elem
|
|
substrs.append(f"-{short_opt} <{type}> ( --{long_opt} <{type}> )")
|
|
|
|
self.expect("help " + cmd_name, substrs=substrs)
|
|
|
|
def run_one_repeat(self, commands, expected_num_errors):
|
|
with open(self.stdin_path, "w") as input_handle:
|
|
input_handle.write(commands)
|
|
|
|
in_fileH = open(self.stdin_path, "r")
|
|
self.dbg.SetInputFileHandle(in_fileH, False)
|
|
|
|
out_fileH = open(self.stdout_path, "w")
|
|
self.dbg.SetOutputFileHandle(out_fileH, False)
|
|
self.dbg.SetErrorFileHandle(out_fileH, False)
|
|
|
|
options = lldb.SBCommandInterpreterRunOptions()
|
|
options.SetEchoCommands(False)
|
|
options.SetPrintResults(True)
|
|
options.SetPrintErrors(True)
|
|
options.SetAllowRepeats(True)
|
|
|
|
n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
|
|
True, False, options, 0, False, False
|
|
)
|
|
|
|
in_fileH.close()
|
|
out_fileH.close()
|
|
|
|
results = None
|
|
with open(self.stdout_path, "r") as out_fileH:
|
|
results = out_fileH.read()
|
|
|
|
self.assertEqual(n_errors, expected_num_errors)
|
|
|
|
return results
|
|
|
|
def handle_completion(
|
|
self,
|
|
cmd_str,
|
|
exp_num_completions,
|
|
exp_matches,
|
|
exp_descriptions,
|
|
match_description,
|
|
):
|
|
matches = lldb.SBStringList()
|
|
descriptions = lldb.SBStringList()
|
|
|
|
interp = self.dbg.GetCommandInterpreter()
|
|
num_completions = interp.HandleCompletionWithDescriptions(
|
|
cmd_str, len(cmd_str), 0, 1000, matches, descriptions
|
|
)
|
|
self.assertEqual(
|
|
num_completions, exp_num_completions, "Number of completions is right."
|
|
)
|
|
num_matches = matches.GetSize()
|
|
self.assertEqual(
|
|
num_matches,
|
|
exp_matches.GetSize(),
|
|
"matches and expected matches of different lengths",
|
|
)
|
|
num_descriptions = descriptions.GetSize()
|
|
if match_description:
|
|
self.assertEqual(
|
|
num_descriptions,
|
|
exp_descriptions.GetSize(),
|
|
"descriptions and expected of different lengths",
|
|
)
|
|
|
|
self.assertEqual(
|
|
matches.GetSize(),
|
|
num_completions + 1,
|
|
"The first element is the complete additional text",
|
|
)
|
|
|
|
for idx in range(0, num_matches):
|
|
match = matches.GetStringAtIndex(idx)
|
|
exp_match = exp_matches.GetStringAtIndex(idx)
|
|
self.assertEqual(
|
|
match, exp_match, f"{match} did not match expectation: {exp_match}"
|
|
)
|
|
if match_description:
|
|
desc = descriptions.GetStringAtIndex(idx)
|
|
exp_desc = exp_descriptions.GetStringAtIndex(idx)
|
|
self.assertEqual(
|
|
desc, exp_desc, f"{desc} didn't match expectation: {exp_desc}"
|
|
)
|
|
|
|
def pycmd_tests(self):
|
|
source_dir = self.getSourceDir()
|
|
test_file_path = os.path.join(source_dir, "test_commands.py")
|
|
self.runCmd("command script import " + test_file_path)
|
|
self.expect("help", substrs=["no-args", "one-arg-no-opt", "two-args"])
|
|
|
|
# Test that we did indeed add these commands as user commands:
|
|
|
|
# This is the function to remove the custom commands in order to have a
|
|
# clean slate for the next test case.
|
|
def cleanup():
|
|
self.runCmd(
|
|
"command script delete no-args one-arg-no-opt two-args", check=False
|
|
)
|
|
|
|
# Execute the cleanup function during test case tear down.
|
|
self.addTearDownHook(cleanup)
|
|
|
|
# First test the no arguments command. Make sure the help is right:
|
|
no_arg_opts = [
|
|
["b", "boolean", "bool-arg"],
|
|
"+a boolean arg, defaults to True",
|
|
["d", "filename", "disk-file-name"],
|
|
"+An on disk filename",
|
|
["e", "none", "enum-option"],
|
|
"+An enum, doesn't actually do anything",
|
|
"+Values: foo | bar | baz",
|
|
["l", "linenum", "line-num"],
|
|
"+A line number",
|
|
["s", "shlib-name", "shlib-name"],
|
|
"+A shared library name",
|
|
]
|
|
substrs = [
|
|
"Example command for use in debugging",
|
|
"Syntax: no-args <cmd-options>",
|
|
]
|
|
|
|
self.check_help_options("no-args", no_arg_opts, substrs)
|
|
|
|
# Make sure the command doesn't accept arguments:
|
|
self.expect(
|
|
"no-args an-arg",
|
|
substrs=["'no-args' doesn't take any arguments."],
|
|
error=True,
|
|
)
|
|
|
|
# Try setting the bool with the wrong value:
|
|
self.expect(
|
|
"no-args -b Something",
|
|
substrs=["Error setting option: bool-arg to Something"],
|
|
error=True,
|
|
)
|
|
# Try setting the enum to an illegal value as well:
|
|
self.expect(
|
|
"no-args --enum-option Something",
|
|
substrs=["error: Error setting option: enum-option to Something"],
|
|
error=True,
|
|
)
|
|
|
|
# Check some of the command groups:
|
|
self.expect(
|
|
"no-args -b true -s Something -l 10",
|
|
substrs=["error: invalid combination of options for the given command"],
|
|
error=True,
|
|
)
|
|
|
|
# Now set the bool arg correctly, note only the first option was set:
|
|
self.expect(
|
|
"no-args -b true",
|
|
substrs=[
|
|
"bool-arg (set: True): True",
|
|
"shlib-name (set: False):",
|
|
"disk-file-name (set: False):",
|
|
"line-num (set: False):",
|
|
"enum-option (set: False):",
|
|
],
|
|
)
|
|
|
|
# Now set the enum arg correctly, note only the first option was set:
|
|
self.expect(
|
|
"no-args -e foo",
|
|
substrs=[
|
|
"bool-arg (set: False):",
|
|
"shlib-name (set: False):",
|
|
"disk-file-name (set: False):",
|
|
"line-num (set: False):",
|
|
"enum-option (set: True): foo",
|
|
],
|
|
)
|
|
# Try a pair together:
|
|
self.expect(
|
|
"no-args -b false -s Something",
|
|
substrs=[
|
|
"bool-arg (set: True): False",
|
|
"shlib-name (set: True): Something",
|
|
"disk-file-name (set: False):",
|
|
"flag-value (set: False):",
|
|
"line-num (set: False):",
|
|
"enum-option (set: False):",
|
|
],
|
|
)
|
|
# Make sure flag values work:
|
|
self.expect(
|
|
"no-args -b false -s Something -f",
|
|
substrs=[
|
|
"bool-arg (set: True): False",
|
|
"shlib-name (set: True): Something",
|
|
"disk-file-name (set: False):",
|
|
"flag-value (set: True):",
|
|
"line-num (set: False):",
|
|
"enum-option (set: False):",
|
|
],
|
|
)
|
|
|
|
# Next try some completion tests:
|
|
|
|
interp = self.dbg.GetCommandInterpreter()
|
|
matches = lldb.SBStringList()
|
|
descriptions = lldb.SBStringList()
|
|
|
|
# First try an enum completion:
|
|
# Note - this is an enum so all the values are returned:
|
|
matches.AppendList(["oo ", "foo"], 2)
|
|
|
|
self.handle_completion("no-args -e f", 1, matches, descriptions, False)
|
|
|
|
# Now try an internal completer, the on disk file one is handy:
|
|
partial_name = os.path.join(source_dir, "test_")
|
|
cmd_str = f"no-args -d '{partial_name}'"
|
|
|
|
matches.Clear()
|
|
descriptions.Clear()
|
|
matches.AppendList(["commands.py' ", test_file_path], 2)
|
|
# We don't have descriptions for the file path completer:
|
|
self.handle_completion(cmd_str, 1, matches, descriptions, False)
|
|
|
|
# Try a command with arguments.
|
|
# FIXME: It should be enough to define an argument and it's type to get the completer
|
|
# wired up for that argument type if it is a known type. But that isn't wired up in the
|
|
# command parser yet, so I don't have any tests for that. We also don't currently check
|
|
# that the arguments passed match the argument specifications, so here I just pass a couple
|
|
# sets of arguments and make sure we get back what we put in:
|
|
self.expect(
|
|
"two-args 'First Argument' 'Second Argument'",
|
|
substrs=["0: First Argument", "1: Second Argument"],
|
|
)
|
|
|
|
# Now test custom completions - two-args has both option and arg completers. In both
|
|
# completers we return different values if the -p option is set, so we can test that too:
|
|
matches.Clear()
|
|
descriptions.Clear()
|
|
cmd_str = "two-args -p something -c other_"
|
|
matches.AppendString("something ")
|
|
matches.AppendString("other_something")
|
|
# This is a full match so no descriptions:
|
|
self.handle_completion(cmd_str, 1, matches, descriptions, False)
|
|
|
|
matches.Clear()
|
|
descriptions.Clear()
|
|
cmd_str = "two-args -c other_"
|
|
matches.AppendList(["", "other_nice", "other_not_nice", "other_mediocre"], 4)
|
|
# The option doesn't return descriptions either:
|
|
self.handle_completion(cmd_str, 3, matches, descriptions, False)
|
|
|
|
# Now try the argument - it says "no completions" if the proc_name was set:
|
|
matches.Clear()
|
|
descriptions.Clear()
|
|
cmd_str = "two-args -p something arg"
|
|
matches.AppendString("")
|
|
self.handle_completion(cmd_str, 0, matches, descriptions, False)
|
|
|
|
cmd_str = "two-args arg_"
|
|
matches.Clear()
|
|
descriptions.Clear()
|
|
matches.AppendList(["", "arg_cool", "arg_yuck"], 3)
|
|
descriptions.AppendList(["", "good idea", "bad idea"], 3)
|
|
self.handle_completion(cmd_str, 2, matches, descriptions, True)
|
|
|
|
# This one gets a single unique match:
|
|
cmd_str = "two-args correct_"
|
|
matches.Clear()
|
|
descriptions.Clear()
|
|
matches.AppendList(["answer ", "correct_answer"], 2)
|
|
self.handle_completion(cmd_str, 1, matches, descriptions, False)
|
|
|
|
# Test completion for a command with arguments but NO options:
|
|
cmd_str = "one-arg-no-opt nonexistent_file_xyz"
|
|
matches.Clear()
|
|
descriptions.Clear()
|
|
matches.AppendString("")
|
|
self.handle_completion(cmd_str, 0, matches, descriptions, False)
|
|
|
|
# Now make sure get_repeat_command works properly:
|
|
|
|
# no-args turns off auto-repeat
|
|
results = self.run_one_repeat("no-args\n\n", 1)
|
|
self.assertIn("no auto repeat", results, "Got auto-repeat error")
|
|
|
|
# one-args does the normal repeat
|
|
results = self.run_one_repeat("one-arg-no-opt ONE_ARG\n\n", 0)
|
|
self.assertEqual(results.count("ONE_ARG"), 2, "We did a normal repeat")
|
|
|
|
# two-args adds an argument:
|
|
results = self.run_one_repeat("two-args FIRST_ARG SECOND_ARG\n\n", 0)
|
|
self.assertEqual(
|
|
results.count("FIRST_ARG"), 2, "Passed first arg to both commands"
|
|
)
|
|
self.assertEqual(
|
|
results.count("SECOND_ARG"), 2, "Passed second arg to both commands"
|
|
)
|
|
self.assertEqual(results.count("THIRD_ARG"), 1, "Passed third arg in repeat")
|