1 // Copyright (C) 2005 Nathaniel Smith <njs@pobox.com>
2 // 2008 Stephen Leake <stephen_leake@stephe-leake.org>
3 //
4 // This program is made available under the GNU GPL version 2.0 or
5 // greater. See the accompanying file COPYING for details.
6 //
7 // This program is distributed WITHOUT ANY WARRANTY; without even the
8 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9 // PURPOSE.
10
11 #include "../../../src/base.hh"
12 #include "../unit_tests.hh"
13 #include "../../../src/merge_roster.hh"
14 #include "../roster_tests.hh"
15 #include "../../../src/constants.hh"
16 #include "../../../src/sanity.hh"
17 #include "../../../src/safe_map.hh"
18
19 using std::make_pair;
20 using std::string;
21 using std::set;
22
23 // cases for testing:
24 //
25 // (DONE:)
26 //
27 // lifecycle, file and dir
28 // alive in both
29 // alive in one and unborn in other (left vs. right)
30 // alive in one and dead in other (left vs. right)
31 //
32 // mark merge:
33 // same in both, same mark
34 // same in both, diff marks
35 // different, left wins with 1 mark
36 // different, right wins with 1 mark
37 // different, conflict with 1 mark
38 // different, left wins with 2 marks
39 // different, right wins with 2 marks
40 // different, conflict with 1 mark winning, 1 mark losing
41 // different, conflict with 2 marks both conflicting
42 //
43 // for:
44 // node name and parent, file and dir
45 // node attr, file and dir
46 // file content
47 //
48 // attr lifecycle:
49 // seen in both -->mark merge cases, above
50 // live in one and unseen in other -->live
51 // dead in one and unseen in other -->dead
52 //
53 // two diff nodes with same name
54 // directory loops
55 // orphans
56 // illegal node ("_MTN")
57 // missing root dir
58 //
59 // (NEEDED:)
60 //
61 // interactions:
62 // in-node name conflict prevents other problems:
63 // in-node name conflict + possible between-node name conflict
64 // a vs. b, plus a, b, exist in result
65 // left: 1: a
66 // 2: b
67 // right: 1: b
68 // 3: a
69 // in-node name conflict + both possible names orphaned
70 // a/foo vs. b/foo conflict, + a, b exist in parents but deleted in
71 // children
72 // left: 1: a
73 // 2: a/foo
74 // right:
75 // 3: b
76 // 2: b/foo
77 // in-node name conflict + directory loop conflict
78 // a/bottom vs. b/bottom, with a and b both moved inside it
79 // in-node name conflict + one name illegal
80 // _MTN vs. foo
81 // in-node name conflict causes other problems:
82 // in-node name conflict + causes missing root dir
83 // "" vs. foo and bar vs. ""
84 // between-node name conflict prevents other problems:
85 // between-node name conflict + both nodes orphaned
86 // this is not possible
87 // between-node name conflict + both nodes cause loop
88 // this is not possible
89 // between-node name conflict + both nodes illegal
90 // two nodes that both merge to _MTN
91 // this is not possible
92 // between-node name conflict causes other problems:
93 // between-node name conflict + causes missing root dir
94 // two nodes that both want ""
95
96 typedef enum { scalar_a, scalar_b, scalar_conflict } scalar_val;
97
98 template <> void
dump(scalar_val const & v,string & out)99 dump(scalar_val const & v, string & out)
100 {
101 switch (v)
102 {
103 case scalar_a:
104 out = "scalar_a";
105 break;
106 case scalar_b:
107 out = "scalar_b";
108 break;
109 case scalar_conflict:
110 out = "scalar_conflict";
111 break;
112 }
113 }
114
string_to_set(string const & from,set<revision_id> & to)115 void string_to_set(string const & from, set<revision_id> & to)
116 {
117 to.clear();
118 for (string::const_iterator i = from.begin(); i != from.end(); ++i)
119 {
120 char label = ((*i - '0') << 4) + (*i - '0');
121 to.insert(revision_id(string(constants::idlen_bytes, label), origin::internal));
122 }
123 }
124
125
126 template <typename S> void
test_a_scalar_merge_impl(scalar_val left_val,string const & left_marks_str,string const & left_uncommon_str,scalar_val right_val,string const & right_marks_str,string const & right_uncommon_str,scalar_val expected_outcome)127 test_a_scalar_merge_impl(scalar_val left_val, string const & left_marks_str,
128 string const & left_uncommon_str,
129 scalar_val right_val, string const & right_marks_str,
130 string const & right_uncommon_str,
131 scalar_val expected_outcome)
132 {
133 MM(left_val);
134 MM(left_marks_str);
135 MM(left_uncommon_str);
136 MM(right_val);
137 MM(right_marks_str);
138 MM(right_uncommon_str);
139 MM(expected_outcome);
140
141 S scalar;
142 roster_t left_parent, right_parent;
143 marking_map left_markings, right_markings;
144 set<revision_id> left_uncommon_ancestors, right_uncommon_ancestors;
145 roster_merge_result result;
146
147 set<revision_id> left_marks, right_marks;
148
149 MM(left_parent);
150 MM(right_parent);
151 MM(left_markings);
152 MM(right_markings);
153 MM(left_uncommon_ancestors);
154 MM(right_uncommon_ancestors);
155 MM(left_marks);
156 MM(right_marks);
157 MM(result);
158
159 string_to_set(left_marks_str, left_marks);
160 scalar.setup_parent(left_val, left_marks, left_parent, left_markings);
161 string_to_set(right_marks_str, right_marks);
162 scalar.setup_parent(right_val, right_marks, right_parent, right_markings);
163
164 string_to_set(left_uncommon_str, left_uncommon_ancestors);
165 string_to_set(right_uncommon_str, right_uncommon_ancestors);
166
167 roster_merge(left_parent, left_markings, left_uncommon_ancestors,
168 right_parent, right_markings, right_uncommon_ancestors,
169 result);
170
171 // go ahead and check the roster_delta code too, while we're at it...
172 test_roster_delta_on(left_parent, left_markings, right_parent, right_markings);
173
174 scalar.check_result(left_val, right_val, result, expected_outcome);
175 }
176
177 static const revision_id root_rid(string(constants::idlen_bytes, '\0'), origin::internal);
178 static const file_id arbitrary_file(string(constants::idlen_bytes, '\0'), origin::internal);
179
180 struct base_scalar
181 {
182 testing_node_id_source nis;
183 node_id root_nid;
184 node_id thing_nid;
base_scalarbase_scalar185 base_scalar() : root_nid(nis.next()), thing_nid(nis.next())
186 {}
187
188 void
make_dirbase_scalar189 make_dir(char const * name, node_id nid, roster_t & r, marking_map & markings)
190 {
191 r.create_dir_node(nid);
192 r.attach_node(nid, file_path_internal(name));
193 marking_t m(new marking());
194 m->birth_revision = root_rid;
195 m->parent_name.insert(root_rid);
196 markings.put_marking(nid, m);
197 }
198
199 void
make_filebase_scalar200 make_file(char const * name, node_id nid, roster_t & r, marking_map & markings)
201 {
202 r.create_file_node(arbitrary_file, nid);
203 r.attach_node(nid, file_path_internal(name));
204 marking_t m(new marking());
205 m->birth_revision = root_rid;
206 m->parent_name.insert(root_rid);
207 m->file_content.insert(root_rid);
208 markings.put_marking(nid, m);
209 }
210
211 void
make_rootbase_scalar212 make_root(roster_t & r, marking_map & markings)
213 {
214 make_dir("", root_nid, r, markings);
215 }
216 };
217
218 struct file_scalar : public virtual base_scalar
219 {
220 file_path thing_name;
file_scalarfile_scalar221 file_scalar() : thing_name(file_path_internal("thing"))
222 {}
223
224 void
make_thingfile_scalar225 make_thing(roster_t & r, marking_map & markings)
226 {
227 make_root(r, markings);
228 make_file("thing", thing_nid, r, markings);
229 }
230 };
231
232 struct dir_scalar : public virtual base_scalar
233 {
234 file_path thing_name;
dir_scalardir_scalar235 dir_scalar() : thing_name(file_path_internal("thing"))
236 {}
237
238 void
make_thingdir_scalar239 make_thing(roster_t & r, marking_map & markings)
240 {
241 make_root(r, markings);
242 make_dir("thing", thing_nid, r, markings);
243 }
244 };
245
246 struct name_shared_stuff : public virtual base_scalar
247 {
248 virtual file_path path_for(scalar_val val) = 0;
pc_forname_shared_stuff249 path_component pc_for(scalar_val val)
250 {
251 return path_for(val).basename();
252 }
253 virtual node_id parent_for(scalar_val val) = 0;
254
255 void
check_resultname_shared_stuff256 check_result(scalar_val left_val, scalar_val right_val,
257 // NB result is writeable -- we can scribble on it
258 roster_merge_result & result, scalar_val expected_val)
259 {
260 switch (expected_val)
261 {
262 case scalar_a: case scalar_b:
263 {
264 file_path fp;
265 result.roster.get_name(thing_nid, fp);
266 I(fp == path_for(expected_val));
267 }
268 break;
269 case scalar_conflict:
270 multiple_name_conflict const & c = idx(result.multiple_name_conflicts, 0);
271 I(c.nid == thing_nid);
272 I(c.left == make_pair(parent_for(left_val), pc_for(left_val)));
273 I(c.right == make_pair(parent_for(right_val), pc_for(right_val)));
274 I(null_node(result.roster.get_node(thing_nid)->parent));
275 I(result.roster.get_node(thing_nid)->name.empty());
276 // resolve the conflict, thus making sure that resolution works and
277 // that this was the only conflict signaled
278 // attach implicitly checks that we were already detached
279 result.roster.attach_node(thing_nid, file_path_internal("thing"));
280 result.multiple_name_conflicts.pop_back();
281 break;
282 }
283 // by now, the merge should have been resolved cleanly, one way or another
284 result.roster.check_sane();
285 I(result.is_clean());
286 }
287
~name_shared_stuffname_shared_stuff288 virtual ~name_shared_stuff() {};
289 };
290
291 template <typename T>
292 struct basename_scalar : public name_shared_stuff, public T
293 {
path_forbasename_scalar294 virtual file_path path_for(scalar_val val)
295 {
296 I(val != scalar_conflict);
297 return file_path_internal((val == scalar_a) ? "a" : "b");
298 }
parent_forbasename_scalar299 virtual node_id parent_for(scalar_val val)
300 {
301 I(val != scalar_conflict);
302 return root_nid;
303 }
304
305 void
setup_parentbasename_scalar306 setup_parent(scalar_val val, set<revision_id> marks,
307 roster_t & r, marking_map & markings)
308 {
309 this->T::make_thing(r, markings);
310 r.detach_node(this->T::thing_name);
311 r.attach_node(thing_nid, path_for(val));
312 markings.get_marking_for_update(thing_nid)->parent_name = marks;
313 }
314
~basename_scalarbasename_scalar315 virtual ~basename_scalar() {}
316 };
317
318 template <typename T>
319 struct parent_scalar : public virtual name_shared_stuff, public T
320 {
321 node_id a_dir_nid, b_dir_nid;
parent_scalarparent_scalar322 parent_scalar() : a_dir_nid(nis.next()), b_dir_nid(nis.next())
323 {}
324
path_forparent_scalar325 virtual file_path path_for(scalar_val val)
326 {
327 I(val != scalar_conflict);
328 return file_path_internal((val == scalar_a) ? "a/thing" : "b/thing");
329 }
parent_forparent_scalar330 virtual node_id parent_for(scalar_val val)
331 {
332 I(val != scalar_conflict);
333 return ((val == scalar_a) ? a_dir_nid : b_dir_nid);
334 }
335
336 void
setup_parentparent_scalar337 setup_parent(scalar_val val, set<revision_id> marks,
338 roster_t & r, marking_map & markings)
339 {
340 this->T::make_thing(r, markings);
341 make_dir("a", a_dir_nid, r, markings);
342 make_dir("b", b_dir_nid, r, markings);
343 r.detach_node(this->T::thing_name);
344 r.attach_node(thing_nid, path_for(val));
345 markings.get_marking_for_update(thing_nid)->parent_name = marks;
346 }
347
~parent_scalarparent_scalar348 virtual ~parent_scalar() {}
349 };
350
351 template <typename T>
352 struct attr_scalar : public virtual base_scalar, public T
353 {
attr_value_forattr_scalar354 attr_value attr_value_for(scalar_val val)
355 {
356 I(val != scalar_conflict);
357 return attr_value((val == scalar_a) ? "a" : "b");
358 }
359
360 void
setup_parentattr_scalar361 setup_parent(scalar_val val, set<revision_id> marks,
362 roster_t & r, marking_map & markings)
363 {
364 this->T::make_thing(r, markings);
365 r.set_attr(this->T::thing_name, attr_key("test_key"), attr_value_for(val));
366 markings.get_marking_for_update(thing_nid)->attrs[attr_key("test_key")] = marks;
367 }
368
369 void
check_resultattr_scalar370 check_result(scalar_val left_val, scalar_val right_val,
371 // NB result is writeable -- we can scribble on it
372 roster_merge_result & result, scalar_val expected_val)
373 {
374 switch (expected_val)
375 {
376 case scalar_a: case scalar_b:
377 I(result.roster.get_node_for_update(thing_nid)->attrs[attr_key("test_key")]
378 == make_pair(true, attr_value_for(expected_val)));
379 break;
380 case scalar_conflict:
381 attribute_conflict const & c = idx(result.attribute_conflicts, 0);
382 I(c.nid == thing_nid);
383 I(c.key == attr_key("test_key"));
384 I(c.left == make_pair(true, attr_value_for(left_val)));
385 I(c.right == make_pair(true, attr_value_for(right_val)));
386 attr_map_t const & attrs = result.roster.get_node(thing_nid)->attrs;
387 I(attrs.find(attr_key("test_key")) == attrs.end());
388 // resolve the conflict, thus making sure that resolution works and
389 // that this was the only conflict signaled
390 result.roster.set_attr(this->T::thing_name, attr_key("test_key"),
391 attr_value("conflict -- RESOLVED"));
392 result.attribute_conflicts.pop_back();
393 break;
394 }
395 // by now, the merge should have been resolved cleanly, one way or another
396 result.roster.check_sane();
397 I(result.is_clean());
398 }
399 };
400
401 struct file_content_scalar : public virtual file_scalar
402 {
content_forfile_content_scalar403 file_id content_for(scalar_val val)
404 {
405 I(val != scalar_conflict);
406 return file_id(string(constants::idlen_bytes,
407 (val == scalar_a) ? '\xaa' : '\xbb'),
408 origin::internal);
409 }
410
411 void
setup_parentfile_content_scalar412 setup_parent(scalar_val val, set<revision_id> marks,
413 roster_t & r, marking_map & markings)
414 {
415 make_thing(r, markings);
416 downcast_to_file_t(r.get_node_for_update(thing_name))->content = content_for(val);
417 markings.get_marking_for_update(thing_nid)->file_content = marks;
418 }
419
420 void
check_resultfile_content_scalar421 check_result(scalar_val left_val, scalar_val right_val,
422 // NB result is writeable -- we can scribble on it
423 roster_merge_result & result, scalar_val expected_val)
424 {
425 switch (expected_val)
426 {
427 case scalar_a: case scalar_b:
428 I(downcast_to_file_t(result.roster.get_node(thing_nid))->content
429 == content_for(expected_val));
430 break;
431 case scalar_conflict:
432 file_content_conflict const & c = idx(result.file_content_conflicts, 0);
433 I(c.nid == thing_nid);
434 I(c.left == content_for(left_val));
435 I(c.right == content_for(right_val));
436 file_id & content = downcast_to_file_t(result.roster.get_node_for_update(thing_nid))->content;
437 I(null_id(content));
438 // resolve the conflict, thus making sure that resolution works and
439 // that this was the only conflict signaled
440 content = file_id(string(constants::idlen_bytes, '\xff'), origin::internal);
441 result.file_content_conflicts.pop_back();
442 break;
443 }
444 // by now, the merge should have been resolved cleanly, one way or another
445 result.roster.check_sane();
446 I(result.is_clean());
447 }
448 };
449
450 void
test_a_scalar_merge(scalar_val left_val,string const & left_marks_str,string const & left_uncommon_str,scalar_val right_val,string const & right_marks_str,string const & right_uncommon_str,scalar_val expected_outcome)451 test_a_scalar_merge(scalar_val left_val, string const & left_marks_str,
452 string const & left_uncommon_str,
453 scalar_val right_val, string const & right_marks_str,
454 string const & right_uncommon_str,
455 scalar_val expected_outcome)
456 {
457 test_a_scalar_merge_impl<basename_scalar<file_scalar> >(left_val, left_marks_str, left_uncommon_str,
458 right_val, right_marks_str, right_uncommon_str,
459 expected_outcome);
460 test_a_scalar_merge_impl<basename_scalar<dir_scalar> >(left_val, left_marks_str, left_uncommon_str,
461 right_val, right_marks_str, right_uncommon_str,
462 expected_outcome);
463 test_a_scalar_merge_impl<parent_scalar<file_scalar> >(left_val, left_marks_str, left_uncommon_str,
464 right_val, right_marks_str, right_uncommon_str,
465 expected_outcome);
466 test_a_scalar_merge_impl<parent_scalar<dir_scalar> >(left_val, left_marks_str, left_uncommon_str,
467 right_val, right_marks_str, right_uncommon_str,
468 expected_outcome);
469 test_a_scalar_merge_impl<attr_scalar<file_scalar> >(left_val, left_marks_str, left_uncommon_str,
470 right_val, right_marks_str, right_uncommon_str,
471 expected_outcome);
472 test_a_scalar_merge_impl<attr_scalar<dir_scalar> >(left_val, left_marks_str, left_uncommon_str,
473 right_val, right_marks_str, right_uncommon_str,
474 expected_outcome);
475 test_a_scalar_merge_impl<file_content_scalar>(left_val, left_marks_str, left_uncommon_str,
476 right_val, right_marks_str, right_uncommon_str,
477 expected_outcome);
478 }
479
UNIT_TEST(scalar_merges)480 UNIT_TEST(scalar_merges)
481 {
482 // Notation: a1* means, "value is a, this is node 1 in the graph, it is
483 // marked". ".2" means, "value is unimportant and different from either a
484 // or b, this is node 2 in the graph, it is not marked".
485 //
486 // Backslashes with dots after them mean, the C++ line continuation rules
487 // are annoying when it comes to drawing ascii graphs -- the dot is only to
488 // stop the backslash from having special meaning to the parser. So just
489 // ignore them :-).
490
491 // same in both, same mark
492 // a1*
493 // / \.
494 // a2 a3
495 test_a_scalar_merge(scalar_a, "1", "2", scalar_a, "1", "3", scalar_a);
496
497 // same in both, diff marks
498 // .1*
499 // / \.
500 // a2* a3*
501 test_a_scalar_merge(scalar_a, "2", "2", scalar_a, "3", "3", scalar_a);
502
503 // different, left wins with 1 mark
504 // a1*
505 // / \.
506 // b2* a3
507 test_a_scalar_merge(scalar_b, "2", "2", scalar_a, "1", "3", scalar_b);
508
509 // different, right wins with 1 mark
510 // a1*
511 // / \.
512 // a2 b3*
513 test_a_scalar_merge(scalar_a, "1", "2", scalar_b, "3", "3", scalar_b);
514
515 // different, conflict with 1 mark
516 // .1*
517 // / \.
518 // a2* b3*
519 test_a_scalar_merge(scalar_a, "2", "2", scalar_b, "3", "3", scalar_conflict);
520
521 // different, left wins with 2 marks
522 // a1*
523 // / \.
524 // a2 a3
525 // / \.
526 // b4* b5*
527 // \ /
528 // b6
529 test_a_scalar_merge(scalar_b, "45", "2456", scalar_a, "1", "3", scalar_b);
530
531 // different, right wins with 2 marks
532 // a1*
533 // / \.
534 // a2 a3
535 // / \.
536 // b4* b5*
537 // \ /
538 // b6
539 test_a_scalar_merge(scalar_a, "1", "2", scalar_b, "45", "3456", scalar_b);
540
541 // different, conflict with 1 mark winning, 1 mark losing
542 // .1*
543 // / \.
544 // a2* a3*
545 // \ / \.
546 // a4 b5*
547 test_a_scalar_merge(scalar_a, "23", "24", scalar_b, "5", "5", scalar_conflict);
548
549 //
550 // .1*
551 // / \.
552 // a2* a3*
553 // / \ /
554 // b4* a5
555 test_a_scalar_merge(scalar_b, "4", "4", scalar_a, "23", "35", scalar_conflict);
556
557 // different, conflict with 2 marks both conflicting
558 //
559 // .1*
560 // / \.
561 // .2 a3*
562 // / \.
563 // b4* b5*
564 // \ /
565 // b6
566 test_a_scalar_merge(scalar_b, "45", "2456", scalar_a, "3", "3", scalar_conflict);
567
568 //
569 // .1*
570 // / \.
571 // a2* .3
572 // / \.
573 // b4* b5*
574 // \ /
575 // b6
576 test_a_scalar_merge(scalar_a, "2", "2", scalar_b, "45", "3456", scalar_conflict);
577
578 //
579 // _.1*_
580 // / \.
581 // .2 .3
582 // / \ / \.
583 // a4* a5* b6* b7*
584 // \ / \ /
585 // a8 b9
586 test_a_scalar_merge(scalar_a, "45", "2458", scalar_b, "67", "3679", scalar_conflict);
587 }
588
589 namespace
590 {
591 const revision_id a_uncommon1(string(constants::idlen_bytes, '\xaa'), origin::internal);
592 const revision_id a_uncommon2(string(constants::idlen_bytes, '\xbb'), origin::internal);
593 const revision_id b_uncommon1(string(constants::idlen_bytes, '\xcc'), origin::internal);
594 const revision_id b_uncommon2(string(constants::idlen_bytes, '\xdd'), origin::internal);
595 const revision_id common1(string(constants::idlen_bytes, '\xee'), origin::internal);
596 const revision_id common2(string(constants::idlen_bytes, '\xff'), origin::internal);
597
598 const file_id fid1(string(constants::idlen_bytes, '\x11'), origin::internal);
599 const file_id fid2(string(constants::idlen_bytes, '\x22'), origin::internal);
600 }
601
602 static void
make_dir(roster_t & r,marking_map & markings,revision_id const & birth_rid,revision_id const & parent_name_rid,string const & name,node_id nid)603 make_dir(roster_t & r, marking_map & markings,
604 revision_id const & birth_rid, revision_id const & parent_name_rid,
605 string const & name, node_id nid)
606 {
607 r.create_dir_node(nid);
608 r.attach_node(nid, file_path_internal(name));
609 marking_t m(new marking());
610 m->birth_revision = birth_rid;
611 m->parent_name.insert(parent_name_rid);
612 markings.put_marking(nid, m);
613 }
614
615 static void
make_file(roster_t & r,marking_map & markings,revision_id const & birth_rid,revision_id const & parent_name_rid,revision_id const & file_content_rid,string const & name,file_id const & content,node_id nid)616 make_file(roster_t & r, marking_map & markings,
617 revision_id const & birth_rid, revision_id const & parent_name_rid,
618 revision_id const & file_content_rid,
619 string const & name, file_id const & content,
620 node_id nid)
621 {
622 r.create_file_node(content, nid);
623 r.attach_node(nid, file_path_internal(name));
624 marking_t m(new marking());
625 m->birth_revision = birth_rid;
626 m->parent_name.insert(parent_name_rid);
627 m->file_content.insert(file_content_rid);
628 markings.put_marking(nid, m);
629 }
630
631 static void
make_node_lifecycle_objs(roster_t & r,marking_map & markings,revision_id const & uncommon,string const & name,node_id common_dir_nid,node_id common_file_nid,node_id & safe_dir_nid,node_id & safe_file_nid,node_id_source & nis)632 make_node_lifecycle_objs(roster_t & r, marking_map & markings, revision_id const & uncommon,
633 string const & name, node_id common_dir_nid, node_id common_file_nid,
634 node_id & safe_dir_nid, node_id & safe_file_nid, node_id_source & nis)
635 {
636 make_dir(r, markings, common1, common1, "common_old_dir", common_dir_nid);
637 make_file(r, markings, common1, common1, common1, "common_old_file", fid1, common_file_nid);
638 safe_dir_nid = nis.next();
639 make_dir(r, markings, uncommon, uncommon, name + "_safe_dir", safe_dir_nid);
640 safe_file_nid = nis.next();
641 make_file(r, markings, uncommon, uncommon, uncommon, name + "_safe_file", fid1, safe_file_nid);
642 make_dir(r, markings, common1, common1, name + "_dead_dir", nis.next());
643 make_file(r, markings, common1, common1, common1, name + "_dead_file", fid1, nis.next());
644 }
645
UNIT_TEST(node_lifecycle)646 UNIT_TEST(node_lifecycle)
647 {
648 roster_t a_roster, b_roster;
649 marking_map a_markings, b_markings;
650 set<revision_id> a_uncommon, b_uncommon;
651 // boilerplate to get uncommon revision sets...
652 a_uncommon.insert(a_uncommon1);
653 a_uncommon.insert(a_uncommon2);
654 b_uncommon.insert(b_uncommon1);
655 b_uncommon.insert(b_uncommon2);
656 testing_node_id_source nis;
657 // boilerplate to set up a root node...
658 {
659 node_id root_nid = nis.next();
660 make_dir(a_roster, a_markings, common1, common1, "", root_nid);
661 make_dir(b_roster, b_markings, common1, common1, "", root_nid);
662 }
663 // create some nodes on each side
664 node_id common_dir_nid = nis.next();
665 node_id common_file_nid = nis.next();
666 node_id a_safe_dir_nid, a_safe_file_nid, b_safe_dir_nid, b_safe_file_nid;
667 make_node_lifecycle_objs(a_roster, a_markings, a_uncommon1, "a", common_dir_nid, common_file_nid,
668 a_safe_dir_nid, a_safe_file_nid, nis);
669 make_node_lifecycle_objs(b_roster, b_markings, b_uncommon1, "b", common_dir_nid, common_file_nid,
670 b_safe_dir_nid, b_safe_file_nid, nis);
671 // do the merge
672 roster_merge_result result;
673 roster_merge(a_roster, a_markings, a_uncommon, b_roster, b_markings, b_uncommon, result);
674 I(result.is_clean());
675 // go ahead and check the roster_delta code too, while we're at it...
676 test_roster_delta_on(a_roster, a_markings, b_roster, b_markings);
677 // 7 = 1 root + 2 common + 2 safe a + 2 safe b
678 I(result.roster.all_nodes().size() == 7);
679 // check that they're the right ones...
680 I(shallow_equal(result.roster.get_node(common_dir_nid),
681 a_roster.get_node(common_dir_nid), false));
682 I(shallow_equal(result.roster.get_node(common_file_nid),
683 a_roster.get_node(common_file_nid), false));
684 I(shallow_equal(result.roster.get_node(common_dir_nid),
685 b_roster.get_node(common_dir_nid), false));
686 I(shallow_equal(result.roster.get_node(common_file_nid),
687 b_roster.get_node(common_file_nid), false));
688 I(shallow_equal(result.roster.get_node(a_safe_dir_nid),
689 a_roster.get_node(a_safe_dir_nid), false));
690 I(shallow_equal(result.roster.get_node(a_safe_file_nid),
691 a_roster.get_node(a_safe_file_nid), false));
692 I(shallow_equal(result.roster.get_node(b_safe_dir_nid),
693 b_roster.get_node(b_safe_dir_nid), false));
694 I(shallow_equal(result.roster.get_node(b_safe_file_nid),
695 b_roster.get_node(b_safe_file_nid), false));
696 }
697
UNIT_TEST(attr_lifecycle)698 UNIT_TEST(attr_lifecycle)
699 {
700 roster_t left_roster, right_roster;
701 marking_map left_markings, right_markings;
702 MM(left_roster);
703 MM(left_markings);
704 MM(right_roster);
705 MM(right_markings);
706 set<revision_id> old_revs, left_revs, right_revs;
707 string_to_set("0", old_revs);
708 string_to_set("1", left_revs);
709 string_to_set("2", right_revs);
710 revision_id old_rid = *old_revs.begin();
711 testing_node_id_source nis;
712 node_id dir_nid = nis.next();
713 make_dir(left_roster, left_markings, old_rid, old_rid, "", dir_nid);
714 make_dir(right_roster, right_markings, old_rid, old_rid, "", dir_nid);
715 node_id file_nid = nis.next();
716 make_file(left_roster, left_markings, old_rid, old_rid, old_rid, "thing", fid1, file_nid);
717 make_file(right_roster, right_markings, old_rid, old_rid, old_rid, "thing", fid1, file_nid);
718
719 // put one live and one dead attr on each thing on each side, with uncommon
720 // marks on them
721 safe_insert(left_roster.get_node_for_update(dir_nid)->attrs,
722 make_pair(attr_key("left_live"), make_pair(true, attr_value("left_live"))));
723 safe_insert(left_markings.get_marking_for_update(dir_nid)->attrs, make_pair(attr_key("left_live"), left_revs));
724 safe_insert(left_roster.get_node_for_update(dir_nid)->attrs,
725 make_pair(attr_key("left_dead"), make_pair(false, attr_value(""))));
726 safe_insert(left_markings.get_marking_for_update(dir_nid)->attrs, make_pair(attr_key("left_dead"), left_revs));
727 safe_insert(left_roster.get_node_for_update(file_nid)->attrs,
728 make_pair(attr_key("left_live"), make_pair(true, attr_value("left_live"))));
729 safe_insert(left_markings.get_marking_for_update(file_nid)->attrs, make_pair(attr_key("left_live"), left_revs));
730 safe_insert(left_roster.get_node_for_update(file_nid)->attrs,
731 make_pair(attr_key("left_dead"), make_pair(false, attr_value(""))));
732 safe_insert(left_markings.get_marking_for_update(file_nid)->attrs, make_pair(attr_key("left_dead"), left_revs));
733
734 safe_insert(right_roster.get_node_for_update(dir_nid)->attrs,
735 make_pair(attr_key("right_live"), make_pair(true, attr_value("right_live"))));
736 safe_insert(right_markings.get_marking_for_update(dir_nid)->attrs, make_pair(attr_key("right_live"), right_revs));
737 safe_insert(right_roster.get_node_for_update(dir_nid)->attrs,
738 make_pair(attr_key("right_dead"), make_pair(false, attr_value(""))));
739 safe_insert(right_markings.get_marking_for_update(dir_nid)->attrs, make_pair(attr_key("right_dead"), right_revs));
740 safe_insert(right_roster.get_node_for_update(file_nid)->attrs,
741 make_pair(attr_key("right_live"), make_pair(true, attr_value("right_live"))));
742 safe_insert(right_markings.get_marking_for_update(file_nid)->attrs, make_pair(attr_key("right_live"), right_revs));
743 safe_insert(right_roster.get_node_for_update(file_nid)->attrs,
744 make_pair(attr_key("right_dead"), make_pair(false, attr_value(""))));
745 safe_insert(right_markings.get_marking_for_update(file_nid)->attrs, make_pair(attr_key("right_dead"), right_revs));
746
747 roster_merge_result result;
748 MM(result);
749 roster_merge(left_roster, left_markings, left_revs,
750 right_roster, right_markings, right_revs,
751 result);
752 // go ahead and check the roster_delta code too, while we're at it...
753 test_roster_delta_on(left_roster, left_markings, right_roster, right_markings);
754 I(result.roster.all_nodes().size() == 2);
755 I(result.roster.get_node(dir_nid)->attrs.size() == 4);
756 I(safe_get(result.roster.get_node(dir_nid)->attrs, attr_key("left_live")) == make_pair(true, attr_value("left_live")));
757 I(safe_get(result.roster.get_node(dir_nid)->attrs, attr_key("left_dead")) == make_pair(false, attr_value("")));
758 I(safe_get(result.roster.get_node(dir_nid)->attrs, attr_key("right_live")) == make_pair(true, attr_value("right_live")));
759 I(safe_get(result.roster.get_node(dir_nid)->attrs, attr_key("left_dead")) == make_pair(false, attr_value("")));
760 I(result.roster.get_node(file_nid)->attrs.size() == 4);
761 I(safe_get(result.roster.get_node(file_nid)->attrs, attr_key("left_live")) == make_pair(true, attr_value("left_live")));
762 I(safe_get(result.roster.get_node(file_nid)->attrs, attr_key("left_dead")) == make_pair(false, attr_value("")));
763 I(safe_get(result.roster.get_node(file_nid)->attrs, attr_key("right_live")) == make_pair(true, attr_value("right_live")));
764 I(safe_get(result.roster.get_node(file_nid)->attrs, attr_key("left_dead")) == make_pair(false, attr_value("")));
765 }
766
767 struct structural_conflict_helper
768 {
769 roster_t left_roster, right_roster;
770 marking_map left_markings, right_markings;
771 set<revision_id> old_revs, left_revs, right_revs;
772 revision_id old_rid, left_rid, right_rid;
773 testing_node_id_source nis;
774 node_id root_nid;
775 roster_merge_result result;
776
777 virtual void setup() = 0;
778 virtual void check() = 0;
779
teststructural_conflict_helper780 void test()
781 {
782 MM(left_roster);
783 MM(left_markings);
784 MM(right_roster);
785 MM(right_markings);
786 string_to_set("0", old_revs);
787 string_to_set("1", left_revs);
788 string_to_set("2", right_revs);
789 old_rid = *old_revs.begin();
790 left_rid = *left_revs.begin();
791 right_rid = *right_revs.begin();
792 root_nid = nis.next();
793 make_dir(left_roster, left_markings, old_rid, old_rid, "", root_nid);
794 make_dir(right_roster, right_markings, old_rid, old_rid, "", root_nid);
795
796 setup();
797
798 MM(result);
799 roster_merge(left_roster, left_markings, left_revs,
800 right_roster, right_markings, right_revs,
801 result);
802 // go ahead and check the roster_delta code too, while we're at it...
803 test_roster_delta_on(left_roster, left_markings, right_roster, right_markings);
804
805 check();
806 }
807
~structural_conflict_helperstructural_conflict_helper808 virtual ~structural_conflict_helper() {}
809 };
810
811 // two diff nodes with same name
812 struct simple_duplicate_name_conflict : public structural_conflict_helper
813 {
814 node_id left_nid, right_nid;
setupsimple_duplicate_name_conflict815 virtual void setup()
816 {
817 left_nid = nis.next();
818 make_dir(left_roster, left_markings, left_rid, left_rid, "thing", left_nid);
819 right_nid = nis.next();
820 make_dir(right_roster, right_markings, right_rid, right_rid, "thing", right_nid);
821 }
822
checksimple_duplicate_name_conflict823 virtual void check()
824 {
825 I(!result.is_clean());
826 duplicate_name_conflict const & c = idx(result.duplicate_name_conflicts, 0);
827 I(c.left_nid == left_nid && c.right_nid == right_nid);
828 I(c.parent_name == make_pair(root_nid, path_component("thing")));
829 // this tests that they were detached, implicitly
830 result.roster.attach_node(left_nid, file_path_internal("left"));
831 result.roster.attach_node(right_nid, file_path_internal("right"));
832 result.duplicate_name_conflicts.pop_back();
833 I(result.is_clean());
834 result.roster.check_sane();
835 }
836 };
837
838 // directory loops
839 struct simple_dir_loop_conflict : public structural_conflict_helper
840 {
841 node_id left_top_nid, right_top_nid;
842
setupsimple_dir_loop_conflict843 virtual void setup()
844 {
845 left_top_nid = nis.next();
846 right_top_nid = nis.next();
847
848 make_dir(left_roster, left_markings, old_rid, old_rid, "top", left_top_nid);
849 make_dir(left_roster, left_markings, old_rid, left_rid, "top/bottom", right_top_nid);
850
851 make_dir(right_roster, right_markings, old_rid, old_rid, "top", right_top_nid);
852 make_dir(right_roster, right_markings, old_rid, right_rid, "top/bottom", left_top_nid);
853 }
854
checksimple_dir_loop_conflict855 virtual void check()
856 {
857 I(!result.is_clean());
858 directory_loop_conflict const & c = idx(result.directory_loop_conflicts, 0);
859 I((c.nid == left_top_nid && c.parent_name == make_pair(right_top_nid, path_component("bottom")))
860 || (c.nid == right_top_nid && c.parent_name == make_pair(left_top_nid, path_component("bottom"))));
861 // this tests it was detached, implicitly
862 result.roster.attach_node(c.nid, file_path_internal("resolved"));
863 result.directory_loop_conflicts.pop_back();
864 I(result.is_clean());
865 result.roster.check_sane();
866 }
867 };
868
869 // orphans
870 struct simple_orphan_conflict : public structural_conflict_helper
871 {
872 node_id a_dead_parent_nid, a_live_child_nid, b_dead_parent_nid, b_live_child_nid;
873
874 // in ancestor, both parents are alive
875 // in left, a_dead_parent is dead, and b_live_child is created
876 // in right, b_dead_parent is dead, and a_live_child is created
877
setupsimple_orphan_conflict878 virtual void setup()
879 {
880 a_dead_parent_nid = nis.next();
881 a_live_child_nid = nis.next();
882 b_dead_parent_nid = nis.next();
883 b_live_child_nid = nis.next();
884
885 make_dir(left_roster, left_markings, old_rid, old_rid, "b_parent", b_dead_parent_nid);
886 make_dir(left_roster, left_markings, left_rid, left_rid, "b_parent/b_child", b_live_child_nid);
887
888 make_dir(right_roster, right_markings, old_rid, old_rid, "a_parent", a_dead_parent_nid);
889 make_dir(right_roster, right_markings, right_rid, right_rid, "a_parent/a_child", a_live_child_nid);
890 }
891
checksimple_orphan_conflict892 virtual void check()
893 {
894 I(!result.is_clean());
895 I(result.orphaned_node_conflicts.size() == 2);
896 orphaned_node_conflict a, b;
897 if (idx(result.orphaned_node_conflicts, 0).nid == a_live_child_nid)
898 {
899 a = idx(result.orphaned_node_conflicts, 0);
900 b = idx(result.orphaned_node_conflicts, 1);
901 }
902 else
903 {
904 a = idx(result.orphaned_node_conflicts, 1);
905 b = idx(result.orphaned_node_conflicts, 0);
906 }
907 I(a.nid == a_live_child_nid);
908 I(a.parent_name == make_pair(a_dead_parent_nid, path_component("a_child")));
909 I(b.nid == b_live_child_nid);
910 I(b.parent_name == make_pair(b_dead_parent_nid, path_component("b_child")));
911 // this tests it was detached, implicitly
912 result.roster.attach_node(a.nid, file_path_internal("resolved_a"));
913 result.roster.attach_node(b.nid, file_path_internal("resolved_b"));
914 result.orphaned_node_conflicts.pop_back();
915 result.orphaned_node_conflicts.pop_back();
916 I(result.is_clean());
917 result.roster.check_sane();
918 }
919 };
920
921 // illegal node ("_MTN")
922 struct simple_invalid_name_conflict : public structural_conflict_helper
923 {
924 node_id new_root_nid, bad_dir_nid;
925
926 // in left, new_root is the root (it existed in old, but was renamed in left)
927 // in right, new_root is still a subdir, the old root still exists, and a
928 // new dir has been created
929
setupsimple_invalid_name_conflict930 virtual void setup()
931 {
932 new_root_nid = nis.next();
933 bad_dir_nid = nis.next();
934
935 left_roster.drop_detached_node(left_roster.detach_node(file_path()));
936 left_markings.remove_marking(root_nid);
937 make_dir(left_roster, left_markings, old_rid, left_rid, "", new_root_nid);
938
939 make_dir(right_roster, right_markings, old_rid, old_rid, "root_to_be", new_root_nid);
940 make_dir(right_roster, right_markings, right_rid, right_rid, "root_to_be/_MTN", bad_dir_nid);
941 }
942
checksimple_invalid_name_conflict943 virtual void check()
944 {
945 I(!result.is_clean());
946 invalid_name_conflict const & c = idx(result.invalid_name_conflicts, 0);
947 I(c.nid == bad_dir_nid);
948 I(c.parent_name == make_pair(new_root_nid, bookkeeping_root_component));
949 // this tests it was detached, implicitly
950 result.roster.attach_node(bad_dir_nid, file_path_internal("dir_formerly_known_as__MTN"));
951 result.invalid_name_conflicts.pop_back();
952 I(result.is_clean());
953 result.roster.check_sane();
954 }
955 };
956
957 // missing root dir
958 struct simple_missing_root_dir : public structural_conflict_helper
959 {
960 node_id other_root_nid;
961
962 // left and right each have different root nodes, and each has deleted the
963 // other's root node
964
setupsimple_missing_root_dir965 virtual void setup()
966 {
967 other_root_nid = nis.next();
968
969 left_roster.drop_detached_node(left_roster.detach_node(file_path()));
970 left_markings.remove_marking(root_nid);
971 make_dir(left_roster, left_markings, old_rid, old_rid, "", other_root_nid);
972 }
973
checksimple_missing_root_dir974 virtual void check()
975 {
976 I(!result.is_clean());
977 I(result.missing_root_conflict);
978 result.roster.attach_node(result.roster.create_dir_node(nis), file_path());
979 result.missing_root_conflict = false;
980 I(result.is_clean());
981 result.roster.check_sane();
982 }
983 };
984
UNIT_TEST(simple_structural_conflicts)985 UNIT_TEST(simple_structural_conflicts)
986 {
987 {
988 simple_duplicate_name_conflict t;
989 t.test();
990 }
991 {
992 simple_dir_loop_conflict t;
993 t.test();
994 }
995 {
996 simple_orphan_conflict t;
997 t.test();
998 }
999 {
1000 simple_invalid_name_conflict t;
1001 t.test();
1002 }
1003 {
1004 simple_missing_root_dir t;
1005 t.test();
1006 }
1007 }
1008
1009 struct multiple_name_plus_helper : public structural_conflict_helper
1010 {
1011 node_id name_conflict_nid;
1012 node_id left_parent, right_parent;
1013 path_component left_name, right_name;
make_multiple_name_conflictmultiple_name_plus_helper1014 void make_multiple_name_conflict(string const & left, string const & right)
1015 {
1016 file_path left_path = file_path_internal(left);
1017 file_path right_path = file_path_internal(right);
1018 name_conflict_nid = nis.next();
1019 make_dir(left_roster, left_markings, old_rid, left_rid, left, name_conflict_nid);
1020 left_parent = left_roster.get_node(left_path)->parent;
1021 left_name = left_roster.get_node(left_path)->name;
1022 make_dir(right_roster, right_markings, old_rid, right_rid, right, name_conflict_nid);
1023 right_parent = right_roster.get_node(right_path)->parent;
1024 right_name = right_roster.get_node(right_path)->name;
1025 }
check_multiple_name_conflictmultiple_name_plus_helper1026 void check_multiple_name_conflict()
1027 {
1028 I(!result.is_clean());
1029 multiple_name_conflict const & c = idx(result.multiple_name_conflicts, 0);
1030 I(c.nid == name_conflict_nid);
1031 I(c.left == make_pair(left_parent, left_name));
1032 I(c.right == make_pair(right_parent, right_name));
1033 result.roster.attach_node(name_conflict_nid, file_path_internal("totally_other_name"));
1034 result.multiple_name_conflicts.pop_back();
1035 I(result.is_clean());
1036 result.roster.check_sane();
1037 }
1038 };
1039
1040 struct multiple_name_plus_duplicate_name : public multiple_name_plus_helper
1041 {
1042 node_id a_nid, b_nid;
1043
setupmultiple_name_plus_duplicate_name1044 virtual void setup()
1045 {
1046 a_nid = nis.next();
1047 b_nid = nis.next();
1048 make_multiple_name_conflict("a", "b");
1049 make_dir(left_roster, left_markings, left_rid, left_rid, "b", b_nid);
1050 make_dir(right_roster, right_markings, right_rid, right_rid, "a", a_nid);
1051 }
1052
checkmultiple_name_plus_duplicate_name1053 virtual void check()
1054 {
1055 // there should just be a single conflict on name_conflict_nid, and a and
1056 // b should have landed fine
1057 I(result.roster.get_node(file_path_internal("a"))->self == a_nid);
1058 I(result.roster.get_node(file_path_internal("b"))->self == b_nid);
1059 check_multiple_name_conflict();
1060 }
1061 };
1062
1063 struct multiple_name_plus_orphan : public multiple_name_plus_helper
1064 {
1065 node_id a_nid, b_nid;
1066
setupmultiple_name_plus_orphan1067 virtual void setup()
1068 {
1069 a_nid = nis.next();
1070 b_nid = nis.next();
1071 make_dir(left_roster, left_markings, old_rid, left_rid, "a", a_nid);
1072 make_dir(right_roster, right_markings, old_rid, right_rid, "b", b_nid);
1073 make_multiple_name_conflict("a/foo", "b/foo");
1074 }
1075
checkmultiple_name_plus_orphan1076 virtual void check()
1077 {
1078 I(result.roster.all_nodes().size() == 2);
1079 check_multiple_name_conflict();
1080 }
1081 };
1082
1083 struct multiple_name_plus_directory_loop : public multiple_name_plus_helper
1084 {
1085 node_id a_nid, b_nid;
1086
setupmultiple_name_plus_directory_loop1087 virtual void setup()
1088 {
1089 a_nid = nis.next();
1090 b_nid = nis.next();
1091 make_dir(left_roster, left_markings, old_rid, old_rid, "a", a_nid);
1092 make_dir(right_roster, right_markings, old_rid, old_rid, "b", b_nid);
1093 make_multiple_name_conflict("a/foo", "b/foo");
1094 make_dir(left_roster, left_markings, old_rid, left_rid, "a/foo/b", b_nid);
1095 make_dir(right_roster, right_markings, old_rid, right_rid, "b/foo/a", a_nid);
1096 }
1097
checkmultiple_name_plus_directory_loop1098 virtual void check()
1099 {
1100 I(downcast_to_dir_t(result.roster.get_node(name_conflict_nid))->children.size() == 2);
1101 check_multiple_name_conflict();
1102 }
1103 };
1104
1105 struct multiple_name_plus_invalid_name : public multiple_name_plus_helper
1106 {
1107 node_id new_root_nid;
1108
setupmultiple_name_plus_invalid_name1109 virtual void setup()
1110 {
1111 new_root_nid = nis.next();
1112 make_dir(left_roster, left_markings, old_rid, old_rid, "new_root", new_root_nid);
1113 right_roster.drop_detached_node(right_roster.detach_node(file_path()));
1114 right_markings.remove_marking(root_nid);
1115 make_dir(right_roster, right_markings, old_rid, right_rid, "", new_root_nid);
1116 make_multiple_name_conflict("new_root/_MTN", "foo");
1117 }
1118
checkmultiple_name_plus_invalid_name1119 virtual void check()
1120 {
1121 I(result.roster.root()->self == new_root_nid);
1122 I(result.roster.all_nodes().size() == 2);
1123 check_multiple_name_conflict();
1124 }
1125 };
1126
1127 struct multiple_name_plus_missing_root : public structural_conflict_helper
1128 {
1129 node_id left_root_nid, right_root_nid;
1130
setupmultiple_name_plus_missing_root1131 virtual void setup()
1132 {
1133 left_root_nid = nis.next();
1134 right_root_nid = nis.next();
1135
1136 left_roster.drop_detached_node(left_roster.detach_node(file_path()));
1137 left_markings.remove_marking(root_nid);
1138 make_dir(left_roster, left_markings, old_rid, left_rid, "", left_root_nid);
1139 make_dir(left_roster, left_markings, old_rid, left_rid, "right_root", right_root_nid);
1140
1141 right_roster.drop_detached_node(right_roster.detach_node(file_path()));
1142 right_markings.remove_marking(root_nid);
1143 make_dir(right_roster, right_markings, old_rid, right_rid, "", right_root_nid);
1144 make_dir(right_roster, right_markings, old_rid, right_rid, "left_root", left_root_nid);
1145 }
check_helpermultiple_name_plus_missing_root1146 void check_helper(multiple_name_conflict const & left_c,
1147 multiple_name_conflict const & right_c)
1148 {
1149 I(left_c.nid == left_root_nid);
1150 I(left_c.left == make_pair(the_null_node, path_component()));
1151 I(left_c.right == make_pair(right_root_nid, path_component("left_root")));
1152
1153 I(right_c.nid == right_root_nid);
1154 I(right_c.left == make_pair(left_root_nid, path_component("right_root")));
1155 I(right_c.right == make_pair(the_null_node, path_component()));
1156 }
checkmultiple_name_plus_missing_root1157 virtual void check()
1158 {
1159 I(!result.is_clean());
1160 I(result.multiple_name_conflicts.size() == 2);
1161
1162 if (idx(result.multiple_name_conflicts, 0).nid == left_root_nid)
1163 check_helper(idx(result.multiple_name_conflicts, 0),
1164 idx(result.multiple_name_conflicts, 1));
1165 else
1166 check_helper(idx(result.multiple_name_conflicts, 1),
1167 idx(result.multiple_name_conflicts, 0));
1168
1169 I(result.missing_root_conflict);
1170
1171 result.roster.attach_node(left_root_nid, file_path());
1172 result.roster.attach_node(right_root_nid, file_path_internal("totally_other_name"));
1173 result.multiple_name_conflicts.pop_back();
1174 result.multiple_name_conflicts.pop_back();
1175 result.missing_root_conflict = false;
1176 I(result.is_clean());
1177 result.roster.check_sane();
1178 }
1179 };
1180
1181 struct duplicate_name_plus_missing_root : public structural_conflict_helper
1182 {
1183 node_id left_root_nid, right_root_nid;
1184
setupduplicate_name_plus_missing_root1185 virtual void setup()
1186 {
1187 left_root_nid = nis.next();
1188 right_root_nid = nis.next();
1189
1190 left_roster.drop_detached_node(left_roster.detach_node(file_path()));
1191 left_markings.remove_marking(root_nid);
1192 make_dir(left_roster, left_markings, left_rid, left_rid, "", left_root_nid);
1193
1194 right_roster.drop_detached_node(right_roster.detach_node(file_path()));
1195 right_markings.remove_marking(root_nid);
1196 make_dir(right_roster, right_markings, right_rid, right_rid, "", right_root_nid);
1197 }
checkduplicate_name_plus_missing_root1198 virtual void check()
1199 {
1200 I(!result.is_clean());
1201 duplicate_name_conflict const & c = idx(result.duplicate_name_conflicts, 0);
1202 I(c.left_nid == left_root_nid && c.right_nid == right_root_nid);
1203 I(c.parent_name == make_pair(the_null_node, path_component()));
1204
1205 I(result.missing_root_conflict);
1206
1207 // we can't just attach one of these as the root -- see the massive
1208 // comment on the old_locations member of roster_t, in roster.hh.
1209 result.roster.attach_node(result.roster.create_dir_node(nis), file_path());
1210 result.roster.attach_node(left_root_nid, file_path_internal("totally_left_name"));
1211 result.roster.attach_node(right_root_nid, file_path_internal("totally_right_name"));
1212 result.duplicate_name_conflicts.pop_back();
1213 result.missing_root_conflict = false;
1214 I(result.is_clean());
1215 result.roster.check_sane();
1216 }
1217 };
1218
UNIT_TEST(complex_structural_conflicts)1219 UNIT_TEST(complex_structural_conflicts)
1220 {
1221 {
1222 multiple_name_plus_duplicate_name t;
1223 t.test();
1224 }
1225 {
1226 multiple_name_plus_orphan t;
1227 t.test();
1228 }
1229 {
1230 multiple_name_plus_directory_loop t;
1231 t.test();
1232 }
1233 {
1234 multiple_name_plus_invalid_name t;
1235 t.test();
1236 }
1237 {
1238 multiple_name_plus_missing_root t;
1239 t.test();
1240 }
1241 {
1242 duplicate_name_plus_missing_root t;
1243 t.test();
1244 }
1245 }
1246
1247 // Local Variables:
1248 // mode: C++
1249 // fill-column: 76
1250 // c-file-style: "gnu"
1251 // indent-tabs-mode: nil
1252 // End:
1253 // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
1254