| /* |
| * 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 <getopt.h> (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 <getopt.h> |
| #endif |
| |
| #include <initializer_list> |
| |
| #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 <typename T> |
| class GetoptCompatTest : public testing::Test { |
| public: |
| inline void SetCmdline(std::initializer_list<const char*> 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<int>(arg_list.size()); |
| for (char*& arg : argv) |
| arg = nullptr; |
| size_t i = 0; |
| for (const char* arg : arg_list) |
| argv[i++] = const_cast<char*>(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<OurGetopt>; |
| #else |
| using GetoptTestTypes = ::testing::Types<OurGetopt, SystemGetopt>; |
| #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 |