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