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