blob: 5640b931e8ae2d3380f91b072054d2d79990f8f6 [file] [log] [blame]
// Copyright 2024 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "absl/strings/str_split.h"
#include "dawn/utils/CommandLineParser.h"
#include "gtest/gtest.h"
namespace dawn {
namespace {
using CLP = utils::CommandLineParser;
std::vector<std::string_view> Split(const char* s) {
return absl::StrSplit(s, ' ');
}
void ExpectSuccess(const CLP::ParseResult& result) {
EXPECT_TRUE(result.success);
EXPECT_EQ(result.errorMessage, "");
}
void ExpectError(const CLP::ParseResult& result, std::string_view message) {
EXPECT_FALSE(result.success);
EXPECT_EQ(result.errorMessage, message);
}
// Tests for BoolOption parsing
TEST(CommandLineParserTest, BoolParsing) {
// Test parsing with nothing afterwards.
{
CLP opts;
auto& opt = opts.AddBool("foo").ShortName('f');
ExpectSuccess(opts.Parse({Split("-f")}));
EXPECT_TRUE(opt.GetValue());
}
// Test parsing with another flag afterwards.
{
CLP opts;
auto& opt = opts.AddBool("foo").ShortName('f');
auto& optB = opts.AddBool("bar").ShortName('b');
ExpectSuccess(opts.Parse(Split("-f -b")));
EXPECT_TRUE(opt.GetValue());
EXPECT_TRUE(optB.IsSet());
}
// Test parsing with garbage afterwards.
{
CLP opts;
auto& opt = opts.AddBool("foo").ShortName('f');
ExpectSuccess(opts.Parse(Split("-f garbage"), {.unknownIsError = false}));
EXPECT_TRUE(opt.GetValue());
}
// Test parsing "true"
{
CLP opts;
auto& opt = opts.AddBool("foo").ShortName('f');
auto& optB = opts.AddBool("bar").ShortName('b');
ExpectSuccess(opts.Parse(Split("-f true -b")));
EXPECT_TRUE(opt.GetValue());
EXPECT_TRUE(optB.IsSet());
}
// Test parsing "false"
{
CLP opts;
auto& opt = opts.AddBool("foo").ShortName('f');
auto& optB = opts.AddBool("bar").ShortName('b');
ExpectSuccess(opts.Parse(Split("-f false -b")));
EXPECT_FALSE(opt.GetValue());
EXPECT_TRUE(optB.IsSet());
}
// Test parsing the option multiple times, with an explicit true argument.
{
CLP opts;
opts.AddBool("foo").ShortName('f');
ExpectError(opts.Parse({Split("-f --foo true")}),
"Failure while parsing \"foo\": cannot set multiple times with explicit "
"true/false arguments");
}
// Test parsing the option multiple times, with an explicit false argument.
{
CLP opts;
opts.AddBool("foo").ShortName('f');
ExpectError(opts.Parse({Split("-f --foo false")}),
"Failure while parsing \"foo\": cannot set multiple times with explicit "
"true/false arguments");
}
// Test parsing the option multiple times, with the implicit true argument.
{
CLP opts;
auto& opt = opts.AddBool("foo").ShortName('f');
ExpectSuccess(opts.Parse({Split("-f -f")}));
EXPECT_TRUE(opt.GetValue());
}
// Test parsing the option multiple times, with the implicit true argument but conflicting
// values
{
CLP opts;
opts.AddBool("foo").ShortName('f');
ExpectError(opts.Parse({Split("-f false --foo")}),
"Failure while parsing \"foo\": cannot be set to both true and false");
}
// Test the default value
{
CLP opts;
auto& opt = opts.AddBool("foo");
ExpectSuccess(opts.Parse(0, nullptr));
EXPECT_FALSE(opt.IsSet());
EXPECT_FALSE(opt.GetValue());
}
}
// Tests for StringOption parsing.
TEST(CommandLineParserTest, StringParsing) {
// Test with nothing afterwards
{
CLP opts;
opts.AddString("foo").ShortName('f');
ExpectError(opts.Parse({Split("-f")}), "Failure while parsing \"foo\": expected a value");
}
// Test parsing with another flag afterwards.
{
CLP opts;
auto& opt = opts.AddString("foo").ShortName('f');
auto& optB = opts.AddBool("bar").ShortName('b');
ExpectSuccess(opts.Parse(Split("-f -b")));
EXPECT_EQ(opt.GetValue(), "-b");
EXPECT_FALSE(optB.IsSet());
}
// Test parsing with some data afterwards
{
CLP opts;
auto& opt = opts.AddString("foo").ShortName('f');
ExpectSuccess(opts.Parse(Split("-f supercalifragilisticexpialidocious")));
EXPECT_EQ(opt.GetValue(), "supercalifragilisticexpialidocious");
}
// Test setting multiple times
{
CLP opts;
opts.AddString("foo").ShortName('f');
ExpectError(opts.Parse({Split("-f aa -f aa")}),
"Failure while parsing \"foo\": cannot be set multiple times");
}
// Test the default value
{
CLP opts;
auto& opt = opts.AddString("foo");
ExpectSuccess(opts.Parse(0, nullptr));
EXPECT_FALSE(opt.IsSet());
EXPECT_TRUE(opt.GetValue().empty());
}
}
// Tests for StringListOption parsing.
TEST(CommandLineParserTest, StringListParsing) {
// Test with nothing afterwards
{
CLP opts;
opts.AddStringList("foo").ShortName('f');
ExpectError(opts.Parse({Split("-f")}), "Failure while parsing \"foo\": expected a value");
}
// Test parsing with another flag afterwards.
{
CLP opts;
auto& opt = opts.AddStringList("foo").ShortName('f');
auto& optB = opts.AddBool("bar").ShortName('b');
ExpectSuccess(opts.Parse(Split("-f -b")));
EXPECT_EQ(opt.GetValue().size(), 1u);
EXPECT_EQ(opt.GetValue()[0], "-b");
EXPECT_FALSE(optB.IsSet());
}
// Test parsing with some data afterwards
{
CLP opts;
auto& opt = opts.AddStringList("foo").ShortName('f');
ExpectSuccess(opts.Parse(Split("-f sugar,butter,flour")));
EXPECT_EQ(opt.GetValue().size(), 3u);
EXPECT_EQ(opt.GetValue()[0], "sugar");
EXPECT_EQ(opt.GetValue()[1], "butter");
EXPECT_EQ(opt.GetValue()[2], "flour");
}
// Test passing the option multiple times, it should add to the list.
{
CLP opts;
auto& opt = opts.AddStringList("foo").ShortName('f');
ExpectSuccess(opts.Parse(Split("-f sugar -foo butter,flour")));
EXPECT_EQ(opt.GetValue().size(), 3u);
EXPECT_EQ(opt.GetValue()[0], "sugar");
EXPECT_EQ(opt.GetValue()[1], "butter");
EXPECT_EQ(opt.GetValue()[2], "flour");
}
// Test the default value
{
CLP opts;
auto& opt = opts.AddStringList("foo");
ExpectSuccess(opts.Parse(0, nullptr));
EXPECT_FALSE(opt.IsSet());
EXPECT_TRUE(opt.GetValue().empty());
}
}
// Tests for EnumOption parsing.
enum class Cell {
Pop,
Six,
Squish,
Uhuh,
Cicero,
Lipschitz,
};
TEST(CommandLineParserTest, EnumParsing) {
std::vector<std::pair<std::string_view, Cell>> conversions = {{
{"pop", Cell::Pop}, {"six", Cell::Six}, {"uh-uh", Cell::Uhuh},
// others left as an exercise to the reader.
}};
// Test with nothing afterwards
{
CLP opts;
opts.AddEnum<Cell>(conversions, "foo").ShortName('f');
ExpectError(opts.Parse({Split("-f")}), "Failure while parsing \"foo\": expected a value");
}
// Test parsing with another flag afterwards.
{
CLP opts;
opts.AddEnum<Cell>(conversions, "foo").ShortName('f');
opts.AddBool("bar").ShortName('b');
ExpectError(opts.Parse({Split("-f -b")}),
"Failure while parsing \"foo\": unknown value \"-b\". Expected one of pop, "
"six, uh-uh.");
}
// Test parsing a correct enum value.
{
CLP opts;
auto& opt = opts.AddEnum<Cell>(conversions, "foo").ShortName('f');
auto& optB = opts.AddBool("bar").ShortName('b');
ExpectSuccess(opts.Parse({Split("-f six -b")}));
EXPECT_EQ(opt.GetValue(), Cell::Six);
EXPECT_TRUE(optB.IsSet());
}
// Test setting multiple times
{
CLP opts;
opts.AddEnum<Cell>(conversions, "foo").ShortName('f');
ExpectError(opts.Parse({Split("-f six -f six")}),
"Failure while parsing \"foo\": cannot be set multiple times");
}
// Test the default value
{
CLP opts;
auto& opt = opts.AddEnum<Cell>(conversions, "foo").Default(Cell::Uhuh);
ExpectSuccess(opts.Parse(0, nullptr));
EXPECT_FALSE(opt.IsSet());
EXPECT_EQ(opt.GetValue(), Cell::Uhuh);
}
}
// Various tests for the handling of long and short names.
TEST(CommandLineParserTest, LongAndShortNames) {
// An option can be referenced by both a long and short name.
{
CLP opts;
auto& opt = opts.AddStringList("foo").ShortName('f');
ExpectSuccess(opts.Parse(Split("-f sugar -foo butter,flour")));
EXPECT_EQ(opt.GetValue().size(), 3u);
EXPECT_EQ(opt.GetValue()[0], "sugar");
EXPECT_EQ(opt.GetValue()[1], "butter");
EXPECT_EQ(opt.GetValue()[2], "flour");
}
// An option without a short name cannot be referenced with it.
{
CLP opts;
opts.AddStringList("foo");
ExpectError(opts.Parse(Split("-f sugar -foo butter,flour")), "Unknown option \"f\"");
}
// Regression test for two options having no short name.
{
CLP opts;
opts.AddStringList("foo");
opts.AddStringList("bar");
ExpectSuccess(opts.Parse(0, nullptr));
}
}
// Tests for option names not being recognized.
TEST(CommandLineParserTest, UnknownOption) {
// An empty arg is not a known option.
{
CLP opts;
ExpectError(opts.Parse(Split("")), "Unknown option \"\"");
ExpectSuccess(opts.Parse(Split(""), {.unknownIsError = false}));
}
// A - is not a known option.
{
CLP opts;
ExpectError(opts.Parse(Split("-")), "Unknown option \"\"");
ExpectSuccess(opts.Parse(Split("-"), {.unknownIsError = false}));
}
// A -- is not a known option.
{
CLP opts;
ExpectError(opts.Parse(Split("--")), "Unknown option \"\"");
ExpectSuccess(opts.Parse(Split("--"), {.unknownIsError = false}));
}
// An unknown short name option.
{
CLP opts;
ExpectError(opts.Parse(Split("-f")), "Unknown option \"f\"");
ExpectError(opts.Parse(Split("-f=")), "Unknown option \"f\"");
ExpectSuccess(opts.Parse(Split("-f"), {.unknownIsError = false}));
ExpectSuccess(opts.Parse(Split("-f="), {.unknownIsError = false}));
}
// An unknown long name option.
{
CLP opts;
ExpectError(opts.Parse(Split("-foo")), "Unknown option \"foo\"");
ExpectError(opts.Parse(Split("-foo=")), "Unknown option \"foo\"");
ExpectSuccess(opts.Parse(Split("-foo"), {.unknownIsError = false}));
ExpectSuccess(opts.Parse(Split("-foo="), {.unknownIsError = false}));
}
}
// Tests for options being set with =
TEST(CommandLineParserTest, EqualSeparator) {
// Test that using an = separator works and lets other arguments be consumed.
{
CLP opts;
auto& opt = opts.AddStringList("foo").ShortName('f');
ExpectSuccess(opts.Parse(Split("-f=sugar -foo butter,flour")));
EXPECT_EQ(opt.GetValue().size(), 3u);
EXPECT_EQ(opt.GetValue()[0], "sugar");
EXPECT_EQ(opt.GetValue()[1], "butter");
EXPECT_EQ(opt.GetValue()[2], "flour");
}
// Test that if the part after the = is not consumed there is an error.
{
CLP opts;
opts.AddBool("foo").ShortName('f');
ExpectError(opts.Parse({Split("-f=garbage")}),
"Argument \"garbage\" was not valid for option \"foo\"");
}
}
// Test that the argc/argv version skips the command name.
TEST(CommandLineParserTest, ArgvArgcSkipCommandName) {
CLP opts;
auto& optA = opts.AddBool("a");
auto& optB = opts.AddBool("b");
auto& optC = opts.AddBool("c");
const char* argv[] = {"-a", "-b", "-c"};
ExpectSuccess(opts.Parse(3, argv));
ASSERT_FALSE(optA.IsSet());
ASSERT_TRUE(optB.IsSet());
ASSERT_TRUE(optC.IsSet());
}
// Tests for the generation of the help strings for the options.
TEST(CommandLineParserTest, PrintHelp) {
// A test with a few options, checks that they are sorted.
{
CLP opts;
opts.AddString("foo");
opts.AddString("bar");
opts.AddString("baz");
std::stringstream s;
opts.PrintHelp(s);
EXPECT_EQ(s.str(), R"(--bar <value>
--baz <value>
--foo <value>
)");
}
// A test with a custom parameter name.
{
CLP opts;
opts.AddString("foo").Parameter("bar");
std::stringstream s;
opts.PrintHelp(s);
EXPECT_EQ(s.str(), R"(--foo <bar>
)");
}
// A test with a boolean value that doesn't get its parameter printed.
{
CLP opts;
opts.AddBool("foo", "enable fooing");
std::stringstream s;
opts.PrintHelp(s);
EXPECT_EQ(s.str(), R"(--foo enable fooing
)");
}
// A test for the parameter of enum options.
{
std::vector<std::pair<std::string_view, Cell>> conversions = {{
{"pop", Cell::Pop},
{"six", Cell::Six},
{"uh-uh", Cell::Uhuh},
}};
CLP opts;
opts.AddEnum(conversions, "cell", "which story to get");
std::stringstream s;
opts.PrintHelp(s);
EXPECT_EQ(s.str(), R"(--cell <pop|six|uh-uh> which story to get
)");
}
// A test for short name handling.
{
CLP opts;
opts.AddString("foo").ShortName('f');
std::stringstream s;
opts.PrintHelp(s);
EXPECT_EQ(s.str(), R"(--foo <value>
-f alias for --foo
)");
}
}
// Tests that AddHelp() add the correct option.
TEST(CommandLineParserTest, HelpOption) {
CLP opts;
CLP::BoolOption& helpOpt = opts.AddHelp();
std::stringstream s;
opts.PrintHelp(s);
EXPECT_EQ(s.str(), R"(--help Shows the help
-h alias for --help
)");
ExpectSuccess(opts.Parse(Split("-h")));
EXPECT_TRUE(helpOpt.GetValue());
}
} // anonymous namespace
} // namespace dawn