blob: 0ae372109fbbe05d29664aed874baf56710acede [file] [log] [blame]
/*
* 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