| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "flutter/fml/command_line.h" | |
| #include <string_view> | |
| #include <utility> | |
| #include "flutter/fml/macros.h" | |
| #include "flutter/fml/size.h" | |
| #include "gtest/gtest.h" | |
| namespace fml { | |
| namespace { | |
| TEST(CommandLineTest, Basic) { | |
| // Making this const verifies that the methods called are const. | |
| const auto cl = CommandLineFromInitializerList( | |
| {"my_program", "--flag1", "--flag2=value2", "arg1", "arg2", "arg3"}); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ("my_program", cl.argv0()); | |
| EXPECT_EQ(2u, cl.options().size()); | |
| EXPECT_EQ("flag1", cl.options()[0].name); | |
| EXPECT_EQ(std::string(), cl.options()[0].value); | |
| EXPECT_EQ("flag2", cl.options()[1].name); | |
| EXPECT_EQ("value2", cl.options()[1].value); | |
| EXPECT_EQ(3u, cl.positional_args().size()); | |
| EXPECT_EQ("arg1", cl.positional_args()[0]); | |
| EXPECT_EQ("arg2", cl.positional_args()[1]); | |
| EXPECT_EQ("arg3", cl.positional_args()[2]); | |
| EXPECT_TRUE(cl.HasOption("flag1")); | |
| EXPECT_TRUE(cl.HasOption("flag1", nullptr)); | |
| size_t index = static_cast<size_t>(-1); | |
| EXPECT_TRUE(cl.HasOption("flag2", &index)); | |
| EXPECT_EQ(1u, index); | |
| EXPECT_FALSE(cl.HasOption("flag3")); | |
| EXPECT_FALSE(cl.HasOption("flag3", nullptr)); | |
| std::string value = "nonempty"; | |
| EXPECT_TRUE(cl.GetOptionValue("flag1", &value)); | |
| EXPECT_EQ(std::string(), value); | |
| EXPECT_TRUE(cl.GetOptionValue("flag2", &value)); | |
| EXPECT_EQ("value2", value); | |
| EXPECT_FALSE(cl.GetOptionValue("flag3", &value)); | |
| EXPECT_EQ(std::string(), cl.GetOptionValueWithDefault("flag1", "nope")); | |
| EXPECT_EQ("value2", cl.GetOptionValueWithDefault("flag2", "nope")); | |
| EXPECT_EQ("nope", cl.GetOptionValueWithDefault("flag3", "nope")); | |
| } | |
| TEST(CommandLineTest, DefaultConstructor) { | |
| CommandLine cl; | |
| EXPECT_FALSE(cl.has_argv0()); | |
| EXPECT_EQ(std::string(), cl.argv0()); | |
| EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options()); | |
| EXPECT_EQ(std::vector<std::string>(), cl.positional_args()); | |
| } | |
| TEST(CommandLineTest, ComponentConstructor) { | |
| const std::string argv0 = "my_program"; | |
| const std::vector<CommandLine::Option> options = { | |
| CommandLine::Option("flag", "value")}; | |
| const std::vector<std::string> positional_args = {"arg"}; | |
| CommandLine cl(argv0, options, positional_args); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(argv0, cl.argv0()); | |
| EXPECT_EQ(options, cl.options()); | |
| EXPECT_EQ(positional_args, cl.positional_args()); | |
| EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope")); | |
| } | |
| TEST(CommandLineTest, CommandLineFromIteratorsFindFirstPositionalArg) { | |
| // This shows how one might process subcommands. | |
| { | |
| static std::vector<std::string> argv = {"my_program", "--flag1", | |
| "--flag2", "subcommand", | |
| "--subflag", "subarg"}; | |
| auto first = argv.cbegin(); | |
| auto last = argv.cend(); | |
| std::vector<std::string>::const_iterator sub_first; | |
| auto cl = | |
| CommandLineFromIteratorsFindFirstPositionalArg(first, last, &sub_first); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(argv[0], cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag1"), CommandLine::Option("flag2")}; | |
| EXPECT_EQ(expected_options, cl.options()); | |
| std::vector<std::string> expected_positional_args = {argv[3], argv[4], | |
| argv[5]}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| EXPECT_TRUE(cl.HasOption("flag1", nullptr)); | |
| EXPECT_TRUE(cl.HasOption("flag2", nullptr)); | |
| EXPECT_FALSE(cl.HasOption("subflag", nullptr)); | |
| EXPECT_EQ(first + 3, sub_first); | |
| auto sub_cl = CommandLineFromIterators(sub_first, last); | |
| EXPECT_TRUE(sub_cl.has_argv0()); | |
| EXPECT_EQ(argv[3], sub_cl.argv0()); | |
| std::vector<CommandLine::Option> expected_sub_options = { | |
| CommandLine::Option("subflag")}; | |
| EXPECT_EQ(expected_sub_options, sub_cl.options()); | |
| std::vector<std::string> expected_sub_positional_args = {argv[5]}; | |
| EXPECT_EQ(expected_sub_positional_args, sub_cl.positional_args()); | |
| EXPECT_FALSE(sub_cl.HasOption("flag1", nullptr)); | |
| EXPECT_FALSE(sub_cl.HasOption("flag2", nullptr)); | |
| EXPECT_TRUE(sub_cl.HasOption("subflag", nullptr)); | |
| } | |
| // No positional argument. | |
| { | |
| static std::vector<std::string> argv = {"my_program", "--flag"}; | |
| std::vector<std::string>::const_iterator sub_first; | |
| auto cl = CommandLineFromIteratorsFindFirstPositionalArg( | |
| argv.cbegin(), argv.cend(), &sub_first); | |
| EXPECT_EQ(argv.cend(), sub_first); | |
| } | |
| // Multiple positional arguments. | |
| { | |
| static std::vector<std::string> argv = {"my_program", "arg1", "arg2"}; | |
| std::vector<std::string>::const_iterator sub_first; | |
| auto cl = CommandLineFromIteratorsFindFirstPositionalArg( | |
| argv.cbegin(), argv.cend(), &sub_first); | |
| EXPECT_EQ(argv.cbegin() + 1, sub_first); | |
| } | |
| // "--". | |
| { | |
| static std::vector<std::string> argv = {"my_program", "--", "--arg"}; | |
| std::vector<std::string>::const_iterator sub_first; | |
| auto cl = CommandLineFromIteratorsFindFirstPositionalArg( | |
| argv.cbegin(), argv.cend(), &sub_first); | |
| EXPECT_EQ(argv.cbegin() + 2, sub_first); | |
| } | |
| } | |
| TEST(CommandLineTest, CommmandLineFromIterators) { | |
| { | |
| // Note (here and below): The |const| ensures that the factory method can | |
| // accept const iterators. | |
| const std::vector<std::string> argv = {"my_program", "--flag=value", "arg"}; | |
| auto cl = CommandLineFromIterators(argv.begin(), argv.end()); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(argv[0], cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag", "value")}; | |
| EXPECT_EQ(expected_options, cl.options()); | |
| std::vector<std::string> expected_positional_args = {argv[2]}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope")); | |
| } | |
| // Can handle empty argv. | |
| { | |
| const std::vector<std::string> argv; | |
| auto cl = CommandLineFromIterators(argv.begin(), argv.end()); | |
| EXPECT_FALSE(cl.has_argv0()); | |
| EXPECT_EQ(std::string(), cl.argv0()); | |
| EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options()); | |
| EXPECT_EQ(std::vector<std::string>(), cl.positional_args()); | |
| } | |
| // Can handle empty |argv[0]|. | |
| { | |
| const std::vector<std::string> argv = {""}; | |
| auto cl = CommandLineFromIterators(argv.begin(), argv.end()); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(std::string(), cl.argv0()); | |
| EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options()); | |
| EXPECT_EQ(std::vector<std::string>(), cl.positional_args()); | |
| } | |
| // Can also take a vector of |const char*|s. | |
| { | |
| const std::vector<const char*> argv = {"my_program", "--flag=value", "arg"}; | |
| auto cl = CommandLineFromIterators(argv.begin(), argv.end()); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(argv[0], cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag", "value")}; | |
| EXPECT_EQ(expected_options, cl.options()); | |
| std::vector<std::string> expected_positional_args = {argv[2]}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope")); | |
| } | |
| // Or a plain old array. | |
| { | |
| static const char* const argv[] = {"my_program", "--flag=value", "arg"}; | |
| auto cl = CommandLineFromIterators(argv, argv + fml::size(argv)); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(argv[0], cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag", "value")}; | |
| EXPECT_EQ(expected_options, cl.options()); | |
| std::vector<std::string> expected_positional_args = {argv[2]}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope")); | |
| } | |
| } | |
| TEST(CommandLineTest, CommandLineFromArgcArgv) { | |
| static const char* const argv[] = {"my_program", "--flag=value", "arg"}; | |
| const int argc = static_cast<int>(fml::size(argv)); | |
| auto cl = CommandLineFromArgcArgv(argc, argv); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(argv[0], cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag", "value")}; | |
| EXPECT_EQ(expected_options, cl.options()); | |
| std::vector<std::string> expected_positional_args = {argv[2]}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope")); | |
| } | |
| TEST(CommandLineTest, CommandLineFromInitializerList) { | |
| { | |
| std::initializer_list<const char*> il = {"my_program", "--flag=value", | |
| "arg"}; | |
| auto cl = CommandLineFromInitializerList(il); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ("my_program", cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag", "value")}; | |
| EXPECT_EQ(expected_options, cl.options()); | |
| std::vector<std::string> expected_positional_args = {"arg"}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope")); | |
| } | |
| { | |
| std::initializer_list<std::string> il = {"my_program", "--flag=value", | |
| "arg"}; | |
| auto cl = CommandLineFromInitializerList(il); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ("my_program", cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag", "value")}; | |
| EXPECT_EQ(expected_options, cl.options()); | |
| std::vector<std::string> expected_positional_args = {"arg"}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope")); | |
| } | |
| } | |
| TEST(CommandLineTest, OddArguments) { | |
| { | |
| // Except for "arg", these are all options. | |
| auto cl = CommandLineFromInitializerList( | |
| {"my_program", "--=", "--=foo", "--bar=", "--==", "--===", "--==x", | |
| "arg"}); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ("my_program", cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("="), CommandLine::Option("=foo"), | |
| CommandLine::Option("bar"), CommandLine::Option("="), | |
| CommandLine::Option("=", "="), CommandLine::Option("=", "x")}; | |
| EXPECT_EQ(expected_options, cl.options()); | |
| std::vector<std::string> expected_positional_args = {"arg"}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| } | |
| // "-x" is an argument, not an options. | |
| { | |
| auto cl = CommandLineFromInitializerList({"", "-x"}); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(std::string(), cl.argv0()); | |
| EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options()); | |
| std::vector<std::string> expected_positional_args = {"-x"}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| } | |
| // Ditto for "-". | |
| { | |
| auto cl = CommandLineFromInitializerList({"", "-"}); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(std::string(), cl.argv0()); | |
| EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options()); | |
| std::vector<std::string> expected_positional_args = {"-"}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| } | |
| // "--" terminates option processing, but isn't an argument in the first | |
| // occurrence. | |
| { | |
| auto cl = CommandLineFromInitializerList( | |
| {"", "--flag=value", "--", "--not-a-flag", "arg", "--"}); | |
| EXPECT_TRUE(cl.has_argv0()); | |
| EXPECT_EQ(std::string(), cl.argv0()); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag", "value")}; | |
| std::vector<std::string> expected_positional_args = {"--not-a-flag", "arg", | |
| "--"}; | |
| EXPECT_EQ(expected_positional_args, cl.positional_args()); | |
| } | |
| } | |
| TEST(CommandLineTest, MultipleOccurrencesOfOption) { | |
| auto cl = CommandLineFromInitializerList( | |
| {"my_program", "--flag1=value1", "--flag2=value2", "--flag1=value3"}); | |
| std::vector<CommandLine::Option> expected_options = { | |
| CommandLine::Option("flag1", "value1"), | |
| CommandLine::Option("flag2", "value2"), | |
| CommandLine::Option("flag1", "value3")}; | |
| EXPECT_EQ("value3", cl.GetOptionValueWithDefault("flag1", "nope")); | |
| EXPECT_EQ("value2", cl.GetOptionValueWithDefault("flag2", "nope")); | |
| std::vector<std::string_view> values = cl.GetOptionValues("flag1"); | |
| ASSERT_EQ(2u, values.size()); | |
| EXPECT_EQ("value1", values[0]); | |
| EXPECT_EQ("value3", values[1]); | |
| } | |
| // |cl1| and |cl2| should be not equal. | |
| void ExpectNotEqual(const char* message, | |
| std::initializer_list<std::string> c1, | |
| std::initializer_list<std::string> c2) { | |
| SCOPED_TRACE(message); | |
| const auto cl1 = CommandLineFromInitializerList(c1); | |
| const auto cl2 = CommandLineFromInitializerList(c2); | |
| // These are tautological. | |
| EXPECT_TRUE(cl1 == cl1); | |
| EXPECT_FALSE(cl1 != cl1); | |
| EXPECT_TRUE(cl2 == cl2); | |
| EXPECT_FALSE(cl2 != cl2); | |
| // These rely on |cl1| not being equal to |cl2|. | |
| EXPECT_FALSE(cl1 == cl2); | |
| EXPECT_TRUE(cl1 != cl2); | |
| EXPECT_FALSE(cl2 == cl1); | |
| EXPECT_TRUE(cl2 != cl1); | |
| } | |
| void ExpectEqual(const char* message, | |
| std::initializer_list<std::string> c1, | |
| std::initializer_list<std::string> c2) { | |
| SCOPED_TRACE(message); | |
| const auto cl1 = CommandLineFromInitializerList(c1); | |
| const auto cl2 = CommandLineFromInitializerList(c2); | |
| // These are tautological. | |
| EXPECT_TRUE(cl1 == cl1); | |
| EXPECT_FALSE(cl1 != cl1); | |
| EXPECT_TRUE(cl2 == cl2); | |
| EXPECT_FALSE(cl2 != cl2); | |
| // These rely on |cl1| being equal to |cl2|. | |
| EXPECT_TRUE(cl1 == cl2); | |
| EXPECT_FALSE(cl1 != cl2); | |
| EXPECT_TRUE(cl2 == cl1); | |
| EXPECT_FALSE(cl2 != cl1); | |
| } | |
| TEST(CommandLineTest, ComparisonOperators) { | |
| ExpectNotEqual("1", {}, {""}); | |
| ExpectNotEqual("2", {"abc"}, {"def"}); | |
| ExpectNotEqual("3", {"abc", "--flag"}, {"abc"}); | |
| ExpectNotEqual("4", {"abc", "--flag1"}, {"abc", "--flag2"}); | |
| ExpectNotEqual("5", {"abc", "--flag1", "--flag2"}, {"abc", "--flag1"}); | |
| ExpectNotEqual("6", {"abc", "arg"}, {"abc"}); | |
| ExpectNotEqual("7", {"abc", "arg1"}, {"abc", "arg2"}); | |
| ExpectNotEqual("8", {"abc", "arg1", "arg2"}, {"abc", "arg1"}); | |
| ExpectNotEqual("9", {"abc", "--flag", "arg1"}, {"abc", "--flag", "arg2"}); | |
| // However, the presence of an unnecessary "--" shouldn't affect what's | |
| // constructed. | |
| ExpectEqual("10", {"abc", "--flag", "arg"}, {"abc", "--flag", "--", "arg"}); | |
| } | |
| TEST(CommandLineTest, MoveAndCopy) { | |
| const auto cl = CommandLineFromInitializerList( | |
| {"my_program", "--flag1=value1", "--flag2", "arg"}); | |
| // Copy constructor. | |
| CommandLine cl2(cl); | |
| EXPECT_EQ(cl, cl2); | |
| // Check that |option_index_| gets copied too. | |
| EXPECT_EQ("value1", cl2.GetOptionValueWithDefault("flag1", "nope")); | |
| // Move constructor. | |
| CommandLine cl3(std::move(cl2)); | |
| EXPECT_EQ(cl, cl3); | |
| EXPECT_EQ("value1", cl3.GetOptionValueWithDefault("flag1", "nope")); | |
| // Copy assignment. | |
| CommandLine cl4; | |
| EXPECT_NE(cl, cl4); | |
| cl4 = cl; | |
| EXPECT_EQ(cl, cl4); | |
| EXPECT_EQ("value1", cl4.GetOptionValueWithDefault("flag1", "nope")); | |
| // Move assignment. | |
| CommandLine cl5; | |
| EXPECT_NE(cl, cl5); | |
| cl5 = std::move(cl4); | |
| EXPECT_EQ(cl, cl5); | |
| EXPECT_EQ("value1", cl5.GetOptionValueWithDefault("flag1", "nope")); | |
| } | |
| void ToArgvHelper(const char* message, std::initializer_list<std::string> c) { | |
| SCOPED_TRACE(message); | |
| std::vector<std::string> argv = c; | |
| auto cl = CommandLineFromInitializerList(c); | |
| EXPECT_EQ(argv, CommandLineToArgv(cl)); | |
| } | |
| TEST(CommandLineTest, CommandLineToArgv) { | |
| ToArgvHelper("1", {}); | |
| ToArgvHelper("2", {""}); | |
| ToArgvHelper("3", {"my_program"}); | |
| ToArgvHelper("4", {"my_program", "--flag"}); | |
| ToArgvHelper("5", {"my_program", "--flag1", "--flag2=value"}); | |
| ToArgvHelper("6", {"my_program", "arg"}); | |
| ToArgvHelper("7", {"my_program", "arg1", "arg2"}); | |
| ToArgvHelper("8", {"my_program", "--flag1", "--flag2=value", "arg1", "arg2"}); | |
| ToArgvHelper("9", {"my_program", "--flag", "--", "--not-a-flag"}); | |
| ToArgvHelper("10", {"my_program", "--flag", "arg", "--"}); | |
| // However, |CommandLineToArgv()| will "strip" an unneeded "--". | |
| { | |
| auto cl = CommandLineFromInitializerList({"my_program", "--"}); | |
| std::vector<std::string> argv = {"my_program"}; | |
| EXPECT_EQ(argv, CommandLineToArgv(cl)); | |
| } | |
| { | |
| auto cl = | |
| CommandLineFromInitializerList({"my_program", "--flag", "--", "arg"}); | |
| std::vector<std::string> argv = {"my_program", "--flag", "arg"}; | |
| EXPECT_EQ(argv, CommandLineToArgv(cl)); | |
| } | |
| } | |
| } // namespace | |
| } // namespace fml |