266 lines · cpp
1//===- unittest/Support/OptionParsingTest.cpp - OptTable tests ------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/ADT/STLExtras.h"10#include "llvm/Option/Arg.h"11#include "llvm/Option/ArgList.h"12#include "llvm/Option/OptTable.h"13#include "llvm/Option/Option.h"14#include "llvm/Support/raw_ostream.h"15#include "gtest/gtest.h"16 17using namespace llvm;18using namespace llvm::opt;19 20#if defined(__clang__)21#pragma clang diagnostic ignored "-Wdeprecated-declarations"22#endif23 24namespace {25enum ID {26 OPT_INVALID = 0,27#define OPTION(PREFIXES, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \28 VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \29 VALUES, SUBCOMMANDIDS_OFFSET) \30 OPT_##ID,31#include "SubCommandOpts.inc"32#undef OPTION33};34#define OPTTABLE_STR_TABLE_CODE35#include "SubCommandOpts.inc"36#undef OPTTABLE_STR_TABLE_CODE37 38#define OPTTABLE_PREFIXES_TABLE_CODE39#include "SubCommandOpts.inc"40#undef OPTTABLE_PREFIXES_TABLE_CODE41 42#define OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE43#include "SubCommandOpts.inc"44#undef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE45 46#define OPTTABLE_SUBCOMMANDS_CODE47#include "SubCommandOpts.inc"48#undef OPTTABLE_SUBCOMMANDS_CODE49 50static constexpr OptTable::Info InfoTable[] = {51#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),52#include "SubCommandOpts.inc"53#undef OPTION54};55 56class TestOptSubCommandTable : public GenericOptTable {57public:58 TestOptSubCommandTable(bool IgnoreCase = false)59 : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable,60 /*IgnoreCase=*/false, OptionSubCommands,61 OptionSubCommandIDsTable) {}62};63 64// Test fixture65template <typename T> class OptSubCommandTableTest : public ::testing::Test {};66 67// Test both precomputed and computed OptTables with the same suite of tests.68using OptSubCommandTableTestTypes = ::testing::Types<TestOptSubCommandTable>;69 70TYPED_TEST_SUITE(OptSubCommandTableTest, OptSubCommandTableTestTypes, );71 72TYPED_TEST(OptSubCommandTableTest, SubCommandParsing) {73 TypeParam T;74 unsigned MAI, MAC;75 76 std::string ErrMsg;77 raw_string_ostream RSO1(ErrMsg);78 79 auto HandleMultipleSubcommands = [&](ArrayRef<StringRef> SubCommands) {80 ErrMsg.clear();81 RSO1 << "Multiple subcommands passed\n";82 for (auto SC : SubCommands)83 RSO1 << "\n" << SC;84 };85 86 auto HandleOtherPositionals = [&](ArrayRef<StringRef> Positionals) {87 ErrMsg.clear();88 RSO1 << "Unregistered positionals passed\n";89 for (auto SC : Positionals)90 RSO1 << "\n" << SC;91 };92 93 {94 // Test case 1: Toplevel option, no subcommand95 const char *Args[] = {"-version"};96 InputArgList AL = T.ParseArgs(Args, MAI, MAC);97 EXPECT_TRUE(AL.hasArg(OPT_version));98 StringRef SC = AL.getSubCommand(99 T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);100 EXPECT_TRUE(SC.empty());101 EXPECT_FALSE(AL.hasArg(OPT_uppercase));102 EXPECT_FALSE(AL.hasArg(OPT_lowercase));103 }104 105 {106 // Test case 2: Subcommand 'foo' with its valid options107 const char *Args[] = {"foo", "-uppercase"};108 InputArgList AL = T.ParseArgs(Args, MAI, MAC);109 StringRef SC = AL.getSubCommand(110 T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);111 EXPECT_EQ(SC, "foo");112 EXPECT_TRUE(AL.hasArg(OPT_uppercase));113 EXPECT_FALSE(AL.hasArg(OPT_lowercase));114 EXPECT_FALSE(AL.hasArg(OPT_version));115 EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed"))116 << "Did not expect error message as this is a valid use case.";117 EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed"))118 << "Did not expect error message as this is a valid use case.";119 }120 121 {122 // Test case 3: Check valid use of subcommand which follows a valid123 // subcommand option.124 const char *Args[] = {"-uppercase", "foo"};125 InputArgList AL = T.ParseArgs(Args, MAI, MAC);126 StringRef SC = AL.getSubCommand(127 T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);128 EXPECT_EQ(SC, "foo");129 EXPECT_TRUE(AL.hasArg(OPT_uppercase));130 EXPECT_FALSE(AL.hasArg(OPT_lowercase));131 EXPECT_FALSE(AL.hasArg(OPT_version));132 EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed"))133 << "Did not expect error message as this is a valid use case.";134 EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed"))135 << "Did not expect error message as this is a valid use case.";136 }137 138 {139 // Test case 4: Check invalid use of passing multiple subcommands.140 const char *Args[] = {"-uppercase", "foo", "bar"};141 InputArgList AL = T.ParseArgs(Args, MAI, MAC);142 StringRef SC = AL.getSubCommand(143 T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);144 // No valid subcommand should be returned as this is an invalid invocation.145 EXPECT_TRUE(SC.empty());146 // Expect the multiple subcommands error message.147 EXPECT_NE(std::string::npos, ErrMsg.find("Multiple subcommands passed"));148 EXPECT_NE(std::string::npos, ErrMsg.find("foo"));149 EXPECT_NE(std::string::npos, ErrMsg.find("bar"));150 EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed"))151 << "Did not expect error message as this is a valid use case.";152 }153 154 {155 // Test case 5: Check invalid use of passing unregistered subcommands.156 const char *Args[] = {"foobar"};157 InputArgList AL = T.ParseArgs(Args, MAI, MAC);158 StringRef SC = AL.getSubCommand(159 T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);160 // No valid subcommand should be returned as this is an invalid invocation.161 EXPECT_TRUE(SC.empty());162 // Expect the unregistered subcommands error message.163 EXPECT_NE(std::string::npos,164 ErrMsg.find("Unregistered positionals passed"));165 EXPECT_NE(std::string::npos, ErrMsg.find("foobar"));166 }167 168 {169 // Test case 6: Check invalid use of a valid subcommand which follows a170 // valid subcommand option but the option is not registered with the given171 // subcommand.172 const char *Args[] = {"-lowercase", "bar"};173 InputArgList AL = T.ParseArgs(Args, MAI, MAC);174 StringRef SC = AL.getSubCommand(175 T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);176 auto HandleSubCommandArg = [&](ID OptionType) {177 if (!AL.hasArg(OptionType))178 return false;179 auto O = T.getOption(OptionType);180 if (!O.isRegisteredSC(SC)) {181 ErrMsg.clear();182 RSO1 << "Option [" << O.getName() << "] is not valid for SubCommand ["183 << SC << "]\n";184 return false;185 }186 return true;187 };188 EXPECT_EQ(SC, "bar"); // valid subcommand189 EXPECT_TRUE(AL.hasArg(OPT_lowercase)); // valid option190 EXPECT_FALSE(HandleSubCommandArg(OPT_lowercase));191 EXPECT_NE(192 std::string::npos,193 ErrMsg.find("Option [lowercase] is not valid for SubCommand [bar]"));194 }195 196 {197 // Test case 7: Check valid use of a valid subcommand following more198 // positional arguments.199 const char *Args[] = {"bar", "input"};200 InputArgList AL = T.ParseArgs(Args, MAI, MAC);201 StringRef SC = AL.getSubCommand(202 T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);203 EXPECT_EQ(SC, "bar"); // valid subcommand204 EXPECT_NE(std::string::npos,205 ErrMsg.find("Unregistered positionals passed"));206 EXPECT_NE(std::string::npos, ErrMsg.find("input"));207 }208}209 210TYPED_TEST(OptSubCommandTableTest, SubCommandHelp) {211 TypeParam T;212 std::string Help;213 raw_string_ostream RSO(Help);214 215 // Toplevel help216 T.printHelp(RSO, "Test Usage String", "OverviewString");217 EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));218 EXPECT_NE(std::string::npos, Help.find("OverviewString"));219 EXPECT_NE(std::string::npos, Help.find("USAGE:"));220 EXPECT_NE(std::string::npos, Help.find("Test Usage String"));221 EXPECT_NE(std::string::npos, Help.find("SUBCOMMANDS:"));222 EXPECT_NE(std::string::npos, Help.find("foo"));223 EXPECT_NE(std::string::npos, Help.find("bar"));224 EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo."));225 EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar."));226 EXPECT_NE(std::string::npos, Help.find("OPTIONS:"));227 EXPECT_NE(std::string::npos, Help.find("--help"));228 EXPECT_NE(std::string::npos, Help.find("-version"));229 // uppercase is not a global option and should not be shown.230 EXPECT_EQ(std::string::npos, Help.find("-uppercase"));231 232 // Help for subcommand foo233 Help.clear();234 StringRef SC1 = "foo";235 T.printHelp(RSO, "Test Usage String", "OverviewString", false, false,236 Visibility(), SC1);237 EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));238 EXPECT_NE(std::string::npos, Help.find("OverviewString"));239 // SubCommand "foo" definition for tablegen has NO dedicated usage string so240 // not expected to see USAGE.241 EXPECT_EQ(std::string::npos, Help.find("USAGE:"));242 EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo."));243 EXPECT_NE(std::string::npos, Help.find("-uppercase"));244 EXPECT_NE(std::string::npos, Help.find("-lowercase"));245 EXPECT_EQ(std::string::npos, Help.find("-version"));246 EXPECT_EQ(std::string::npos, Help.find("SUBCOMMANDS:"));247 248 // Help for subcommand bar249 Help.clear();250 StringRef SC2 = "bar";251 T.printHelp(RSO, "Test Usage String", "OverviewString", false, false,252 Visibility(), SC2);253 EXPECT_NE(std::string::npos, Help.find("OVERVIEW:"));254 EXPECT_NE(std::string::npos, Help.find("OverviewString"));255 // SubCommand "bar" definition for tablegen has a dedicated usage string.256 EXPECT_NE(std::string::npos, Help.find("USAGE:"));257 EXPECT_NE(std::string::npos, Help.find("Subcommand bar <options>"));258 EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar."));259 EXPECT_NE(std::string::npos, Help.find("-uppercase"));260 // lowercase is not an option for bar and should not be shown.261 EXPECT_EQ(std::string::npos, Help.find("-lowercase"));262 // version is a global option and should not be shown.263 EXPECT_EQ(std::string::npos, Help.find("-version"));264}265} // end anonymous namespace266