llvm-mirror/unittests/Option/OptionParsingTest.cpp
Brian Gesiak a80ee75318 [Option] Add 'findNearest' method to catch typos
Summary:
Add a method `OptTable::findNearest`, which allows users of OptTable to
check user input for misspelled options. In addition, have llvm-mt
check for misspelled options. For example, if a user invokes
`llvm-mt /oyt:foo`, the error message will indicate that while an
option named `/oyt:` does not exist, `/out:` does.

The method ports the functionality of the `LookupNearestOption` method
from LLVM CommandLine to libLLVMOption. This allows tools like Clang
and Swift, which do not use CommandLine, to use this functionality to
suggest similarly spelled options.

As room for future improvement, the new method as-is cannot yet properly suggest
nearby "joined" options -- that is, for an option string "-FozBar", where
"-Foo" is the correct option name and "Bar" is the value being passed along
with the misspelled option, this method will calculate an edit distance of 4,
by deleting "Bar" and changing "z" to "o". It should instead calculate an edit
distance of just 1, by changing "z" to "o" and recognizing "Bar" as a
value. This commit includes a disabled test that expresses this limitation.

Test Plan: `check-llvm`

Reviewers: yamaguchi, v.g.vassilev, teemperor, ruiu, jroelofs

Reviewed By: jroelofs

Subscribers: jroelofs, llvm-commits

Differential Revision: https://reviews.llvm.org/D41732

llvm-svn: 321877
2018-01-05 17:10:39 +00:00

312 lines
9.1 KiB
C++

//===- unittest/Support/OptionParsingTest.cpp - OptTable tests ------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/STLExtras.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace llvm::opt;
enum ID {
OPT_INVALID = 0, // This is not an option ID.
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \
HELPTEXT, METAVAR, VALUES) \
OPT_##ID,
#include "Opts.inc"
LastOption
#undef OPTION
};
#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
#include "Opts.inc"
#undef PREFIX
enum OptionFlags {
OptFlag1 = (1 << 4),
OptFlag2 = (1 << 5),
OptFlag3 = (1 << 6)
};
static const OptTable::Info InfoTable[] = {
#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \
HELPTEXT, METAVAR, VALUES) \
{PREFIX, NAME, HELPTEXT, METAVAR, OPT_##ID, Option::KIND##Class, \
PARAM, FLAGS, OPT_##GROUP, OPT_##ALIAS, ALIASARGS, VALUES},
#include "Opts.inc"
#undef OPTION
};
namespace {
class TestOptTable : public OptTable {
public:
TestOptTable(bool IgnoreCase = false)
: OptTable(InfoTable, IgnoreCase) {}
};
}
const char *Args[] = {
"-A",
"-Bhi",
"--C=desu",
"-C", "bye",
"-D,adena",
"-E", "apple", "bloom",
"-Fblarg",
"-F", "42",
"-Gchuu", "2"
};
TEST(Option, OptionParsing) {
TestOptTable T;
unsigned MAI, MAC;
InputArgList AL = T.ParseArgs(Args, MAI, MAC);
// Check they all exist.
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_TRUE(AL.hasArg(OPT_B));
EXPECT_TRUE(AL.hasArg(OPT_C));
EXPECT_TRUE(AL.hasArg(OPT_D));
EXPECT_TRUE(AL.hasArg(OPT_E));
EXPECT_TRUE(AL.hasArg(OPT_F));
EXPECT_TRUE(AL.hasArg(OPT_G));
// Check the values.
EXPECT_EQ("hi", AL.getLastArgValue(OPT_B));
EXPECT_EQ("bye", AL.getLastArgValue(OPT_C));
EXPECT_EQ("adena", AL.getLastArgValue(OPT_D));
std::vector<std::string> Es = AL.getAllArgValues(OPT_E);
EXPECT_EQ("apple", Es[0]);
EXPECT_EQ("bloom", Es[1]);
EXPECT_EQ("42", AL.getLastArgValue(OPT_F));
std::vector<std::string> Gs = AL.getAllArgValues(OPT_G);
EXPECT_EQ("chuu", Gs[0]);
EXPECT_EQ("2", Gs[1]);
// Check the help text.
std::string Help;
raw_string_ostream RSO(Help);
T.PrintHelp(RSO, "test", "title!");
EXPECT_NE(std::string::npos, Help.find("-A"));
// Test aliases.
auto Cs = AL.filtered(OPT_C);
ASSERT_NE(Cs.begin(), Cs.end());
EXPECT_EQ("desu", StringRef((*Cs.begin())->getValue()));
ArgStringList ASL;
(*Cs.begin())->render(AL, ASL);
ASSERT_EQ(2u, ASL.size());
EXPECT_EQ("-C", StringRef(ASL[0]));
EXPECT_EQ("desu", StringRef(ASL[1]));
}
TEST(Option, ParseWithFlagExclusions) {
TestOptTable T;
unsigned MAI, MAC;
// Exclude flag3 to avoid parsing as OPT_SLASH_C.
InputArgList AL = T.ParseArgs(Args, MAI, MAC,
/*FlagsToInclude=*/0,
/*FlagsToExclude=*/OptFlag3);
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_TRUE(AL.hasArg(OPT_C));
EXPECT_FALSE(AL.hasArg(OPT_SLASH_C));
// Exclude flag1 to avoid parsing as OPT_C.
AL = T.ParseArgs(Args, MAI, MAC,
/*FlagsToInclude=*/0,
/*FlagsToExclude=*/OptFlag1);
EXPECT_TRUE(AL.hasArg(OPT_B));
EXPECT_FALSE(AL.hasArg(OPT_C));
EXPECT_TRUE(AL.hasArg(OPT_SLASH_C));
const char *NewArgs[] = { "/C", "foo", "--C=bar" };
AL = T.ParseArgs(NewArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_SLASH_C));
EXPECT_TRUE(AL.hasArg(OPT_C));
EXPECT_EQ("foo", AL.getLastArgValue(OPT_SLASH_C));
EXPECT_EQ("bar", AL.getLastArgValue(OPT_C));
}
TEST(Option, ParseAliasInGroup) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-I" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_H));
}
TEST(Option, AliasArgs) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-J", "-Joo" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_B));
EXPECT_EQ("foo", AL.getAllArgValues(OPT_B)[0]);
EXPECT_EQ("bar", AL.getAllArgValues(OPT_B)[1]);
}
TEST(Option, IgnoreCase) {
TestOptTable T(true);
unsigned MAI, MAC;
const char *MyArgs[] = { "-a", "-joo" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_TRUE(AL.hasArg(OPT_B));
}
TEST(Option, DoNotIgnoreCase) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-a", "-joo" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_FALSE(AL.hasArg(OPT_A));
EXPECT_FALSE(AL.hasArg(OPT_B));
}
TEST(Option, SlurpEmpty) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-A", "-slurp" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_TRUE(AL.hasArg(OPT_Slurp));
EXPECT_EQ(0U, AL.getAllArgValues(OPT_Slurp).size());
}
TEST(Option, Slurp) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-A", "-slurp", "-B", "--", "foo" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_EQ(AL.size(), 2U);
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_FALSE(AL.hasArg(OPT_B));
EXPECT_TRUE(AL.hasArg(OPT_Slurp));
EXPECT_EQ(3U, AL.getAllArgValues(OPT_Slurp).size());
EXPECT_EQ("-B", AL.getAllArgValues(OPT_Slurp)[0]);
EXPECT_EQ("--", AL.getAllArgValues(OPT_Slurp)[1]);
EXPECT_EQ("foo", AL.getAllArgValues(OPT_Slurp)[2]);
}
TEST(Option, SlurpJoinedEmpty) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-A", "-slurpjoined" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_TRUE(AL.hasArg(OPT_SlurpJoined));
EXPECT_EQ(AL.getAllArgValues(OPT_SlurpJoined).size(), 0U);
}
TEST(Option, SlurpJoinedOneJoined) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-A", "-slurpjoinedfoo" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_TRUE(AL.hasArg(OPT_SlurpJoined));
EXPECT_EQ(AL.getAllArgValues(OPT_SlurpJoined).size(), 1U);
EXPECT_EQ(AL.getAllArgValues(OPT_SlurpJoined)[0], "foo");
}
TEST(Option, SlurpJoinedAndSeparate) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-A", "-slurpjoinedfoo", "bar", "baz" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_TRUE(AL.hasArg(OPT_SlurpJoined));
EXPECT_EQ(3U, AL.getAllArgValues(OPT_SlurpJoined).size());
EXPECT_EQ("foo", AL.getAllArgValues(OPT_SlurpJoined)[0]);
EXPECT_EQ("bar", AL.getAllArgValues(OPT_SlurpJoined)[1]);
EXPECT_EQ("baz", AL.getAllArgValues(OPT_SlurpJoined)[2]);
}
TEST(Option, SlurpJoinedButSeparate) {
TestOptTable T;
unsigned MAI, MAC;
const char *MyArgs[] = { "-A", "-slurpjoined", "foo", "bar", "baz" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_TRUE(AL.hasArg(OPT_A));
EXPECT_TRUE(AL.hasArg(OPT_SlurpJoined));
EXPECT_EQ(3U, AL.getAllArgValues(OPT_SlurpJoined).size());
EXPECT_EQ("foo", AL.getAllArgValues(OPT_SlurpJoined)[0]);
EXPECT_EQ("bar", AL.getAllArgValues(OPT_SlurpJoined)[1]);
EXPECT_EQ("baz", AL.getAllArgValues(OPT_SlurpJoined)[2]);
}
TEST(Option, FlagAliasToJoined) {
TestOptTable T;
unsigned MAI, MAC;
// Check that a flag alias provides an empty argument to a joined option.
const char *MyArgs[] = { "-K" };
InputArgList AL = T.ParseArgs(MyArgs, MAI, MAC);
EXPECT_EQ(AL.size(), 1U);
EXPECT_TRUE(AL.hasArg(OPT_B));
EXPECT_EQ(1U, AL.getAllArgValues(OPT_B).size());
EXPECT_EQ("", AL.getAllArgValues(OPT_B)[0]);
}
TEST(Option, FindNearest) {
TestOptTable T;
std::string Nearest;
// Options that are too short should not be considered
// "near" other short options.
EXPECT_GT(T.findNearest("-A", Nearest), 4U);
EXPECT_GT(T.findNearest("/C", Nearest), 4U);
EXPECT_GT(T.findNearest("--C=foo", Nearest), 4U);
// The nearest candidate should mirror the amount of prefix
// characters used in the original string.
EXPECT_EQ(1U, T.findNearest("-blorb", Nearest));
EXPECT_EQ(Nearest, "-blorp");
EXPECT_EQ(1U, T.findNearest("--blorm", Nearest));
EXPECT_EQ(Nearest, "--blorp");
// The nearest candidate respects the prefix and value delimiter
// of the original string.
EXPECT_EQ(1U, T.findNearest("/framb:foo", Nearest));
EXPECT_EQ(Nearest, "/cramb:foo");
// Flags should be included and excluded as specified.
EXPECT_EQ(1U, T.findNearest("-doopf", Nearest, /*FlagsToInclude=*/OptFlag2));
EXPECT_EQ(Nearest, "-doopf2");
EXPECT_EQ(1U, T.findNearest("-doopf", Nearest,
/*FlagsToInclude=*/0,
/*FlagsToExclude=*/OptFlag2));
EXPECT_EQ(Nearest, "-doopf1");
}
TEST(DISABLED_Option, FindNearestFIXME) {
TestOptTable T;
std::string Nearest;
// FIXME: Options with joined values should not have those values considered
// when calculating distance. The test below would fail if run, but it should
// succeed.
EXPECT_EQ(1U, T.findNearest("--erbghFoo", Nearest));
EXPECT_EQ(Nearest, "--ermghFoo");
}