From 306397adf560264fdc0dde6874e212df53992765 Mon Sep 17 00:00:00 2001 From: Angela Chen Date: Fri, 18 Aug 2023 19:22:55 -0400 Subject: [PATCH] Make changes to allow subsequent json files to overwrite earlier ones, and command line flags to overwrite json files after that. --- include/ppx/command_line_parser.h | 15 ++-- src/ppx/application.cpp | 11 ++- src/ppx/command_line_parser.cpp | 49 ++++++++++-- src/test/command_line_parser_test.cpp | 107 ++++++++++++-------------- 4 files changed, 104 insertions(+), 78 deletions(-) diff --git a/include/ppx/command_line_parser.h b/include/ppx/command_line_parser.h index 1c6a84b5d..8a54bd6c4 100644 --- a/include/ppx/command_line_parser.h +++ b/include/ppx/command_line_parser.h @@ -108,6 +108,9 @@ class CliOptions // Same as above, but appends an array of values at the same key void AddOption(std::string_view optionName, const std::vector& valueArray); + // For all options existing in newOptions, current entries in mAllOptions will be replaced by them + void OverwriteOptions(const CliOptions& newOptions); + template T GetParsedOrDefault(std::string_view valueStr, const T& defaultValue) const { @@ -160,8 +163,12 @@ class CommandLineParser // and write the error to `out_error`. std::optional Parse(int argc, const char* argv[]); - // Adds all options specified within jsonConfig to mOpts. - std::optional AddJsonOptions(const nlohmann::json& jsonConfig); + // Parses all options specified within jsonConfig and adds them to cliOptions. + std::optional ParseJson(CliOptions& cliOptions, const nlohmann::json& jsonConfig); + + // Parses an option, handles the special --no-flag-name case, then adds the option to cliOptions + // Expects option names without the "--" prefix. + std::optional ParseOption(CliOptions& cliOptions, std::string_view optionName, std::string_view valueStr); std::string GetJsonConfigFlagName() const { return mJsonConfigFlagName; } const CliOptions& GetOptions() const { return mOpts; } @@ -170,10 +177,6 @@ class CommandLineParser void AppendUsageMsg(const std::string& additionalMsg) { mUsageMsg += additionalMsg; } private: - // Adds an option to mOpts and handles the special --no-flag-name case. - // Expects option names without the "--" prefix. - std::optional AddOption(std::string_view optionName, std::string_view valueStr); - CliOptions mOpts; std::string mJsonConfigFlagName = "config-json-path"; std::string mUsageMsg = R"( diff --git a/src/ppx/application.cpp b/src/ppx/application.cpp index 4b95fb574..5045c9c00 100644 --- a/src/ppx/application.cpp +++ b/src/ppx/application.cpp @@ -672,8 +672,8 @@ void Application::DispatchConfig() auto assetPaths = mStandardOpts.pAssetsPaths->GetValue(); if (!assetPaths.empty()) { - // Asset directories specified later have higher priority - for (auto it = assetPaths.begin(); it < assetPaths.end(); it++) { + // Insert at front, in reverse order, so we respect the command line ordering for priority. + for (auto it = assetPaths.rbegin(); it < assetPaths.rend(); it++) { AddAssetDir(*it, /* insert_at_front= */ true); } } @@ -810,15 +810,14 @@ void Application::InitStandardKnobs() mStandardOpts.pAssetsPaths = mKnobManager.CreateKnob>>("assets-path", defaultEmptyList); mStandardOpts.pAssetsPaths->SetFlagDescription( - "Add a path in front of the assets search path list."); + "Add a path before the default assets folder in the search list."); mStandardOpts.pAssetsPaths->SetFlagParameters(""); mStandardOpts.pConfigJsonPaths = mKnobManager.CreateKnob>>(mCommandLineParser.GetJsonConfigFlagName(), defaultEmptyList); mStandardOpts.pConfigJsonPaths->SetFlagDescription( "Additional commandline flags specified in a JSON file. Values specified in JSON files are " - "always lower priority than those specified on the command line. Between different files, the " - "later ones take priority. For lists, the JSON values will come earlier in the array than the " - "command-line values"); + "always overwritten by those specified on the command line. Between different files, the " + "later ones take priority."); mStandardOpts.pConfigJsonPaths->SetFlagParameters(""); mStandardOpts.pDeterministic = diff --git a/src/ppx/command_line_parser.cpp b/src/ppx/command_line_parser.cpp index 192a59a1c..9b9bba0e8 100644 --- a/src/ppx/command_line_parser.cpp +++ b/src/ppx/command_line_parser.cpp @@ -38,6 +38,22 @@ bool StartsWithDoubleDash(std::string_view s) namespace ppx { +void CliOptions::OverwriteOptions(const CliOptions& newOptions) +{ + PPX_LOG_INFO("UNICORN entered OverwriteOptions"); + PPX_LOG_INFO("UNICORN GetNumUniqueOptions: " << newOptions.GetNumUniqueOptions()); + PPX_LOG_INFO("UNICORN SHOW newOptions"); + for (auto& it : newOptions.mAllOptions) { + PPX_LOG_INFO("UNICORN key: " << it.first << "value: " << ppx::string_util::ToString(it.second)); + mAllOptions[it.first] = it.second; + } + + PPX_LOG_INFO("UNICORN SHOW mAllOptions"); + for (auto& it : mAllOptions) { + PPX_LOG_INFO("UNICORN key: " << it.first << "value: " << ppx::string_util::ToString(it.second)); + } +} + std::pair CliOptions::GetOptionValueOrDefault(const std::string& optionName, const std::pair& defaultValue) const { auto it = mAllOptions.find(optionName); @@ -57,27 +73,33 @@ std::pair CliOptions::GetOptionValueOrDefault(const std::string& optio void CliOptions::AddOption(std::string_view optionName, std::string_view value) { + PPX_LOG_INFO("UNICORN AddOption About to add option: " << optionName << " valueStr: " << value); std::string optionNameStr(optionName); std::string valueStr(value); auto it = mAllOptions.find(optionNameStr); if (it == mAllOptions.cend()) { std::vector v{std::move(valueStr)}; mAllOptions.emplace(std::move(optionNameStr), std::move(v)); + PPX_LOG_INFO("UNICORN AddOption added new option"); return; } it->second.push_back(valueStr); + PPX_LOG_INFO("UNICORN AddOption appended to existing option"); } void CliOptions::AddOption(std::string_view optionName, const std::vector& valueArray) { + PPX_LOG_INFO("UNICORN AddOption About to add list option: " << optionName << " valueArray: " << ppx::string_util::ToString(valueArray)); std::string optionNameStr(optionName); auto it = mAllOptions.find(optionNameStr); if (it == mAllOptions.cend()) { mAllOptions.emplace(std::move(optionNameStr), valueArray); + PPX_LOG_INFO("UNICORN AddOption list : added new option"); return; } auto storedValueArray = it->second; storedValueArray.insert(storedValueArray.end(), valueArray.cbegin(), valueArray.cend()); + PPX_LOG_INFO("UNICORN AddOption list : appended to existing option"); } bool CliOptions::Parse(std::string_view valueStr, bool defaultValue) const @@ -163,11 +185,17 @@ std::optional CommandLineParser::Parse(int argc return "The following config file could not be parsed as a JSON object: " + jsonPath; } - if (auto error = AddJsonOptions(data)) { + CliOptions jsonOptions; + if (auto error = ParseJson(jsonOptions, data)) { return error; } + PPX_LOG_INFO("UNICORN jsonOptions num unique: " << jsonOptions.GetNumUniqueOptions()); + PPX_LOG_INFO("UNICORN About to overwrite main options"); + mOpts.OverwriteOptions(jsonOptions); + PPX_LOG_INFO("UNICORN mOpts num unique: " << mOpts.GetNumUniqueOptions()); } + CliOptions commandlineOptions; // Main pass, process arguments into either standalone flags or options with parameters. for (size_t i = 0; i < args.size(); ++i) { std::string_view name = ppx::string_util::TrimBothEnds(args[i]); @@ -186,41 +214,45 @@ std::optional CommandLineParser::Parse(int argc } } - if (auto error = AddOption(name, value)) { + if (auto error = ParseOption(commandlineOptions, name, value)) { return error; } } + PPX_LOG_INFO("UNICORN commandlineOptions num unique: " << commandlineOptions.GetNumUniqueOptions()); + PPX_LOG_INFO("UNICORN About to overwrite main options"); + mOpts.OverwriteOptions(commandlineOptions); + PPX_LOG_INFO("UNICORN mOpts num unique: " << mOpts.GetNumUniqueOptions()); return std::nullopt; } -std::optional CommandLineParser::AddJsonOptions(const nlohmann::json& jsonConfig) +std::optional CommandLineParser::ParseJson(CliOptions& cliOptions, const nlohmann::json& jsonConfig) { std::stringstream ss; for (auto it = jsonConfig.cbegin(); it != jsonConfig.cend(); ++it) { if ((it.value()).is_array()) { - // Special case, arrays specified in JSON are added directly to mOpts to avoid inserting element by element + // Special case, arrays specified in JSON are added directly to cliOptions to avoid inserting element by element std::vector jsonStringArray; for (const auto& elem : it.value()) { ss << elem; jsonStringArray.push_back(std::string(ppx::string_util::TrimBothEnds(ss.str(), " \t\""))); ss.str(""); } - mOpts.AddOption(it.key(), jsonStringArray); + cliOptions.AddOption(it.key(), jsonStringArray); continue; } ss << it.value(); std::string value = ss.str(); ss.str(""); - if (auto error = AddOption(it.key(), ppx::string_util::TrimBothEnds(value, " \t\""))) { + if (auto error = ParseOption(cliOptions, it.key(), ppx::string_util::TrimBothEnds(value, " \t\""))) { return error; } } return std::nullopt; } -std::optional CommandLineParser::AddOption(std::string_view optionName, std::string_view valueStr) +std::optional CommandLineParser::ParseOption(CliOptions& cliOptions, std::string_view optionName, std::string_view valueStr) { if (optionName.length() > 2 && optionName.substr(0, 3) == "no-") { if (valueStr.length() > 0) { @@ -229,7 +261,8 @@ std::optional CommandLineParser::AddOption(std: optionName = optionName.substr(3); valueStr = "0"; } - mOpts.AddOption(optionName, valueStr); + PPX_LOG_INFO("UNICORN ParseOption About to add option: " << optionName << " valueStr: " << valueStr); + cliOptions.AddOption(optionName, valueStr); return std::nullopt; } diff --git a/src/test/command_line_parser_test.cpp b/src/test/command_line_parser_test.cpp index b2d2c0efd..f38d49fd4 100644 --- a/src/test/command_line_parser_test.cpp +++ b/src/test/command_line_parser_test.cpp @@ -22,7 +22,7 @@ namespace { using ::testing::HasSubstr; -TEST(CommandLineParserTest, ZeroArguments) +TEST(CommandLineParserTest, Parse_ZeroArguments) { CommandLineParser parser; if (auto error = parser.Parse(0, nullptr)) { @@ -31,7 +31,7 @@ TEST(CommandLineParserTest, ZeroArguments) EXPECT_EQ(parser.GetOptions().GetNumUniqueOptions(), 0); } -TEST(CommandLineParserTest, FirstArgumentIgnored) +TEST(CommandLineParserTest, Parse_FirstArgumentIgnored) { CommandLineParser parser; const char* args[] = {"/path/to/executable"}; @@ -41,7 +41,7 @@ TEST(CommandLineParserTest, FirstArgumentIgnored) EXPECT_EQ(parser.GetOptions().GetNumUniqueOptions(), 0); } -TEST(CommandLineParserTest, BooleansSuccessfullyParsed) +TEST(CommandLineParserTest, Parse_Booleans) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "--b", "1", "--c", "true", "--no-d", "--e", "0", "--f", "false"}; @@ -58,7 +58,7 @@ TEST(CommandLineParserTest, BooleansSuccessfullyParsed) EXPECT_EQ(gotOptions.GetOptionValueOrDefault("f", true), false); } -TEST(CommandLineParserTest, StringsSuccessfullyParsed) +TEST(CommandLineParserTest, Parse_Strings) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "filename with spaces", "--b", "filenameWithoutSpaces", "--c", "filename,with/.punctuation,", "--d", "", "--e"}; @@ -74,7 +74,7 @@ TEST(CommandLineParserTest, StringsSuccessfullyParsed) EXPECT_EQ(gotOptions.GetOptionValueOrDefault("e", "foo"), ""); } -TEST(CommandLineParserTest, IntegersSuccessfullyParsed) +TEST(CommandLineParserTest, Parse_Integers) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "0", "--b", "-5", "--c", "300", "--d", "0", "--e", "1000"}; @@ -90,7 +90,7 @@ TEST(CommandLineParserTest, IntegersSuccessfullyParsed) EXPECT_EQ(gotOptions.GetOptionValueOrDefault("e", -1), 1000); } -TEST(CommandLineParserTest, FloatsSuccessfullyParsed) +TEST(CommandLineParserTest, Parse_Floats) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "1.0", "--b", "-6.5", "--c", "300"}; @@ -104,7 +104,7 @@ TEST(CommandLineParserTest, FloatsSuccessfullyParsed) EXPECT_EQ(gotOptions.GetOptionValueOrDefault("c", 0.0f), 300.0f); } -TEST(CommandLineParserTest, StringListSuccesfullyParsed) +TEST(CommandLineParserTest, Parse_StringList) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "some-path", "--a", "some-other-path", "--a", "last-path"}; @@ -122,7 +122,7 @@ TEST(CommandLineParserTest, StringListSuccesfullyParsed) } } -TEST(CommandLineParserTest, ResolutionSuccesfullyParsed) +TEST(CommandLineParserTest, Parse_Resolution) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "1000x2000"}; @@ -136,7 +136,7 @@ TEST(CommandLineParserTest, ResolutionSuccesfullyParsed) EXPECT_EQ(res.second, 2000); } -TEST(CommandLineParserTest, ResolutionSuccessfullyParsedButDefaulted) +TEST(CommandLineParserTest, Parse_ResolutionDefaulted) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "1000X2000"}; @@ -150,7 +150,7 @@ TEST(CommandLineParserTest, ResolutionSuccessfullyParsedButDefaulted) EXPECT_EQ(res.second, 0); } -TEST(CommandLineParserTest, EqualSignsSuccessfullyParsed) +TEST(CommandLineParserTest, Parse_EqualSigns) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "--b=5", "--c", "--d", "11"}; @@ -165,7 +165,7 @@ TEST(CommandLineParserTest, EqualSignsSuccessfullyParsed) EXPECT_EQ(gotOptions.GetOptionValueOrDefault("d", 0), 11); } -TEST(CommandLineParserTest, EqualSignsMultipleFailedParsed) +TEST(CommandLineParserTest, Parse_EqualSignsMultipleFail) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "--b=5=8", "--c", "--d", "11"}; @@ -174,7 +174,7 @@ TEST(CommandLineParserTest, EqualSignsMultipleFailedParsed) EXPECT_THAT(error->errorMsg, HasSubstr("Unexpected number of '=' symbols in the following string")); } -TEST(CommandLineParserTest, EqualSignsMalformedFailedParsed) +TEST(CommandLineParserTest, Parse_EqualSignsMalformedFail) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "--b=", "--c", "--d", "11"}; @@ -183,7 +183,7 @@ TEST(CommandLineParserTest, EqualSignsMalformedFailedParsed) EXPECT_THAT(error->errorMsg, HasSubstr("Malformed flag with '='")); } -TEST(CommandLineParserTest, LeadingParameterFailedParsed) +TEST(CommandLineParserTest, Parse_LeadingParameterFail) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "10", "--a", "--b", "5", "--c", "--d", "11"}; @@ -192,7 +192,7 @@ TEST(CommandLineParserTest, LeadingParameterFailedParsed) EXPECT_THAT(error->errorMsg, HasSubstr("Invalid command-line option")); } -TEST(CommandLineParserTest, AdjacentParameterFailedParsed) +TEST(CommandLineParserTest, Parse_AdjacentParameterFail) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "--b", "5", "8", "--c", "--d", "11"}; @@ -201,7 +201,7 @@ TEST(CommandLineParserTest, AdjacentParameterFailedParsed) EXPECT_THAT(error->errorMsg, HasSubstr("Invalid command-line option")); } -TEST(CommandLineParserTest, LastValueIsTaken) +TEST(CommandLineParserTest, Parse_LastValueIsTaken) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--a", "1", "--b", "1", "--a", "2", "--a", "3"}; @@ -214,7 +214,7 @@ TEST(CommandLineParserTest, LastValueIsTaken) EXPECT_EQ(gotOptions.GetOptionValueOrDefault("b", 0), 1); } -TEST(CommandLineParserTest, ExtraOptionsSuccessfullyParsed) +TEST(CommandLineParserTest, Parse_ExtraOptions) { CommandLineParser parser; const char* args[] = {"/path/to/executable", "--extra-option-bool", "true", "--extra-option-int", "123", "--extra-option-no-param", "--extra-option-str", "option string value"}; @@ -230,20 +230,21 @@ TEST(CommandLineParserTest, ExtraOptionsSuccessfullyParsed) EXPECT_TRUE(opts.HasExtraOption("extra-option-no-param")); } -TEST(CommandLineParserTest, JsonEmptyParsedSuccessfully) +TEST(CommandLineParserTest, ParseJson_Empty) { CommandLineParser parser; + CliOptions opts; nlohmann::json jsonConfig; - if (auto error = parser.AddJsonOptions(jsonConfig)) { + if (auto error = parser.ParseJson(opts, jsonConfig)) { FAIL() << error->errorMsg; } - auto opts = parser.GetOptions(); EXPECT_EQ(opts.GetNumUniqueOptions(), 0); } -TEST(CommandLineParserTest, JsonSimpleParsedSuccessfully) +TEST(CommandLineParserTest, ParseJson_Simple) { CommandLineParser parser; + CliOptions opts; std::string jsonText = R"( { "a": true, @@ -256,10 +257,9 @@ TEST(CommandLineParserTest, JsonSimpleParsedSuccessfully) } )"; nlohmann::json jsonConfig = nlohmann::json::parse(jsonText); - if (auto error = parser.AddJsonOptions(jsonConfig)) { + if (auto error = parser.ParseJson(opts, jsonConfig)) { FAIL() << error->errorMsg; } - auto opts = parser.GetOptions(); EXPECT_EQ(opts.GetNumUniqueOptions(), 7); EXPECT_EQ(opts.GetOptionValueOrDefault("a", false), true); EXPECT_EQ(opts.GetOptionValueOrDefault("b", true), false); @@ -272,9 +272,10 @@ TEST(CommandLineParserTest, JsonSimpleParsedSuccessfully) EXPECT_EQ(gFlag.second, 300); } -TEST(CommandLineParserTest, JsonWithNestedStructure) +TEST(CommandLineParserTest, ParseJson_NestedStructure) { CommandLineParser parser; + CliOptions opts; std::string jsonText = R"( { "a": true, @@ -285,10 +286,9 @@ TEST(CommandLineParserTest, JsonWithNestedStructure) } )"; nlohmann::json jsonConfig = nlohmann::json::parse(jsonText); - if (auto error = parser.AddJsonOptions(jsonConfig)) { + if (auto error = parser.ParseJson(opts, jsonConfig)) { FAIL() << error->errorMsg; } - auto opts = parser.GetOptions(); EXPECT_EQ(opts.GetNumUniqueOptions(), 2); EXPECT_EQ(opts.GetOptionValueOrDefault("a", false), true); EXPECT_TRUE(opts.HasExtraOption("b")); @@ -297,9 +297,10 @@ TEST(CommandLineParserTest, JsonWithNestedStructure) EXPECT_FALSE(opts.HasExtraOption("d")); } -TEST(CommandLineParserTest, JsonWithIntArray) +TEST(CommandLineParserTest, ParseJson_IntArray) { CommandLineParser parser; + CliOptions opts; std::string jsonText = R"( { "a": true, @@ -307,10 +308,9 @@ TEST(CommandLineParserTest, JsonWithIntArray) } )"; nlohmann::json jsonConfig = nlohmann::json::parse(jsonText); - if (auto error = parser.AddJsonOptions(jsonConfig)) { + if (auto error = parser.ParseJson(opts, jsonConfig)) { FAIL() << error->errorMsg; } - auto opts = parser.GetOptions(); EXPECT_EQ(opts.GetNumUniqueOptions(), 2); EXPECT_EQ(opts.GetOptionValueOrDefault("a", false), true); EXPECT_TRUE(opts.HasExtraOption("b")); @@ -322,9 +322,10 @@ TEST(CommandLineParserTest, JsonWithIntArray) EXPECT_EQ(gotB.at(2), 3); } -TEST(CommandLineParserTest, JsonWithStrArray) +TEST(CommandLineParserTest, ParseJson_StrArray) { CommandLineParser parser; + CliOptions opts; std::string jsonText = R"( { "a": true, @@ -332,10 +333,9 @@ TEST(CommandLineParserTest, JsonWithStrArray) } )"; nlohmann::json jsonConfig = nlohmann::json::parse(jsonText); - if (auto error = parser.AddJsonOptions(jsonConfig)) { + if (auto error = parser.ParseJson(opts, jsonConfig)) { FAIL() << error->errorMsg; } - auto opts = parser.GetOptions(); EXPECT_EQ(opts.GetNumUniqueOptions(), 2); EXPECT_EQ(opts.GetOptionValueOrDefault("a", false), true); EXPECT_TRUE(opts.HasExtraOption("b")); @@ -347,9 +347,10 @@ TEST(CommandLineParserTest, JsonWithStrArray) EXPECT_EQ(gotB.at(2), "third"); } -TEST(CommandLineParserTest, JsonWithHeterogeneousArray) +TEST(CommandLineParserTest, ParseJson_HeterogeneousArray) { CommandLineParser parser; + CliOptions opts; std::string jsonText = R"( { "a": true, @@ -357,10 +358,9 @@ TEST(CommandLineParserTest, JsonWithHeterogeneousArray) } )"; nlohmann::json jsonConfig = nlohmann::json::parse(jsonText); - if (auto error = parser.AddJsonOptions(jsonConfig)) { + if (auto error = parser.ParseJson(opts, jsonConfig)) { FAIL() << error->errorMsg; } - auto opts = parser.GetOptions(); EXPECT_EQ(opts.GetNumUniqueOptions(), 2); EXPECT_EQ(opts.GetOptionValueOrDefault("a", false), true); EXPECT_TRUE(opts.HasExtraOption("b")); @@ -373,37 +373,28 @@ TEST(CommandLineParserTest, JsonWithHeterogeneousArray) EXPECT_EQ(gotB.at(3), "4.0"); } -TEST(CommandLineParserTest, JsonVsCommandlinePriority) +TEST(CommandLineParserTest, ParseOption_Simple) { CommandLineParser parser; - std::string jsonText = R"( - { - "a": 1, - "b": ["one", "two", "three"] - } -)"; - nlohmann::json jsonConfig = nlohmann::json::parse(jsonText); - if (auto error = parser.AddJsonOptions(jsonConfig)) { + CliOptions opts; + if (auto error = parser.ParseOption(opts, "flag-name", "true")) { FAIL() << error->errorMsg; } + EXPECT_EQ(opts.GetNumUniqueOptions(), 1); + EXPECT_TRUE(opts.HasExtraOption("flag-name")); + EXPECT_EQ(opts.GetOptionValueOrDefault("flag-name", false), true); +} - const char* args[] = {"/path/to/executable", "--b", "four", "--a", "2", "--b", "five", "--a", "3"}; - if (auto error = parser.Parse(sizeof(args) / sizeof(args[0]), args)) { +TEST(CommandLineParserTest, ParseOption_NoPrefix) +{ + CommandLineParser parser; + CliOptions opts; + if (auto error = parser.ParseOption(opts, "no-flag-name", "")) { FAIL() << error->errorMsg; } - - auto opts = parser.GetOptions(); - EXPECT_EQ(opts.GetNumUniqueOptions(), 2); - EXPECT_EQ(opts.GetOptionValueOrDefault("a", 0), 3); - EXPECT_TRUE(opts.HasExtraOption("b")); - std::vector defaultB = {}; - std::vector gotB = opts.GetOptionValueOrDefault("b", defaultB); - EXPECT_EQ(gotB.size(), 5); - EXPECT_EQ(gotB.at(0), "one"); - EXPECT_EQ(gotB.at(1), "two"); - EXPECT_EQ(gotB.at(2), "three"); - EXPECT_EQ(gotB.at(3), "four"); - EXPECT_EQ(gotB.at(4), "five"); + EXPECT_EQ(opts.GetNumUniqueOptions(), 1); + EXPECT_TRUE(opts.HasExtraOption("flag-name")); + EXPECT_EQ(opts.GetOptionValueOrDefault("flag-name", true), false); } } // namespace