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