1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2015 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 /*
19  * Unit tests for the cb::getopt implementation of getopt / getopt_long.
20  */
21 
22 #include <folly/portability/GTest.h>
23 #include <platform/cb_malloc.h>
24 #include <platform/cbassert.h>
25 #include <platform/getopt.h>
26 #include <array>
27 #include <cstdlib>
28 #include <cstring>
29 #include <iostream>
30 #include <string>
31 #include <vector>
32 
vec2array(const std::vector<std::string>& vec)33 char** vec2array(const std::vector<std::string>& vec) {
34     auto** arr = new char*[vec.size()];
35     for (unsigned int ii = 0; ii < (unsigned int)vec.size(); ++ii) {
36         arr[ii] = cb_strdup(vec[ii].c_str());
37     }
38     return arr;
39 }
40 
release(char** arr, size_t size)41 static void release(char** arr, size_t size) {
42     for (size_t ii = 0; ii < size; ++ii) {
43         cb_free(arr[ii]);
44     }
45     delete[] arr;
46 }
47 
48 typedef std::vector<std::string> getoptvec;
49 
50 class GetoptTest : public ::testing::Test {
51 protected:
52     void SetUp() override {
53         cb::getopt::mute_stderr();
54         cb::getopt::reset();
55     }
56 };
57 
TEST_F(GetoptTest, NormalWithOneUnknownProvided)58 TEST_F(GetoptTest, NormalWithOneUnknownProvided) {
59     getoptvec vec;
60     vec.push_back("program");
61     vec.push_back("-a");
62     vec.push_back("-b");
63     auto argc = (int)vec.size();
64     auto** argv = vec2array(vec);
65     ASSERT_EQ(1, cb::getopt::optind);
66     ASSERT_EQ('a', cb::getopt::getopt(argc, argv, "a"));
67     ASSERT_EQ(2, cb::getopt::optind);
68     ASSERT_EQ('?', cb::getopt::getopt(argc, argv, "a"));
69     ASSERT_EQ(3, cb::getopt::optind);
70 
71     release(argv, vec.size());
72 }
73 
TEST_F(GetoptTest, NormalWithTermination)74 TEST_F(GetoptTest, NormalWithTermination) {
75     getoptvec vec;
76     vec.push_back("program");
77     vec.push_back("-a");
78     vec.push_back("--");
79     vec.push_back("-b");
80     auto argc = (int)vec.size();
81     auto** argv = vec2array(vec);
82     ASSERT_EQ('a', cb::getopt::getopt(argc, argv, "a"));
83     ASSERT_EQ(-1, cb::getopt::getopt(argc, argv, "a"));
84     ASSERT_EQ(3, cb::getopt::optind);
85 
86     release(argv, vec.size());
87 }
88 
TEST_F(GetoptTest, RegressionTestFromEpEngine)89 TEST_F(GetoptTest, RegressionTestFromEpEngine) {
90     getoptvec vec;
91     vec.push_back("..\\memcached\\engine_testapp");
92     vec.push_back("-E");
93     vec.push_back("ep.dll");
94     vec.push_back("-T");
95     vec.push_back("ep_testsuite.dll");
96     vec.push_back("-e");
97     vec.push_back("flushall_enabled=true;ht_size=13;ht_locks=7");
98     vec.push_back("-v");
99     vec.push_back("-C");
100     vec.push_back("7");
101     vec.push_back("-s");
102     vec.push_back("foo");
103 
104     auto argc = (int)vec.size();
105     auto** argv = vec2array(vec);
106 
107     ASSERT_EQ('E', cb::getopt::getopt(argc, argv, "E:T:e:vC:s"));
108     ASSERT_STREQ(argv[2], cb::getopt::optarg);
109     ASSERT_EQ('T', cb::getopt::getopt(argc, argv, "E:T:e:vC:s"));
110     ASSERT_STREQ(argv[4], cb::getopt::optarg);
111     ASSERT_EQ('e', cb::getopt::getopt(argc, argv, "E:T:e:vC:s"));
112     ASSERT_STREQ(argv[6], cb::getopt::optarg);
113     ASSERT_EQ('v', cb::getopt::getopt(argc, argv, "E:T:e:vC:s"));
114     ASSERT_EQ('C', cb::getopt::getopt(argc, argv, "E:T:e:vC:s"));
115     ASSERT_STREQ(argv[9], cb::getopt::optarg);
116     ASSERT_EQ('s', cb::getopt::getopt(argc, argv, "E:T:e:vC:s"));
117     ASSERT_EQ(-1, cb::getopt::getopt(argc, argv, "E:T:e:vC:s"));
118     ASSERT_EQ(11, cb::getopt::optind);
119 
120     release(argv, vec.size());
121 }
122 
TEST_F(GetoptTest, TestLongOptions)123 TEST_F(GetoptTest, TestLongOptions) {
124     static cb::getopt::option long_options[] = {
125             {"first", cb::getopt::no_argument, 0, 'f'},
126             {"second", cb::getopt::no_argument, 0, 's'},
127             {"third", cb::getopt::no_argument, 0, 't'},
128             {0, 0, 0, 0}};
129 
130     getoptvec vec;
131 
132     vec.push_back("getopt_long_test");
133     vec.push_back("--first");
134     vec.push_back("--wrong");
135     vec.push_back("--second");
136     vec.push_back("--third");
137 
138     auto argc = (int)vec.size();
139     auto** argv = vec2array(vec);
140 
141     int option_index = 0;
142     int c = 1;
143 
144     bool first, second, third;
145     first = second = third = false;
146 
147     while ((c = cb::getopt::getopt_long(
148                     argc, argv, "fst", long_options, &option_index)) != -1) {
149         switch (c) {
150         case 'f':
151             first = true;
152             break;
153         case 's':
154             second = true;
155             break;
156         case 't':
157             third = true;
158             break;
159         }
160     }
161     EXPECT_TRUE(first) << "--first not found";
162     EXPECT_TRUE(second) << "--second not found";
163     EXPECT_TRUE(third) << "--third not found";
164 
165     release(argv, vec.size());
166 }
167 
TEST_F(GetoptTest, TestLongOptionsWithArguments)168 TEST_F(GetoptTest, TestLongOptionsWithArguments) {
169     static cb::getopt::option long_options[] = {
170             {"host", cb::getopt::required_argument, nullptr, 'h'},
171             {"port", cb::getopt::required_argument, nullptr, 'p'},
172             {nullptr, 0, nullptr, 0}};
173 
174     getoptvec vec;
175 
176     vec.push_back("TestLongOptionsWithArguments");
177     vec.push_back("--host=localhost");
178     vec.push_back("--port");
179     vec.push_back("11210");
180 
181     auto argc = (int)vec.size();
182     auto** argv = vec2array(vec);
183 
184     int option_index = 0;
185     int c;
186 
187     std::string host;
188     std::string port;
189 
190     while ((c = cb::getopt::getopt_long(
191                     argc, argv, "h:p:", long_options, &option_index)) != -1) {
192         switch (c) {
193         case 'h':
194             ASSERT_NE(nullptr, cb::getopt::optarg)
195                     << "host should have argument";
196             host.assign(cb::getopt::optarg);
197             break;
198         case 'p':
199             ASSERT_NE(nullptr, cb::getopt::optarg)
200                     << "port should have argument";
201             port.assign(cb::getopt::optarg);
202             break;
203         default:
204             FAIL() << "getopt_long returned " << char(c);
205         }
206     }
207 
208     EXPECT_EQ(host, "localhost");
209     EXPECT_EQ(port, "11210");
210 
211     release(argv, vec.size());
212 }
213 
TEST_F(GetoptTest, TestLongOptionsWithMissingLastArguments)214 TEST_F(GetoptTest, TestLongOptionsWithMissingLastArguments) {
215     static cb::getopt::option long_options[] = {
216             {"port", cb::getopt::required_argument, nullptr, 'p'},
217             {nullptr, 0, nullptr, 0}};
218 
219     getoptvec vec;
220 
221     vec.push_back("TestLongOptionsWithMissingLastArguments");
222     vec.push_back("--port");
223 
224     auto argc = (int)vec.size();
225     auto** argv = vec2array(vec);
226 
227     int option_index = 0;
228     ASSERT_EQ('?',
229               cb::getopt::getopt_long(
230                       argc, argv, "p:", long_options, &option_index));
231 
232     release(argv, vec.size());
233 }
234 
TEST_F(GetoptTest, TestLongOptionsWithOptionalArguments)235 TEST_F(GetoptTest, TestLongOptionsWithOptionalArguments) {
236     static cb::getopt::option long_options[] = {
237             {"none", cb::getopt::optional_argument, nullptr, 'n'},
238             {"with", cb::getopt::optional_argument, nullptr, 'w'},
239             {nullptr, 0, nullptr, 0}};
240 
241     getoptvec vec;
242 
243     vec.push_back("TestLongOptionsWithOptionalArguments");
244     vec.push_back("--none");
245     vec.push_back("--with=true");
246 
247     auto argc = (int)vec.size();
248     auto** argv = vec2array(vec);
249 
250     int option_index = 0;
251     int c;
252     bool none = false;
253     bool with = false;
254 
255     while ((c = cb::getopt::getopt_long(
256                     argc, argv, "n:w:", long_options, &option_index)) != -1) {
257         switch (c) {
258         case 'n':
259             ASSERT_EQ(nullptr, cb::getopt::optarg);
260             none = true;
261             break;
262         case 'w':
263             ASSERT_STREQ("true", cb::getopt::optarg);
264             with = true;
265             break;
266         default:
267             FAIL() << "getopt_long returned " << char(c);
268         }
269     }
270 
271     EXPECT_TRUE(none);
272     EXPECT_TRUE(with);
273 
274     release(argv, vec.size());
275 }
276