• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "perfetto/ext/base/getopt_compat.h"
18 
19 // This test has two roles:
20 // 1. In Windows builds it's a plain unittest for our getopt_compat.cc
21 // 2. On other builds it also checks that the behavior of our getopt_compat.cc
22 //    is the same of <getopt.h> (for the options we support).
23 // It does so creating a gtest typed test, and defining two structs that inject
24 // getopt functions and global variables like optind.
25 
26 #include "perfetto/base/build_config.h"
27 
28 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
29 #include <getopt.h>
30 #endif
31 
32 #include <initializer_list>
33 
34 #include "test/gtest_and_gmock.h"
35 
36 using testing::ElementsAre;
37 using testing::ElementsAreArray;
38 
39 namespace perfetto {
40 namespace base {
41 namespace {
42 
43 struct OurGetopt {
44   using LongOptionType = getopt_compat::option;
45   using GetoptFn = decltype(&getopt_compat::getopt);
46   using GetoptLongFn = decltype(&getopt_compat::getopt_long);
47   GetoptFn getopt = &getopt_compat::getopt;
48   GetoptLongFn getopt_long = &getopt_compat::getopt_long;
49   int& optind = getopt_compat::optind;
50   char*& optarg = getopt_compat::optarg;
51 };
52 
53 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
54 struct SystemGetopt {
55   using LongOptionType = ::option;
56   using GetoptFn = decltype(&::getopt);
57   using GetoptLongFn = decltype(&::getopt_long);
58   GetoptFn getopt = &::getopt;
59   GetoptLongFn getopt_long = &::getopt_long;
60   int& optind = ::optind;
61   char*& optarg = ::optarg;
62 };
63 #endif
64 
65 template <typename T>
66 class GetoptCompatTest : public testing::Test {
67  public:
SetCmdline(std::initializer_list<const char * > arg_list)68   inline void SetCmdline(std::initializer_list<const char*> arg_list) {
69     // Reset the getopt() state.
70     // When calling getopt() several times, MacOS requires that optind is reset
71     // to 1, while Linux requires optind to be reset to 0. Also MacOS requires
72     // optreset to be set as well.
73 #if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
74     impl.optind = 1;
75     optreset = 1;  // It has no corresponding variable in other OSes.
76 #else
77     impl.optind = 0;
78 #endif
79     argc = static_cast<int>(arg_list.size());
80     for (char*& arg : argv)
81       arg = nullptr;
82     size_t i = 0;
83     for (const char* arg : arg_list)
84       argv[i++] = const_cast<char*>(arg);
85   }
86   int argc;
87   char* argv[32];  // We don't use more than 32 entries on our tests.
88   T impl;
89 };
90 
91 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
92 using GetoptTestTypes = ::testing::Types<OurGetopt>;
93 #else
94 using GetoptTestTypes = ::testing::Types<OurGetopt, SystemGetopt>;
95 #endif
96 TYPED_TEST_SUITE(GetoptCompatTest, GetoptTestTypes, /* trailing ',' for GCC*/);
97 
TYPED_TEST(GetoptCompatTest,ShortOptions)98 TYPED_TEST(GetoptCompatTest, ShortOptions) {
99   auto& t = this->impl;
100 
101   const char* sops = "";
102   this->SetCmdline({"argv0"});
103   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
104 
105   sops = "h";
106   this->SetCmdline({"argv0"});
107   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
108 
109   sops = "h";
110   this->SetCmdline({"argv0", "-h"});
111   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h');
112   EXPECT_EQ(t.optarg, nullptr);
113   EXPECT_EQ(t.optind, 2);
114   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
115   EXPECT_EQ(t.optind, 2);
116 
117   sops = "h";
118   this->SetCmdline({"argv0", "positional1", "positional2"});
119   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
120 
121   sops = "h";
122   this->SetCmdline({"argv0", "--", "positional1", "positional2"});
123   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
124   EXPECT_EQ(t.optind, 2);
125 
126   sops = "h";
127   this->SetCmdline({"argv0", "-h"});
128   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h');
129   EXPECT_EQ(t.optind, 2);
130   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
131   EXPECT_EQ(t.optind, 2);
132 
133   sops = "abc";
134   this->SetCmdline({"argv0", "-c", "-a", "-b"});
135   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
136   EXPECT_EQ(t.optarg, nullptr);
137   EXPECT_EQ(t.optind, 2);
138   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
139   EXPECT_EQ(t.optind, 3);
140   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
141   EXPECT_EQ(t.optind, 4);
142   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
143   EXPECT_EQ(t.optind, 4);
144 
145   sops = "abc";
146   this->SetCmdline({"argv0", "-c", "-a", "--", "nonopt"});
147   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
148   EXPECT_EQ(t.optarg, nullptr);
149   EXPECT_EQ(t.optind, 2);
150   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
151   EXPECT_EQ(t.optarg, nullptr);
152   EXPECT_EQ(t.optind, 3);
153   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
154   EXPECT_EQ(t.optarg, nullptr);
155   EXPECT_EQ(t.optind, 4);
156 
157   sops = "abc";
158   this->SetCmdline({"argv0", "-cb"});
159   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
160   EXPECT_EQ(t.optarg, nullptr);
161   EXPECT_EQ(t.optind, 1);
162   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
163   EXPECT_EQ(t.optarg, nullptr);
164   EXPECT_EQ(t.optind, 2);
165   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
166   EXPECT_EQ(t.optarg, nullptr);
167   EXPECT_EQ(t.optind, 2);
168 
169   sops = "abc";
170   this->SetCmdline({"argv0", "-aa", "-c"});
171   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
172   EXPECT_EQ(t.optind, 1);
173   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
174   EXPECT_EQ(t.optind, 2);
175   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
176   EXPECT_EQ(t.optind, 3);
177   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
178   EXPECT_EQ(t.optind, 3);
179 
180   sops = "a:bc";
181   // The semantic here is `-a b -c`
182   this->SetCmdline({"argv0", "-ab", "-c"});
183   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
184   EXPECT_EQ(t.optind, 2);
185   EXPECT_STREQ(t.optarg, "b");
186   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
187   EXPECT_EQ(t.optind, 3);
188   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
189   EXPECT_EQ(t.optind, 3);
190 
191   sops = "a:bc";
192   this->SetCmdline({"argv0", "-ab", "--", "-c"});
193   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
194   EXPECT_EQ(t.optind, 2);
195   EXPECT_STREQ(t.optarg, "b");
196   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
197   EXPECT_EQ(t.optind, 3);
198 
199   sops = "a:b:c:";
200   this->SetCmdline({"argv0", "-a", "arg1", "-b", "--", "-c", "-carg"});
201   // This is sbutle, the "--" is an arg value for "-b", not a separator.
202   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
203   EXPECT_STREQ(t.optarg, "arg1");
204   EXPECT_EQ(t.optind, 3);
205   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
206   EXPECT_STREQ(t.optarg, "--");
207   EXPECT_EQ(t.optind, 5);
208   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
209   EXPECT_STREQ(t.optarg, "-carg");
210   EXPECT_EQ(t.optind, 7);
211   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
212   EXPECT_EQ(t.optind, 7);
213 
214   sops = "a";
215   this->SetCmdline({"argv0", "-q"});
216   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?');
217   EXPECT_EQ(t.optind, 2);
218 }
219 
TYPED_TEST(GetoptCompatTest,LongOptions)220 TYPED_TEST(GetoptCompatTest, LongOptions) {
221   auto& t = this->impl;
222   using LongOptionType = typename decltype(this->impl)::LongOptionType;
223 
224   {
225     LongOptionType lopts[]{
226         {nullptr, 0, nullptr, 0},
227     };
228     const char* sops = "";
229     this->SetCmdline({"argv0"});
230     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
231     EXPECT_EQ(t.optarg, nullptr);
232     EXPECT_EQ(t.optind, 1);
233   }
234 
235   {
236     LongOptionType lopts[]{
237         {nullptr, 0, nullptr, 0},
238     };
239     const char* sops = "";
240     this->SetCmdline({"argv0", "--unknown"});
241     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
242     EXPECT_EQ(t.optarg, nullptr);
243     EXPECT_EQ(t.optind, 2);
244   }
245 
246   {
247     LongOptionType lopts[]{
248         {"one", 0 /*no_argument*/, nullptr, 1},
249         {"two", 0 /*no_argument*/, nullptr, 2},
250         {nullptr, 0, nullptr, 0},
251     };
252     const char* sops = "";
253     this->SetCmdline({"argv0", "--two", "--one"});
254     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
255     EXPECT_EQ(t.optarg, nullptr);
256     EXPECT_EQ(t.optind, 2);
257     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
258     EXPECT_EQ(t.optarg, nullptr);
259     EXPECT_EQ(t.optind, 3);
260     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
261     EXPECT_EQ(t.optarg, nullptr);
262     EXPECT_EQ(t.optind, 3);
263   }
264 
265   {
266     LongOptionType lopts[]{
267         {"one", 0 /*no_argument*/, nullptr, 1},
268         {"two", 0 /*no_argument*/, nullptr, 2},
269         {nullptr, 0, nullptr, 0},
270     };
271     const char* sops = "";
272     this->SetCmdline({"argv0", "--two", "--one", "--not-an-opt"});
273     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
274     EXPECT_EQ(t.optarg, nullptr);
275     EXPECT_EQ(t.optind, 2);
276     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
277     EXPECT_EQ(t.optarg, nullptr);
278     EXPECT_EQ(t.optind, 3);
279     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
280     EXPECT_EQ(t.optarg, nullptr);
281     EXPECT_EQ(t.optind, 4);
282   }
283 
284   {
285     LongOptionType lopts[]{
286         {"one", 0 /*no_argument*/, nullptr, 1},
287         {"two", 0 /*no_argument*/, nullptr, 2},
288         {nullptr, 0, nullptr, 0},
289     };
290     const char* sops = "";
291     this->SetCmdline({"argv0", "--two", "--one", "--", "--not-an-opt"});
292     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
293     EXPECT_EQ(t.optind, 2);
294     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
295     EXPECT_EQ(t.optind, 3);
296     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
297     EXPECT_EQ(t.optind, 4);
298   }
299 
300   {
301     LongOptionType lopts[]{
302         {"no1", 0 /*no_argument*/, nullptr, 1},
303         {"req2", 1 /*required_argument*/, nullptr, 2},
304         {"req3", 1 /*required_argument*/, nullptr, 3},
305         {nullptr, 0, nullptr, 0},
306     };
307     const char* sops = "";
308     // This is subtle: the "--" really is an argument for req2, not an argument
309     // separator. The first positional arg is "!!!".
310     this->SetCmdline({"argv0", "--req3", "-", "--no1", "--req2", "--", "!!!"});
311     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3);
312     EXPECT_EQ(t.optind, 3);
313     EXPECT_STREQ(t.optarg, "-");
314     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
315     EXPECT_EQ(t.optind, 4);
316     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
317     EXPECT_STREQ(t.optarg, "--");
318     EXPECT_EQ(t.optind, 6);
319     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
320     EXPECT_EQ(t.optind, 6);
321   }
322 
323   {
324     LongOptionType lopts[]{
325         {"no1", 0 /*no_argument*/, nullptr, 1},
326         {"req2", 1 /*required_argument*/, nullptr, 2},
327         {nullptr, 0, nullptr, 0},
328     };
329     const char* sops = "";
330     this->SetCmdline({"argv0", "--req2", "foo", "--", "--no1"});
331     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
332     EXPECT_EQ(t.optind, 3);
333     EXPECT_STREQ(t.optarg, "foo");
334     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
335     EXPECT_EQ(t.optind, 4);
336   }
337 }
338 
TYPED_TEST(GetoptCompatTest,ShortAndLongOptions)339 TYPED_TEST(GetoptCompatTest, ShortAndLongOptions) {
340   auto& t = this->impl;
341   using LongOptionType = typename decltype(this->impl)::LongOptionType;
342 
343   {
344     LongOptionType lopts[]{
345         {"one", 0 /*no_argument*/, nullptr, 1},
346         {"two", 0 /*no_argument*/, nullptr, 2},
347         {"three", 0 /*no_argument*/, nullptr, 3},
348         {nullptr, 0, nullptr, 0},
349     };
350     const char* sops = "123";
351 
352     this->SetCmdline({"argv0"});
353     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
354     EXPECT_EQ(t.optarg, nullptr);
355     EXPECT_EQ(t.optind, 1);
356 
357     this->SetCmdline({"argv0", "-13", "--two", "--three", "--", "--one"});
358     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
359     EXPECT_EQ(t.optind, 1);
360     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3');
361     EXPECT_EQ(t.optind, 2);
362     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
363     EXPECT_EQ(t.optind, 3);
364     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3);
365     EXPECT_EQ(t.optind, 4);
366     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
367     EXPECT_EQ(t.optind, 5);
368 
369     this->SetCmdline({"argv0", "--two", "-1", "--two", "-13"});
370     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
371     EXPECT_EQ(t.optind, 2);
372     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
373     EXPECT_EQ(t.optind, 3);
374     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
375     EXPECT_EQ(t.optind, 4);
376     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
377     EXPECT_EQ(t.optind, 4);
378     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3');
379     EXPECT_EQ(t.optind, 5);
380     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
381     EXPECT_EQ(t.optind, 5);
382   }
383 }
384 
385 }  // namespace
386 }  // namespace base
387 }  // namespace perfetto
388