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 util.c
20 */
21
22 #include <platform/cb_malloc.h>
23 #include <platform/platform.h>
24
25 #include <memcached/util.h>
26 #include <memcached/config_parser.h>
27 #include "string_utilities.h"
28
29 #include <gtest/gtest.h>
30 #include <gmock/gmock.h>
31
32 #define TMP_TEMPLATE "testapp_tmp_file.XXXXXXX"
33
TEST(StringTest, safe_strtoul)34 TEST(StringTest, safe_strtoul) {
35 uint32_t val;
36 EXPECT_TRUE(safe_strtoul("123", val));
37 EXPECT_EQ(123u, val);
38 EXPECT_TRUE(safe_strtoul("+123", val));
39 EXPECT_EQ(123u, val);
40 EXPECT_FALSE(safe_strtoul("", val)); /* empty */
41 EXPECT_FALSE(safe_strtoul("123BOGUS", val)); /* non-numeric */
42 /* Not sure what it does, but this works with ICC :/
43 EXPECT_FALSE(safe_strtoul("92837498237498237498029383", val)); // out of range
44 */
45
46 /* extremes: */
47 EXPECT_TRUE(safe_strtoul("4294967295", val)); /* 2**32 - 1 */
48 EXPECT_EQ(4294967295L, val);
49 /* This actually works on 64-bit ubuntu
50 EXPECT_FALSE(safe_strtoul("4294967296", val)); 2**32
51 */
52 EXPECT_FALSE(safe_strtoul("-1", val)); /* negative */
53 }
54
55
TEST(StringTest, safe_strtoull)56 TEST(StringTest, safe_strtoull) {
57 uint64_t val;
58 uint64_t exp = -1;
59 EXPECT_TRUE(safe_strtoull("123", val));
60 EXPECT_EQ(123u, val);
61 EXPECT_TRUE(safe_strtoull("+123", val));
62 EXPECT_EQ(123u, val);
63 EXPECT_FALSE(safe_strtoull("", val)); /* empty */
64 EXPECT_FALSE(safe_strtoull("123BOGUS", val)); /* non-numeric */
65 EXPECT_FALSE(safe_strtoull("92837498237498237498029383", val)); /* out of range */
66
67 /* extremes: */
68 EXPECT_TRUE(safe_strtoull("18446744073709551615", val)); /* 2**64 - 1 */
69 EXPECT_EQ(exp, val);
70 EXPECT_FALSE(safe_strtoull("18446744073709551616", val)); /* 2**64 */
71 EXPECT_FALSE(safe_strtoull("-1", val)); /* negative */
72 }
73
TEST(StringTest, safe_strtoll)74 TEST(StringTest, safe_strtoll) {
75 int64_t val;
76 EXPECT_TRUE(safe_strtoll("123", val));
77 EXPECT_EQ(123, val);
78 EXPECT_TRUE(safe_strtoll("+123", val));
79 EXPECT_EQ(123, val);
80 EXPECT_TRUE(safe_strtoll("-123", val));
81 EXPECT_EQ(-123, val);
82 EXPECT_FALSE(safe_strtoll("", val)); /* empty */
83 EXPECT_FALSE(safe_strtoll("123BOGUS", val)); /* non-numeric */
84 EXPECT_FALSE(safe_strtoll("92837498237498237498029383", val)); /* out of range */
85
86 /* extremes: */
87 EXPECT_FALSE(safe_strtoll("18446744073709551615", val)); /* 2**64 - 1 */
88 EXPECT_TRUE(safe_strtoll("9223372036854775807", val)); /* 2**63 - 1 */
89
90 EXPECT_EQ(std::numeric_limits<int64_t>::max(),
91 val); /* 9223372036854775807LL); */
92 /*
93 EXPECT_EQ(safe_strtoll("-9223372036854775808", val)); // -2**63
94 EXPECT_EQ(val, -9223372036854775808LL);
95 */
96 EXPECT_FALSE(safe_strtoll("-9223372036854775809", val)); /* -2**63 - 1 */
97
98 /* We'll allow space to terminate the string. And leading space. */
99 EXPECT_TRUE(safe_strtoll(" 123 foo", val));
100 EXPECT_EQ(123, val);
101 }
102
TEST(StringTest, safe_strtol)103 TEST(StringTest, safe_strtol) {
104 int32_t val;
105 EXPECT_TRUE(safe_strtol("123", val));
106 EXPECT_EQ(123, val);
107 EXPECT_TRUE(safe_strtol("+123", val));
108 EXPECT_EQ(123, val);
109 EXPECT_TRUE(safe_strtol("-123", val));
110 EXPECT_EQ(-123, val);
111 EXPECT_FALSE(safe_strtol("", val)); /* empty */
112 EXPECT_FALSE(safe_strtol("123BOGUS", val)); /* non-numeric */
113 EXPECT_FALSE(safe_strtol("92837498237498237498029383", val)); /* out of range */
114
115 /* extremes: */
116 /* This actually works on 64-bit ubuntu
117 EXPECT_FALSE(safe_strtol("2147483648", val)); // (expt 2.0 31.0)
118 */
119 EXPECT_TRUE(safe_strtol("2147483647", val)); /* (- (expt 2.0 31) 1) */
120 EXPECT_EQ(2147483647L, val);
121 /* This actually works on 64-bit ubuntu
122 EXPECT_FALSE(safe_strtol("-2147483649", val)); // (- (expt -2.0 31) 1)
123 */
124
125 /* We'll allow space to terminate the string. And leading space. */
126 EXPECT_TRUE(safe_strtol(" 123 foo", val));
127 EXPECT_EQ(123, val);
128 }
129
TEST(StringTest, safe_strtof)130 TEST(StringTest, safe_strtof) {
131 float val;
132 EXPECT_TRUE(safe_strtof("123", val));
133 EXPECT_EQ(123.00f, val);
134 EXPECT_TRUE(safe_strtof("+123", val));
135 EXPECT_EQ(123.00f, val);
136 EXPECT_TRUE(safe_strtof("-123", val));
137 EXPECT_EQ(-123.00f, val);
138 EXPECT_FALSE(safe_strtof("", val)); /* empty */
139 EXPECT_FALSE(safe_strtof("123BOGUS", val)); /* non-numeric */
140
141 /* We'll allow space to terminate the string. And leading space. */
142 EXPECT_TRUE(safe_strtof(" 123 foo", val));
143 EXPECT_EQ(123.00f, val);
144
145 EXPECT_TRUE(safe_strtof("123.23", val));
146 EXPECT_EQ(123.23f, val);
147
148 EXPECT_TRUE(safe_strtof("123.00", val));
149 EXPECT_EQ(123.00f, val);
150 }
151
TEST(StringTest, split_string)152 TEST(StringTest, split_string) {
153 using namespace testing;
154
155 EXPECT_THAT(split_string("123:456", ":"), ElementsAre("123", "456"));
156 EXPECT_THAT(split_string("123::456", ":"), ElementsAre("123", "", "456"));
157 EXPECT_THAT(split_string("123:456:", ":"), ElementsAre("123", "456", ""));
158 EXPECT_THAT(split_string("123:456:789", ":", 1),
159 ElementsAre("123", "456:789"));
160 EXPECT_THAT(split_string("123:456:789", ":", 2),
161 ElementsAre("123", "456", "789"));
162 EXPECT_THAT(split_string("123::456", ":", 1),
163 ElementsAre("123", ":456"));
164 EXPECT_THAT(split_string(":", ":", 2),
165 ElementsAre("", ""));
166 EXPECT_THAT(split_string(":abcd", ":", 200),
167 ElementsAre("", "abcd"));
168 EXPECT_THAT(split_string("Hello, World!", ", ", 200),
169 ElementsAre("Hello", "World!"));
170 EXPECT_THAT(split_string("Hello<BOOM>World<BOOM>!", "<BOOM>", 200),
171 ElementsAre("Hello", "World", "!"));
172 EXPECT_THAT(split_string("Hello<BOOM>World<BOOM>!", "<BOOM>", 1),
173 ElementsAre("Hello", "World<BOOM>!"));
174 }
175
TEST(StringTest, percent_decode)176 TEST(StringTest, percent_decode) {
177 // Test every character from 0x00->0xFF that they can be converted to
178 // percent encoded strings and back again
179 for (int i = 0; i < 255; ++i) {
180 std::stringstream s, t;
181 s << "%" << std::setfill('0') << std::setw(2) << std::hex << i;
182 t << static_cast<char>(i);
183 EXPECT_EQ(t.str(), percent_decode(s.str()));
184 }
185
186 EXPECT_EQ("abcdef!abcdef", percent_decode("abcdef%21abcdef"));
187 EXPECT_EQ("!", percent_decode("%21"));
188 EXPECT_EQ("!!", percent_decode("%21%21"));
189 EXPECT_EQ("%21", percent_decode("%25%32%31"));
190
191 EXPECT_THROW(percent_decode("%"), std::invalid_argument);
192 EXPECT_THROW(percent_decode("%%"), std::invalid_argument);
193 EXPECT_THROW(percent_decode("%3"), std::invalid_argument);
194 EXPECT_THROW(percent_decode("%%%"), std::invalid_argument);
195 EXPECT_THROW(percent_decode("%GG"), std::invalid_argument);
196 }
197
TEST(StringTest, decode_query)198 TEST(StringTest, decode_query) {
199 using namespace testing;
200 std::pair<std::string, StrToStrMap> request;
201
202 request = decode_query("key?arg=val&arg2=val2&arg3=val?=");
203 EXPECT_EQ("key", request.first);
204 EXPECT_THAT(request.second, UnorderedElementsAre(Pair("arg", "val"),
205 Pair("arg2", "val2"),
206 Pair("arg3", "val?=")));
207
208 request = decode_query("key");
209 EXPECT_EQ("key", request.first);
210 EXPECT_THAT(request.second, UnorderedElementsAre());
211
212 request = decode_query("key?");
213 EXPECT_EQ("key", request.first);
214 EXPECT_THAT(request.second, UnorderedElementsAre());
215
216 request = decode_query("key\?\?=?");
217 EXPECT_EQ("key", request.first);
218 EXPECT_THAT(request.second, UnorderedElementsAre(Pair("?", "?")));
219
220 request = decode_query("key?%25=%26&%26=%25");
221 EXPECT_EQ("key", request.first);
222 EXPECT_THAT(request.second, UnorderedElementsAre(Pair("%", "&"),
223 Pair("&", "%")));
224
225 EXPECT_THROW(decode_query("key?=&a=b"), std::invalid_argument);
226 EXPECT_THROW(decode_query("key?a&a=b"), std::invalid_argument);
227
228 }
229
trim(char* ptr)230 static char* trim(char* ptr) {
231 char *start = ptr;
232 char *end;
233
234 while (isspace(*start)) {
235 ++start;
236 }
237 end = start + strlen(start) - 1;
238 if (end != start) {
239 while (isspace(*end)) {
240 *end = '\0';
241 --end;
242 }
243 }
244 return start;
245 }
246
TEST(ConfigParserTest, A)247 TEST(ConfigParserTest, A) {
248 bool bool_val = false;
249 size_t size_val = 0;
250 ssize_t ssize_val = 0;
251 float float_val = 0;
252 char *string_val = 0;
253 int ii;
254 char buffer[1024];
255 FILE *cfg;
256 char outfile[sizeof(TMP_TEMPLATE)+1];
257 char cfgfile[sizeof(TMP_TEMPLATE)+1];
258 FILE *error;
259
260 /* Set up the different items I can handle */
261 struct config_item items[7];
262 memset(&items, 0, sizeof(items));
263 ii = 0;
264 items[ii].key = "bool";
265 items[ii].datatype = DT_BOOL;
266 items[ii].value.dt_bool = &bool_val;
267 ++ii;
268
269 items[ii].key = "size_t";
270 items[ii].datatype = DT_SIZE;
271 items[ii].value.dt_size = &size_val;
272 ++ii;
273
274 items[ii].key = "ssize_t";
275 items[ii].datatype = DT_SSIZE;
276 items[ii].value.dt_ssize = &ssize_val;
277 ++ii;
278
279 items[ii].key = "float";
280 items[ii].datatype = DT_FLOAT;
281 items[ii].value.dt_float = &float_val;
282 ++ii;
283
284 items[ii].key = "string";
285 items[ii].datatype = DT_STRING;
286 items[ii].value.dt_string = &string_val;
287 ++ii;
288
289 items[ii].key = "config_file";
290 items[ii].datatype = DT_CONFIGFILE;
291 ++ii;
292
293 items[ii].key = NULL;
294 ++ii;
295
296 ASSERT_EQ(7, ii);
297 strncpy(outfile, TMP_TEMPLATE, sizeof(TMP_TEMPLATE)+1);
298 strncpy(cfgfile, TMP_TEMPLATE, sizeof(TMP_TEMPLATE)+1);
299
300 ASSERT_NE(cb_mktemp(outfile), nullptr);
301 error = fopen(outfile, "w");
302
303 ASSERT_NE(error, nullptr);
304 ASSERT_EQ(0, parse_config("", items, error));
305 /* Nothing should be found */
306 for (ii = 0; ii < 5; ++ii) {
307 EXPECT_FALSE(items[0].found);
308 }
309
310 ASSERT_EQ(0, parse_config("bool=true", items, error));
311 EXPECT_TRUE(bool_val);
312 /* only bool should be found */
313 EXPECT_TRUE(items[0].found);
314 items[0].found = false;
315 for (ii = 0; ii < 5; ++ii) {
316 EXPECT_FALSE(items[0].found);
317 }
318
319 /* It should allow illegal keywords */
320 ASSERT_EQ(1, parse_config("pacman=dead", items, error));
321 /* and illegal values */
322 ASSERT_EQ(-1, parse_config("bool=12", items, error));
323 EXPECT_FALSE(items[0].found);
324 /* and multiple occurences of the same value */
325 ASSERT_EQ(0, parse_config("size_t=1; size_t=1024", items, error));
326 EXPECT_TRUE(items[1].found);
327 EXPECT_EQ(1024u, size_val);
328 items[1].found = false;
329
330 /* Empty string */
331 /* XXX: This test fails on Linux, but works on OS X.
332 cb_assert(parse_config("string=", items, error) == 0);
333 cb_assert(items[4].found);
334 cb_assert(strcmp(string_val, "") == 0);
335 items[4].found = false;
336 */
337 /* Plain string */
338 ASSERT_EQ(0, parse_config("string=sval", items, error));
339 EXPECT_TRUE(items[4].found);
340 EXPECT_STREQ("sval", string_val);
341 items[4].found = false;
342 cb_free(string_val);
343 /* Leading space */
344 ASSERT_EQ(0, parse_config("string= sval", items, error));
345 EXPECT_TRUE(items[4].found);
346 EXPECT_STREQ("sval", string_val);
347 items[4].found = false;
348 cb_free(string_val);
349 /* Escaped leading space */
350 ASSERT_EQ(0, parse_config("string=\\ sval", items, error));
351 EXPECT_TRUE(items[4].found);
352 EXPECT_STREQ(" sval", string_val);
353 items[4].found = false;
354 cb_free(string_val);
355 /* trailing space */
356 ASSERT_EQ(0, parse_config("string=sval ", items, error));
357 EXPECT_TRUE(items[4].found);
358 EXPECT_STREQ("sval", string_val);
359 items[4].found = false;
360 cb_free(string_val);
361 /* escaped trailing space */
362 ASSERT_EQ(0, parse_config("string=sval\\ ", items, error));
363 EXPECT_TRUE(items[4].found);
364 EXPECT_STREQ("sval ", string_val);
365 items[4].found = false;
366 cb_free(string_val);
367 /* escaped stop char */
368 ASSERT_EQ(0, parse_config("string=sval\\;blah=x", items, error));
369 EXPECT_TRUE(items[4].found);
370 EXPECT_STREQ("sval;blah=x", string_val);
371 items[4].found = false;
372 cb_free(string_val);
373 /* middle space */
374 ASSERT_EQ(0, parse_config("string=s val", items, error));
375 EXPECT_TRUE(items[4].found);
376 EXPECT_STREQ("s val", string_val);
377 items[4].found = false;
378 cb_free(string_val);
379
380 /* And all of the variables */
381 ASSERT_EQ(0, parse_config("bool=true;size_t=1024;float=12.5;string=somestr",
382 items, error));
383 EXPECT_TRUE(bool_val);
384 EXPECT_EQ(1024u, size_val);
385 EXPECT_EQ(12.5f, float_val);
386 EXPECT_STREQ("somestr", string_val);
387 cb_free(string_val);
388 for (ii = 0; ii < 5; ++ii) {
389 items[ii].found = false;
390 }
391
392 ASSERT_EQ(0, parse_config("size_t=1k", items, error));
393 EXPECT_TRUE(items[1].found);
394 EXPECT_EQ(1024u, size_val);
395 items[1].found = false;
396 ASSERT_EQ(0, parse_config("size_t=1m", items, error));
397 EXPECT_TRUE(items[1].found);
398 EXPECT_EQ(1024u * 1024u, size_val);
399 items[1].found = false;
400 ASSERT_EQ(0, parse_config("size_t=1g", items, error));
401 EXPECT_TRUE(items[1].found);
402 EXPECT_EQ(1024u * 1024u * 1024u ,size_val);
403 items[1].found = false;
404 ASSERT_EQ(0, parse_config("size_t=1K", items, error));
405 EXPECT_TRUE(items[1].found);
406 EXPECT_EQ(1024u, size_val);
407 items[1].found = false;
408 ASSERT_EQ(0, parse_config("size_t=1M", items, error));
409 EXPECT_TRUE(items[1].found);
410 EXPECT_EQ(1024u * 1024u, size_val);
411 items[1].found = false;
412 ASSERT_EQ(0, parse_config("size_t=1G", items, error));
413 EXPECT_TRUE(items[1].found);
414 EXPECT_EQ(1024u * 1024u * 1024u, size_val);
415 items[1].found = false;
416
417 // Check negative and positive input
418 std::vector<std::pair<std::string, int> >suffixes = {{ "k", 1024},
419 {"m", 1024*1024},
420 {"g", 1024*1024*1024},
421 {"K", 1024},
422 {"M", 1024*1024},
423 {"G", 1024*1024*1024}};
424
425 /*
426 * This is a hack to work around problems with Visual Studio in
427 * debug builds. Initially the construct looked like:
428 *
429 * for (ssize_t test_val : { -1000, -1, 0, 1, 1000 );
430 *
431 * but that results in
432 *
433 * SEH exception with code 0xc0000005 thrown in the test body
434 */
435 const ssize_t values[5] = { -1000, -1, 0, 1, 1000 };
436 for (int ii = 0; ii < 5; ++ii) {
437 const ssize_t test_val = values[ii];
438 for (auto suffix : suffixes) {
439 std::string config = "ssize_t=" +
440 std::to_string(test_val) + suffix.first;
441 ASSERT_EQ(0, parse_config(config.c_str(), items, error));
442 EXPECT_TRUE(items[2].found);
443 EXPECT_EQ(suffix.second * test_val, ssize_val);
444 items[2].found = false;
445 }
446 }
447
448 ASSERT_NE(cb_mktemp(cfgfile), nullptr);
449 cfg = fopen(cfgfile, "w");
450 ASSERT_NE(cfg, nullptr);
451 fprintf(cfg, "# This is a config file\nbool=true\nsize_t=1023\nfloat=12.4\n");
452 fclose(cfg);
453 sprintf(buffer, "config_file=%s", cfgfile);
454 ASSERT_EQ(0, parse_config(buffer, items, error));
455 EXPECT_TRUE(bool_val);
456 EXPECT_EQ(1023u, size_val);
457 EXPECT_EQ(12.4f, float_val);
458 fclose(error);
459
460 remove(cfgfile);
461 /* Verify that I received the error messages ;-) */
462 error = fopen(outfile, "r");
463 ASSERT_TRUE(error);
464
465 EXPECT_TRUE(fgets(buffer, sizeof(buffer), error));
466 EXPECT_STREQ("Unsupported key: <pacman>", trim(buffer));
467 EXPECT_TRUE(fgets(buffer, sizeof(buffer), error));
468 EXPECT_STREQ("Invalid entry, Key: <bool> Value: <12>", trim(buffer));
469 EXPECT_TRUE(fgets(buffer, sizeof(buffer), error));
470 EXPECT_STREQ("WARNING: Found duplicate entry for \"size_t\"", trim(buffer));
471 EXPECT_EQ(nullptr, fgets(buffer, sizeof(buffer), error));
472
473 EXPECT_EQ(0, fclose(error));
474 remove(outfile);
475 }
476