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