/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "perfetto/ext/base/getopt_compat.h" // This test has two roles: // 1. In Windows builds it's a plain unittest for our getopt_compat.cc // 2. On other builds it also checks that the behavior of our getopt_compat.cc // is the same of (for the options we support). // It does so creating a gtest typed test, and defining two structs that inject // getopt functions and global variables like optind. #include "perfetto/base/build_config.h" #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) #include #endif #include #include "test/gtest_and_gmock.h" using testing::ElementsAre; using testing::ElementsAreArray; namespace perfetto { namespace base { namespace { struct OurGetopt { using LongOptionType = getopt_compat::option; using GetoptFn = decltype(&getopt_compat::getopt); using GetoptLongFn = decltype(&getopt_compat::getopt_long); GetoptFn getopt = &getopt_compat::getopt; GetoptLongFn getopt_long = &getopt_compat::getopt_long; int& optind = getopt_compat::optind; int& optopt = getopt_compat::optopt; int& opterr = getopt_compat::opterr; char*& optarg = getopt_compat::optarg; }; #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) struct SystemGetopt { using LongOptionType = ::option; using GetoptFn = decltype(&::getopt); using GetoptLongFn = decltype(&::getopt_long); GetoptFn getopt = &::getopt; GetoptLongFn getopt_long = &::getopt_long; int& optind = ::optind; int& optopt = ::optopt; int& opterr = ::opterr; char*& optarg = ::optarg; }; #endif template class GetoptCompatTest : public testing::Test { public: inline void SetCmdline(std::initializer_list arg_list) { // Reset the getopt() state. // When calling getopt() several times, MacOS requires that optind is reset // to 1, while Linux requires optind to be reset to 0. Also MacOS requires // optreset to be set as well. #if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) impl.optind = 1; optreset = 1; // It has no corresponding variable in other OSes. #else impl.optind = 0; #endif argc = static_cast(arg_list.size()); for (char*& arg : argv) arg = nullptr; size_t i = 0; for (const char* arg : arg_list) argv[i++] = const_cast(arg); } int argc; char* argv[32]; // We don't use more than 32 entries on our tests. T impl; }; #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) using GetoptTestTypes = ::testing::Types; #else using GetoptTestTypes = ::testing::Types; #endif TYPED_TEST_SUITE(GetoptCompatTest, GetoptTestTypes, /* trailing ',' for GCC*/); TYPED_TEST(GetoptCompatTest, ShortOptions) { auto& t = this->impl; const char* sops = ""; this->SetCmdline({"argv0"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); sops = "h"; this->SetCmdline({"argv0"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); sops = "h"; this->SetCmdline({"argv0", "-h"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h'); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 2); sops = "h"; this->SetCmdline({"argv0", "positional1", "positional2"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); sops = "h"; this->SetCmdline({"argv0", "--", "positional1", "positional2"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 2); sops = "h"; this->SetCmdline({"argv0", "-h"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h'); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 2); sops = "abc"; this->SetCmdline({"argv0", "-c", "-a", "-b"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c'); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a'); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b'); EXPECT_EQ(t.optind, 4); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 4); sops = "abc"; this->SetCmdline({"argv0", "-c", "-a", "--", "nonopt"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c'); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a'); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 4); sops = "abc"; this->SetCmdline({"argv0", "-cb"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c'); EXPECT_EQ(t.optind, 1); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b'); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 2); sops = "abc"; this->SetCmdline({"argv0", "-aa", "-c"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a'); EXPECT_EQ(t.optind, 1); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a'); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c'); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 3); sops = "a:bc"; // The semantic here is `-a b -c` this->SetCmdline({"argv0", "-ab", "-c"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a'); EXPECT_EQ(t.optind, 2); EXPECT_STREQ(t.optarg, "b"); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c'); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 3); sops = "a:bc"; this->SetCmdline({"argv0", "-ab", "--", "-c"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a'); EXPECT_EQ(t.optind, 2); EXPECT_STREQ(t.optarg, "b"); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 3); sops = "a:b:c:"; this->SetCmdline({"argv0", "-a", "arg1", "-b", "--", "-c", "-carg"}); // This is sbutle, the "--" is an arg value for "-b", not a separator. EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a'); EXPECT_STREQ(t.optarg, "arg1"); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b'); EXPECT_STREQ(t.optarg, "--"); EXPECT_EQ(t.optind, 5); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c'); EXPECT_STREQ(t.optarg, "-carg"); EXPECT_EQ(t.optind, 7); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); EXPECT_EQ(t.optind, 7); sops = "a"; this->SetCmdline({"argv0", "-q"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?'); EXPECT_EQ(t.optind, 2); } TYPED_TEST(GetoptCompatTest, LongOptions) { auto& t = this->impl; using LongOptionType = typename decltype(this->impl)::LongOptionType; { LongOptionType lopts[]{ {nullptr, 0, nullptr, 0}, }; const char* sops = ""; this->SetCmdline({"argv0"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); EXPECT_EQ(t.optind, 1); } { LongOptionType lopts[]{ {nullptr, 0, nullptr, 0}, }; const char* sops = ""; this->SetCmdline({"argv0", "--unknown"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?'); EXPECT_EQ(t.optind, 2); } { LongOptionType lopts[]{ {"one", 0 /*no_argument*/, nullptr, 1}, {"two", 0 /*no_argument*/, nullptr, 2}, {nullptr, 0, nullptr, 0}, }; const char* sops = ""; this->SetCmdline({"argv0", "--two", "--one"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); EXPECT_EQ(t.optind, 3); } { LongOptionType lopts[]{ {"one", 0 /*no_argument*/, nullptr, 1}, {"two", 0 /*no_argument*/, nullptr, 2}, {nullptr, 0, nullptr, 0}, }; const char* sops = ""; this->SetCmdline({"argv0", "--two", "--one", "--not-an-opt"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?'); EXPECT_EQ(t.optind, 4); } { LongOptionType lopts[]{ {"one", 0 /*no_argument*/, nullptr, 1}, {"two", 0 /*no_argument*/, nullptr, 2}, {nullptr, 0, nullptr, 0}, }; const char* sops = ""; this->SetCmdline({"argv0", "--two", "--one", "--", "--not-an-opt"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); EXPECT_EQ(t.optind, 4); } { LongOptionType lopts[]{ {"no1", 0 /*no_argument*/, nullptr, 1}, {"req2", 1 /*required_argument*/, nullptr, 2}, {"req3", 1 /*required_argument*/, nullptr, 3}, {nullptr, 0, nullptr, 0}, }; const char* sops = ""; // This is subtle: the "--" really is an argument for req2, not an argument // separator. The first positional arg is "!!!". this->SetCmdline({"argv0", "--req3", "-", "--no1", "--req2", "--", "!!!"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3); EXPECT_EQ(t.optind, 3); EXPECT_STREQ(t.optarg, "-"); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1); EXPECT_EQ(t.optind, 4); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2); EXPECT_STREQ(t.optarg, "--"); EXPECT_EQ(t.optind, 6); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); EXPECT_EQ(t.optind, 6); } { LongOptionType lopts[]{ {"no1", 0 /*no_argument*/, nullptr, 1}, {"req2", 1 /*required_argument*/, nullptr, 2}, {nullptr, 0, nullptr, 0}, }; const char* sops = ""; this->SetCmdline({"argv0", "--req2", "foo", "--", "--no1"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2); EXPECT_EQ(t.optind, 3); EXPECT_STREQ(t.optarg, "foo"); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); EXPECT_EQ(t.optind, 4); } } TYPED_TEST(GetoptCompatTest, ShortAndLongOptions) { auto& t = this->impl; using LongOptionType = typename decltype(this->impl)::LongOptionType; { LongOptionType lopts[]{ {"one", 0 /*no_argument*/, nullptr, 1}, {"two", 0 /*no_argument*/, nullptr, 2}, {"three", 0 /*no_argument*/, nullptr, 3}, {nullptr, 0, nullptr, 0}, }; const char* sops = "123"; this->SetCmdline({"argv0"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); EXPECT_EQ(t.optind, 1); this->SetCmdline({"argv0", "-13", "--two", "--three", "--", "--one"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1'); EXPECT_EQ(t.optind, 1); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3'); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3); EXPECT_EQ(t.optind, 4); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); EXPECT_EQ(t.optind, 5); this->SetCmdline({"argv0", "--two", "-1", "--two", "-13"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2); EXPECT_EQ(t.optind, 2); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1'); EXPECT_EQ(t.optind, 3); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2); EXPECT_EQ(t.optind, 4); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1'); EXPECT_EQ(t.optind, 4); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3'); EXPECT_EQ(t.optind, 5); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); EXPECT_EQ(t.optind, 5); } } TYPED_TEST(GetoptCompatTest, OpterrHandling) { auto& t = this->impl; t.opterr = 0; // Make errors silent. const char* sops = "ab:"; this->SetCmdline({"argv0", "-a", "-c", "-b"}); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a'); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?'); EXPECT_EQ(t.optopt, 'c'); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?'); EXPECT_EQ(t.optopt, 'b'); EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1); using LongOptionType = typename decltype(this->impl)::LongOptionType; LongOptionType lopts[]{ {"requires_arg", 1 /*required_argument*/, nullptr, 42}, {nullptr, 0, nullptr, 0}, }; this->SetCmdline({"argv0", "-a", "--unkonwn", "--requires_arg"}); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 'a'); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?'); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?'); EXPECT_EQ(t.optopt, 42); EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1); } } // namespace } // namespace base } // namespace perfetto