//===- 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]"));
  }

  {
    // Test case 7: Check valid use of a valid subcommand following more
    // positional arguments.
    const char *Args[] = {"bar", "input"};
    InputArgList AL = T.ParseArgs(Args, MAI, MAC);
    StringRef SC = AL.getSubCommand(
        T.getSubCommands(), HandleMultipleSubcommands, HandleOtherPositionals);
    EXPECT_EQ(SC, "bar"); // valid subcommand
    EXPECT_NE(std::string::npos,
              ErrMsg.find("Unregistered positionals passed"));
    EXPECT_NE(std::string::npos, ErrMsg.find("input"));
  }
}

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
