Adds orc_rt::CommandLineParser, a small header-only option parser in
orc-rt-utils for use by orc-rt tools that need argument handling.
Features:
- Long (--name, --name=value) and short (-n, -nvalue) options.
- Short-flag clustering (e.g. -vp9999) and "--" to terminate option
parsing, with remaining tokens collected as positionals.
- Typed values, with parse failures returning an orc_rt::Error.
- printHelp() with aligned, optionally short-name-aware output.
Values are bound to caller-owned storage supplied at registration time and
reset to their defaults on each parse().
Also adds CommandLineTest.cpp to the CoreTests unittest and fixes the
indentation of the docs subdirectory guard in orc-rt/CMakeLists.txt.
146 lines
4.7 KiB
C++
146 lines
4.7 KiB
C++
//===- CommandLineTest.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-utils/CommandLine.h"
|
|
#include "orc-rt/Error.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace orc_rt;
|
|
|
|
class CommandLineParserTest : public ::testing::Test {
|
|
protected:
|
|
std::string Host;
|
|
int Port = 0;
|
|
bool Verbose = false;
|
|
bool Help = false;
|
|
CommandLineParser Parser;
|
|
|
|
void SetUp() override {
|
|
Parser.addValue("host", "Hostname", std::string("localhost"), Host,
|
|
CommandLineParser::OptionKind::Value, 'h');
|
|
Parser.addFlag("help", "Display this help message", false, Help, '?');
|
|
Parser.addValue("port", "Port number", 8080, Port,
|
|
CommandLineParser::OptionKind::Value, 'p');
|
|
Parser.addFlag("verbose", "Enable verbose logging", false, Verbose, 'v');
|
|
}
|
|
};
|
|
|
|
TEST_F(CommandLineParserTest, NoopTest) {
|
|
CommandLineParser Parser;
|
|
const char *Argv[] = {"appname"};
|
|
auto Err = Parser.parse(1, const_cast<char **>(Argv));
|
|
EXPECT_FALSE(!!Err);
|
|
}
|
|
|
|
TEST_F(CommandLineParserTest, ValueRequired) {
|
|
const char *Argv[] = {"appname", "--host"};
|
|
auto Err = Parser.parse(std::begin(Argv), std::end(Argv));
|
|
if (!Err) {
|
|
ADD_FAILURE() << "--host requires a value, shouldn't succeed.";
|
|
} else {
|
|
orc_rt::consumeError(std::move(Err));
|
|
}
|
|
}
|
|
|
|
TEST_F(CommandLineParserTest, UnknownOption) {
|
|
const char *Argv[] = {"appname", "--unknown=foo"};
|
|
auto Err = Parser.parse(std::begin(Argv), std::end(Argv));
|
|
if (!Err) {
|
|
ADD_FAILURE() << "unknown option, shouldn't succeed.";
|
|
} else {
|
|
orc_rt::consumeError(std::move(Err));
|
|
}
|
|
}
|
|
|
|
TEST_F(CommandLineParserTest, InvalidInteger) {
|
|
const char *Argv[] = {"appname", "--port=not_a_number"};
|
|
auto Err = Parser.parse(std::begin(Argv), std::end(Argv));
|
|
if (!Err) {
|
|
ADD_FAILURE() << "Invalid integer, shouldn't succeed.";
|
|
} else {
|
|
orc_rt::consumeError(std::move(Err));
|
|
}
|
|
}
|
|
|
|
TEST_F(CommandLineParserTest, ParseFullConfiguration) {
|
|
const char *Argv[] = {"appname", "--host=example.com", "--port=8080",
|
|
"--verbose=true"};
|
|
cantFail(Parser.parse(std::begin(Argv), std::end(Argv)));
|
|
EXPECT_EQ(Host, "example.com");
|
|
EXPECT_EQ(Port, 8080);
|
|
EXPECT_EQ(Verbose, true);
|
|
}
|
|
|
|
TEST_F(CommandLineParserTest, ShortFlagClustering) {
|
|
const char *Argv[] = {"appname", "-v?"};
|
|
cantFail(Parser.parse(std::begin(Argv), std::end(Argv)));
|
|
EXPECT_TRUE(Verbose);
|
|
EXPECT_TRUE(Help);
|
|
}
|
|
|
|
TEST_F(CommandLineParserTest, ShortFlagWithValue) {
|
|
const char *Argv[] = {"appname", "-p", "1234", "-hlocalhost"};
|
|
cantFail(Parser.parse(std::begin(Argv), std::end(Argv)));
|
|
EXPECT_EQ(Port, 1234);
|
|
EXPECT_EQ(Host, "localhost");
|
|
}
|
|
|
|
TEST_F(CommandLineParserTest, ClusterWithValueAtEnd) {
|
|
const char *Argv[] = {"appname", "-vp9999"};
|
|
cantFail(Parser.parse(std::begin(Argv), std::end(Argv)));
|
|
EXPECT_TRUE(Verbose);
|
|
EXPECT_EQ(Port, 9999);
|
|
}
|
|
|
|
TEST_F(CommandLineParserTest, DoubleDashTerminatesOptionParsing) {
|
|
const char *Argv[] = {"appname", "-v", "--", "-p", "1234"};
|
|
cantFail(Parser.parse(static_cast<int>(std::size(Argv)),
|
|
const_cast<char **>(Argv)));
|
|
|
|
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(CommandLineParserTest, PrintHelpAlignmentWithShortFlags) {
|
|
std::string LogFile;
|
|
Parser.addValue("log-file", "Path to log", std::string("out.log"), LogFile);
|
|
|
|
std::stringstream SS;
|
|
Parser.printHelp(SS, "appname");
|
|
std::string Result = SS.str();
|
|
|
|
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);
|
|
}
|