Line data Source code
1 : // Copyright (c) 2021-2026 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
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 : /** \file
20 : * \brief Verify that the path functions work.
21 : *
22 : * This file implements tests for the pathinfo function.
23 : */
24 :
25 : // self
26 : //
27 : #include <snapdev/pathinfo.h>
28 :
29 : #include "catch_main.h"
30 :
31 :
32 : // C++
33 : //
34 : #include <set>
35 :
36 :
37 : // last include
38 : //
39 : #include <snapdev/poison.h>
40 :
41 :
42 :
43 :
44 5 : CATCH_TEST_CASE("pathinfo_replace_suffix", "[filename][pathinfo]")
45 : {
46 5 : CATCH_START_SECTION("pathinfo_replace_suffix: replace existing suffix")
47 : {
48 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
49 : "/full/path/example/pathinfo.cpp"
50 : , ".cpp"
51 : , ".h") == "/full/path/example/pathinfo.h");
52 :
53 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
54 : "/full/path/example/pathinfo.h"
55 : , ".h"
56 : , ".cpp") == "/full/path/example/pathinfo.cpp");
57 :
58 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
59 : "relative/path/pathinfo.cpp"
60 : , ".cpp"
61 : , ".h") == "relative/path/pathinfo.h");
62 :
63 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
64 : "relative/path/pathinfo.h"
65 : , ".h"
66 : , ".cpp") == "relative/path/pathinfo.cpp");
67 :
68 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
69 : "no-path.cpp"
70 : , ".cpp"
71 : , ".h") == "no-path.h");
72 :
73 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
74 : "no-path.h"
75 : , ".h"
76 : , ".cpp") == "no-path.cpp");
77 : }
78 5 : CATCH_END_SECTION()
79 :
80 5 : CATCH_START_SECTION("pathinfo_replace_suffix: replace non-existant suffix")
81 : {
82 3 : std::string full_path_c("/full/path/example/pathinfo.c");
83 3 : std::string full_path_hpp("/full/path/example/pathinfo.hpp");
84 :
85 5 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix(
86 : full_path_c
87 : , ".cpp"
88 : , ".h") == "/full/path/example/pathinfo.c.h");
89 :
90 5 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix(
91 : full_path_hpp
92 : , ".h"
93 : , ".cpp") == "/full/path/example/pathinfo.hpp.cpp");
94 :
95 3 : std::string relative_path_c("relative/path/pathinfo.c");
96 3 : std::string relative_path_hpp("relative/path/pathinfo.hpp");
97 :
98 5 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix(
99 : relative_path_c
100 : , ".cpp"
101 : , ".h") == "relative/path/pathinfo.c.h");
102 :
103 5 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix(
104 : relative_path_hpp
105 : , ".h"
106 : , ".cpp") == "relative/path/pathinfo.hpp.cpp");
107 :
108 3 : std::string no_path_c("no-path.c");
109 3 : std::string no_path_hpp("no-path.hpp");
110 :
111 5 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix(
112 : no_path_c
113 : , ".cpp"
114 : , ".h") == "no-path.c.h");
115 :
116 5 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix(
117 : no_path_hpp
118 : , ".h"
119 : , ".cpp") == "no-path.hpp.cpp");
120 1 : }
121 5 : CATCH_END_SECTION()
122 :
123 5 : CATCH_START_SECTION("pathinfo_replace_suffix: remove suffix when present")
124 : {
125 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
126 : "/full/path/example/pathinfo.cpp"
127 : , ".cpp") == "/full/path/example/pathinfo");
128 :
129 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
130 : "/full/path/example/pathinfo.h"
131 : , ".h") == "/full/path/example/pathinfo");
132 :
133 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
134 : "relative/path/pathinfo.cpp"
135 : , ".cpp") == "relative/path/pathinfo");
136 :
137 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
138 : "relative/path/pathinfo.h"
139 : , ".h") == "relative/path/pathinfo");
140 :
141 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
142 : "no-path.cpp"
143 : , ".cpp") == "no-path");
144 :
145 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
146 : "no-path.h"
147 : , ".h") == "no-path");
148 : }
149 5 : CATCH_END_SECTION()
150 :
151 5 : CATCH_START_SECTION("pathinfo_replace_suffix: remove suffix when absent")
152 : {
153 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
154 : "/full/path/example/pathinfo.c"
155 : , ".cpp") == "/full/path/example/pathinfo.c");
156 :
157 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
158 : "/full/path/example/pathinfo.hpp"
159 : , ".h") == "/full/path/example/pathinfo.hpp");
160 :
161 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
162 : "relative/path/pathinfo.c"
163 : , ".cpp") == "relative/path/pathinfo.c");
164 :
165 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
166 : "relative/path/pathinfo.hpp"
167 : , ".h") == "relative/path/pathinfo.hpp");
168 :
169 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
170 : "no-path.c"
171 : , ".cpp") == "no-path.c");
172 :
173 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
174 : "no-path.hpp"
175 : , ".h") == "no-path.hpp");
176 : }
177 5 : CATCH_END_SECTION()
178 :
179 5 : CATCH_START_SECTION("pathinfo_replace_suffix: do nothing if non-existant suffix")
180 : {
181 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
182 : "/full/path/example/pathinfo.c"
183 : , ".cpp"
184 : , ".h"
185 : , true) == "/full/path/example/pathinfo.c");
186 :
187 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
188 : "/full/path/example/pathinfo.c"
189 : , ".cpp"
190 : , ""
191 : , true) == "/full/path/example/pathinfo.c");
192 :
193 7 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
194 : "/full/path/example/pathinfo.hpp"
195 : , ".h"
196 : , ".cpp"
197 : , true) == "/full/path/example/pathinfo.hpp");
198 :
199 5 : CATCH_REQUIRE(snapdev::pathinfo::replace_suffix<std::string>(
200 : "/full/path/example/pathinfo.hpp"
201 : , ".h"
202 : , std::string()
203 : , true) == "/full/path/example/pathinfo.hpp");
204 : }
205 5 : CATCH_END_SECTION()
206 5 : }
207 :
208 :
209 2 : CATCH_TEST_CASE("pathinfo_canonicalize", "[filename][pathinfo]")
210 : {
211 2 : CATCH_START_SECTION("pathinfo_canonicalize: canonicalize 2-segment paths")
212 : {
213 1 : char const * to_canonicalize[] =
214 : {
215 : "", "", ".",
216 :
217 : "/", "/", "/",
218 :
219 : "/full/path", "", "/full/path",
220 : "///full//path/", "", "/full/path",
221 : "///full//path/", "and/more", "/full/path/and/more",
222 : "///full//path/", "/and/much/more/", "/full/path/and/much/more",
223 :
224 : "relative///path", "and.this", "relative/path/and.this",
225 : "relative///path/", "and.this", "relative/path/and.this",
226 : "relative///path", "/and.this", "relative/path/and.this",
227 :
228 : "relative///with///", "///more-path///", "relative/with/more-path",
229 : };
230 :
231 10 : for(std::size_t idx(0); idx < std::size(to_canonicalize) / 3; idx += 3)
232 : {
233 4 : CATCH_REQUIRE(snapdev::pathinfo::canonicalize(
234 : to_canonicalize[idx + 0]
235 : , to_canonicalize[idx + 1]) == to_canonicalize[idx + 2]);
236 : }
237 : }
238 2 : CATCH_END_SECTION()
239 :
240 2 : CATCH_START_SECTION("pathinfo_canonicalize: canonicalize 3-segment paths")
241 : {
242 1 : char const * to_canonicalize[] =
243 : {
244 : "", "", "", ".",
245 :
246 : "/", "/", "/", "/",
247 :
248 : "/full/path", "", "*.txt", "/full/path/*.txt",
249 : "///full//path/", "", "", "/full/path",
250 : "///full//path/", "and", "more", "/full/path/and/more",
251 : "///full//path/", "", "/and/much/more/", "/full/path/and/much/more",
252 :
253 : "relative///path", "and.this", "or.that", "relative/path/and.this/or.that",
254 : "relative///path/", "and.this", "///or.that", "relative/path/and.this/or.that",
255 : "relative///path", "/and.this", "or.that///", "relative/path/and.this/or.that",
256 :
257 : "relative///with///", "///more-path///", "and/a/pattern/*.mp3", "relative/with/more-path/and/a/pattern/*.mp3",
258 : };
259 :
260 8 : for(std::size_t idx(0); idx < std::size(to_canonicalize) / 4; idx += 4)
261 : {
262 3 : CATCH_REQUIRE(snapdev::pathinfo::canonicalize(
263 : to_canonicalize[idx + 0]
264 : , to_canonicalize[idx + 1]
265 : , to_canonicalize[idx + 2]) == to_canonicalize[idx + 3]);
266 : }
267 : }
268 2 : CATCH_END_SECTION()
269 2 : }
270 :
271 :
272 1 : CATCH_TEST_CASE("pathinfo_has_pattern", "[filename][pathinfo]")
273 : {
274 1 : CATCH_START_SECTION("pathinfo_has_pattern: has pattern function")
275 : {
276 : struct test_data
277 : {
278 : char const * f_path = nullptr;
279 : int f_true = 0;
280 : };
281 1 : constexpr test_data const patterns[] =
282 : {
283 : // no pattern
284 : { ".", 0x00 },
285 : { "without-pattern", 0x00 },
286 : { "regular/path/without/pattern", 0x00 },
287 : { "path/../without/../pattern", 0x00 },
288 :
289 : // *
290 : { "path/*/with/pattern", 0xFF },
291 : { "path/\\*/with/pattern", 0x55 },
292 :
293 : // ?
294 : { "path/?/with/pattern", 0xFF },
295 : { "path/\\?/with/pattern", 0x55 },
296 :
297 : // [...]
298 : { "path/[a-z]/with/pattern", 0xFF },
299 : { "path/[a-\\]/with/pattern", 0xFF },
300 : { "path/\\[a-z]/with/pattern", 0x55 },
301 : { "path/\\[a-z*]/with/pattern", 0xFF },
302 : { "path/\\[a-z?]/with/pattern", 0xFF },
303 :
304 : // {...}
305 : { "path/{abc,def}/with/pattern", 0xCC },
306 : { "path/\\{abc,dev}/with/pattern", 0x44 },
307 : { "path/\\{abc,*dev}/with/pattern", 0xFF },
308 : { "path/\\{abc?,dev}/with/pattern", 0xFF },
309 :
310 : // [*?+@!](...)
311 : { "path/*(abc,def)/with/pattern", 0xFF },
312 : { "path/?(abc,dev)/with/pattern", 0xFF },
313 : { "path/+(abc,dev)/with/pattern", 0xF0 },
314 : { "path/@(abc,dev)/with/pattern", 0xF0 },
315 : { "path/!(abc,dev)/with/pattern", 0xF0 },
316 : { "path/+(a*bc,dev)/with/pattern", 0xFF },
317 : { "path/@(ab*c,dev)/with/pattern", 0xFF },
318 : { "path/!(abc*,dev)/with/pattern", 0xFF },
319 : { "path/+(a?bc,dev)/with/pattern", 0xFF },
320 : { "path/@(ab?c,dev)/with/pattern", 0xFF },
321 : { "path/!(abc?,dev)/with/pattern", 0xFF },
322 : { "path/\\*(abc,def)/with/pattern", 0x55 },
323 : { "path/\\?(abc,dev)/with/pattern", 0x55 },
324 : { "path/\\+(abc,dev)/with/pattern", 0x50 },
325 : { "path/\\@(abc,dev)/with/pattern", 0x50 },
326 : { "path/\\!(abc,dev)/with/pattern", 0x50 },
327 : { "path/*\\(abc,def)/with/pattern", 0xFF },
328 : { "path/?\\(abc,dev)/with/pattern", 0xFF },
329 : { "path/+\\(abc,dev)/with/pattern", 0x00 },
330 : { "path/@\\(abc,dev)/with/pattern", 0x00 },
331 : { "path/!\\(abc,dev)/with/pattern", 0x00 },
332 : };
333 :
334 78 : for(std::size_t idx(0); idx < std::size(patterns); ++idx)
335 : {
336 342 : for(int mask(0); mask < 8; ++mask)
337 : {
338 304 : bool const result((patterns[idx].f_true & (1 << mask)) != 0);
339 : //std::cerr << "--- idx: " << idx << " path: \"" << patterns[idx].f_path << "\" mask: " << mask << " result: " << std::boolalpha << result << "\n";
340 912 : CATCH_REQUIRE(snapdev::pathinfo::has_pattern(
341 : patterns[idx].f_path
342 : , (mask & 1) != 0
343 : , (mask & 2) != 0
344 : , (mask & 4) != 0) == result);
345 : }
346 : }
347 : }
348 1 : CATCH_END_SECTION()
349 1 : }
350 :
351 :
352 1 : CATCH_TEST_CASE("pathinfo_is_child_path", "[filename][pathinfo]")
353 : {
354 1 : CATCH_START_SECTION("pathinfo_is_child_path: is child path function")
355 : {
356 : struct test_data
357 : {
358 : char const * f_parent = nullptr;
359 : char const * f_child = nullptr;
360 : bool f_equal = true;
361 : bool f_result = false;
362 : };
363 1 : constexpr test_data const parent_child[] =
364 : {
365 : // not a child / mismatch
366 : {
367 : ".",
368 : "..",
369 : true,
370 : false,
371 : },
372 : {
373 : ".",
374 : "..",
375 : false,
376 : false,
377 : },
378 : {
379 : "/var",
380 : "/usr/share/snapdev",
381 : true,
382 : false,
383 : },
384 : {
385 : "/var",
386 : "/usr/share/snapdev",
387 : false,
388 : false,
389 : },
390 : {
391 : "/var",
392 : "Desktop",
393 : true,
394 : false,
395 : },
396 : {
397 : "/var",
398 : "Desktop",
399 : false,
400 : false,
401 : },
402 : {
403 : "",
404 : "/usr/share/snapdev",
405 : true,
406 : false,
407 : },
408 : {
409 : "/usr/share",
410 : "",
411 : true,
412 : false,
413 : },
414 :
415 : // child, not equal
416 : {
417 : "/var",
418 : "/var/lib/snapdev",
419 : true,
420 : true,
421 : },
422 : {
423 : "/var",
424 : "/var//lib/snapdev",
425 : true,
426 : true,
427 : },
428 : {
429 : "//var",
430 : "/var/lib/snapdev/",
431 : true,
432 : true,
433 : },
434 : {
435 : "//var//",
436 : "/var/lib//snapdev/",
437 : true,
438 : true,
439 : },
440 : {
441 : "/var",
442 : "/var/lib/snapdev",
443 : false,
444 : true,
445 : },
446 : {
447 : "/var",
448 : "/var//lib/snapdev",
449 : false,
450 : true,
451 : },
452 : {
453 : "//var",
454 : "/var/lib/snapdev/",
455 : false,
456 : true,
457 : },
458 : {
459 : "//var//",
460 : "/var/lib//snapdev/",
461 : false,
462 : true,
463 : },
464 :
465 : // child, equal
466 : {
467 : "/var",
468 : "/var",
469 : true,
470 : true,
471 : },
472 : {
473 : "/var",
474 : "/var",
475 : false,
476 : false,
477 : },
478 : {
479 : "/var/",
480 : "/var",
481 : true,
482 : true,
483 : },
484 : {
485 : "/var/",
486 : "/var",
487 : false,
488 : false,
489 : },
490 : {
491 : "/var",
492 : "/var/",
493 : true,
494 : true,
495 : },
496 : {
497 : "/var",
498 : "/var/",
499 : false,
500 : false,
501 : },
502 : {
503 : "",
504 : "",
505 : true,
506 : true,
507 : },
508 : {
509 : "",
510 : "",
511 : false,
512 : false,
513 : },
514 : };
515 :
516 50 : for(std::size_t idx(0); idx < std::size(parent_child); ++idx)
517 : {
518 120 : bool const result(snapdev::pathinfo::is_child_path(
519 24 : parent_child[idx].f_parent
520 24 : , parent_child[idx].f_child
521 24 : , parent_child[idx].f_equal
522 48 : ));
523 :
524 : //std::cout << "--- idx: " << idx
525 : // << " path: \"" << parent_child[idx].f_parent
526 : // << "\" child: " << parent_child[idx].f_child
527 : // << " equal: " << std::boolalpha << parent_child[idx].f_equal
528 : // << " expected result: " << std::boolalpha << parent_child[idx].f_result
529 : // << " result: " << std::boolalpha << result
530 : // << "\n";
531 24 : CATCH_REQUIRE(result == parent_child[idx].f_result);
532 : }
533 : }
534 1 : CATCH_END_SECTION()
535 1 : }
536 :
537 :
538 1 : CATCH_TEST_CASE("pathinfo_is_absolute", "[filename][pathinfo]")
539 : {
540 1 : CATCH_START_SECTION("pathinfo_is_absolute: check absolute and relative paths")
541 : {
542 : struct path_expected_t
543 : {
544 : char const * const f_path = nullptr;
545 : bool f_absolute = false;
546 : };
547 1 : path_expected_t paths[] = {
548 : { "/absolute", true },
549 : { "/the/length/has/no/effect", true },
550 : { "/", true },
551 : { "relative", false },
552 : { ".", false },
553 : { "", false },
554 : };
555 7 : for(auto const & p : paths)
556 : {
557 6 : CATCH_REQUIRE(snapdev::pathinfo::is_absolute(p.f_path) == p.f_absolute);
558 6 : CATCH_REQUIRE(snapdev::pathinfo::is_relative(p.f_path) == !p.f_absolute);
559 : }
560 : }
561 1 : CATCH_END_SECTION()
562 1 : }
563 :
564 :
565 1 : CATCH_TEST_CASE("pathinfo_relative", "[filename][pathinfo]")
566 : {
567 1 : CATCH_START_SECTION("pathinfo_relative: verify that path gets properly changed")
568 : {
569 : struct path_expected_t
570 : {
571 : char const * const f_base = nullptr;
572 : char const * const f_path = nullptr;
573 : char const * const f_result = nullptr;
574 : };
575 1 : path_expected_t paths[] = {
576 : {
577 : "/a/b/c/d/e",
578 : "/a/b/c/g/h/i",
579 : "../../g/h/i",
580 : },
581 : {
582 : "/a/b/c/d/e/f",
583 : "/a/b/c/g/h/i",
584 : "../../../g/h/i",
585 : },
586 : {
587 : "/a/b/c/d/e/f",
588 : "/x/y/z/g/h/i",
589 : "../../../../../../x/y/z/g/h/i",
590 : },
591 : {
592 : "a/b/c/d/e/f",
593 : "/x/y/z/g/h/i",
594 : "",
595 : },
596 : {
597 : "/a/b/c/d/e/f",
598 : "x/y/z/g/h/i",
599 : "",
600 : },
601 : };
602 6 : for(auto const & p : paths)
603 : {
604 25 : CATCH_REQUIRE(snapdev::pathinfo::relative_path(p.f_base, p.f_path) == p.f_result);
605 : }
606 : }
607 1 : CATCH_END_SECTION()
608 1 : }
609 :
610 :
611 3 : CATCH_TEST_CASE("pathinfo_realpath", "[filename][pathinfo]")
612 : {
613 3 : CATCH_START_SECTION("pathinfo_realpath: make sure the realpath works")
614 : {
615 1 : std::string error_msg;
616 1 : std::string const cwd(snapdev::pathinfo::getcwd(error_msg));
617 1 : CATCH_REQUIRE(!cwd.empty());
618 1 : CATCH_REQUIRE(error_msg.empty());
619 3 : CATCH_REQUIRE(snapdev::pathinfo::realpath(".", error_msg) == cwd);
620 1 : }
621 3 : CATCH_END_SECTION()
622 :
623 3 : CATCH_START_SECTION("pathinfo_realpath: realpath fails for non-existant files")
624 : {
625 1 : std::string error_msg;
626 3 : CATCH_REQUIRE(snapdev::pathinfo::realpath("/this/is/not/valid", error_msg) == std::string());
627 1 : CATCH_REQUIRE(error_msg == "realpath(\"/this/is/not/valid\") could not find the specified file.");
628 1 : }
629 3 : CATCH_END_SECTION()
630 :
631 3 : CATCH_START_SECTION("pathinfo_realpath: realpath fails for file instead of directory")
632 : {
633 1 : std::string error_msg;
634 3 : CATCH_REQUIRE(snapdev::pathinfo::realpath("/bin/ls/invalid", error_msg) == std::string());
635 1 : CATCH_REQUIRE(error_msg == "realpath(\"/bin/ls/invalid\") found a file instead of a directory within the path.");
636 1 : }
637 3 : CATCH_END_SECTION()
638 3 : }
639 :
640 :
641 :
642 : // vim: ts=4 sw=4 et
|