| //===- OptionParserTest.cpp -----------------------------------------------===// | |
| // | |
| // 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 "orc-rt-internal/tools/OptionParser.h" | |
| #include "orc-rt/support/Error.h" | |
| #include "ErrorMatchers.h" | |
| #include "gtest/gtest.h" | |
| using namespace orc_rt; | |
| using namespace orc_rt::test; | |
| class OptionParserTest : public ::testing::Test { | |
| protected: | |
| std::string Host; | |
| int Port = 0; | |
| bool Verbose = false; | |
| bool Help = false; | |
| OptionParser Parser; | |
| void SetUp() override { | |
| Parser.addValue("host", "Hostname", std::string("localhost"), Host, | |
| OptionParser::OptionKind::Value, 'h'); | |
| Parser.addFlag("help", "Display this help message", false, Help, '?'); | |
| Parser.addValue("port", "Port number", 8080, Port, | |
| OptionParser::OptionKind::Value, 'p'); | |
| Parser.addFlag("verbose", "Enable verbose logging", false, Verbose, 'v'); | |
| } | |
| }; | |
| TEST_F(OptionParserTest, NoopTest) { | |
| OptionParser Parser; | |
| const char *Argv[] = {""}; | |
| EXPECT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), | |
| Succeeded()); | |
| } | |
| TEST_F(OptionParserTest, ValueRequired) { | |
| const char *Argv[] = {"--host"}; | |
| EXPECT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), Failed()); | |
| } | |
| TEST_F(OptionParserTest, UnknownOption) { | |
| const char *Argv[] = {"--unknown=foo"}; | |
| EXPECT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), Failed()); | |
| } | |
| TEST_F(OptionParserTest, InvalidInteger) { | |
| const char *Argv[] = {"--port=not_a_number"}; | |
| EXPECT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), Failed()); | |
| } | |
| TEST_F(OptionParserTest, ParseFullConfiguration) { | |
| const char *Argv[] = {"--host=example.com", "--port=8080", "--verbose=true"}; | |
| ASSERT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), | |
| Succeeded()); | |
| EXPECT_EQ(Host, "example.com"); | |
| EXPECT_EQ(Port, 8080); | |
| EXPECT_EQ(Verbose, true); | |
| } | |
| TEST_F(OptionParserTest, ShortFlagClustering) { | |
| const char *Argv[] = {"-v?"}; | |
| ASSERT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), | |
| Succeeded()); | |
| EXPECT_TRUE(Verbose); | |
| EXPECT_TRUE(Help); | |
| } | |
| TEST_F(OptionParserTest, ShortFlagWithValue) { | |
| const char *Argv[] = {"-p", "1234", "-hlocalhost"}; | |
| ASSERT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), | |
| Succeeded()); | |
| EXPECT_EQ(Port, 1234); | |
| EXPECT_EQ(Host, "localhost"); | |
| } | |
| TEST_F(OptionParserTest, ClusterWithValueAtEnd) { | |
| const char *Argv[] = {"-vp9999"}; | |
| ASSERT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), | |
| Succeeded()); | |
| EXPECT_TRUE(Verbose); | |
| EXPECT_EQ(Port, 9999); | |
| } | |
| TEST_F(OptionParserTest, DoubleDashTerminatesOptionParsing) { | |
| const char *Argv[] = {"-v", "--", "-p", "1234"}; | |
| ASSERT_THAT_ERROR(Parser.parse(std::begin(Argv), std::end(Argv)), | |
| Succeeded()); | |
| EXPECT_TRUE(Verbose); | |
| EXPECT_EQ(Port, 8080); // Should remain default | |
| ASSERT_EQ(Parser.positionals().size(), 2u); | |
| EXPECT_EQ(Parser.positionals()[0], "-p"); | |
| EXPECT_EQ(Parser.positionals()[1], "1234"); | |
| } | |
| TEST_F(OptionParserTest, ParseAsMainWithEmptyArgsSucceeds) { | |
| const char *Argv[] = {"appname"}; | |
| EXPECT_THAT_ERROR( | |
| Parser.parseAsMainArgs(std::size(Argv), const_cast<char **>(Argv)), | |
| Succeeded()); | |
| } | |
| TEST_F(OptionParserTest, ParseAsMainWithRegularArgsSucceeds) { | |
| const char *Argv[] = {"appname", "-v", "--", "-p", "1234"}; | |
| ASSERT_THAT_ERROR( | |
| Parser.parseAsMainArgs(std::size(Argv), const_cast<char **>(Argv)), | |
| Succeeded()); | |
| EXPECT_TRUE(Verbose); | |
| EXPECT_EQ(Port, 8080); // Should remain default | |
| ASSERT_EQ(Parser.positionals().size(), 2u); | |
| EXPECT_EQ(Parser.positionals()[0], "-p"); | |
| EXPECT_EQ(Parser.positionals()[1], "1234"); | |
| } | |
| TEST_F(OptionParserTest, ParseAsMainWithEmptyListFails) { | |
| std::vector<char *> Argv; | |
| EXPECT_THAT_ERROR( | |
| Parser.parseAsMainArgs(static_cast<int>(Argv.size()), Argv.data()), | |
| Failed()); | |
| } | |
| TEST_F(OptionParserTest, PrintHelpAlignmentWithShortFlags) { | |
| std::string LogFile; | |
| Parser.addValue("log-file", "Path to log", std::string("out.log"), LogFile); | |
| std::string Result = Parser.formatHelp("appname"); | |
| auto GetColumn = [&](std::string_view SearchTerm) -> size_t { | |
| size_t Pos = Result.find(SearchTerm); | |
| if (Pos == std::string::npos) | |
| return 0; | |
| size_t LineStart = Result.rfind('\n', Pos); | |
| return (LineStart == std::string::npos) ? Pos : (Pos - LineStart - 1); | |
| }; | |
| size_t PortDescCol = GetColumn("Port number"); | |
| size_t LogDescCol = GetColumn("Path to log"); | |
| size_t VerbDescCol = GetColumn("Enable verbose"); | |
| ASSERT_NE(PortDescCol, 0u); | |
| EXPECT_EQ(PortDescCol, LogDescCol) | |
| << "Descriptions should align even if short flag is missing"; | |
| EXPECT_EQ(LogDescCol, VerbDescCol); | |
| size_t LogFlagPos = Result.find("--log-file"); | |
| size_t PortFlagPos = Result.find("--port"); | |
| size_t LogFlagCol = (LogFlagPos - Result.rfind('\n', LogFlagPos) - 1); | |
| size_t PortFlagCol = (PortFlagPos - Result.rfind('\n', PortFlagPos) - 1); | |
| EXPECT_EQ(LogFlagCol, PortFlagCol); | |
| } | |
| TEST_F(OptionParserTest, HelpDescribesPositionalsGenericallyByDefault) { | |
| std::string Result = Parser.formatHelp("appname"); | |
| EXPECT_EQ( | |
| Result.rfind("Usage: appname [options] [positional arguments]\n", 0), 0u); | |
| EXPECT_EQ(Result.back(), '\n') << "nothing follows the options by default"; | |
| EXPECT_NE(Result.find("Enable verbose logging\n"), std::string::npos); | |
| } | |
| TEST_F(OptionParserTest, HelpUsesPositionalUsageAndEpilogue) { | |
| Parser.setPositionalUsage("<input-file>").setEpilogue("INPUT FILES:\n x\n"); | |
| std::string Result = Parser.formatHelp("appname"); | |
| EXPECT_EQ(Result.rfind("Usage: appname [options] <input-file>\n", 0), 0u); | |
| // The epilogue follows the options, after a blank line. | |
| size_t LastOpt = Result.find("Enable verbose logging\n"); | |
| ASSERT_NE(LastOpt, std::string::npos); | |
| EXPECT_EQ(Result.substr(LastOpt), "Enable verbose logging\n" | |
| "\n" | |
| "INPUT FILES:\n" | |
| " x\n"); | |
| } |