monotone

monotone Mtn Source Tree

Root/paths.cc

1// copyright (C) 2005 nathaniel smith <njs@pobox.com>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6#include <iostream>
7#include <string>
8
9#include <boost/filesystem/path.hpp>
10#include <boost/filesystem/operations.hpp>
11#include <boost/filesystem/convenience.hpp>
12
13#include "constants.hh"
14#include "paths.hh"
15#include "platform.hh"
16#include "sanity.hh"
17#include "interner.hh"
18#include "transforms.hh"
19
20// some structure to ensure we aren't doing anything broken when resolving
21// filenames. the idea is to make sure
22// -- we don't depend on the existence of something before it has been set
23// -- we don't re-set something that has already been used
24template <typename T>
25struct access_tracker
26{
27 void set(T const & val, bool may_be_initialized)
28 {
29 I(may_be_initialized || !initialized);
30 I(!used);
31 initialized = true;
32 value = val;
33 }
34 T const & get()
35 {
36 I(initialized);
37 used = true;
38 return value;
39 }
40 T const & get_but_unused()
41 {
42 I(initialized);
43 return value;
44 }
45 // for unit tests
46 void unset()
47 {
48 used = initialized = false;
49 }
50 T value;
51 bool initialized, used;
52 access_tracker() : initialized(false), used(false) {};
53};
54
55// paths to use in interpreting paths from various sources,
56// conceptually:
57// working_root / initial_rel_path == initial_abs_path
58
59// initial_abs_path is for interpreting relative system_path's
60static access_tracker<system_path> initial_abs_path;
61// initial_rel_path is for interpreting external file_path's
62static access_tracker<file_path> initial_rel_path;
63// working_root is for converting file_path's and bookkeeping_path's to
64// system_path's.
65static access_tracker<system_path> working_root;
66
67bookkeeping_path const bookkeeping_root("MT");
68
69void
70save_initial_path()
71{
72 // FIXME: BUG: this only works if the current working dir is in utf8
73 initial_abs_path.set(system_path(get_current_working_dir()), false);
74 // We still use boost::fs, so let's continue to initialize it properly.
75 fs::initial_path();
76 fs::path::default_name_check(fs::native);
77 L(F("initial abs path is: %s") % initial_abs_path.get_but_unused());
78}
79
80///////////////////////////////////////////////////////////////////////////
81// verifying that internal paths are indeed normalized.
82// this code must be superfast
83///////////////////////////////////////////////////////////////////////////
84
85// normalized means:
86// -- / as path separator
87// -- not an absolute path (on either posix or win32)
88// operationally, this means: first character != '/', first character != '\',
89// second character != ':'
90// -- no illegal characters
91// -- 0x00 -- 0x1f, 0x7f, \ are the illegal characters. \ is illegal
92// unconditionally to prevent people checking in files on posix that
93// have a different interpretation on win32
94// -- (may want to allow 0x0a and 0x0d (LF and CR) in the future, but this
95// is blocked on manifest format changing)
96// (also requires changes to 'automate inventory', possibly others, to
97// handle quoting)
98// -- no doubled /'s
99// -- no trailing /
100// -- no "." or ".." path components
101static inline bool
102bad_component(std::string const & component)
103{
104 if (component == "")
105 return true;
106 if (component == ".")
107 return true;
108 if (component == "..")
109 return true;
110 return false;
111}
112
113static inline bool
114fully_normalized_path(std::string const & path)
115{
116 // FIXME: probably should make this a 256-byte static lookup table
117 const static std::string bad_chars = std::string("\\") + constants::illegal_path_bytes + std::string(1, '\0');
118
119 // empty path is fine
120 if (path.empty())
121 return true;
122 // could use is_absolute_somewhere, but this is the only part of it that
123 // wouldn't be redundant
124 if (path.size() > 1 && path[1] == ':')
125 return false;
126 // first scan for completely illegal bytes
127 if (path.find_first_of(bad_chars) != std::string::npos)
128 return false;
129 // now check each component
130 std::string::size_type start, stop;
131 start = 0;
132 while (1)
133 {
134 stop = path.find('/', start);
135 if (stop == std::string::npos)
136 {
137 if (bad_component(path.substr(start)))
138 return false;
139 break;
140 }
141 if (bad_component(path.substr(start, stop - start)))
142 return false;
143 start = stop + 1;
144 }
145 return true;
146}
147
148static inline bool
149in_bookkeeping_dir(std::string const & path)
150{
151 return path == "MT" || (path.size() >= 3 && (path.substr(0, 3) == "MT/"));
152}
153
154static inline bool
155is_valid_internal(std::string const & path)
156{
157 return (fully_normalized_path(path)
158 && !in_bookkeeping_dir(path));
159}
160
161file_path::file_path(file_path::source_type type, std::string const & path)
162{
163 switch (type)
164 {
165 case internal:
166 data = path;
167 break;
168 case internal_from_user:
169 data = path;
170 N(is_valid_internal(path),
171 F("path '%s' is invalid") % path);
172 break;
173 case external:
174 N(!path.empty(), F("empty path '%s' is invalid") % path);
175 fs::path out, base, relative;
176 try
177 {
178 base = fs::path(initial_rel_path.get().as_internal());
179 // the fs::native is needed to get it to accept paths like ".foo".
180 relative = fs::path(path, fs::native);
181 out = (base / relative).normalize();
182 }
183 catch (std::exception & e)
184 {
185 N(false, F("path '%s' is invalid") % path);
186 }
187 data = utf8(out.string());
188 if (data() == ".")
189 data = std::string("");
190 N(!relative.has_root_path(),
191 F("absolute path '%s' is invalid") % relative.string());
192 N(fully_normalized_path(data()), F("path '%s' is invalid") % data);
193 N(!in_bookkeeping_dir(data()), F("path '%s' is in bookkeeping dir") % data);
194 break;
195 }
196 I(is_valid_internal(data()));
197}
198
199bookkeeping_path::bookkeeping_path(std::string const & path)
200{
201 I(fully_normalized_path(path));
202 I(in_bookkeeping_dir(path));
203 data = path;
204}
205
206bool
207bookkeeping_path::is_bookkeeping_path(std::string const & path)
208{
209 return in_bookkeeping_dir(path);
210}
211
212///////////////////////////////////////////////////////////////////////////
213// splitting/joining
214// this code must be superfast
215// it depends very much on knowing that it can only be applied to fully
216// normalized, relative, paths.
217///////////////////////////////////////////////////////////////////////////
218
219static interner<path_component> pc_interner("", the_null_component);
220
221// This function takes a vector of path components and joins them into a
222// single file_path. Valid input may be a single-element vector whose sole
223// element is the empty path component (""); this represents the null path,
224// which we use to represent non-existent files. Alternatively, input may be
225// a multi-element vector, in which case all elements of the vector are
226// required to be non-null. The following are valid inputs (with strings
227// replaced by their interned version, of course):
228// - [""]
229// - ["foo"]
230// - ["foo", "bar"]
231// The following are not:
232// - []
233// - ["foo", ""]
234// - ["", "bar"]
235file_path::file_path(split_path const & sp)
236{
237 split_path::const_iterator i = sp.begin();
238 I(i != sp.end());
239 if (sp.size() > 1)
240 I(!null_name(*i));
241 std::string tmp = pc_interner.lookup(*i);
242 I(tmp != bookkeeping_root.as_internal());
243 for (++i; i != sp.end(); ++i)
244 {
245 I(!null_name(*i));
246 tmp += "/";
247 tmp += pc_interner.lookup(*i);
248 }
249 data = tmp;
250}
251
252//
253// this takes a path of the form
254//
255// "p[0]/p[1]/.../p[n-1]/p[n]"
256//
257// and fills in a vector of paths corresponding to p[0] ... p[n-1]
258//
259// FIXME: this code carefully duplicates the behavior of the old path
260// splitting functions, in that it is _not_ the inverse to the above joining
261// function. The difference is that if you pass a null path (the one
262// represented by the empty string, or 'file_path()'), then it will return an
263// _empty_ vector. This vector will not be suitable to pass to the above path
264// joiner; to get the null path back again, you have to pass the above
265// function a single-element vector containing a the null component.
266//
267// Why does it work this way? Because that's what the old code did, and
268// that's what change_set.cc was written around; and it's much much easier to
269// make this code do something weird than to try and fix change_set.cc. When
270// change_set.cc is rewritten, however, you should revisit the semantics of
271// this function.
272void
273file_path::split(split_path & sp) const
274{
275 sp.clear();
276 if (empty())
277 return;
278 std::string::size_type start, stop;
279 start = 0;
280 std::string const & s = data();
281 while (1)
282 {
283 stop = s.find('/', start);
284 if (stop < 0 || stop > s.length())
285 {
286 sp.push_back(pc_interner.intern(s.substr(start)));
287 break;
288 }
289 sp.push_back(pc_interner.intern(s.substr(start, stop - start)));
290 start = stop + 1;
291 }
292}
293
294///////////////////////////////////////////////////////////////////////////
295// localizing file names (externalizing them)
296// this code must be superfast when there is no conversion needed
297///////////////////////////////////////////////////////////////////////////
298
299std::string
300any_path::as_external() const
301{
302#ifdef __APPLE__
303 // on OS X paths for the filesystem/kernel are UTF-8 encoded, regardless of
304 // locale.
305 return data();
306#else
307 // on normal systems we actually have some work to do, alas.
308 // not much, though, because utf8_to_system does all the hard work. it is
309 // carefully optimized. do not screw it up.
310 external out;
311 utf8_to_system(data, out);
312 return out();
313#endif
314}
315
316///////////////////////////////////////////////////////////////////////////
317// writing out paths
318///////////////////////////////////////////////////////////////////////////
319
320std::ostream &
321operator <<(std::ostream & o, any_path const & a)
322{
323 o << a.as_internal();
324 return o;
325}
326
327///////////////////////////////////////////////////////////////////////////
328// path manipulation
329// this code's speed does not matter much
330///////////////////////////////////////////////////////////////////////////
331
332static bool
333is_absolute_here(std::string const & path)
334{
335 if (path.empty())
336 return false;
337 if (path[0] == '/')
338 return true;
339#ifdef WIN32
340 if (path[0] == '\\')
341 return true;
342 if (path.size() > 1 && path[1] == ':')
343 return true;
344#endif
345 return false;
346}
347
348static inline bool
349is_absolute_somewhere(std::string const & path)
350{
351 if (path.empty())
352 return false;
353 if (path[0] == '/')
354 return true;
355 if (path[0] == '\\')
356 return true;
357 if (path.size() > 1 && path[1] == ':')
358 return true;
359 return false;
360}
361
362file_path
363file_path::operator /(std::string const & to_append) const
364{
365 I(!is_absolute_somewhere(to_append));
366 if (empty())
367 return file_path_internal(to_append);
368 else
369 return file_path_internal(data() + "/" + to_append);
370}
371
372bookkeeping_path
373bookkeeping_path::operator /(std::string const & to_append) const
374{
375 I(!is_absolute_somewhere(to_append));
376 I(!empty());
377 return bookkeeping_path(data() + "/" + to_append);
378}
379
380system_path
381system_path::operator /(std::string const & to_append) const
382{
383 I(!empty());
384 I(!is_absolute_here(to_append));
385 return system_path(data() + "/" + to_append);
386}
387
388///////////////////////////////////////////////////////////////////////////
389// system_path
390///////////////////////////////////////////////////////////////////////////
391
392static std::string
393normalize_out_dots(std::string const & path)
394{
395#ifdef WIN32
396 return fs::path(path, fs::native).normalize().string();
397#else
398 return fs::path(path, fs::native).normalize().native_file_string();
399#endif
400}
401
402system_path::system_path(any_path const & other, bool in_true_working_copy)
403{
404 I(!is_absolute_here(other.as_internal()));
405 system_path wr;
406 if (in_true_working_copy)
407 wr = working_root.get();
408 else
409 wr = working_root.get_but_unused();
410 data = normalize_out_dots((wr / other.as_internal()).as_internal());
411}
412
413static inline std::string const_system_path(utf8 const & path)
414{
415 N(!path().empty(), F("invalid path ''"));
416 std::string expanded = tilde_expand(path)();
417 if (is_absolute_here(expanded))
418 return normalize_out_dots(expanded);
419 else
420 return normalize_out_dots((initial_abs_path.get() / expanded).as_internal());
421}
422
423system_path::system_path(std::string const & path)
424{
425 data = const_system_path(path);
426}
427
428system_path::system_path(utf8 const & path)
429{
430 data = const_system_path(path);
431}
432
433///////////////////////////////////////////////////////////////////////////
434// working copy (and path roots) handling
435///////////////////////////////////////////////////////////////////////////
436
437bool
438find_and_go_to_working_copy(system_path const & search_root)
439{
440 // unimplemented
441 fs::path root(search_root.as_external(), fs::native);
442 fs::path bookdir(bookkeeping_root.as_external(), fs::native);
443 fs::path current(fs::initial_path());
444 fs::path removed;
445 fs::path check = current / bookdir;
446
447 L(F("searching for '%s' directory with root '%s'\n")
448 % bookdir.string()
449 % root.string());
450
451 while (current != root
452 && current.has_branch_path()
453 && current.has_leaf()
454 && !fs::exists(check))
455 {
456 L(F("'%s' not found in '%s' with '%s' removed\n")
457 % bookdir.string() % current.string() % removed.string());
458 removed = fs::path(current.leaf(), fs::native) / removed;
459 current = current.branch_path();
460 check = current / bookdir;
461 }
462
463 L(F("search for '%s' ended at '%s' with '%s' removed\n")
464 % bookdir.string() % current.string() % removed.string());
465
466 if (!fs::exists(check))
467 {
468 L(F("'%s' does not exist\n") % check.string());
469 return false;
470 }
471
472 if (!fs::is_directory(check))
473 {
474 L(F("'%s' is not a directory\n") % check.string());
475 return false;
476 }
477
478 // check for MT/. and MT/.. to see if mt dir is readable
479 if (!fs::exists(check / ".") || !fs::exists(check / ".."))
480 {
481 L(F("problems with '%s' (missing '.' or '..')\n") % check.string());
482 return false;
483 }
484
485 working_root.set(current.native_file_string(), true);
486 initial_rel_path.set(file_path_internal(removed.string()), true);
487
488 L(F("working root is '%s'") % working_root.get_but_unused());
489 L(F("initial relative path is '%s'") % initial_rel_path.get_but_unused());
490
491 change_current_working_dir(working_root.get_but_unused());
492
493 return true;
494}
495
496void
497go_to_working_copy(system_path const & new_working_copy)
498{
499 working_root.set(new_working_copy, true);
500 initial_rel_path.set(file_path(), true);
501 change_current_working_dir(new_working_copy);
502}
503
504///////////////////////////////////////////////////////////////////////////
505// tests
506///////////////////////////////////////////////////////////////////////////
507
508#ifdef BUILD_UNIT_TESTS
509#include "unit_tests.hh"
510
511static void test_null_name()
512{
513 BOOST_CHECK(null_name(the_null_component));
514}
515
516static void test_file_path_internal()
517{
518 char const * baddies[] = {"/foo",
519 "foo//bar",
520 "foo/../bar",
521 "../bar",
522 "MT/blah",
523 "foo/bar/",
524 "foo/./bar",
525 "./foo",
526 ".",
527 "..",
528 "c:\\foo",
529 "c:foo",
530 "c:/foo",
531 0 };
532 initial_rel_path.unset();
533 initial_rel_path.set(file_path(), true);
534 for (char const ** c = baddies; *c; ++c)
535 {
536 BOOST_CHECK_THROW(file_path_internal(*c), std::logic_error);
537 BOOST_CHECK_THROW(file_path_internal_from_user(std::string(*c)),
538 informative_failure);
539 }
540 initial_rel_path.unset();
541 initial_rel_path.set(file_path_internal("blah/blah/blah"), true);
542 for (char const ** c = baddies; *c; ++c)
543 {
544 BOOST_CHECK_THROW(file_path_internal(*c), std::logic_error);
545 BOOST_CHECK_THROW(file_path_internal_from_user(std::string(*c)),
546 informative_failure);
547 }
548
549 BOOST_CHECK(file_path().empty());
550 BOOST_CHECK(file_path_internal("").empty());
551
552 char const * goodies[] = {"",
553 "a",
554 "foo",
555 "foo/bar/baz",
556 "foo/bar.baz",
557 "foo/with-hyphen/bar",
558 "foo/with_underscore/bar",
559 "foo/with,other+@weird*%#$=stuff/bar",
560 ".foo/bar",
561 "..foo/bar",
562 "MTfoo/bar",
563#ifndef WIN32
564 "foo:bar",
565#endif
566 0 };
567
568 for (int i = 0; i < 2; ++i)
569 {
570 initial_rel_path.unset();
571 initial_rel_path.set(i ? file_path()
572 : file_path_internal("blah/blah/blah"),
573 true);
574 for (char const ** c = goodies; *c; ++c)
575 {
576 file_path fp = file_path_internal(*c);
577 BOOST_CHECK(fp.as_internal() == *c);
578 BOOST_CHECK(file_path_internal(fp.as_internal()) == fp);
579 std::vector<path_component> split_test;
580 fp.split(split_test);
581 if (fp.empty())
582 BOOST_CHECK(split_test.empty());
583 else
584 {
585 BOOST_CHECK(!split_test.empty());
586 file_path fp2(split_test);
587 BOOST_CHECK(fp == fp2);
588 }
589 for (std::vector<path_component>::const_iterator i = split_test.begin();
590 i != split_test.end(); ++i)
591 BOOST_CHECK(!null_name(*i));
592 file_path fp_user = file_path_internal_from_user(std::string(*c));
593 BOOST_CHECK(fp == fp_user);
594 }
595 }
596
597 initial_rel_path.unset();
598}
599
600static void check_fp_normalizes_to(char * before, char * after)
601{
602 L(F("check_fp_normalizes_to: '%s' -> '%s'") % before % after);
603 file_path fp = file_path_external(std::string(before));
604 L(F(" (got: %s)") % fp);
605 BOOST_CHECK(fp.as_internal() == after);
606 BOOST_CHECK(file_path_internal(fp.as_internal()) == fp);
607 // we compare after to the external form too, since as far as we know
608 // relative normalized posix paths are always good win32 paths too
609 BOOST_CHECK(fp.as_external() == after);
610 std::vector<path_component> split_test;
611 fp.split(split_test);
612 if (fp.empty())
613 BOOST_CHECK(split_test.empty());
614 else
615 {
616 BOOST_CHECK(!split_test.empty());
617 file_path fp2(split_test);
618 BOOST_CHECK(fp == fp2);
619 }
620 for (std::vector<path_component>::const_iterator i = split_test.begin();
621 i != split_test.end(); ++i)
622 BOOST_CHECK(!null_name(*i));
623}
624
625static void test_file_path_external_no_prefix()
626{
627 initial_rel_path.unset();
628 initial_rel_path.set(file_path(), true);
629
630 char const * baddies[] = {"/foo",
631 "../bar",
632 "MT/blah",
633 "MT",
634 "//blah",
635 "\\foo",
636 "..",
637 "c:\\foo",
638 "c:foo",
639 "c:/foo",
640 "",
641 0 };
642 for (char const ** c = baddies; *c; ++c)
643 {
644 L(F("test_file_path_external_no_prefix: trying baddie: %s") % *c);
645 BOOST_CHECK_THROW(file_path_external(utf8(*c)), informative_failure);
646 }
647
648 check_fp_normalizes_to("a", "a");
649 check_fp_normalizes_to("foo", "foo");
650 check_fp_normalizes_to("foo/bar", "foo/bar");
651 check_fp_normalizes_to("foo/bar/baz", "foo/bar/baz");
652 check_fp_normalizes_to("foo/bar.baz", "foo/bar.baz");
653 check_fp_normalizes_to("foo/with-hyphen/bar", "foo/with-hyphen/bar");
654 check_fp_normalizes_to("foo/with_underscore/bar", "foo/with_underscore/bar");
655 check_fp_normalizes_to(".foo/bar", ".foo/bar");
656 check_fp_normalizes_to("..foo/bar", "..foo/bar");
657 check_fp_normalizes_to(".", "");
658#ifndef WIN32
659 check_fp_normalizes_to("foo:bar", "foo:bar");
660#endif
661 check_fp_normalizes_to("foo/with,other+@weird*%#$=stuff/bar",
662 "foo/with,other+@weird*%#$=stuff/bar");
663
664 // Why are these tests with // in them commented out? because boost::fs
665 // sucks and can't normalize them. FIXME.
666 //check_fp_normalizes_to("foo//bar", "foo/bar");
667 check_fp_normalizes_to("foo/../bar", "bar");
668 check_fp_normalizes_to("foo/bar/", "foo/bar");
669 check_fp_normalizes_to("foo/./bar/", "foo/bar");
670 check_fp_normalizes_to("./foo", "foo");
671 //check_fp_normalizes_to("foo///.//", "foo");
672
673 initial_rel_path.unset();
674}
675
676static void test_file_path_external_prefix_a_b()
677{
678 initial_rel_path.unset();
679 initial_rel_path.set(file_path_internal("a/b"), true);
680
681 char const * baddies[] = {"/foo",
682 "../../../bar",
683 "../../..",
684 "//blah",
685 "\\foo",
686 "c:\\foo",
687#ifdef WIN32
688 "c:foo",
689 "c:/foo",
690#endif
691 "",
692 0 };
693 for (char const ** c = baddies; *c; ++c)
694 {
695 L(F("test_file_path_external_prefix_a_b: trying baddie: %s") % *c);
696 BOOST_CHECK_THROW(file_path_external(utf8(*c)), informative_failure);
697 }
698
699 check_fp_normalizes_to("foo", "a/b/foo");
700 check_fp_normalizes_to("a", "a/b/a");
701 check_fp_normalizes_to("foo/bar", "a/b/foo/bar");
702 check_fp_normalizes_to("foo/bar/baz", "a/b/foo/bar/baz");
703 check_fp_normalizes_to("foo/bar.baz", "a/b/foo/bar.baz");
704 check_fp_normalizes_to("foo/with-hyphen/bar", "a/b/foo/with-hyphen/bar");
705 check_fp_normalizes_to("foo/with_underscore/bar", "a/b/foo/with_underscore/bar");
706 check_fp_normalizes_to(".foo/bar", "a/b/.foo/bar");
707 check_fp_normalizes_to("..foo/bar", "a/b/..foo/bar");
708 check_fp_normalizes_to(".", "a/b");
709#ifndef WIN32
710 check_fp_normalizes_to("foo:bar", "a/b/foo:bar");
711#endif
712 check_fp_normalizes_to("foo/with,other+@weird*%#$=stuff/bar",
713 "a/b/foo/with,other+@weird*%#$=stuff/bar");
714 // why are the tests with // in them commented out? because boost::fs sucks
715 // and can't normalize them. FIXME.
716 //check_fp_normalizes_to("foo//bar", "a/b/foo/bar");
717 check_fp_normalizes_to("foo/../bar", "a/b/bar");
718 check_fp_normalizes_to("foo/bar/", "a/b/foo/bar");
719 check_fp_normalizes_to("foo/./bar/", "a/b/foo/bar");
720 check_fp_normalizes_to("./foo", "a/b/foo");
721 //check_fp_normalizes_to("foo///.//", "a/b/foo");
722 // things that would have been bad without the initial_rel_path:
723 check_fp_normalizes_to("../foo", "a/foo");
724 check_fp_normalizes_to("..", "a");
725 check_fp_normalizes_to("../..", "");
726 check_fp_normalizes_to("MT/foo", "a/b/MT/foo");
727 check_fp_normalizes_to("MT", "a/b/MT");
728#ifndef WIN32
729 check_fp_normalizes_to("c:foo", "a/b/c:foo");
730 check_fp_normalizes_to("c:/foo", "a/b/c:/foo");
731#endif
732
733 initial_rel_path.unset();
734}
735
736static void test_split_join()
737{
738 file_path fp1 = file_path_internal("foo/bar/baz");
739 file_path fp2 = file_path_internal("bar/baz/foo");
740 typedef std::vector<path_component> pcv;
741 pcv split1, split2;
742 fp1.split(split1);
743 fp2.split(split2);
744 BOOST_CHECK(fp1 == file_path(split1));
745 BOOST_CHECK(fp2 == file_path(split2));
746 BOOST_CHECK(!(fp1 == file_path(split2)));
747 BOOST_CHECK(!(fp2 == file_path(split1)));
748 BOOST_CHECK(split1.size() == 3);
749 BOOST_CHECK(split2.size() == 3);
750 BOOST_CHECK(split1[0] != split1[1]);
751 BOOST_CHECK(split1[0] != split1[2]);
752 BOOST_CHECK(split1[1] != split1[2]);
753 BOOST_CHECK(!null_name(split1[0])
754 && !null_name(split1[1])
755 && !null_name(split1[2]));
756 BOOST_CHECK(split1[0] == split2[2]);
757 BOOST_CHECK(split1[1] == split2[0]);
758 BOOST_CHECK(split1[2] == split2[1]);
759
760 file_path fp3 = file_path_internal("");
761 pcv split3;
762 fp3.split(split3);
763 BOOST_CHECK(split3.empty());
764
765 pcv split4;
766 // this comparison tricks the compiler into not completely eliminating this
767 // code as dead...
768 BOOST_CHECK_THROW(file_path(split4) == file_path(), std::logic_error);
769 split4.push_back(the_null_component);
770 BOOST_CHECK(file_path(split4) == file_path());
771 split4.clear();
772 split4.push_back(split1[0]);
773 split4.push_back(the_null_component);
774 split4.push_back(split1[0]);
775 // this comparison tricks the compiler into not completely eliminating this
776 // code as dead...
777 BOOST_CHECK_THROW(file_path(split4) == file_path(), std::logic_error);
778
779 // Make sure that we can't use joining to create a path into the bookkeeping
780 // dir
781 pcv split_mt1, split_mt2;
782 file_path_internal("foo/MT").split(split_mt1);
783 BOOST_CHECK(split_mt1.size() == 2);
784 split_mt2.push_back(split_mt1[1]);
785 // split_mt2 now contains the component "MT"
786 BOOST_CHECK_THROW(file_path(split_mt2) == file_path(), std::logic_error);
787 split_mt2.push_back(split_mt1[0]);
788 // split_mt2 now contains the components "MT", "foo" in that order
789 // this comparison tricks the compiler into not completely eliminating this
790 // code as dead...
791 BOOST_CHECK_THROW(file_path(split_mt2) == file_path(), std::logic_error);
792}
793
794static void check_bk_normalizes_to(char * before, char * after)
795{
796 bookkeeping_path bp(bookkeeping_root / before);
797 L(F("normalizing %s to %s (got %s)") % before % after % bp);
798 BOOST_CHECK(bp.as_external() == after);
799 BOOST_CHECK(bookkeeping_path(bp.as_internal()).as_internal() == bp.as_internal());
800}
801
802static void test_bookkeeping_path()
803{
804 char const * baddies[] = {"/foo",
805 "foo//bar",
806 "foo/../bar",
807 "../bar",
808 "foo/bar/",
809 "foo/./bar",
810 "./foo",
811 ".",
812 "..",
813 "c:\\foo",
814 "c:foo",
815 "c:/foo",
816 "",
817 "a:b",
818 0 };
819 std::string tmp_path_string;
820
821 for (char const ** c = baddies; *c; ++c)
822 {
823 L(F("test_bookkeeping_path baddie: trying '%s'") % *c);
824 BOOST_CHECK_THROW(bookkeeping_path(tmp_path_string.assign(*c)), std::logic_error);
825 BOOST_CHECK_THROW(bookkeeping_root / tmp_path_string.assign(*c), std::logic_error);
826 }
827 BOOST_CHECK_THROW(bookkeeping_path(tmp_path_string.assign("foo/bar")), std::logic_error);
828 BOOST_CHECK_THROW(bookkeeping_path(tmp_path_string.assign("a")), std::logic_error);
829
830 check_bk_normalizes_to("a", "MT/a");
831 check_bk_normalizes_to("foo", "MT/foo");
832 check_bk_normalizes_to("foo/bar", "MT/foo/bar");
833 check_bk_normalizes_to("foo/bar/baz", "MT/foo/bar/baz");
834}
835
836static void check_system_normalizes_to(char * before, char * after)
837{
838 system_path sp(before);
839 L(F("normalizing '%s' to '%s' (got '%s')") % before % after % sp);
840 BOOST_CHECK(sp.as_external() == after);
841 BOOST_CHECK(system_path(sp.as_internal()).as_internal() == sp.as_internal());
842}
843
844static void test_system_path()
845{
846 initial_abs_path.unset();
847 initial_abs_path.set(system_path("/a/b"), true);
848
849 BOOST_CHECK_THROW(system_path(""), informative_failure);
850
851 check_system_normalizes_to("foo", "/a/b/foo");
852 check_system_normalizes_to("foo/bar", "/a/b/foo/bar");
853 check_system_normalizes_to("/foo/bar", "/foo/bar");
854 check_system_normalizes_to("//foo/bar", "//foo/bar");
855#ifdef WIN32
856 check_system_normalizes_to("c:foo", "c:foo");
857 check_system_normalizes_to("c:/foo", "c:/foo");
858#else
859 check_system_normalizes_to("c:foo", "/a/b/c:foo");
860 check_system_normalizes_to("c:/foo", "/a/b/c:/foo");
861 check_system_normalizes_to("c:\\foo", "/a/b/c:\\foo");
862 check_system_normalizes_to("foo:bar", "/a/b/foo:bar");
863#endif
864 // we require that system_path normalize out ..'s, because of the following
865 // case:
866 // /work mkdir newdir
867 // /work$ cd newdir
868 // /work/newdir$ monotone setup --db=../foo.db
869 // Now they have either "/work/foo.db" or "/work/newdir/../foo.db" in
870 // MT/options
871 // /work/newdir$ cd ..
872 // /work$ mv newdir newerdir # better name
873 // Oops, now, if we stored the version with ..'s in, this working directory
874 // is broken.
875 check_system_normalizes_to("../foo", "/a/foo");
876 check_system_normalizes_to("foo/..", "/a/b");
877 check_system_normalizes_to("/foo/bar/..", "/foo");
878 check_system_normalizes_to("/foo/..", "/");
879 // can't do particularly interesting checking of tilde expansion, but at
880 // least we can check that it's doing _something_...
881 std::string tilde_expanded = system_path("~/foo").as_external();
882#ifdef WIN32
883 BOOST_CHECK(tilde_expanded[1] == ':');
884#else
885 BOOST_CHECK(tilde_expanded[0] == '/');
886#endif
887 BOOST_CHECK(tilde_expanded.find('~') == std::string::npos);
888 // and check for the weird WIN32 version
889#ifdef WIN32
890 std::string tilde_expanded2 = system_path("~this_user_does_not_exist_anywhere").as_external();
891 BOOST_CHECK(tilde_expanded2[0] = '/');
892 BOOST_CHECK(tilde_expanded2.find('~') == std::string::npos);
893#else
894 BOOST_CHECK_THROW(system_path("~this_user_does_not_exist_anywhere"), informative_failure);
895#endif
896
897 // finally, make sure that the copy-from-any_path constructor works right
898 // in particular, it should interpret the paths it gets as being relative to
899 // the project root, not the initial path
900 working_root.unset();
901 working_root.set(system_path("/working/root"), true);
902 initial_rel_path.unset();
903 initial_rel_path.set(file_path_internal("rel/initial"), true);
904
905 BOOST_CHECK(system_path(system_path("foo/bar")).as_internal() == "/a/b/foo/bar");
906 BOOST_CHECK(!working_root.used);
907 BOOST_CHECK(system_path(system_path("/foo/bar")).as_internal() == "/foo/bar");
908 BOOST_CHECK(!working_root.used);
909 BOOST_CHECK(system_path(file_path_internal("foo/bar"), false).as_internal()
910 == "/working/root/foo/bar");
911 BOOST_CHECK(!working_root.used);
912 BOOST_CHECK(system_path(file_path_internal("foo/bar")).as_internal()
913 == "/working/root/foo/bar");
914 BOOST_CHECK(working_root.used);
915 BOOST_CHECK(system_path(file_path_external(std::string("foo/bar"))).as_external()
916 == "/working/root/rel/initial/foo/bar");
917 file_path a_file_path;
918 BOOST_CHECK(system_path(a_file_path).as_external()
919 == "/working/root");
920 BOOST_CHECK(system_path(bookkeeping_path("MT/foo/bar")).as_internal()
921 == "/working/root/MT/foo/bar");
922 BOOST_CHECK(system_path(bookkeeping_root).as_internal()
923 == "/working/root/MT");
924 initial_abs_path.unset();
925 working_root.unset();
926 initial_rel_path.unset();
927}
928
929static void test_access_tracker()
930{
931 access_tracker<int> a;
932 BOOST_CHECK_THROW(a.get(), std::logic_error);
933 a.set(1, false);
934 BOOST_CHECK_THROW(a.set(2, false), std::logic_error);
935 a.set(2, true);
936 BOOST_CHECK_THROW(a.set(3, false), std::logic_error);
937 BOOST_CHECK(a.get() == 2);
938 BOOST_CHECK_THROW(a.set(3, true), std::logic_error);
939}
940
941void add_paths_tests(test_suite * suite)
942{
943 I(suite);
944 suite->add(BOOST_TEST_CASE(&test_null_name));
945 suite->add(BOOST_TEST_CASE(&test_file_path_internal));
946 suite->add(BOOST_TEST_CASE(&test_file_path_external_no_prefix));
947 suite->add(BOOST_TEST_CASE(&test_file_path_external_prefix_a_b));
948 suite->add(BOOST_TEST_CASE(&test_split_join));
949 suite->add(BOOST_TEST_CASE(&test_bookkeeping_path));
950 suite->add(BOOST_TEST_CASE(&test_system_path));
951 suite->add(BOOST_TEST_CASE(&test_access_tracker));
952}
953
954#endif // BUILD_UNIT_TESTS

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status