// 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 #include #include "flutter/fml/macros.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(-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(), cl.options()); EXPECT_EQ(std::vector(), cl.positional_args()); } TEST(CommandLineTest, ComponentConstructor) { const std::string argv0 = "my_program"; const std::vector options = { CommandLine::Option("flag", "value")}; const std::vector 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 argv = {"my_program", "--flag1", "--flag2", "subcommand", "--subflag", "subarg"}; auto first = argv.cbegin(); auto last = argv.cend(); std::vector::const_iterator sub_first; auto cl = CommandLineFromIteratorsFindFirstPositionalArg(first, last, &sub_first); EXPECT_TRUE(cl.has_argv0()); EXPECT_EQ(argv[0], cl.argv0()); std::vector expected_options = { CommandLine::Option("flag1"), CommandLine::Option("flag2")}; EXPECT_EQ(expected_options, cl.options()); std::vector 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 expected_sub_options = { CommandLine::Option("subflag")}; EXPECT_EQ(expected_sub_options, sub_cl.options()); std::vector 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 argv = {"my_program", "--flag"}; std::vector::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 argv = {"my_program", "arg1", "arg2"}; std::vector::const_iterator sub_first; auto cl = CommandLineFromIteratorsFindFirstPositionalArg( argv.cbegin(), argv.cend(), &sub_first); EXPECT_EQ(argv.cbegin() + 1, sub_first); } // "--". { static std::vector argv = {"my_program", "--", "--arg"}; std::vector::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 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 expected_options = { CommandLine::Option("flag", "value")}; EXPECT_EQ(expected_options, cl.options()); std::vector 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 argv; auto cl = CommandLineFromIterators(argv.begin(), argv.end()); EXPECT_FALSE(cl.has_argv0()); EXPECT_EQ(std::string(), cl.argv0()); EXPECT_EQ(std::vector(), cl.options()); EXPECT_EQ(std::vector(), cl.positional_args()); } // Can handle empty |argv[0]|. { const std::vector argv = {""}; auto cl = CommandLineFromIterators(argv.begin(), argv.end()); EXPECT_TRUE(cl.has_argv0()); EXPECT_EQ(std::string(), cl.argv0()); EXPECT_EQ(std::vector(), cl.options()); EXPECT_EQ(std::vector(), cl.positional_args()); } // Can also take a vector of |const char*|s. { const std::vector 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 expected_options = { CommandLine::Option("flag", "value")}; EXPECT_EQ(expected_options, cl.options()); std::vector 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 + std::size(argv)); EXPECT_TRUE(cl.has_argv0()); EXPECT_EQ(argv[0], cl.argv0()); std::vector expected_options = { CommandLine::Option("flag", "value")}; EXPECT_EQ(expected_options, cl.options()); std::vector 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(std::size(argv)); auto cl = CommandLineFromArgcArgv(argc, argv); EXPECT_TRUE(cl.has_argv0()); EXPECT_EQ(argv[0], cl.argv0()); std::vector expected_options = { CommandLine::Option("flag", "value")}; EXPECT_EQ(expected_options, cl.options()); std::vector 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 il = {"my_program", "--flag=value", "arg"}; auto cl = CommandLineFromInitializerList(il); EXPECT_TRUE(cl.has_argv0()); EXPECT_EQ("my_program", cl.argv0()); std::vector expected_options = { CommandLine::Option("flag", "value")}; EXPECT_EQ(expected_options, cl.options()); std::vector expected_positional_args = {"arg"}; EXPECT_EQ(expected_positional_args, cl.positional_args()); EXPECT_EQ("value", cl.GetOptionValueWithDefault("flag", "nope")); } { std::initializer_list il = {"my_program", "--flag=value", "arg"}; auto cl = CommandLineFromInitializerList(il); EXPECT_TRUE(cl.has_argv0()); EXPECT_EQ("my_program", cl.argv0()); std::vector expected_options = { CommandLine::Option("flag", "value")}; EXPECT_EQ(expected_options, cl.options()); std::vector 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 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 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(), cl.options()); std::vector 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(), cl.options()); std::vector 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 expected_options = { CommandLine::Option("flag", "value")}; std::vector 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 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 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 c1, std::initializer_list 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 c1, std::initializer_list 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 c) { SCOPED_TRACE(message); std::vector 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 argv = {"my_program"}; EXPECT_EQ(argv, CommandLineToArgv(cl)); } { auto cl = CommandLineFromInitializerList({"my_program", "--flag", "--", "arg"}); std::vector argv = {"my_program", "--flag", "arg"}; EXPECT_EQ(argv, CommandLineToArgv(cl)); } } } // namespace } // namespace fml