• 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   int& optopt = getopt_compat::optopt;
51   int& opterr = getopt_compat::opterr;
52   char*& optarg = getopt_compat::optarg;
53 };
54 
55 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
56 struct SystemGetopt {
57   using LongOptionType = ::option;
58   using GetoptFn = decltype(&::getopt);
59   using GetoptLongFn = decltype(&::getopt_long);
60   GetoptFn getopt = &::getopt;
61   GetoptLongFn getopt_long = &::getopt_long;
62   int& optind = ::optind;
63   int& optopt = ::optopt;
64   int& opterr = ::opterr;
65   char*& optarg = ::optarg;
66 };
67 #endif
68 
69 template <typename T>
70 class GetoptCompatTest : public testing::Test {
71  public:
SetCmdline(std::initializer_list<const char * > arg_list)72   inline void SetCmdline(std::initializer_list<const char*> arg_list) {
73     // Reset the getopt() state.
74     // When calling getopt() several times, MacOS requires that optind is reset
75     // to 1, while Linux requires optind to be reset to 0. Also MacOS requires
76     // optreset to be set as well.
77 #if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
78     impl.optind = 1;
79     optreset = 1;  // It has no corresponding variable in other OSes.
80 #else
81     impl.optind = 0;
82 #endif
83     argc = static_cast<int>(arg_list.size());
84     for (char*& arg : argv)
85       arg = nullptr;
86     size_t i = 0;
87     for (const char* arg : arg_list)
88       argv[i++] = const_cast<char*>(arg);
89   }
90   int argc;
91   char* argv[32];  // We don't use more than 32 entries on our tests.
92   T impl;
93 };
94 
95 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
96 using GetoptTestTypes = ::testing::Types<OurGetopt>;
97 #else
98 using GetoptTestTypes = ::testing::Types<OurGetopt, SystemGetopt>;
99 #endif
100 TYPED_TEST_SUITE(GetoptCompatTest, GetoptTestTypes, /* trailing ',' for GCC*/);
101 
TYPED_TEST(GetoptCompatTest,ShortOptions)102 TYPED_TEST(GetoptCompatTest, ShortOptions) {
103   auto& t = this->impl;
104 
105   const char* sops = "";
106   this->SetCmdline({"argv0"});
107   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
108 
109   sops = "h";
110   this->SetCmdline({"argv0"});
111   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
112 
113   sops = "h";
114   this->SetCmdline({"argv0", "-h"});
115   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h');
116   EXPECT_EQ(t.optind, 2);
117   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
118   EXPECT_EQ(t.optind, 2);
119 
120   sops = "h";
121   this->SetCmdline({"argv0", "positional1", "positional2"});
122   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
123 
124   sops = "h";
125   this->SetCmdline({"argv0", "--", "positional1", "positional2"});
126   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
127   EXPECT_EQ(t.optind, 2);
128 
129   sops = "h";
130   this->SetCmdline({"argv0", "-h"});
131   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h');
132   EXPECT_EQ(t.optind, 2);
133   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
134   EXPECT_EQ(t.optind, 2);
135 
136   sops = "abc";
137   this->SetCmdline({"argv0", "-c", "-a", "-b"});
138   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
139   EXPECT_EQ(t.optind, 2);
140   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
141   EXPECT_EQ(t.optind, 3);
142   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
143   EXPECT_EQ(t.optind, 4);
144   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
145   EXPECT_EQ(t.optind, 4);
146 
147   sops = "abc";
148   this->SetCmdline({"argv0", "-c", "-a", "--", "nonopt"});
149   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
150   EXPECT_EQ(t.optind, 2);
151   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
152   EXPECT_EQ(t.optind, 3);
153   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
154   EXPECT_EQ(t.optind, 4);
155 
156   sops = "abc";
157   this->SetCmdline({"argv0", "-cb"});
158   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
159   EXPECT_EQ(t.optind, 1);
160   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
161   EXPECT_EQ(t.optind, 2);
162   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
163   EXPECT_EQ(t.optind, 2);
164 
165   sops = "abc";
166   this->SetCmdline({"argv0", "-aa", "-c"});
167   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
168   EXPECT_EQ(t.optind, 1);
169   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
170   EXPECT_EQ(t.optind, 2);
171   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
172   EXPECT_EQ(t.optind, 3);
173   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
174   EXPECT_EQ(t.optind, 3);
175 
176   sops = "a:bc";
177   // The semantic here is `-a b -c`
178   this->SetCmdline({"argv0", "-ab", "-c"});
179   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
180   EXPECT_EQ(t.optind, 2);
181   EXPECT_STREQ(t.optarg, "b");
182   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
183   EXPECT_EQ(t.optind, 3);
184   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
185   EXPECT_EQ(t.optind, 3);
186 
187   sops = "a:bc";
188   this->SetCmdline({"argv0", "-ab", "--", "-c"});
189   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
190   EXPECT_EQ(t.optind, 2);
191   EXPECT_STREQ(t.optarg, "b");
192   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
193   EXPECT_EQ(t.optind, 3);
194 
195   sops = "a:b:c:";
196   this->SetCmdline({"argv0", "-a", "arg1", "-b", "--", "-c", "-carg"});
197   // This is sbutle, the "--" is an arg value for "-b", not a separator.
198   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
199   EXPECT_STREQ(t.optarg, "arg1");
200   EXPECT_EQ(t.optind, 3);
201   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
202   EXPECT_STREQ(t.optarg, "--");
203   EXPECT_EQ(t.optind, 5);
204   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
205   EXPECT_STREQ(t.optarg, "-carg");
206   EXPECT_EQ(t.optind, 7);
207   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
208   EXPECT_EQ(t.optind, 7);
209 
210   sops = "a";
211   this->SetCmdline({"argv0", "-q"});
212   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?');
213   EXPECT_EQ(t.optind, 2);
214 }
215 
TYPED_TEST(GetoptCompatTest,LongOptions)216 TYPED_TEST(GetoptCompatTest, LongOptions) {
217   auto& t = this->impl;
218   using LongOptionType = typename decltype(this->impl)::LongOptionType;
219 
220   {
221     LongOptionType lopts[]{
222         {nullptr, 0, nullptr, 0},
223     };
224     const char* sops = "";
225     this->SetCmdline({"argv0"});
226     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
227     EXPECT_EQ(t.optind, 1);
228   }
229 
230   {
231     LongOptionType lopts[]{
232         {nullptr, 0, nullptr, 0},
233     };
234     const char* sops = "";
235     this->SetCmdline({"argv0", "--unknown"});
236     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
237     EXPECT_EQ(t.optind, 2);
238   }
239 
240   {
241     LongOptionType lopts[]{
242         {"one", 0 /*no_argument*/, nullptr, 1},
243         {"two", 0 /*no_argument*/, nullptr, 2},
244         {nullptr, 0, nullptr, 0},
245     };
246     const char* sops = "";
247     this->SetCmdline({"argv0", "--two", "--one"});
248     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
249     EXPECT_EQ(t.optind, 2);
250     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
251     EXPECT_EQ(t.optind, 3);
252     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
253     EXPECT_EQ(t.optind, 3);
254   }
255 
256   {
257     LongOptionType lopts[]{
258         {"one", 0 /*no_argument*/, nullptr, 1},
259         {"two", 0 /*no_argument*/, nullptr, 2},
260         {nullptr, 0, nullptr, 0},
261     };
262     const char* sops = "";
263     this->SetCmdline({"argv0", "--two", "--one", "--not-an-opt"});
264     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
265     EXPECT_EQ(t.optind, 2);
266     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
267     EXPECT_EQ(t.optind, 3);
268     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
269     EXPECT_EQ(t.optind, 4);
270   }
271 
272   {
273     LongOptionType lopts[]{
274         {"one", 0 /*no_argument*/, nullptr, 1},
275         {"two", 0 /*no_argument*/, nullptr, 2},
276         {nullptr, 0, nullptr, 0},
277     };
278     const char* sops = "";
279     this->SetCmdline({"argv0", "--two", "--one", "--", "--not-an-opt"});
280     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
281     EXPECT_EQ(t.optind, 2);
282     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
283     EXPECT_EQ(t.optind, 3);
284     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
285     EXPECT_EQ(t.optind, 4);
286   }
287 
288   {
289     LongOptionType lopts[]{
290         {"no1", 0 /*no_argument*/, nullptr, 1},
291         {"req2", 1 /*required_argument*/, nullptr, 2},
292         {"req3", 1 /*required_argument*/, nullptr, 3},
293         {nullptr, 0, nullptr, 0},
294     };
295     const char* sops = "";
296     // This is subtle: the "--" really is an argument for req2, not an argument
297     // separator. The first positional arg is "!!!".
298     this->SetCmdline({"argv0", "--req3", "-", "--no1", "--req2", "--", "!!!"});
299     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3);
300     EXPECT_EQ(t.optind, 3);
301     EXPECT_STREQ(t.optarg, "-");
302     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
303     EXPECT_EQ(t.optind, 4);
304     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
305     EXPECT_STREQ(t.optarg, "--");
306     EXPECT_EQ(t.optind, 6);
307     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
308     EXPECT_EQ(t.optind, 6);
309   }
310 
311   {
312     LongOptionType lopts[]{
313         {"no1", 0 /*no_argument*/, nullptr, 1},
314         {"req2", 1 /*required_argument*/, nullptr, 2},
315         {nullptr, 0, nullptr, 0},
316     };
317     const char* sops = "";
318     this->SetCmdline({"argv0", "--req2", "foo", "--", "--no1"});
319     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
320     EXPECT_EQ(t.optind, 3);
321     EXPECT_STREQ(t.optarg, "foo");
322     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
323     EXPECT_EQ(t.optind, 4);
324   }
325 }
326 
TYPED_TEST(GetoptCompatTest,ShortAndLongOptions)327 TYPED_TEST(GetoptCompatTest, ShortAndLongOptions) {
328   auto& t = this->impl;
329   using LongOptionType = typename decltype(this->impl)::LongOptionType;
330 
331   {
332     LongOptionType lopts[]{
333         {"one", 0 /*no_argument*/, nullptr, 1},
334         {"two", 0 /*no_argument*/, nullptr, 2},
335         {"three", 0 /*no_argument*/, nullptr, 3},
336         {nullptr, 0, nullptr, 0},
337     };
338     const char* sops = "123";
339 
340     this->SetCmdline({"argv0"});
341     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
342     EXPECT_EQ(t.optind, 1);
343 
344     this->SetCmdline({"argv0", "-13", "--two", "--three", "--", "--one"});
345     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
346     EXPECT_EQ(t.optind, 1);
347     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3');
348     EXPECT_EQ(t.optind, 2);
349     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
350     EXPECT_EQ(t.optind, 3);
351     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3);
352     EXPECT_EQ(t.optind, 4);
353     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
354     EXPECT_EQ(t.optind, 5);
355 
356     this->SetCmdline({"argv0", "--two", "-1", "--two", "-13"});
357     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
358     EXPECT_EQ(t.optind, 2);
359     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
360     EXPECT_EQ(t.optind, 3);
361     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
362     EXPECT_EQ(t.optind, 4);
363     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
364     EXPECT_EQ(t.optind, 4);
365     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3');
366     EXPECT_EQ(t.optind, 5);
367     EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
368     EXPECT_EQ(t.optind, 5);
369   }
370 }
371 
TYPED_TEST(GetoptCompatTest,OpterrHandling)372 TYPED_TEST(GetoptCompatTest, OpterrHandling) {
373   auto& t = this->impl;
374   t.opterr = 0;  // Make errors silent.
375 
376   const char* sops = "ab:";
377   this->SetCmdline({"argv0", "-a", "-c", "-b"});
378   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
379   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?');
380   EXPECT_EQ(t.optopt, 'c');
381   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?');
382   EXPECT_EQ(t.optopt, 'b');
383   EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
384 
385   using LongOptionType = typename decltype(this->impl)::LongOptionType;
386   LongOptionType lopts[]{
387       {"requires_arg", 1 /*required_argument*/, nullptr, 42},
388       {nullptr, 0, nullptr, 0},
389   };
390   this->SetCmdline({"argv0", "-a", "--unkonwn", "--requires_arg"});
391   EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 'a');
392   EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
393   EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
394   EXPECT_EQ(t.optopt, 42);
395   EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
396 }
397 
398 }  // namespace
399 }  // namespace base
400 }  // namespace perfetto
401