Line data Source code
1 : // Copyright (c) 2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/versiontheca
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 3 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
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // tested file
20 : //
21 : #include "versiontheca/decimal.h"
22 :
23 :
24 : // self
25 : //
26 : #include "catch_main.h"
27 :
28 :
29 :
30 : // versiontheca
31 : //
32 : #include "versiontheca/exception.h"
33 : #include "versiontheca/versiontheca.h"
34 :
35 :
36 : // C++
37 : //
38 : #include <cstring>
39 : #include <iomanip>
40 : #include <stdexcept>
41 :
42 :
43 :
44 :
45 : namespace
46 : {
47 :
48 :
49 :
50 10017 : versiontheca::versiontheca::pointer_t create(char const * version, char const * verify = nullptr)
51 : {
52 20034 : versiontheca::decimal::pointer_t t(std::make_shared<versiontheca::decimal>());
53 10017 : versiontheca::versiontheca::pointer_t v(std::make_shared<versiontheca::versiontheca>(t, version));
54 10017 : if(verify == nullptr)
55 : {
56 : // if not specified, same as version
57 : //
58 5012 : verify = version;
59 : }
60 10017 : CATCH_REQUIRE(v->get_version() == verify);
61 10017 : double expected(strtod(verify, nullptr));
62 10017 : CATCH_REQUIRE_FLOATING_POINT(t->get_decimal_version(), expected);
63 20034 : return v;
64 : }
65 :
66 :
67 22 : void invalid_version(char const * version, char const * errmsg)
68 : {
69 44 : versiontheca::decimal::pointer_t t(std::make_shared<versiontheca::decimal>());
70 44 : versiontheca::versiontheca::pointer_t v(std::make_shared<versiontheca::versiontheca>(t, version));
71 22 : CATCH_REQUIRE_FALSE(v->is_valid());
72 22 : if(v->get_last_error(false) != errmsg)
73 : {
74 0 : std::cerr << "--- verifying invalid version [" << version << "]\n";
75 : }
76 22 : CATCH_REQUIRE(v->get_last_error(false) == errmsg);
77 22 : CATCH_REQUIRE(v->get_last_error() == errmsg);
78 22 : CATCH_REQUIRE(v->get_last_error().empty());
79 22 : }
80 :
81 :
82 15000 : std::string generate_number()
83 : {
84 15000 : versiontheca::part_integer_t value;
85 15000 : SNAP_CATCH2_NAMESPACE::random(value);
86 15000 : return std::to_string(value);
87 : }
88 :
89 :
90 10000 : std::string generate_version(std::size_t max)
91 : {
92 10000 : std::string v;
93 25000 : for(std::size_t i(0); i < max; ++i)
94 : {
95 15000 : if(!v.empty())
96 : {
97 5000 : v += '.';
98 : }
99 15000 : v += generate_number();
100 : }
101 10000 : return v;
102 : }
103 :
104 :
105 :
106 : }
107 : // no name namespace
108 :
109 :
110 5 : CATCH_TEST_CASE("decimal_versions", "[valid]")
111 : {
112 6 : CATCH_START_SECTION("decimal_versions: verify test checker for version 1.0")
113 : {
114 1 : create("1.0", "1.0");
115 : }
116 : CATCH_END_SECTION()
117 :
118 6 : CATCH_START_SECTION("decimal_versions: verify that decimal canonicalization happens")
119 : {
120 : {
121 2 : versiontheca::versiontheca::pointer_t v(create("3", "3.0"));
122 1 : CATCH_REQUIRE(v->get_major() == 3);
123 1 : CATCH_REQUIRE(v->get_minor() == 0);
124 1 : CATCH_REQUIRE(v->get_patch() == 0);
125 1 : CATCH_REQUIRE(v->get_build() == 0);
126 : }
127 : {
128 2 : versiontheca::versiontheca::pointer_t v(create("3.000", "3.000"));
129 1 : CATCH_REQUIRE(v->get_major() == 3);
130 1 : CATCH_REQUIRE(v->get_minor() == 0);
131 1 : CATCH_REQUIRE(v->get_patch() == 0);
132 1 : CATCH_REQUIRE(v->get_build() == 0);
133 : }
134 : {
135 2 : versiontheca::versiontheca::pointer_t v(create("3.001"));
136 1 : CATCH_REQUIRE(v->get_major() == 3);
137 1 : CATCH_REQUIRE(v->get_minor() == 1);
138 1 : CATCH_REQUIRE(v->get_patch() == 0);
139 1 : CATCH_REQUIRE(v->get_build() == 0);
140 : }
141 : }
142 : CATCH_END_SECTION()
143 :
144 6 : CATCH_START_SECTION("decimal_versions: many valid versions")
145 : {
146 : // many valid versions generated randomly to increase the likelyhood
147 : // of things I would otherwise not think of
148 : //
149 10001 : for(int i(0); i < 10'000; ++i)
150 : {
151 10000 : int const parts(i % 2 + 1);
152 10000 : if(parts == 1)
153 : {
154 10000 : std::string v(generate_version(parts));
155 5000 : create(v.c_str(), (v + ".0").c_str());
156 : }
157 : else
158 : {
159 10000 : std::string v(generate_version(parts));
160 5000 : create(v.c_str());
161 : }
162 : }
163 : }
164 : CATCH_END_SECTION()
165 3 : }
166 :
167 :
168 3 : CATCH_TEST_CASE("next_previous_decimal_versions", "[valid][next][previous]")
169 : {
170 2 : CATCH_START_SECTION("next_previous_decimal_versions: next/previous at level 1, 0")
171 : {
172 : {
173 2 : versiontheca::versiontheca::pointer_t a(create("1.3"));
174 1 : CATCH_REQUIRE(a->next(1));
175 1 : CATCH_REQUIRE(a->get_version() == "1.4");
176 1 : CATCH_REQUIRE(a->previous(1));
177 1 : CATCH_REQUIRE(a->get_version() == "1.3");
178 1 : CATCH_REQUIRE(a->previous(1));
179 1 : CATCH_REQUIRE(a->get_version() == "1.2");
180 1 : CATCH_REQUIRE(a->next(1));
181 1 : CATCH_REQUIRE(a->get_version() == "1.3");
182 : }
183 :
184 : {
185 2 : versiontheca::versiontheca::pointer_t a(create("1.3"));
186 1 : CATCH_REQUIRE(a->next(0));
187 1 : CATCH_REQUIRE(a->get_version() == "2.0");
188 1 : CATCH_REQUIRE(a->previous(0));
189 1 : CATCH_REQUIRE(a->get_version() == "1.0");
190 1 : CATCH_REQUIRE(a->previous(0));
191 1 : CATCH_REQUIRE(a->get_version() == "0.0");
192 1 : CATCH_REQUIRE(a->next(0));
193 1 : CATCH_REQUIRE(a->get_version() == "1.0");
194 : }
195 : }
196 : CATCH_END_SECTION()
197 1 : }
198 :
199 :
200 3 : CATCH_TEST_CASE("compare_decimal_versions", "[valid][compare]")
201 : {
202 2 : CATCH_START_SECTION("compare_decimal_versions: compare many versions")
203 : {
204 2 : versiontheca::versiontheca::pointer_t a(create("1.2"));
205 2 : versiontheca::versiontheca::pointer_t b(create("1.1"));
206 2 : versiontheca::versiontheca::pointer_t c(create("1.2"));
207 :
208 1 : CATCH_REQUIRE(a->is_valid());
209 1 : CATCH_REQUIRE(b->is_valid());
210 1 : CATCH_REQUIRE(c->is_valid());
211 :
212 1 : CATCH_REQUIRE(*a == *a);
213 1 : CATCH_REQUIRE_FALSE(*a != *a);
214 1 : CATCH_REQUIRE_FALSE(*a > *a);
215 1 : CATCH_REQUIRE(*a >= *a);
216 1 : CATCH_REQUIRE_FALSE(*a < *a);
217 1 : CATCH_REQUIRE(*a <= *a);
218 :
219 1 : CATCH_REQUIRE_FALSE(*a == *b);
220 1 : CATCH_REQUIRE(*a != *b);
221 1 : CATCH_REQUIRE(*a > *b);
222 1 : CATCH_REQUIRE(*a >= *b);
223 1 : CATCH_REQUIRE_FALSE(*a < *b);
224 1 : CATCH_REQUIRE_FALSE(*a <= *b);
225 :
226 1 : CATCH_REQUIRE_FALSE(*b == *a);
227 1 : CATCH_REQUIRE(*b != *a);
228 1 : CATCH_REQUIRE_FALSE(*b > *a);
229 1 : CATCH_REQUIRE_FALSE(*b >= *a);
230 1 : CATCH_REQUIRE(*b < *a);
231 1 : CATCH_REQUIRE(*b <= *a);
232 :
233 1 : CATCH_REQUIRE(*a == *c);
234 1 : CATCH_REQUIRE_FALSE(*a != *c);
235 1 : CATCH_REQUIRE_FALSE(*a > *c);
236 1 : CATCH_REQUIRE(*a >= *c);
237 1 : CATCH_REQUIRE_FALSE(*a < *c);
238 1 : CATCH_REQUIRE(*a <= *c);
239 :
240 1 : CATCH_REQUIRE(*c == *a);
241 1 : CATCH_REQUIRE_FALSE(*c != *a);
242 1 : CATCH_REQUIRE_FALSE(*c > *a);
243 1 : CATCH_REQUIRE(*c >= *a);
244 1 : CATCH_REQUIRE_FALSE(*c < *a);
245 1 : CATCH_REQUIRE(*c <= *a);
246 :
247 : {
248 2 : std::stringstream ss;
249 1 : ss << *a;
250 1 : CATCH_REQUIRE(ss.str() == "1.2");
251 : }
252 : {
253 2 : std::stringstream ss;
254 1 : ss << *b;
255 1 : CATCH_REQUIRE(ss.str() == "1.1");
256 : }
257 : {
258 2 : std::stringstream ss;
259 1 : ss << *c;
260 1 : CATCH_REQUIRE(ss.str() == "1.2");
261 : }
262 : }
263 : CATCH_END_SECTION()
264 1 : }
265 :
266 :
267 7 : CATCH_TEST_CASE("invalid_decimal_versions", "[invalid]")
268 : {
269 10 : CATCH_START_SECTION("invalid_debian_versions: empty")
270 : {
271 : // empty
272 : //
273 : // note: the empty version is "invalid" as far as versions go,
274 : // but it does not generetate an error message
275 : //
276 2 : versiontheca::decimal::pointer_t t(std::make_shared<versiontheca::decimal>());
277 2 : versiontheca::versiontheca v(t, "");
278 1 : CATCH_REQUIRE_FALSE(v.is_valid());
279 1 : CATCH_REQUIRE(v.get_last_error().empty());
280 :
281 1 : CATCH_REQUIRE(v.get_version().empty());
282 1 : CATCH_REQUIRE(v.get_last_error() == "no parts to output.");
283 : }
284 : CATCH_END_SECTION()
285 :
286 10 : CATCH_START_SECTION("invalid_decimal_versions: too many periods")
287 : {
288 : {
289 2 : versiontheca::versiontheca::pointer_t v(create("1.0.0", ""));
290 1 : CATCH_REQUIRE_FALSE(v->is_valid());
291 1 : CATCH_REQUIRE(v->get_major() == 0);
292 1 : CATCH_REQUIRE(v->get_minor() == 0);
293 1 : CATCH_REQUIRE(v->get_patch() == 0);
294 1 : CATCH_REQUIRE(v->get_build() == 0);
295 : }
296 : {
297 2 : versiontheca::versiontheca::pointer_t v(create("11.0.0.0", ""));
298 1 : CATCH_REQUIRE_FALSE(v->is_valid());
299 1 : CATCH_REQUIRE(v->get_major() == 0);
300 1 : CATCH_REQUIRE(v->get_minor() == 0);
301 1 : CATCH_REQUIRE(v->get_patch() == 0);
302 1 : CATCH_REQUIRE(v->get_build() == 0);
303 : }
304 : }
305 : CATCH_END_SECTION()
306 :
307 10 : CATCH_START_SECTION("invalid_decimal_versions: no support for ':' or '-' or '#' or '$'...")
308 : {
309 1 : invalid_version("3A3:1.2.3-pre55", "found unexpected character: \\U000041 in input.");
310 1 : invalid_version("33:-55", "found unexpected character: \\U00003A in input.");
311 1 : invalid_version(":", "found unexpected character: \\U00003A in input.");
312 1 : invalid_version("a:", "found unexpected character: \\U000061 in input.");
313 1 : invalid_version("-10:", "found unexpected character: \\U00002D in input.");
314 1 : invalid_version("99999999999999999:", "integer too large for a valid version.");
315 1 : invalid_version("3:", "found unexpected character: \\U00003A in input.");
316 1 : invalid_version("-751", "found unexpected character: \\U00002D in input.");
317 1 : invalid_version("-", "found unexpected character: \\U00002D in input.");
318 1 : invalid_version("--", "found unexpected character: \\U00002D in input.");
319 1 : invalid_version("+-", "found unexpected character: \\U00002B in input.");
320 1 : invalid_version("#-", "found unexpected character: \\U000023 in input.");
321 1 : invalid_version("55:435123-", "found unexpected character: \\U00003A in input.");
322 1 : invalid_version("-a", "found unexpected character: \\U00002D in input.");
323 1 : invalid_version("-0", "found unexpected character: \\U00002D in input.");
324 1 : invalid_version("-+", "found unexpected character: \\U00002D in input.");
325 1 : invalid_version("-3$7", "found unexpected character: \\U00002D in input.");
326 1 : invalid_version("32:1.2.55-3:7", "found unexpected character: \\U00003A in input.");
327 1 : invalid_version("-3.7", "found unexpected character: \\U00002D in input.");
328 1 : invalid_version("3.7#", "found unexpected character: \\U000023 in input.");
329 1 : invalid_version("3$7", "found unexpected character: \\U000024 in input.");
330 1 : invalid_version("3;7", "found unexpected character: \\U00003B in input.");
331 : }
332 : CATCH_END_SECTION()
333 :
334 10 : CATCH_START_SECTION("invalid_decimal_versions: max + 1 fails")
335 : {
336 2 : versiontheca::versiontheca::pointer_t a(create("4294967295.4294967295"));
337 1 : CATCH_REQUIRE(a->is_valid());
338 1 : CATCH_REQUIRE_FALSE(a->next(1));
339 1 : CATCH_REQUIRE_FALSE(a->is_valid());
340 1 : CATCH_REQUIRE(a->get_last_error() == "maximum limit reached; cannot increment version any further.");
341 : }
342 : CATCH_END_SECTION()
343 :
344 10 : CATCH_START_SECTION("invalid_decimal_versions: min - 1 fails")
345 : {
346 2 : versiontheca::versiontheca::pointer_t a(create("0.0"));
347 1 : CATCH_REQUIRE(a->is_valid());
348 1 : CATCH_REQUIRE_FALSE(a->previous(1));
349 1 : CATCH_REQUIRE_FALSE(a->is_valid());
350 1 : CATCH_REQUIRE(a->get_last_error() == "minimum limit reached; cannot decrement version any further.");
351 : }
352 : CATCH_END_SECTION()
353 5 : }
354 :
355 :
356 8 : CATCH_TEST_CASE("bad_decimal_calls", "[invalid]")
357 : {
358 12 : CATCH_START_SECTION("bad_decimal_calls: next without a version")
359 : {
360 2 : versiontheca::decimal::pointer_t t(std::make_shared<versiontheca::decimal>());
361 2 : versiontheca::versiontheca v(t);
362 1 : CATCH_REQUIRE(v.next(0));
363 1 : CATCH_REQUIRE(v.get_last_error() == "");
364 1 : CATCH_REQUIRE(v.get_version() == "1.0");
365 : }
366 : CATCH_END_SECTION()
367 :
368 12 : CATCH_START_SECTION("bad_decimal_calls: previous without a version")
369 : {
370 2 : versiontheca::decimal::pointer_t t(std::make_shared<versiontheca::decimal>());
371 2 : versiontheca::versiontheca v(t);
372 1 : CATCH_REQUIRE_FALSE(v.previous(0));
373 1 : CATCH_REQUIRE(v.get_last_error() == "minimum limit reached; cannot decrement version any further.");
374 : }
375 : CATCH_END_SECTION()
376 :
377 12 : CATCH_START_SECTION("bad_decimal_calls: next out of bounds")
378 : {
379 2 : versiontheca::versiontheca::pointer_t a(create("1.5"));
380 101 : for(int p(-100); p < 0; ++p)
381 : {
382 100 : CATCH_REQUIRE_THROWS_MATCHES(
383 : a->next(p)
384 : , versiontheca::invalid_parameter
385 : , Catch::Matchers::ExceptionMessage(
386 : "versiontheca_exception: position calling next() cannot be a negative number."));
387 : }
388 101 : for(int p(versiontheca::MAX_PARTS); p < static_cast<int>(versiontheca::MAX_PARTS + 100); ++p)
389 : {
390 100 : CATCH_REQUIRE_THROWS_MATCHES(
391 : a->next(p)
392 : , versiontheca::invalid_parameter
393 : , Catch::Matchers::ExceptionMessage(
394 : "versiontheca_exception: position calling next() cannot be more than "
395 : + std::to_string(versiontheca::MAX_PARTS)
396 : + "."));
397 : }
398 : }
399 : CATCH_END_SECTION()
400 :
401 12 : CATCH_START_SECTION("bad_decimal_calls: previous out of bounds")
402 : {
403 2 : versiontheca::versiontheca::pointer_t a(create("1.5"));
404 101 : for(int p(-100); p < 0; ++p)
405 : {
406 100 : CATCH_REQUIRE_THROWS_MATCHES(
407 : a->previous(p)
408 : , versiontheca::invalid_parameter
409 : , Catch::Matchers::ExceptionMessage(
410 : "versiontheca_exception: position calling previous() cannot be a negative number."));
411 : }
412 101 : for(int p(versiontheca::MAX_PARTS); p < static_cast<int>(versiontheca::MAX_PARTS + 100); ++p)
413 : {
414 100 : CATCH_REQUIRE_THROWS_MATCHES(
415 : a->previous(p)
416 : , versiontheca::invalid_parameter
417 : , Catch::Matchers::ExceptionMessage(
418 : "versiontheca_exception: position calling previous() cannot be more than "
419 : + std::to_string(versiontheca::MAX_PARTS)
420 : + "."));
421 : }
422 : }
423 : CATCH_END_SECTION()
424 :
425 12 : CATCH_START_SECTION("bad_decimal_calls: compare against an empty (invalid) version")
426 : {
427 2 : versiontheca::versiontheca::pointer_t a(create("1.2"));
428 2 : versiontheca::decimal::pointer_t t(std::make_shared<versiontheca::decimal>());
429 2 : versiontheca::versiontheca empty(t, "");
430 :
431 1 : CATCH_REQUIRE(a->is_valid());
432 1 : CATCH_REQUIRE_FALSE(empty.is_valid());
433 :
434 1 : CATCH_REQUIRE_THROWS_MATCHES(
435 : a->compare(empty)
436 : , versiontheca::invalid_version
437 : , Catch::Matchers::ExceptionMessage(
438 : "versiontheca_exception: one or both of the input versions are not valid."));
439 :
440 1 : CATCH_REQUIRE_THROWS_MATCHES(
441 : a->get_trait()->compare(t)
442 : , versiontheca::empty_version
443 : , Catch::Matchers::ExceptionMessage(
444 : "versiontheca_exception: one or both of the input versions are empty."));
445 : }
446 : CATCH_END_SECTION()
447 :
448 12 : CATCH_START_SECTION("bad_decimal_calls: compare using an empty (invalid) version")
449 : {
450 2 : versiontheca::decimal::pointer_t t(std::make_shared<versiontheca::decimal>());
451 2 : versiontheca::versiontheca empty(t, "");
452 2 : versiontheca::versiontheca::pointer_t b(create("5.3"));
453 :
454 1 : CATCH_REQUIRE_FALSE(empty.is_valid());
455 1 : CATCH_REQUIRE(b->is_valid());
456 :
457 1 : CATCH_REQUIRE(empty.get_major() == 0);
458 1 : CATCH_REQUIRE(empty.get_minor() == 0);
459 1 : CATCH_REQUIRE(empty.get_patch() == 0);
460 1 : CATCH_REQUIRE(empty.get_build() == 0);
461 :
462 1 : CATCH_REQUIRE_THROWS_MATCHES(
463 : empty.compare(*b)
464 : , versiontheca::invalid_version
465 : , Catch::Matchers::ExceptionMessage(
466 : "versiontheca_exception: one or both of the input versions are not valid."));
467 :
468 1 : CATCH_REQUIRE_THROWS_MATCHES(
469 : t->compare(b->get_trait())
470 : , versiontheca::empty_version
471 : , Catch::Matchers::ExceptionMessage(
472 : "versiontheca_exception: one or both of the input versions are empty."));
473 : }
474 : CATCH_END_SECTION()
475 12 : }
476 :
477 :
478 :
479 : // vim: ts=4 sw=4 et
|