Line data Source code
1 : // Copyright (c) 2006-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/advgetopt
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : // self
21 : //
22 : #include "catch_main.h"
23 :
24 :
25 : // advgetopt lib
26 : //
27 : #include <advgetopt/exception.h>
28 : #include <advgetopt/validator_duration.h>
29 : #include <advgetopt/validator_size.h>
30 :
31 :
32 : // snapdev lib
33 : //
34 : #include <snapdev/ostream_int128.h>
35 :
36 :
37 : // C++ lib
38 : //
39 : #include <cmath>
40 : #include <fstream>
41 : #include <iomanip>
42 :
43 :
44 : // last include
45 : //
46 : #include <snapdev/poison.h>
47 :
48 :
49 :
50 :
51 :
52 : namespace
53 : {
54 :
55 : struct duration_t
56 : {
57 : char const * const f_suffix = nullptr;
58 : double f_factor = 1.0;
59 : };
60 :
61 : constexpr duration_t const g_duration_suffixes[] =
62 : {
63 : { "", 1.0 },
64 : { "s", 1.0 },
65 : { "second", 1.0 },
66 : { "seconds", 1.0 },
67 :
68 : { "m", -1.0 }, // may represent minutes or months
69 : { "minute", 60.0 },
70 : { "minutes", 60.0 },
71 :
72 : { "h", 3600.0 },
73 : { "hour", 3600.0 },
74 : { "hours", 3600.0 },
75 :
76 : { "d", 86400.0 },
77 : { "day", 86400.0 },
78 : { "days", 86400.0 },
79 :
80 : { "w", 86400.0 * 7.0 },
81 : { "week", 86400.0 * 7.0 },
82 : { "weeks", 86400.0 * 7.0 },
83 :
84 : { "month", 86400.0 * 30.0 },
85 : { "months", 86400.0 * 30.0 },
86 :
87 : { "y", 86400.0 * 365.0 },
88 : { "year", 86400.0 * 365.0 },
89 : { "years", 86400.0 * 365.0 },
90 : };
91 :
92 : struct size_suffix_t
93 : {
94 : char const * const f_suffix = nullptr;
95 : int f_base = 1000.0;
96 : int f_power = 0.0;
97 : };
98 :
99 : constexpr size_suffix_t const g_size_suffixes[] =
100 : {
101 : { "", 1000, 0 },
102 : { "B", 1000, 0 },
103 :
104 : { "kB", 1000, 1 },
105 : { "KiB", 1024, 1 },
106 :
107 : { "MB", 1000, 2 },
108 : { "MiB", 1024, 2 },
109 :
110 : { "GB", 1000, 3 },
111 : { "GiB", 1024, 3 },
112 :
113 : { "TB", 1000, 4 },
114 : { "TiB", 1024, 4 },
115 :
116 : { "PB", 1000, 5 },
117 : { "PiB", 1024, 5 },
118 :
119 : { "EB", 1000, 6 },
120 : { "EiB", 1024, 6 },
121 :
122 : { "ZB", 1000, 7 },
123 : { "ZiB", 1024, 7 },
124 :
125 : { "YB", 1000, 8 },
126 : { "YiB", 1024, 8 },
127 : };
128 :
129 123693 : std::int64_t large_rnd(bool zero_allowed = true)
130 : {
131 : for(;;)
132 : {
133 123693 : std::int64_t const result((static_cast<std::int64_t>(rand()) << 0)
134 123693 : ^ (static_cast<std::int64_t>(rand()) << 16)
135 123693 : ^ (static_cast<std::int64_t>(rand()) << 32)
136 123693 : ^ (static_cast<std::int64_t>(rand()) << 48));
137 123693 : if(result != 0
138 0 : || zero_allowed)
139 : {
140 247386 : return result;
141 : }
142 0 : }
143 : }
144 :
145 : }
146 :
147 :
148 :
149 4 : CATCH_TEST_CASE("unknown_validator", "[validator][valid][validation]")
150 : {
151 4 : CATCH_START_SECTION("Undefined validator")
152 : // this is a valid case, it does not throw, it just returns a nullptr
153 : //
154 1 : CATCH_REQUIRE(advgetopt::validator::create("unknown", advgetopt::string_list_t()) == nullptr);
155 : CATCH_END_SECTION()
156 :
157 4 : CATCH_START_SECTION("Empty string")
158 1 : CATCH_REQUIRE(advgetopt::validator::create(std::string()) == nullptr);
159 : CATCH_END_SECTION()
160 2 : }
161 :
162 :
163 :
164 5 : CATCH_TEST_CASE("integer_validator", "[validator][valid][validation]")
165 : {
166 6 : CATCH_START_SECTION("Verify the integer validator")
167 2 : advgetopt::validator::pointer_t integer_validator(advgetopt::validator::create("integer", advgetopt::string_list_t()));
168 :
169 1 : CATCH_REQUIRE(integer_validator != nullptr);
170 1 : CATCH_REQUIRE(integer_validator->name() == "integer");
171 :
172 1 : CATCH_REQUIRE_FALSE(integer_validator->validate(""));
173 1 : CATCH_REQUIRE_FALSE(integer_validator->validate("+"));
174 1 : CATCH_REQUIRE_FALSE(integer_validator->validate("-"));
175 :
176 1001 : for(int idx(0); idx < 1000; ++idx)
177 : {
178 1000 : std::int64_t value(large_rnd());
179 2000 : std::string const v(std::to_string(value));
180 :
181 1000 : CATCH_REQUIRE(integer_validator->validate(v));
182 :
183 1000 : if(value >= 0)
184 : {
185 489 : CATCH_REQUIRE(integer_validator->validate('+' + v));
186 : }
187 :
188 2000 : std::string const space_before(' ' + v);
189 1000 : CATCH_REQUIRE_FALSE(integer_validator->validate(space_before));
190 :
191 2000 : std::string const space_after(v + ' ');
192 1000 : CATCH_REQUIRE_FALSE(integer_validator->validate(space_after));
193 :
194 2000 : std::string const before(static_cast<char>(rand() % 26 + 'a') + v);
195 1000 : CATCH_REQUIRE_FALSE(integer_validator->validate(before));
196 :
197 2000 : std::string const after(v + static_cast<char>(rand() % 26 + 'a'));
198 1000 : CATCH_REQUIRE_FALSE(integer_validator->validate(after));
199 : }
200 :
201 : // max number
202 1 : CATCH_REQUIRE(integer_validator->validate("9223372036854775807"));
203 1 : CATCH_REQUIRE(integer_validator->validate("+9223372036854775807"));
204 :
205 : // overflow
206 1 : CATCH_REQUIRE_FALSE(integer_validator->validate("9223372036854775808"));
207 1 : CATCH_REQUIRE_FALSE(integer_validator->validate("+9223372036854775808"));
208 :
209 : // min number
210 1 : CATCH_REQUIRE(integer_validator->validate("-9223372036854775808"));
211 :
212 : // underflow
213 1 : CATCH_REQUIRE_FALSE(integer_validator->validate("-9223372036854775809"));
214 :
215 : // too many digits
216 1 : CATCH_REQUIRE_FALSE(integer_validator->validate("92233720368547758091"));
217 1 : CATCH_REQUIRE_FALSE(integer_validator->validate("+92233720368547758092"));
218 1 : CATCH_REQUIRE_FALSE(integer_validator->validate("-92233720368547758093"));
219 : CATCH_END_SECTION()
220 :
221 6 : CATCH_START_SECTION("Verify the integer ranges")
222 1 : bool had_standalone(false);
223 21 : for(int count(0); count < 20 || !had_standalone; ++count)
224 : {
225 20 : std::int64_t min(large_rnd());
226 20 : std::int64_t max(large_rnd());
227 20 : if(min > max)
228 : {
229 10 : std::swap(min, max);
230 : }
231 :
232 40 : std::string const & smin(std::to_string(min));
233 40 : std::string const & smax(std::to_string(max));
234 :
235 40 : std::string range("...");
236 80 : for(int three(0); three < 3; ++three)
237 : {
238 60 : if(rand() % 5 == 0)
239 : {
240 13 : range = ' ' + range;
241 : }
242 60 : if(rand() % 5 == 0)
243 : {
244 12 : range = range + ' ';
245 : }
246 : }
247 20 : range = smin + range + smax;
248 80 : for(int three(0); three < 3; ++three)
249 : {
250 60 : if(rand() % 5 == 0)
251 : {
252 11 : range = ' ' + range;
253 : }
254 60 : if(rand() % 5 == 0)
255 : {
256 12 : range = range + ' ';
257 : }
258 : }
259 :
260 20 : std::int64_t standalone(0);
261 20 : bool standalone_included(rand() % 4 == 0);
262 20 : if(standalone_included)
263 : {
264 10 : if(min == std::numeric_limits<std::int64_t>::min()
265 5 : && max == std::numeric_limits<std::int64_t>::max())
266 : {
267 0 : standalone_included = false;
268 : }
269 : else
270 : {
271 5 : had_standalone = true;
272 1 : do
273 : {
274 6 : standalone = large_rnd();
275 : }
276 6 : while(standalone >= min && standalone <= max);
277 :
278 10 : std::string sep(",");
279 5 : if(rand() % 3 == 0)
280 : {
281 1 : sep = ' ' + sep;
282 : }
283 5 : if(rand() % 3 == 0)
284 : {
285 1 : sep = sep + ' ';
286 : }
287 5 : if(rand() % 2 == 0)
288 : {
289 1 : range = std::to_string(standalone) + "," + range;
290 : }
291 : else
292 : {
293 4 : range = range + "," + std::to_string(standalone);
294 : }
295 : }
296 : }
297 40 : advgetopt::string_list_t range_list;
298 40 : advgetopt::split_string(range
299 : , range_list
300 20 : , {","});
301 40 : advgetopt::validator::pointer_t integer_validator(advgetopt::validator::create("integer", range_list));
302 :
303 20 : CATCH_REQUIRE(integer_validator != nullptr);
304 20 : CATCH_REQUIRE(integer_validator->name() == "integer");
305 :
306 20020 : for(int idx(0); idx < 1000; ++idx)
307 : {
308 20000 : std::int64_t value(large_rnd());
309 :
310 : // force valid values otherwise we're likely to only have
311 : // invalid ones
312 : //
313 20000 : if(idx % 10 == 0)
314 : {
315 2000 : value %= max - min + 1;
316 2000 : value += min;
317 : }
318 18000 : else if(idx % 50 == 1 && standalone_included)
319 : {
320 100 : value = standalone;
321 : }
322 :
323 40000 : std::string const v(std::to_string(value));
324 :
325 20000 : if((standalone_included && value == standalone)
326 19900 : || (value >= min && value <= max))
327 : {
328 7020 : CATCH_REQUIRE(integer_validator->validate(v));
329 : }
330 : else
331 : {
332 12980 : CATCH_REQUIRE_FALSE(integer_validator->validate(v));
333 : }
334 :
335 20000 : if(value >= 0)
336 : {
337 9593 : if((standalone_included && value == standalone)
338 9553 : || (value >= min && value <= max))
339 : {
340 3649 : CATCH_REQUIRE(integer_validator->validate('+' + v));
341 : }
342 : else
343 : {
344 5944 : CATCH_REQUIRE_FALSE(integer_validator->validate('+' + v));
345 : }
346 : }
347 :
348 40000 : std::string const space_before(' ' + v);
349 20000 : CATCH_REQUIRE_FALSE(integer_validator->validate(space_before));
350 :
351 40000 : std::string const space_after(v + ' ');
352 20000 : CATCH_REQUIRE_FALSE(integer_validator->validate(space_after));
353 :
354 40000 : std::string const before(static_cast<char>(rand() % 26 + 'a') + v);
355 20000 : CATCH_REQUIRE_FALSE(integer_validator->validate(before));
356 :
357 40000 : std::string const after(v + static_cast<char>(rand() % 26 + 'a'));
358 20000 : CATCH_REQUIRE_FALSE(integer_validator->validate(after));
359 : }
360 : }
361 : CATCH_END_SECTION()
362 :
363 6 : CATCH_START_SECTION("Verify the integer standalone list")
364 21 : for(int count(0); count < 20; ++count)
365 : {
366 20 : int valid(rand() % 10 + 5);
367 40 : std::vector<std::int64_t> numbers;
368 20 : numbers.reserve(valid);
369 40 : std::string standalone_values;
370 211 : for(int idx(0); idx < valid; ++idx)
371 : {
372 191 : std::int64_t const value(large_rnd());
373 191 : numbers.push_back(value);
374 382 : std::string const & svalue(std::to_string(value));
375 191 : if(rand() % 5 == 0)
376 : {
377 44 : standalone_values += ' ';
378 : }
379 191 : if(idx != 0)
380 : {
381 171 : standalone_values += ',';
382 : }
383 191 : if(rand() % 5 == 0)
384 : {
385 39 : standalone_values += ' ';
386 : }
387 191 : standalone_values += svalue;
388 : }
389 20 : if(rand() % 5 == 0)
390 : {
391 2 : standalone_values += ' ';
392 : }
393 40 : advgetopt::string_list_t range_list;
394 40 : advgetopt::split_string(standalone_values
395 : , range_list
396 20 : , {","});
397 :
398 40 : advgetopt::validator::pointer_t integer_validator(advgetopt::validator::create("integer", range_list));
399 :
400 20 : CATCH_REQUIRE(integer_validator != nullptr);
401 20 : CATCH_REQUIRE(integer_validator->name() == "integer");
402 :
403 211 : for(size_t idx(0); idx < numbers.size(); ++idx)
404 : {
405 382 : std::string const svalue(std::to_string(numbers[idx]));
406 :
407 191 : CATCH_REQUIRE(integer_validator->validate(svalue));
408 : }
409 :
410 20020 : for(int idx(0); idx < 1000; ++idx)
411 : {
412 20000 : std::int64_t value;
413 :
414 : for(;;)
415 : {
416 20000 : value = large_rnd();
417 20000 : if(std::find(numbers.begin(), numbers.end(), value) == numbers.end())
418 : {
419 20000 : break;
420 : }
421 : }
422 :
423 20000 : CATCH_REQUIRE_FALSE(integer_validator->validate(std::to_string(value)));
424 : }
425 : }
426 : CATCH_END_SECTION()
427 3 : }
428 :
429 :
430 :
431 :
432 5 : CATCH_TEST_CASE("double_validator", "[validator][valid][validation]")
433 : {
434 6 : CATCH_START_SECTION("Verify the double validator")
435 2 : advgetopt::validator::pointer_t double_validator(advgetopt::validator::create("double", advgetopt::string_list_t()));
436 :
437 1 : CATCH_REQUIRE(double_validator != nullptr);
438 1 : CATCH_REQUIRE(double_validator->name() == "double");
439 :
440 1 : CATCH_REQUIRE_FALSE(double_validator->validate(""));
441 1 : CATCH_REQUIRE_FALSE(double_validator->validate("+"));
442 1 : CATCH_REQUIRE_FALSE(double_validator->validate("-"));
443 1 : CATCH_REQUIRE_FALSE(double_validator->validate("alpha"));
444 :
445 1001 : for(int idx(0); idx < 1000; ++idx)
446 : {
447 1000 : double value(static_cast<double>(large_rnd()) / static_cast<double>(large_rnd(false)));
448 2000 : std::string const v(std::to_string(value));
449 :
450 1000 : CATCH_REQUIRE(double_validator->validate(v));
451 :
452 1000 : if(value >= 0)
453 : {
454 494 : CATCH_REQUIRE(double_validator->validate('+' + v));
455 : }
456 :
457 2000 : std::string const space_before(' ' + v);
458 1000 : CATCH_REQUIRE_FALSE(double_validator->validate(space_before));
459 :
460 2000 : std::string const space_after(v + ' ');
461 1000 : CATCH_REQUIRE_FALSE(double_validator->validate(space_after));
462 :
463 2000 : std::string const before(static_cast<char>(rand() % 26 + 'a') + v);
464 1000 : CATCH_REQUIRE_FALSE(double_validator->validate(before));
465 :
466 2000 : std::string const after(v + static_cast<char>(rand() % 26 + 'a'));
467 1000 : CATCH_REQUIRE_FALSE(double_validator->validate(after));
468 : }
469 : CATCH_END_SECTION()
470 :
471 6 : CATCH_START_SECTION("Verify the double ranges")
472 1 : bool had_standalone(false);
473 21 : for(int count(0); count < 20 || !had_standalone; ++count)
474 : {
475 20 : double min(static_cast<double>(large_rnd()) / static_cast<double>(large_rnd(false)));
476 20 : double max(static_cast<double>(large_rnd()) / static_cast<double>(large_rnd(false)));
477 20 : if(min > max)
478 : {
479 6 : std::swap(min, max);
480 : }
481 :
482 40 : std::string const & smin(std::to_string(min));
483 40 : std::string const & smax(std::to_string(max));
484 :
485 40 : std::string range("...");
486 80 : for(int three(0); three < 3; ++three)
487 : {
488 60 : if(rand() % 5 == 0)
489 : {
490 11 : range = ' ' + range;
491 : }
492 60 : if(rand() % 5 == 0)
493 : {
494 9 : range = range + ' ';
495 : }
496 : }
497 20 : range = smin + range + smax;
498 80 : for(int three(0); three < 3; ++three)
499 : {
500 60 : if(rand() % 5 == 0)
501 : {
502 18 : range = ' ' + range;
503 : }
504 60 : if(rand() % 5 == 0)
505 : {
506 15 : range = range + ' ';
507 : }
508 : }
509 :
510 20 : double standalone(0);
511 20 : bool standalone_included(rand() % 4 == 0);
512 20 : if(standalone_included)
513 : {
514 16 : if(min <= std::numeric_limits<double>::min()
515 8 : && max >= std::numeric_limits<double>::max())
516 : {
517 0 : standalone_included = false;
518 : }
519 : else
520 : {
521 8 : had_standalone = true;
522 9 : do
523 : {
524 17 : standalone = static_cast<double>(large_rnd()) / static_cast<double>(large_rnd(false));
525 : }
526 17 : while(standalone >= min && standalone <= max);
527 :
528 16 : std::string sep(",");
529 8 : if(rand() % 3 == 0)
530 : {
531 2 : sep = ' ' + sep;
532 : }
533 8 : if(rand() % 3 == 0)
534 : {
535 1 : sep = sep + ' ';
536 : }
537 8 : if(rand() % 2 == 0)
538 : {
539 4 : range = std::to_string(standalone) + "," + range;
540 : }
541 : else
542 : {
543 4 : range = range + "," + std::to_string(standalone);
544 : }
545 : }
546 : }
547 40 : advgetopt::string_list_t range_list;
548 40 : advgetopt::split_string(range
549 : , range_list
550 20 : , {","});
551 40 : advgetopt::validator::pointer_t double_validator(advgetopt::validator::create("double", range_list));
552 :
553 20 : CATCH_REQUIRE(double_validator != nullptr);
554 20 : CATCH_REQUIRE(double_validator->name() == "double");
555 :
556 20020 : for(int idx(0); idx < 1000; ++idx)
557 : {
558 20000 : double value(static_cast<double>(large_rnd()) / static_cast<double>(large_rnd(false)));
559 :
560 : // force valid values otherwise we're likely to only have
561 : // invalid ones
562 : //
563 20000 : if(idx % 10 == 0)
564 : {
565 2000 : value = fmod(value, max - min + 1.0) + min;
566 : }
567 18000 : else if(idx % 50 == 1 && standalone_included)
568 : {
569 160 : value = standalone;
570 : }
571 :
572 40000 : std::string const v(std::to_string(value));
573 :
574 : #pragma GCC diagnostic push
575 : #pragma GCC diagnostic ignored "-Wfloat-equal"
576 20000 : if((standalone_included && value == standalone)
577 19840 : || (value >= min && value <= max))
578 : {
579 6967 : CATCH_REQUIRE(double_validator->validate(v));
580 : }
581 : else
582 : {
583 13033 : CATCH_REQUIRE_FALSE(double_validator->validate(v));
584 : }
585 :
586 20000 : if(value >= 0.0)
587 : {
588 9608 : if((standalone_included && value == standalone)
589 9508 : || (value >= min && value <= max))
590 : {
591 2876 : CATCH_REQUIRE(double_validator->validate('+' + v));
592 : }
593 : else
594 : {
595 6732 : CATCH_REQUIRE_FALSE(double_validator->validate('+' + v));
596 : }
597 : }
598 : #pragma GCC diagnostic pop
599 :
600 40000 : std::string const space_before(' ' + v);
601 20000 : CATCH_REQUIRE_FALSE(double_validator->validate(space_before));
602 :
603 40000 : std::string const space_after(v + ' ');
604 20000 : CATCH_REQUIRE_FALSE(double_validator->validate(space_after));
605 :
606 40000 : std::string const before(static_cast<char>(rand() % 26 + 'a') + v);
607 20000 : CATCH_REQUIRE_FALSE(double_validator->validate(before));
608 :
609 40000 : std::string const after(v + static_cast<char>(rand() % 26 + 'a'));
610 20000 : CATCH_REQUIRE_FALSE(double_validator->validate(after));
611 : }
612 : }
613 : CATCH_END_SECTION()
614 :
615 6 : CATCH_START_SECTION("Verify the double standalone list")
616 21 : for(int count(0); count < 20; ++count)
617 : {
618 20 : int valid(rand() % 10 + 5);
619 40 : std::vector<double> numbers;
620 20 : numbers.reserve(valid);
621 40 : std::string standalone_values;
622 191 : for(int idx(0); idx < valid; ++idx)
623 : {
624 171 : double const value(static_cast<double>(large_rnd()) / static_cast<double>(large_rnd(false)));
625 171 : numbers.push_back(value);
626 342 : std::string const & svalue(std::to_string(value));
627 171 : if(rand() % 5 == 0)
628 : {
629 24 : standalone_values += ' ';
630 : }
631 171 : if(idx != 0)
632 : {
633 151 : standalone_values += ',';
634 : }
635 171 : if(rand() % 5 == 0)
636 : {
637 31 : standalone_values += ' ';
638 : }
639 171 : standalone_values += svalue;
640 : }
641 20 : if(rand() % 5 == 0)
642 : {
643 6 : standalone_values += ' ';
644 : }
645 40 : advgetopt::string_list_t range_list;
646 40 : advgetopt::split_string(standalone_values
647 : , range_list
648 20 : , {","});
649 :
650 40 : advgetopt::validator::pointer_t double_validator(advgetopt::validator::create("double", range_list));
651 :
652 20 : CATCH_REQUIRE(double_validator != nullptr);
653 20 : CATCH_REQUIRE(double_validator->name() == "double");
654 :
655 191 : for(size_t idx(0); idx < numbers.size(); ++idx)
656 : {
657 342 : std::string const svalue(std::to_string(numbers[idx]));
658 :
659 171 : CATCH_REQUIRE(double_validator->validate(svalue));
660 : }
661 :
662 20020 : for(int idx(0); idx < 1000; ++idx)
663 : {
664 20000 : std::int64_t value;
665 :
666 : for(;;)
667 : {
668 20000 : value = static_cast<double>(large_rnd()) / static_cast<double>(large_rnd(false));
669 20000 : if(std::find(numbers.begin(), numbers.end(), value) == numbers.end())
670 : {
671 20000 : break;
672 : }
673 : }
674 :
675 20000 : CATCH_REQUIRE_FALSE(double_validator->validate(std::to_string(value)));
676 : }
677 : }
678 : CATCH_END_SECTION()
679 3 : }
680 :
681 :
682 :
683 :
684 5 : CATCH_TEST_CASE("duration_validator", "[validator][valid][validation]")
685 : {
686 6 : CATCH_START_SECTION("Verify the duration validator (simple values)")
687 : {
688 1 : double duration(0.0);
689 :
690 : // simple seconds with decimal point
691 : //
692 1 : CATCH_REQUIRE(advgetopt::validator_duration::convert_string("22.3s", 0, duration));
693 1 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(duration, 22.3, 0.0));
694 :
695 : // "seconds" is the default
696 : //
697 1 : CATCH_REQUIRE(advgetopt::validator_duration::convert_string("1.05", 0, duration));
698 1 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(duration, 1.05, 0.0));
699 :
700 : // number can start with a decimal point
701 : //
702 1 : CATCH_REQUIRE(advgetopt::validator_duration::convert_string(".0503", 0, duration));
703 1 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(duration, 0.0503, 0.0));
704 : }
705 : CATCH_END_SECTION()
706 :
707 6 : CATCH_START_SECTION("Verify the duration validator (multiple values)")
708 : {
709 1 : double duration(0.0);
710 1 : CATCH_REQUIRE(advgetopt::validator_duration::convert_string("1d 3h 2m 15.3s", 0, duration));
711 1 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(duration, 1.0 * 86400.0 + 3.0 * 3600.0 + 2.0 * 60.0 + 15.3, 0.0));
712 :
713 1 : CATCH_REQUIRE(advgetopt::validator_duration::convert_string("3d 15h 52m 21.801s", 0, duration));
714 1 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(duration, 3.0 * 86400.0 + 15.0 * 3600.0 + 52.0 * 60.0 + 21.801, 0.0));
715 : }
716 : CATCH_END_SECTION()
717 :
718 6 : CATCH_START_SECTION("Verify the duration validator (one value)")
719 : {
720 : // this test does not verify that double conversion works since we
721 : // have a separate test for that specific validator
722 : //
723 4 : for(int size(0); size < 3; ++size)
724 : {
725 3 : advgetopt::validator_duration::flag_t flg(advgetopt::validator_duration::VALIDATOR_DURATION_DEFAULT_FLAGS);
726 6 : advgetopt::string_list_t flags;
727 3 : if(size == 1)
728 : {
729 1 : flags.push_back("small");
730 : }
731 2 : else if(size == 2)
732 : {
733 1 : flags.push_back("large");
734 1 : flg = advgetopt::validator_duration::VALIDATOR_DURATION_LONG;
735 : }
736 6 : advgetopt::validator::pointer_t duration_validator(advgetopt::validator::create("duration", flags));
737 :
738 3 : CATCH_REQUIRE(duration_validator != nullptr);
739 3 : CATCH_REQUIRE(duration_validator->name() == "duration");
740 :
741 3 : CATCH_REQUIRE_FALSE(duration_validator->validate(""));
742 3 : CATCH_REQUIRE_FALSE(duration_validator->validate("+"));
743 3 : CATCH_REQUIRE_FALSE(duration_validator->validate("-"));
744 3 : CATCH_REQUIRE_FALSE(duration_validator->validate("alpha"));
745 3 : CATCH_REQUIRE_FALSE(duration_validator->validate("3.5 beta"));
746 3 : CATCH_REQUIRE_FALSE(duration_validator->validate("7.5delta"));
747 3 : CATCH_REQUIRE_FALSE(duration_validator->validate("+8.1 gamma"));
748 3 : CATCH_REQUIRE_FALSE(duration_validator->validate("-2.3eta"));
749 :
750 3003 : for(int idx(0); idx < 1000; ++idx)
751 : {
752 : // use smaller values between 0 and 1
753 : // (the loop is to make sure we don't end up with "123e-10"
754 : // type of numbers... which do not work here)
755 : //
756 3000 : double value(0.0);
757 : #pragma GCC diagnostic push
758 : #pragma GCC diagnostic ignored "-Wfloat-equal"
759 0 : do
760 : {
761 3000 : value = static_cast<double>(rand()) / static_cast<double>(RAND_MAX);
762 : }
763 3000 : while(value < 0.0001 && value != 0.0);
764 : #pragma GCC diagnostic pop
765 3000 : if(rand() % 2 == 0)
766 : {
767 1509 : value *= -1.0;
768 : }
769 6000 : std::stringstream ss;
770 3000 : ss.precision(std::numeric_limits<double>::max_digits10);
771 3000 : ss << value;
772 6000 : std::string const v(ss.str());
773 :
774 66000 : for(std::size_t i(0); i < std::size(g_duration_suffixes); ++i)
775 : {
776 441000 : for(int j(0); j <= 5; ++j)
777 : {
778 756000 : std::string duration(v);
779 1323000 : for(int k(0); k < j; ++k)
780 : {
781 : // any number of spaces in between are allowed
782 : //
783 945000 : duration += ' ';
784 : }
785 378000 : duration += g_duration_suffixes[i].f_suffix;
786 :
787 378000 : CATCH_REQUIRE(duration_validator->validate(duration));
788 378000 : if(value >= 0)
789 : {
790 187866 : CATCH_REQUIRE(duration_validator->validate('+' + duration));
791 : }
792 :
793 378000 : double result(0.0);
794 378000 : CATCH_REQUIRE(advgetopt::validator_duration::convert_string(duration, flg, result));
795 378000 : if(g_duration_suffixes[i].f_factor < 0.0)
796 : {
797 : // the 'm' special case
798 : //
799 18000 : if(size == 2)
800 : {
801 : // 'large' -- 1 month
802 : //
803 6000 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(result, value * (86400.0 * 30.0)));
804 : }
805 : else
806 : {
807 : // 'small' -- 1 minute
808 : //
809 12000 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(result, value * 60.0));
810 : }
811 : }
812 : else
813 : {
814 360000 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(result, value * g_duration_suffixes[i].f_factor));
815 : }
816 : }
817 : }
818 : }
819 : }
820 : }
821 : CATCH_END_SECTION()
822 3 : }
823 :
824 :
825 :
826 :
827 3 : CATCH_TEST_CASE("size_validator", "[validator][valid][validation]")
828 : {
829 : #pragma GCC diagnostic push
830 : #pragma GCC diagnostic ignored "-Wpedantic"
831 2 : CATCH_START_SECTION("Verify the size validator")
832 : {
833 : // this test does not verify that double conversion works since we
834 : // have a separate test for that specific validator
835 : //
836 4 : for(int mode(0); mode < 3; ++mode)
837 : {
838 3 : advgetopt::validator_size::flag_t flg(advgetopt::validator_size::VALIDATOR_SIZE_DEFAULT_FLAGS);
839 6 : advgetopt::string_list_t flags;
840 3 : if(mode == 1)
841 : {
842 1 : flags.push_back("si");
843 : }
844 2 : else if(mode == 2)
845 : {
846 1 : flags.push_back("legacy");
847 1 : flg = advgetopt::validator_size::VALIDATOR_SIZE_POWER_OF_TWO;
848 : }
849 6 : advgetopt::validator::pointer_t size_validator(advgetopt::validator::create("size", flags));
850 :
851 3 : CATCH_REQUIRE(size_validator != nullptr);
852 3 : CATCH_REQUIRE(size_validator->name() == "size");
853 :
854 3 : CATCH_REQUIRE_FALSE(size_validator->validate(""));
855 3 : CATCH_REQUIRE_FALSE(size_validator->validate("+"));
856 3 : CATCH_REQUIRE_FALSE(size_validator->validate("-"));
857 3 : CATCH_REQUIRE_FALSE(size_validator->validate("alpha"));
858 3 : CATCH_REQUIRE_FALSE(size_validator->validate("3.5 beta"));
859 3 : CATCH_REQUIRE_FALSE(size_validator->validate("7.5delta"));
860 3 : CATCH_REQUIRE_FALSE(size_validator->validate("+8.1 gamma"));
861 3 : CATCH_REQUIRE_FALSE(size_validator->validate("-2.3eta"));
862 :
863 3003 : for(int idx(0); idx < 1000; ++idx)
864 : {
865 : // use smaller values between 0 and about 5
866 : //
867 3000 : double value(static_cast<double>(rand()) / static_cast<double>(RAND_MAX / 5));
868 3000 : if(rand() % 2 == 0)
869 : {
870 1509 : value *= -1.0;
871 : }
872 6000 : std::stringstream ss;
873 3000 : ss.precision(std::numeric_limits<double>::max_digits10);
874 3000 : ss << value;
875 6000 : std::string const v(ss.str());
876 :
877 57000 : for(std::size_t i(0); i < std::size(g_size_suffixes); ++i)
878 : {
879 378000 : for(int j(0); j <= 5; ++j)
880 : {
881 648000 : std::string size(v);
882 1134000 : for(int k(0); k < j; ++k)
883 : {
884 : // any number of spaces in between are allowed
885 : //
886 810000 : size += ' ';
887 : }
888 324000 : size += g_size_suffixes[i].f_suffix;
889 :
890 324000 : CATCH_REQUIRE(size_validator->validate(size));
891 324000 : if(value >= 0)
892 : {
893 161028 : CATCH_REQUIRE(size_validator->validate('+' + size));
894 : }
895 :
896 324000 : __int128 result(0.0);
897 324000 : CATCH_REQUIRE(advgetopt::validator_size::convert_string(size, flg, result));
898 :
899 324000 : long double const base(mode == 2 ? 1024.0L : g_size_suffixes[i].f_base);
900 324000 : long double expected(1);
901 1620000 : for(int p(0); p < g_size_suffixes[i].f_power; ++p)
902 : {
903 1296000 : expected *= base;
904 : }
905 324000 : __int128 int_expected(expected * static_cast<long double>(value));
906 :
907 : //std::cerr << "converted [" << size << "] to [" << result << "] wanted [" << int_expected << "]\n";
908 324000 : CATCH_REQUIRE(result == int_expected);
909 : }
910 : }
911 : }
912 : }
913 : }
914 : CATCH_END_SECTION()
915 : #pragma GCC diagnostic pop
916 1 : }
917 :
918 :
919 :
920 :
921 6 : CATCH_TEST_CASE("regex_validator", "[validator][valid][validation]")
922 : {
923 8 : CATCH_START_SECTION("Verify the regex validator")
924 2 : advgetopt::validator::pointer_t regex_validator(advgetopt::validator::create("regex", {".*@.*\\..*"}));
925 :
926 1 : CATCH_REQUIRE(regex_validator != nullptr);
927 1 : CATCH_REQUIRE(regex_validator->name() == "regex");
928 :
929 1 : CATCH_REQUIRE(regex_validator->validate("@m2osw."));
930 1 : CATCH_REQUIRE(regex_validator->validate("contact@m2osw.com"));
931 1 : CATCH_REQUIRE(regex_validator->validate("Contact@m2osw.com"));
932 1 : CATCH_REQUIRE(regex_validator->validate("Contact@M2OSW.com"));
933 :
934 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact@m2osw:com"));
935 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact!m2osw.com"));
936 : CATCH_END_SECTION()
937 :
938 8 : CATCH_START_SECTION("Verify the regex string (case sensitive)")
939 2 : advgetopt::validator::pointer_t regex_validator(advgetopt::validator::create("regex", {"/contact@.*\\..*/"}));
940 :
941 1 : CATCH_REQUIRE(regex_validator != nullptr);
942 1 : CATCH_REQUIRE(regex_validator->name() == "regex");
943 :
944 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("@m2osw."));
945 1 : CATCH_REQUIRE(regex_validator->validate("contact@m2osw.com"));
946 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("Contact@m2osw.com"));
947 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("Contact@M2OSW.com"));
948 :
949 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact@m2osw:com"));
950 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact!m2osw.com"));
951 : CATCH_END_SECTION()
952 :
953 8 : CATCH_START_SECTION("Verify the regex string (case insensitive)")
954 2 : advgetopt::validator::pointer_t regex_validator(advgetopt::validator::create("regex", {"/contact@.*\\..*/i"}));
955 :
956 1 : CATCH_REQUIRE(regex_validator != nullptr);
957 1 : CATCH_REQUIRE(regex_validator->name() == "regex");
958 :
959 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("@m2osw."));
960 1 : CATCH_REQUIRE(regex_validator->validate("contact@m2osw.com"));
961 1 : CATCH_REQUIRE(regex_validator->validate("Contact@m2osw.com"));
962 1 : CATCH_REQUIRE(regex_validator->validate("Contact@M2OSW.com"));
963 :
964 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact@m2osw:com"));
965 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact!m2osw.com"));
966 : CATCH_END_SECTION()
967 :
968 8 : CATCH_START_SECTION("Verify direct regex string (case insensitive)")
969 2 : advgetopt::validator::pointer_t regex_validator(advgetopt::validator::create("/contact@.*\\..*/i"));
970 :
971 1 : CATCH_REQUIRE(regex_validator != nullptr);
972 1 : CATCH_REQUIRE(regex_validator->name() == "regex");
973 :
974 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("@m2osw."));
975 1 : CATCH_REQUIRE(regex_validator->validate("contact@m2osw.com"));
976 1 : CATCH_REQUIRE(regex_validator->validate("Contact@m2osw.com"));
977 1 : CATCH_REQUIRE(regex_validator->validate("Contact@M2OSW.com"));
978 :
979 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact@m2osw:com"));
980 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact!m2osw.com"));
981 : CATCH_END_SECTION()
982 4 : }
983 :
984 :
985 :
986 :
987 :
988 :
989 :
990 :
991 8 : CATCH_TEST_CASE("invalid_validator", "[validator][invalid][validation]")
992 : {
993 12 : CATCH_START_SECTION("Register duplicated factories")
994 0 : class duplicate_integer
995 : : public advgetopt::validator
996 : {
997 : public:
998 0 : virtual std::string const name() const
999 : {
1000 0 : return "integer";
1001 : }
1002 :
1003 0 : virtual bool validate(std::string const & value) const override
1004 : {
1005 0 : return value == "123";
1006 : }
1007 : };
1008 3 : class duplicate_factory
1009 : : public advgetopt::validator_factory
1010 : {
1011 : public:
1012 2 : virtual std::string get_name() const
1013 : {
1014 2 : return "integer";
1015 : }
1016 :
1017 0 : virtual std::shared_ptr<advgetopt::validator> create(advgetopt::string_list_t const & data) const override
1018 : {
1019 0 : snapdev::NOT_USED(data); // ignore `data`
1020 0 : return std::make_shared<duplicate_integer>();
1021 : }
1022 : };
1023 2 : std::unique_ptr<advgetopt::validator_factory> factory(new duplicate_factory());
1024 1 : CATCH_REQUIRE_THROWS_MATCHES(
1025 : advgetopt::validator::register_validator(*factory.get())
1026 : , advgetopt::getopt_logic_error
1027 : , Catch::Matchers::ExceptionMessage(
1028 : "getopt_logic_error: you have two or more validator factories named \"integer\"."));
1029 : CATCH_END_SECTION()
1030 :
1031 :
1032 12 : CATCH_START_SECTION("Verify invalid ranges")
1033 1 : advgetopt::string_list_t range{
1034 : "abc",
1035 : "abc...6",
1036 : "3...def",
1037 2 : "10...1"};
1038 :
1039 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: abc is not a valid value for your ranges; it must only be digits, optionally preceeded by a sign (+ or -) and not overflow an int64_t value.");
1040 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: abc is not a valid value for your ranges; it must only be digits, optionally preceeded by a sign (+ or -) and not overflow an int64_t value.");
1041 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: def is not a valid value for your ranges; it must only be digits, optionally preceeded by a sign (+ or -) and not overflow an int64_t value.");
1042 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: 10 has to be smaller or equal to 1; you have an invalid range.");
1043 :
1044 2 : advgetopt::validator::pointer_t integer_validator(advgetopt::validator::create("integer", range));
1045 1 : SNAP_CATCH2_NAMESPACE::expected_logs_stack_is_empty();
1046 : CATCH_END_SECTION()
1047 :
1048 12 : CATCH_START_SECTION("Verify invalid regex flags")
1049 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag f in regular expression \"/contact@.*\\..*/f\".");
1050 :
1051 2 : advgetopt::validator::pointer_t regex_validator(advgetopt::validator::create("regex", {"/contact@.*\\..*/f"}));
1052 1 : SNAP_CATCH2_NAMESPACE::expected_logs_stack_is_empty();
1053 :
1054 1 : CATCH_REQUIRE(regex_validator != nullptr);
1055 1 : CATCH_REQUIRE(regex_validator->name() == "regex");
1056 :
1057 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("@m2osw."));
1058 1 : CATCH_REQUIRE(regex_validator->validate("contact@m2osw.com"));
1059 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("Contact@m2osw.com"));
1060 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("Contact@M2OSW.com"));
1061 :
1062 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact@m2osw:com"));
1063 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact!m2osw.com"));
1064 : CATCH_END_SECTION()
1065 :
1066 12 : CATCH_START_SECTION("Verify invalid regex flags")
1067 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag * in regular expression \"/contact@.*\\..*\".");
1068 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag . in regular expression \"/contact@.*\\..*\".");
1069 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag . in regular expression \"/contact@.*\\..*\".");
1070 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag \\ in regular expression \"/contact@.*\\..*\".");
1071 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag * in regular expression \"/contact@.*\\..*\".");
1072 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag . in regular expression \"/contact@.*\\..*\".");
1073 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag @ in regular expression \"/contact@.*\\..*\".");
1074 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag t in regular expression \"/contact@.*\\..*\".");
1075 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag c in regular expression \"/contact@.*\\..*\".");
1076 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag a in regular expression \"/contact@.*\\..*\".");
1077 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag t in regular expression \"/contact@.*\\..*\".");
1078 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag n in regular expression \"/contact@.*\\..*\".");
1079 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag o in regular expression \"/contact@.*\\..*\".");
1080 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: unsupported regex flag c in regular expression \"/contact@.*\\..*\".");
1081 1 : SNAP_CATCH2_NAMESPACE::push_expected_log("error: invalid regex definition, ending / is missing in \"/contact@.*\\..*\".");
1082 :
1083 2 : advgetopt::validator::pointer_t regex_validator(advgetopt::validator::create("regex", {"/contact@.*\\..*"}));
1084 1 : SNAP_CATCH2_NAMESPACE::expected_logs_stack_is_empty();
1085 :
1086 1 : CATCH_REQUIRE(regex_validator != nullptr);
1087 1 : CATCH_REQUIRE(regex_validator->name() == "regex");
1088 :
1089 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("@m2osw."));
1090 1 : CATCH_REQUIRE(regex_validator->validate("contact@m2osw.com"));
1091 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("Contact@m2osw.com"));
1092 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("Contact@M2OSW.com"));
1093 :
1094 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact@m2osw:com"));
1095 1 : CATCH_REQUIRE_FALSE(regex_validator->validate("contact!m2osw.com"));
1096 : CATCH_END_SECTION()
1097 :
1098 12 : CATCH_START_SECTION("Verify regex refuses more than one parameter")
1099 1 : SNAP_CATCH2_NAMESPACE::push_expected_log(
1100 : "error: validator_regex() only supports one parameter;"
1101 : " 2 were supplied;"
1102 : " single or double quotation may be required?");
1103 1 : advgetopt::validator::create("regex", {"[a-z]+", "[0-9]+"});
1104 1 : SNAP_CATCH2_NAMESPACE::expected_logs_stack_is_empty();
1105 :
1106 1 : SNAP_CATCH2_NAMESPACE::push_expected_log(
1107 : "error: validator_regex() only supports one parameter;"
1108 : " 2 were supplied;"
1109 : " single or double quotation may be required?");
1110 1 : advgetopt::validator::create("regex([a-z]+, [0-9]+)");
1111 1 : SNAP_CATCH2_NAMESPACE::expected_logs_stack_is_empty();
1112 :
1113 1 : SNAP_CATCH2_NAMESPACE::push_expected_log(
1114 : "error: validator_regex() only supports one parameter;"
1115 : " 3 were supplied;"
1116 : " single or double quotation may be required?");
1117 1 : advgetopt::validator::create("regex", {"[a-z]+", "[0-9]+", "[#!@]"});
1118 1 : SNAP_CATCH2_NAMESPACE::expected_logs_stack_is_empty();
1119 :
1120 1 : SNAP_CATCH2_NAMESPACE::push_expected_log(
1121 : "error: validator_regex() only supports one parameter;"
1122 : " 3 were supplied;"
1123 : " single or double quotation may be required?");
1124 1 : advgetopt::validator::create("regex(\"[a-z]+\", \"[0-9]+\", \"[#!@]\")");
1125 1 : SNAP_CATCH2_NAMESPACE::expected_logs_stack_is_empty();
1126 : CATCH_END_SECTION()
1127 :
1128 12 : CATCH_START_SECTION("Verify missing ')' in string based create")
1129 1 : CATCH_REQUIRE_THROWS_MATCHES(
1130 : advgetopt::validator::create("integer(1...7")
1131 : , advgetopt::getopt_logic_error
1132 : , Catch::Matchers::ExceptionMessage(
1133 : "getopt_logic_error: invalid validator parameter definition: \"integer(1...7\", the ')' is missing."));
1134 :
1135 1 : CATCH_REQUIRE_THROWS_MATCHES(
1136 : advgetopt::validator::create("regex([a-z]+")
1137 : , advgetopt::getopt_logic_error
1138 : , Catch::Matchers::ExceptionMessage(
1139 : "getopt_logic_error: invalid validator parameter definition: \"regex([a-z]+\", the ')' is missing."));
1140 : CATCH_END_SECTION()
1141 12 : }
1142 :
1143 :
1144 : // vim: ts=4 sw=4 et
|