diff options
Diffstat (limited to 'llvm/unittests/Option')
-rw-r--r-- | llvm/unittests/Option/CMakeLists.txt | 6 | ||||
-rw-r--r-- | llvm/unittests/Option/OptionMarshallingTest.cpp | 5 | ||||
-rw-r--r-- | llvm/unittests/Option/OptionSubCommandsTest.cpp | 252 | ||||
-rw-r--r-- | llvm/unittests/Option/SubCommandOpts.td | 16 |
4 files changed, 276 insertions, 3 deletions
diff --git a/llvm/unittests/Option/CMakeLists.txt b/llvm/unittests/Option/CMakeLists.txt index 7be4300..5fefb5e 100644 --- a/llvm/unittests/Option/CMakeLists.txt +++ b/llvm/unittests/Option/CMakeLists.txt @@ -4,11 +4,15 @@ set(LLVM_LINK_COMPONENTS ) set(LLVM_TARGET_DEFINITIONS Opts.td) - tablegen(LLVM Opts.inc -gen-opt-parser-defs) + +set(LLVM_TARGET_DEFINITIONS SubCommandOpts.td) +tablegen(LLVM SubCommandOpts.inc -gen-opt-parser-defs) + add_public_tablegen_target(OptsTestTableGen) add_llvm_unittest(OptionTests OptionParsingTest.cpp OptionMarshallingTest.cpp + OptionSubCommandsTest.cpp ) diff --git a/llvm/unittests/Option/OptionMarshallingTest.cpp b/llvm/unittests/Option/OptionMarshallingTest.cpp index 005144b..15917cc 100644 --- a/llvm/unittests/Option/OptionMarshallingTest.cpp +++ b/llvm/unittests/Option/OptionMarshallingTest.cpp @@ -29,8 +29,9 @@ static const OptionWithMarshallingInfo MarshallingTable[] = { #define OPTION_WITH_MARSHALLING( \ PREFIX_TYPE, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, ALIASARGS, \ FLAGS, VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, \ - SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, IMPLIED_CHECK, \ - IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, TABLE_INDEX) \ + SUBCOMMANDIDS_OFFSET, SHOULD_PARSE, ALWAYS_EMIT, KEYPATH, DEFAULT_VALUE, \ + IMPLIED_CHECK, IMPLIED_VALUE, NORMALIZER, DENORMALIZER, MERGER, EXTRACTOR, \ + TABLE_INDEX) \ {PREFIXED_NAME_OFFSET, #KEYPATH, #IMPLIED_CHECK, #IMPLIED_VALUE}, #include "Opts.inc" #undef OPTION_WITH_MARSHALLING diff --git a/llvm/unittests/Option/OptionSubCommandsTest.cpp b/llvm/unittests/Option/OptionSubCommandsTest.cpp new file mode 100644 index 0000000..e31a326 --- /dev/null +++ b/llvm/unittests/Option/OptionSubCommandsTest.cpp @@ -0,0 +1,252 @@ +//===- unittest/Support/OptionParsingTest.cpp - OptTable tests ------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/STLExtras.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::opt; + +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +namespace { +enum ID { + OPT_INVALID = 0, +#define OPTION(PREFIXES, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ + VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ + VALUES, SUBCOMMANDIDS_OFFSET) \ + OPT_##ID, +#include "SubCommandOpts.inc" +#undef OPTION +}; +#define OPTTABLE_STR_TABLE_CODE +#include "SubCommandOpts.inc" +#undef OPTTABLE_STR_TABLE_CODE + +#define OPTTABLE_PREFIXES_TABLE_CODE +#include "SubCommandOpts.inc" +#undef OPTTABLE_PREFIXES_TABLE_CODE + +#define OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE +#include "SubCommandOpts.inc" +#undef OPTTABLE_SUBCOMMAND_IDS_TABLE_CODE + +#define OPTTABLE_SUBCOMMANDS_CODE +#include "SubCommandOpts.inc" +#undef OPTTABLE_SUBCOMMANDS_CODE + +static constexpr OptTable::Info InfoTable[] = { +#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), +#include "SubCommandOpts.inc" +#undef OPTION +}; + +class TestOptSubCommandTable : public GenericOptTable { +public: + TestOptSubCommandTable(bool IgnoreCase = false) + : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable, + /*IgnoreCase=*/false, OptionSubCommands, + OptionSubCommandIDsTable) {} +}; + +// Test fixture +template <typename T> class OptSubCommandTableTest : public ::testing::Test {}; + +// Test both precomputed and computed OptTables with the same suite of tests. +using OptSubCommandTableTestTypes = ::testing::Types<TestOptSubCommandTable>; + +TYPED_TEST_SUITE(OptSubCommandTableTest, OptSubCommandTableTestTypes, ); + +TYPED_TEST(OptSubCommandTableTest, SubCommandParsing) { + TypeParam T; + unsigned MAI, MAC; + + std::string ErrMsg; + raw_string_ostream RSO1(ErrMsg); + + auto HandleMultipleSubcommands = [&](ArrayRef<StringRef> SubCommands) { + ErrMsg.clear(); + RSO1 << "Multiple subcommands passed\n"; + for (auto SC : SubCommands) + RSO1 << "\n" << SC; + }; + + auto HandleOtherPositionals = [&](ArrayRef<StringRef> Positionals) { + ErrMsg.clear(); + RSO1 << "Unregistered positionals passed\n"; + for (auto SC : Positionals) + RSO1 << "\n" << SC; + }; + + { + // Test case 1: Toplevel option, no subcommand + const char *Args[] = {"-version"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + EXPECT_TRUE(AL.hasArg(OPT_version)); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + EXPECT_TRUE(SC.empty()); + EXPECT_FALSE(AL.hasArg(OPT_uppercase)); + EXPECT_FALSE(AL.hasArg(OPT_lowercase)); + } + + { + // Test case 2: Subcommand 'foo' with its valid options + const char *Args[] = {"foo", "-uppercase"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + EXPECT_EQ(SC, "foo"); + EXPECT_TRUE(AL.hasArg(OPT_uppercase)); + EXPECT_FALSE(AL.hasArg(OPT_lowercase)); + EXPECT_FALSE(AL.hasArg(OPT_version)); + EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed")) + << "Did not expect error message as this is a valid use case."; + EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed")) + << "Did not expect error message as this is a valid use case."; + } + + { + // Test case 3: Check valid use of subcommand which follows a valid + // subcommand option. + const char *Args[] = {"-uppercase", "foo"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + EXPECT_EQ(SC, "foo"); + EXPECT_TRUE(AL.hasArg(OPT_uppercase)); + EXPECT_FALSE(AL.hasArg(OPT_lowercase)); + EXPECT_FALSE(AL.hasArg(OPT_version)); + EXPECT_EQ(std::string::npos, ErrMsg.find("Multiple subcommands passed")) + << "Did not expect error message as this is a valid use case."; + EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed")) + << "Did not expect error message as this is a valid use case."; + } + + { + // Test case 4: Check invalid use of passing multiple subcommands. + const char *Args[] = {"-uppercase", "foo", "bar"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + // No valid subcommand should be returned as this is an invalid invocation. + EXPECT_TRUE(SC.empty()); + // Expect the multiple subcommands error message. + EXPECT_NE(std::string::npos, ErrMsg.find("Multiple subcommands passed")); + EXPECT_NE(std::string::npos, ErrMsg.find("foo")); + EXPECT_NE(std::string::npos, ErrMsg.find("bar")); + EXPECT_EQ(std::string::npos, ErrMsg.find("Unregistered positionals passed")) + << "Did not expect error message as this is a valid use case."; + } + + { + // Test case 5: Check invalid use of passing unregistered subcommands. + const char *Args[] = {"foobar"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + // No valid subcommand should be returned as this is an invalid invocation. + EXPECT_TRUE(SC.empty()); + // Expect the unregistered subcommands error message. + EXPECT_NE(std::string::npos, + ErrMsg.find("Unregistered positionals passed")); + EXPECT_NE(std::string::npos, ErrMsg.find("foobar")); + } + + { + // Test case 6: Check invalid use of a valid subcommand which follows a + // valid subcommand option but the option is not registered with the given + // subcommand. + const char *Args[] = {"-lowercase", "bar"}; + InputArgList AL = T.ParseArgs(Args, MAI, MAC); + StringRef SC = AL.getSubCommand( + T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals); + auto HandleSubCommandArg = [&](ID OptionType) { + if (!AL.hasArg(OptionType)) + return false; + auto O = T.getOption(OptionType); + if (!O.isRegisteredSC(SC)) { + ErrMsg.clear(); + RSO1 << "Option [" << O.getName() << "] is not valid for SubCommand [" + << SC << "]\n"; + return false; + } + return true; + }; + EXPECT_EQ(SC, "bar"); // valid subcommand + EXPECT_TRUE(AL.hasArg(OPT_lowercase)); // valid option + EXPECT_FALSE(HandleSubCommandArg(OPT_lowercase)); + EXPECT_NE( + std::string::npos, + ErrMsg.find("Option [lowercase] is not valid for SubCommand [bar]")); + } +} + +TYPED_TEST(OptSubCommandTableTest, SubCommandHelp) { + TypeParam T; + std::string Help; + raw_string_ostream RSO(Help); + + // Toplevel help + T.printHelp(RSO, "Test Usage String", "OverviewString"); + EXPECT_NE(std::string::npos, Help.find("OVERVIEW:")); + EXPECT_NE(std::string::npos, Help.find("OverviewString")); + EXPECT_NE(std::string::npos, Help.find("USAGE:")); + EXPECT_NE(std::string::npos, Help.find("Test Usage String")); + EXPECT_NE(std::string::npos, Help.find("SUBCOMMANDS:")); + EXPECT_NE(std::string::npos, Help.find("foo")); + EXPECT_NE(std::string::npos, Help.find("bar")); + EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo.")); + EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar.")); + EXPECT_NE(std::string::npos, Help.find("OPTIONS:")); + EXPECT_NE(std::string::npos, Help.find("--help")); + EXPECT_NE(std::string::npos, Help.find("-version")); + // uppercase is not a global option and should not be shown. + EXPECT_EQ(std::string::npos, Help.find("-uppercase")); + + // Help for subcommand foo + Help.clear(); + StringRef SC1 = "foo"; + T.printHelp(RSO, "Test Usage String", "OverviewString", false, false, + Visibility(), SC1); + EXPECT_NE(std::string::npos, Help.find("OVERVIEW:")); + EXPECT_NE(std::string::npos, Help.find("OverviewString")); + // SubCommand "foo" definition for tablegen has NO dedicated usage string so + // not expected to see USAGE. + EXPECT_EQ(std::string::npos, Help.find("USAGE:")); + EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand foo.")); + EXPECT_NE(std::string::npos, Help.find("-uppercase")); + EXPECT_NE(std::string::npos, Help.find("-lowercase")); + EXPECT_EQ(std::string::npos, Help.find("-version")); + EXPECT_EQ(std::string::npos, Help.find("SUBCOMMANDS:")); + + // Help for subcommand bar + Help.clear(); + StringRef SC2 = "bar"; + T.printHelp(RSO, "Test Usage String", "OverviewString", false, false, + Visibility(), SC2); + EXPECT_NE(std::string::npos, Help.find("OVERVIEW:")); + EXPECT_NE(std::string::npos, Help.find("OverviewString")); + // SubCommand "bar" definition for tablegen has a dedicated usage string. + EXPECT_NE(std::string::npos, Help.find("USAGE:")); + EXPECT_NE(std::string::npos, Help.find("Subcommand bar <options>")); + EXPECT_NE(std::string::npos, Help.find("HelpText for SubCommand bar.")); + EXPECT_NE(std::string::npos, Help.find("-uppercase")); + // lowercase is not an option for bar and should not be shown. + EXPECT_EQ(std::string::npos, Help.find("-lowercase")); + // version is a global option and should not be shown. + EXPECT_EQ(std::string::npos, Help.find("-version")); +} +} // end anonymous namespace diff --git a/llvm/unittests/Option/SubCommandOpts.td b/llvm/unittests/Option/SubCommandOpts.td new file mode 100644 index 0000000..b9750da --- /dev/null +++ b/llvm/unittests/Option/SubCommandOpts.td @@ -0,0 +1,16 @@ +include "llvm/Option/OptParser.td" + +def sc_foo : SubCommand<"foo", "HelpText for SubCommand foo.">; + +def sc_bar : SubCommand<"bar", "HelpText for SubCommand bar.", + "Subcommand bar <options>">; + +def help : Flag<["--"], "help">, HelpText<"Subcommand <subcommand> <options>">; + +def version : Flag<["-"], "version">, HelpText<"Display the version number">; + +def uppercase : Flag<["-"], "uppercase", [sc_foo, sc_bar]>, + HelpText<"Print in uppercase">; + +def lowercase : Flag<["-"], "lowercase", [sc_foo]>, + HelpText<"Print in lowercase">; |