monotone

monotone Mtn Source Tree

Root/cset.cc

1// Copyright (C) 2005 Nathaniel Smith <njs@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include <map>
11#include <set>
12#include <string>
13
14#include "basic_io.hh"
15#include "cset.hh"
16#include "sanity.hh"
17#include "safe_map.hh"
18
19using std::set;
20using std::map;
21using std::pair;
22using std::string;
23using std::make_pair;
24
25static void
26check_normalized(cset const & cs)
27{
28 MM(cs);
29
30 // normalize:
31 //
32 // add_file foo@id1 + apply_delta id1->id2
33 // clear_attr foo:bar + set_attr foo:bar=baz
34 //
35 // possibly more?
36
37 // no file appears in both the "added" list and the "patched" list
38 {
39 map<split_path, file_id>::const_iterator a = cs.files_added.begin();
40 map<split_path, pair<file_id, file_id> >::const_iterator
41 d = cs.deltas_applied.begin();
42 while (a != cs.files_added.end() && d != cs.deltas_applied.end())
43 {
44 // SPEEDUP?: should this use lower_bound instead of ++? it should
45 // make this loop iterate about as many times as the shorter list,
46 // rather the sum of the two lists...
47 if (a->first < d->first)
48 ++a;
49 else if (d->first < a->first)
50 ++d;
51 else
52 I(false);
53 }
54 }
55
56 // no file+attr pair appears in both the "set" list and the "cleared" list
57 {
58 set<pair<split_path, attr_key> >::const_iterator c = cs.attrs_cleared.begin();
59 map<pair<split_path, attr_key>, attr_value>::const_iterator
60 s = cs.attrs_set.begin();
61 while (c != cs.attrs_cleared.end() && s != cs.attrs_set.end())
62 {
63 if (*c < s->first)
64 ++c;
65 else if (s->first < *c)
66 ++s;
67 else
68 I(false);
69 }
70 }
71}
72
73bool
74cset::empty() const
75{
76 return
77 nodes_deleted.empty()
78 && dirs_added.empty()
79 && files_added.empty()
80 && nodes_renamed.empty()
81 && deltas_applied.empty()
82 && attrs_cleared.empty()
83 && attrs_set.empty();
84}
85
86void
87cset::clear()
88{
89 nodes_deleted.clear();
90 dirs_added.clear();
91 files_added.clear();
92 nodes_renamed.clear();
93 deltas_applied.clear();
94 attrs_cleared.clear();
95 attrs_set.clear();
96}
97
98struct
99detach
100{
101 detach(split_path const & src)
102 : src_path(src),
103 reattach(false)
104 {}
105
106 detach(split_path const & src,
107 split_path const & dst)
108 : src_path(src),
109 reattach(true),
110 dst_path(dst)
111 {}
112
113 split_path src_path;
114 bool reattach;
115 split_path dst_path;
116
117 bool operator<(struct detach const & other) const
118 {
119 // We sort detach operations bottom-up by src path
120 // SPEEDUP?: simply sort by path.size() rather than full lexicographical
121 // comparison?
122 return src_path > other.src_path;
123 }
124};
125
126struct
127attach
128{
129 attach(node_id n,
130 split_path const & p)
131 : node(n), path(p)
132 {}
133
134 node_id node;
135 split_path path;
136
137 bool operator<(struct attach const & other) const
138 {
139 // We sort attach operations top-down by path
140 // SPEEDUP?: simply sort by path.size() rather than full lexicographical
141 // comparison?
142 return path < other.path;
143 }
144};
145
146void
147cset::apply_to(editable_tree & t) const
148{
149 // SPEEDUP?: use vectors and sort them once, instead of maintaining sorted
150 // sets?
151 set<detach> detaches;
152 set<attach> attaches;
153 set<node_id> drops;
154
155 MM(*this);
156
157 check_normalized(*this);
158
159 // Decompose all additions into a set of pending attachments to be
160 // executed top-down. We might as well do this first, to be sure we
161 // can form the new nodes -- such as in a filesystem -- before we do
162 // anything else potentially destructive. This should all be
163 // happening in a temp directory anyways.
164
165 // NB: it's very important we do safe_insert's here, because our comparison
166 // operator for attach and detach does not distinguish all nodes! the nodes
167 // that it does not distinguish are ones where we're attaching or detaching
168 // repeatedly from the same place, so they're impossible anyway, but we need
169 // to error out if someone tries to add them.
170
171 for (path_set::const_iterator i = dirs_added.begin();
172 i != dirs_added.end(); ++i)
173 safe_insert(attaches, attach(t.create_dir_node(), *i));
174
175 for (map<split_path, file_id>::const_iterator i = files_added.begin();
176 i != files_added.end(); ++i)
177 safe_insert(attaches, attach(t.create_file_node(i->second), i->first));
178
179
180 // Decompose all path deletion and the first-half of renamings on
181 // existing paths into the set of pending detaches, to be executed
182 // bottom-up.
183
184 for (path_set::const_iterator i = nodes_deleted.begin();
185 i != nodes_deleted.end(); ++i)
186 safe_insert(detaches, detach(*i));
187
188 for (map<split_path, split_path>::const_iterator i = nodes_renamed.begin();
189 i != nodes_renamed.end(); ++i)
190 safe_insert(detaches, detach(i->first, i->second));
191
192
193 // Execute all the detaches, rescheduling the results of each detach
194 // for either attaching or dropping.
195
196 for (set<detach>::const_iterator i = detaches.begin();
197 i != detaches.end(); ++i)
198 {
199 node_id n = t.detach_node(i->src_path);
200 if (i->reattach)
201 safe_insert(attaches, attach(n, i->dst_path));
202 else
203 safe_insert(drops, n);
204 }
205
206
207 // Execute all the attaches.
208
209 for (set<attach>::const_iterator i = attaches.begin(); i != attaches.end(); ++i)
210 t.attach_node(i->node, i->path);
211
212
213 // Execute all the drops.
214
215 for (set<node_id>::const_iterator i = drops.begin(); i != drops.end(); ++i)
216 t.drop_detached_node (*i);
217
218
219 // Execute all the in-place edits
220 for (map<split_path, pair<file_id, file_id> >::const_iterator i = deltas_applied.begin();
221 i != deltas_applied.end(); ++i)
222 t.apply_delta(i->first, i->second.first, i->second.second);
223
224 for (set<pair<split_path, attr_key> >::const_iterator i = attrs_cleared.begin();
225 i != attrs_cleared.end(); ++i)
226 t.clear_attr(i->first, i->second);
227
228 for (map<pair<split_path, attr_key>, attr_value>::const_iterator i = attrs_set.begin();
229 i != attrs_set.end(); ++i)
230 t.set_attr(i->first.first, i->first.second, i->second);
231
232 t.commit();
233}
234
235////////////////////////////////////////////////////////////////////
236// I/O routines
237////////////////////////////////////////////////////////////////////
238
239namespace
240{
241 namespace syms
242 {
243 // cset symbols
244 symbol const delete_node("delete");
245 symbol const rename_node("rename");
246 symbol const content("content");
247 symbol const add_file("add_file");
248 symbol const add_dir("add_dir");
249 symbol const patch("patch");
250 symbol const from("from");
251 symbol const to("to");
252 symbol const clear("clear");
253 symbol const set("set");
254 symbol const attr("attr");
255 symbol const value("value");
256 }
257}
258
259void
260print_cset(basic_io::printer & printer,
261 cset const & cs)
262{
263 for (path_set::const_iterator i = cs.nodes_deleted.begin();
264 i != cs.nodes_deleted.end(); ++i)
265 {
266 basic_io::stanza st;
267 st.push_file_pair(syms::delete_node, file_path(*i));
268 printer.print_stanza(st);
269 }
270
271 for (map<split_path, split_path>::const_iterator i = cs.nodes_renamed.begin();
272 i != cs.nodes_renamed.end(); ++i)
273 {
274 basic_io::stanza st;
275 st.push_file_pair(syms::rename_node, file_path(i->first));
276 st.push_file_pair(syms::to, file_path(i->second));
277 printer.print_stanza(st);
278 }
279
280 for (path_set::const_iterator i = cs.dirs_added.begin();
281 i != cs.dirs_added.end(); ++i)
282 {
283 basic_io::stanza st;
284 st.push_file_pair(syms::add_dir, file_path(*i));
285 printer.print_stanza(st);
286 }
287
288 for (map<split_path, file_id>::const_iterator i = cs.files_added.begin();
289 i != cs.files_added.end(); ++i)
290 {
291 basic_io::stanza st;
292 st.push_file_pair(syms::add_file, file_path(i->first));
293 st.push_hex_pair(syms::content, i->second.inner());
294 printer.print_stanza(st);
295 }
296
297 for (map<split_path, pair<file_id, file_id> >::const_iterator i = cs.deltas_applied.begin();
298 i != cs.deltas_applied.end(); ++i)
299 {
300 basic_io::stanza st;
301 st.push_file_pair(syms::patch, file_path(i->first));
302 st.push_hex_pair(syms::from, i->second.first.inner());
303 st.push_hex_pair(syms::to, i->second.second.inner());
304 printer.print_stanza(st);
305 }
306
307 for (set<pair<split_path, attr_key> >::const_iterator i = cs.attrs_cleared.begin();
308 i != cs.attrs_cleared.end(); ++i)
309 {
310 basic_io::stanza st;
311 st.push_file_pair(syms::clear, file_path(i->first));
312 st.push_str_pair(syms::attr, i->second());
313 printer.print_stanza(st);
314 }
315
316 for (map<pair<split_path, attr_key>, attr_value>::const_iterator i = cs.attrs_set.begin();
317 i != cs.attrs_set.end(); ++i)
318 {
319 basic_io::stanza st;
320 st.push_file_pair(syms::set, file_path(i->first.first));
321 st.push_str_pair(syms::attr, i->first.second());
322 st.push_str_pair(syms::value, i->second());
323 printer.print_stanza(st);
324 }
325}
326
327
328static inline void
329parse_path(basic_io::parser & parser, split_path & sp)
330{
331 string s;
332 parser.str(s);
333 file_path_internal(s).split(sp);
334}
335
336void
337parse_cset(basic_io::parser & parser,
338 cset & cs)
339{
340 cs.clear();
341 string t1, t2;
342 MM(t1);
343 MM(t2);
344 split_path p1, p2;
345 MM(p1);
346 MM(p2);
347
348 split_path prev_path;
349 MM(prev_path);
350 pair<split_path, attr_key> prev_pair;
351 MM(prev_pair.first);
352 MM(prev_pair.second);
353
354 // we make use of the fact that a valid split_path is never empty
355 prev_path.clear();
356 while (parser.symp(syms::delete_node))
357 {
358 parser.sym();
359 parse_path(parser, p1);
360 I(prev_path.empty() || p1 > prev_path);
361 prev_path = p1;
362 safe_insert(cs.nodes_deleted, p1);
363 }
364
365 prev_path.clear();
366 while (parser.symp(syms::rename_node))
367 {
368 parser.sym();
369 parse_path(parser, p1);
370 I(prev_path.empty() || p1 > prev_path);
371 prev_path = p1;
372 parser.esym(syms::to);
373 parse_path(parser, p2);
374 safe_insert(cs.nodes_renamed, make_pair(p1, p2));
375 }
376
377 prev_path.clear();
378 while (parser.symp(syms::add_dir))
379 {
380 parser.sym();
381 parse_path(parser, p1);
382 I(prev_path.empty() || p1 > prev_path);
383 prev_path = p1;
384 safe_insert(cs.dirs_added, p1);
385 }
386
387 prev_path.clear();
388 while (parser.symp(syms::add_file))
389 {
390 parser.sym();
391 parse_path(parser, p1);
392 I(prev_path.empty() || p1 > prev_path);
393 prev_path = p1;
394 parser.esym(syms::content);
395 parser.hex(t1);
396 safe_insert(cs.files_added, make_pair(p1, file_id(t1)));
397 }
398
399 prev_path.clear();
400 while (parser.symp(syms::patch))
401 {
402 parser.sym();
403 parse_path(parser, p1);
404 I(prev_path.empty() || p1 > prev_path);
405 prev_path = p1;
406 parser.esym(syms::from);
407 parser.hex(t1);
408 parser.esym(syms::to);
409 parser.hex(t2);
410 safe_insert(cs.deltas_applied,
411 make_pair(p1, make_pair(file_id(t1), file_id(t2))));
412 }
413
414 prev_pair.first.clear();
415 while (parser.symp(syms::clear))
416 {
417 parser.sym();
418 parse_path(parser, p1);
419 parser.esym(syms::attr);
420 parser.str(t1);
421 pair<split_path, attr_key> new_pair(p1, attr_key(t1));
422 I(prev_pair.first.empty() || new_pair > prev_pair);
423 prev_pair = new_pair;
424 safe_insert(cs.attrs_cleared, new_pair);
425 }
426
427 prev_pair.first.clear();
428 while (parser.symp(syms::set))
429 {
430 parser.sym();
431 parse_path(parser, p1);
432 parser.esym(syms::attr);
433 parser.str(t1);
434 pair<split_path, attr_key> new_pair(p1, attr_key(t1));
435 I(prev_pair.first.empty() || new_pair > prev_pair);
436 prev_pair = new_pair;
437 parser.esym(syms::value);
438 parser.str(t2);
439 safe_insert(cs.attrs_set, make_pair(new_pair, attr_value(t2)));
440 }
441}
442
443void
444write_cset(cset const & cs, data & dat)
445{
446 basic_io::printer pr;
447 print_cset(pr, cs);
448 dat = data(pr.buf);
449}
450
451void
452read_cset(data const & dat, cset & cs)
453{
454 MM(dat);
455 MM(cs);
456 basic_io::input_source src(dat(), "cset");
457 basic_io::tokenizer tok(src);
458 basic_io::parser pars(tok);
459 parse_cset(pars, cs);
460 I(src.lookahead == EOF);
461}
462
463template <> void
464dump(cset const & cs, string & out)
465{
466 data dat;
467 write_cset(cs, dat);
468 out = dat();
469}
470
471#ifdef BUILD_UNIT_TESTS
472#include "unit_tests.hh"
473
474#include "roster.hh"
475
476using std::logic_error;
477
478static void
479setup_roster(roster_t & r, file_id const & fid, node_id_source & nis)
480{
481 // sets up r to have a root dir, a dir in it name "foo", and a file under
482 // that named "bar", and the file has the given id.
483 // the file has attr "attr_file=value_file", and the dir has
484 // "attr_dir=value_dir".
485 r = roster_t();
486
487 {
488 split_path sp;
489 file_path().split(sp);
490 r.attach_node(r.create_dir_node(nis), sp);
491 }
492 {
493 split_path sp;
494 file_path_internal("foo").split(sp);
495 r.attach_node(r.create_dir_node(nis), sp);
496 r.set_attr(sp, attr_key("attr_dir"), attr_value("value_dir"));
497 }
498 {
499 split_path sp;
500 file_path_internal("foo/bar").split(sp);
501 r.attach_node(r.create_file_node(fid, nis), sp);
502 r.set_attr(sp, attr_key("attr_file"), attr_value("value_file"));
503 }
504}
505
506UNIT_TEST(cset, cset_written)
507{
508 {
509 L(FL("TEST: cset reading - operation misordering"));
510 // bad cset, add_dir should be before add_file
511 string s("delete \"foo\"\n"
512 "\n"
513 "rename \"quux\"\n"
514 " to \"baz\"\n"
515 "\n"
516 "add_file \"bar\"\n"
517 " content [0000000000000000000000000000000000000000]\n"
518 "\n"
519 "add_dir \"pling\"\n");
520 data d1(s);
521 cset cs;
522 BOOST_CHECK_THROW(read_cset(d1, cs), logic_error);
523 // check that it still fails if there's extra stanzas past the
524 // mis-ordered entries
525 data d2(s + "\n"
526 " set \"bar\"\n"
527 " attr \"flavoursome\"\n"
528 "value \"mostly\"\n");
529 BOOST_CHECK_THROW(read_cset(d2, cs), logic_error);
530 }
531
532 {
533 L(FL("TEST: cset reading - misordered files in delete"));
534 // bad cset, bar should be before foo
535 data dat("delete \"foo\"\n"
536 "\n"
537 "delete \"bar\"\n");
538 cset cs;
539 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
540 }
541
542 {
543 L(FL("TEST: cset reading - misordered files in rename"));
544 // bad cset, bar should be before foo
545 data dat("rename \"foo\"\n"
546 " to \"foonew\"\n"
547 "\n"
548 "rename \"bar\"\n"
549 " to \"barnew\"\n");
550 cset cs;
551 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
552 }
553
554 {
555 L(FL("TEST: cset reading - misordered files in add_dir"));
556 // bad cset, bar should be before foo
557 data dat("add_dir \"foo\"\n"
558 "\n"
559 "add_dir \"bar\"\n");
560 cset cs;
561 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
562 }
563
564 {
565 L(FL("TEST: cset reading - misordered files in add_file"));
566 // bad cset, bar should be before foo
567 data dat("add_file \"foo\"\n"
568 " content [0000000000000000000000000000000000000000]\n"
569 "\n"
570 "add_file \"bar\"\n"
571 " content [0000000000000000000000000000000000000000]\n");
572 cset cs;
573 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
574 }
575
576 {
577 L(FL("TEST: cset reading - misordered files in add_file"));
578 // bad cset, bar should be before foo
579 data dat("add_file \"foo\"\n"
580 " content [0000000000000000000000000000000000000000]\n"
581 "\n"
582 "add_file \"bar\"\n"
583 " content [0000000000000000000000000000000000000000]\n");
584 cset cs;
585 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
586 }
587
588 {
589 L(FL("TEST: cset reading - misordered files in patch"));
590 // bad cset, bar should be before foo
591 data dat("patch \"foo\"\n"
592 " from [0000000000000000000000000000000000000000]\n"
593 " to [1000000000000000000000000000000000000000]\n"
594 "\n"
595 "patch \"bar\"\n"
596 " from [0000000000000000000000000000000000000000]\n"
597 " to [1000000000000000000000000000000000000000]\n");
598 cset cs;
599 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
600 }
601
602 {
603 L(FL("TEST: cset reading - misordered files in clear"));
604 // bad cset, bar should be before foo
605 data dat("clear \"foo\"\n"
606 " attr \"flavoursome\"\n"
607 "\n"
608 "clear \"bar\"\n"
609 " attr \"flavoursome\"\n");
610 cset cs;
611 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
612 }
613
614 {
615 L(FL("TEST: cset reading - misordered files in set"));
616 // bad cset, bar should be before foo
617 data dat(" set \"foo\"\n"
618 " attr \"flavoursome\"\n"
619 "value \"yes\"\n"
620 "\n"
621 " set \"bar\"\n"
622 " attr \"flavoursome\"\n"
623 "value \"yes\"\n");
624 cset cs;
625 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
626 }
627
628 {
629 L(FL("TEST: cset reading - duplicate entries"));
630 data dat("delete \"foo\"\n"
631 "\n"
632 "delete \"foo\"\n");
633 cset cs;
634 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
635 }
636
637 {
638 L(FL("TEST: cset reading - multiple different attrs"));
639 // should succeed
640 data dat( " set \"bar\"\n"
641 " attr \"flavoursome\"\n"
642 "value \"mostly\"\n"
643 "\n"
644 " set \"bar\"\n"
645 " attr \"smell\"\n"
646 "value \"socks\"\n");
647 cset cs;
648 BOOST_CHECK_NOT_THROW(read_cset(dat, cs), logic_error);
649 }
650
651 {
652 L(FL("TEST: cset reading - wrong attr ordering in clear"));
653 // fooish should be before quuxy
654 data dat( "clear \"bar\"\n"
655 " attr \"quuxy\"\n"
656 "\n"
657 "clear \"bar\"\n"
658 " attr \"fooish\"\n");
659 cset cs;
660 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
661 }
662
663 {
664 L(FL("TEST: cset reading - wrong attr ordering in set"));
665 // fooish should be before quuxy
666 data dat( " set \"bar\"\n"
667 " attr \"quuxy\"\n"
668 "value \"mostly\"\n"
669 "\n"
670 " set \"bar\"\n"
671 " attr \"fooish\"\n"
672 "value \"seldom\"\n");
673 cset cs;
674 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
675 }
676
677 {
678 L(FL("TEST: cset reading - duplicate attrs"));
679 // can't have dups.
680 data dat( " set \"bar\"\n"
681 " attr \"flavoursome\"\n"
682 "value \"mostly\"\n"
683 "\n"
684 " set \"bar\"\n"
685 " attr \"flavoursome\"\n"
686 "value \"sometimes\"\n");
687 cset cs;
688 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
689 }
690
691 {
692 L(FL("TEST: cset writing - normalisation"));
693 cset cs; MM(cs);
694 split_path foo, bar, quux, foo_quux, idle, fish, womble, policeman;
695 file_id f1(string("1234567800000000000000000000000000000000"));
696 file_id f2(string("9876543212394657263900000000000000000000"));
697 file_id f3(string("0000000000011111111000000000000000000000"));
698 file_path_internal("foo").split(foo);
699 file_path_internal("foo/quux").split(foo_quux);
700 file_path_internal("bar").split(bar);
701 file_path_internal("quux").split(quux);
702 file_path_internal("idle").split(idle);
703 file_path_internal("fish").split(fish);
704 file_path_internal("womble").split(womble);
705 file_path_internal("policeman").split(policeman);
706
707 cs.dirs_added.insert(foo_quux);
708 cs.dirs_added.insert(foo);
709 cs.files_added.insert(make_pair(bar, f1));
710 cs.nodes_deleted.insert(quux);
711 cs.nodes_deleted.insert(idle);
712 cs.nodes_renamed.insert(make_pair(fish, womble));
713 cs.deltas_applied.insert(make_pair(womble, make_pair(f2, f3)));
714 cs.attrs_cleared.insert(make_pair(policeman, attr_key("yodel")));
715 cs.attrs_set.insert(make_pair(make_pair(policeman,
716 attr_key("axolotyl")), attr_value("fruitily")));
717 cs.attrs_set.insert(make_pair(make_pair(policeman,
718 attr_key("spin")), attr_value("capybara")));
719
720 data dat; MM(dat);
721 write_cset(cs, dat);
722 data expected("delete \"idle\"\n"
723 "\n"
724 "delete \"quux\"\n"
725 "\n"
726 "rename \"fish\"\n"
727 " to \"womble\"\n"
728 "\n"
729 "add_dir \"foo\"\n"
730 "\n"
731 "add_dir \"foo/quux\"\n"
732 "\n"
733 "add_file \"bar\"\n"
734 " content [1234567800000000000000000000000000000000]\n"
735 "\n"
736 "patch \"womble\"\n"
737 " from [9876543212394657263900000000000000000000]\n"
738 " to [0000000000011111111000000000000000000000]\n"
739 "\n"
740 "clear \"policeman\"\n"
741 " attr \"yodel\"\n"
742 "\n"
743 " set \"policeman\"\n"
744 " attr \"axolotyl\"\n"
745 "value \"fruitily\"\n"
746 "\n"
747 " set \"policeman\"\n"
748 " attr \"spin\"\n"
749 "value \"capybara\"\n"
750 );
751 MM(expected);
752 // I() so that it'll dump on failure
753 BOOST_CHECK_NOT_THROW(I(expected == dat), logic_error);
754 }
755}
756
757UNIT_TEST(cset, basic_csets)
758{
759
760 temp_node_id_source nis;
761 roster_t r;
762 MM(r);
763
764 editable_roster_base tree(r, nis);
765
766 file_id f1(string("0000000000000000000000000000000000000001"));
767 file_id f2(string("0000000000000000000000000000000000000002"));
768
769 split_path root, foo, foo_bar, baz, quux;
770 file_path().split(root);
771 file_path_internal("foo").split(foo);
772 file_path_internal("foo/bar").split(foo_bar);
773 file_path_internal("baz").split(baz);
774 file_path_internal("quux").split(quux);
775
776 // some basic tests that should succeed
777 {
778 L(FL("TEST: cset add file"));
779 setup_roster(r, f1, nis);
780 cset cs; MM(cs);
781 cs.files_added.insert(make_pair(baz, f2));
782 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
783 BOOST_CHECK(is_file_t(r.get_node(baz)));
784 BOOST_CHECK(downcast_to_file_t(r.get_node(baz))->content == f2);
785 BOOST_CHECK(r.all_nodes().size() == 4);
786 }
787
788 {
789 L(FL("TEST: cset add dir"));
790 setup_roster(r, f1, nis);
791 cset cs; MM(cs);
792 cs.dirs_added.insert(quux);
793 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
794 BOOST_CHECK(is_dir_t(r.get_node(quux)));
795 BOOST_CHECK(r.all_nodes().size() == 4);
796 }
797
798 {
799 L(FL("TEST: cset delete"));
800 setup_roster(r, f1, nis);
801 cset cs; MM(cs);
802 cs.nodes_deleted.insert(foo_bar);
803 cs.nodes_deleted.insert(foo);
804 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
805 BOOST_CHECK(r.all_nodes().size() == 1); // only the root left
806 }
807
808 {
809 L(FL("TEST: cset rename file"));
810 setup_roster(r, f1, nis);
811 cset cs; MM(cs);
812 cs.nodes_renamed.insert(make_pair(foo_bar, quux));
813 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
814 BOOST_CHECK(is_file_t(r.get_node(quux)));
815 BOOST_CHECK(is_dir_t(r.get_node(foo)));
816 BOOST_CHECK(!r.has_node(foo_bar));
817 BOOST_CHECK(r.all_nodes().size() == 3);
818 }
819
820 {
821 L(FL("TEST: cset rename dir"));
822 split_path quux_bar;
823 file_path_internal("quux/bar").split(quux_bar);
824 setup_roster(r, f1, nis);
825 cset cs; MM(cs);
826 cs.nodes_renamed.insert(make_pair(foo, quux));
827 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
828 BOOST_CHECK(is_dir_t(r.get_node(quux)));
829 BOOST_CHECK(is_file_t(r.get_node(quux_bar)));
830 BOOST_CHECK(!r.has_node(foo));
831 BOOST_CHECK(r.all_nodes().size() == 3);
832 }
833
834 {
835 L(FL("TEST: patch file"));
836 setup_roster(r, f1, nis);
837 cset cs; MM(cs);
838 cs.deltas_applied.insert(make_pair(foo_bar, make_pair(f1, f2)));
839 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
840 BOOST_CHECK(is_dir_t(r.get_node(foo)));
841 BOOST_CHECK(is_file_t(r.get_node(foo_bar)));
842 BOOST_CHECK(downcast_to_file_t(r.get_node(foo_bar))->content == f2);
843 BOOST_CHECK(r.all_nodes().size() == 3);
844 }
845
846 {
847 L(FL("TEST: set attr"));
848 setup_roster(r, f1, nis);
849 cset cs; MM(cs);
850 cs.attrs_set.insert(make_pair(make_pair(foo_bar, attr_key("ping")),
851 attr_value("klang")));
852 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
853
854 full_attr_map_t attrs = (r.get_node(foo_bar))->attrs;
855 BOOST_CHECK(attrs[attr_key("ping")] == make_pair(true, attr_value("klang")));
856
857 attrs = (r.get_node(foo))->attrs;
858 BOOST_CHECK(attrs[attr_key("attr_dir")] == make_pair(true, attr_value("value_dir")));
859
860 BOOST_CHECK(r.all_nodes().size() == 3);
861 }
862
863 {
864 L(FL("TEST: clear attr file"));
865 setup_roster(r, f1, nis);
866 cset cs; MM(cs);
867 cs.attrs_set.insert(make_pair(make_pair(foo_bar, attr_key("ping")),
868 attr_value("klang")));
869 cs.attrs_cleared.insert(make_pair(foo_bar, attr_key("attr_file")));
870 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
871 BOOST_CHECK((r.get_node(foo_bar))->attrs[attr_key("attr_file")]
872 == make_pair(false, attr_value("")));
873 BOOST_CHECK(r.all_nodes().size() == 3);
874 }
875
876 // some renaming tests
877 {
878 L(FL("TEST: renaming at different levels"));
879 setup_roster(r, f1, nis);
880 split_path quux_sub, foo_sub, foo_sub_deep, foo_subsub,
881 foo_subsub_deep, quux_bar, foo_bar,
882 quux_sub_thing, foo_sub_thing;
883 file_path_internal("quux/bar").split(quux_bar);
884 file_path_internal("foo/bar").split(foo_bar);
885 file_path_internal("quux/sub").split(quux_sub);
886 file_path_internal("foo/sub").split(foo_sub);
887 file_path_internal("foo/sub/thing").split(foo_sub_thing);
888 file_path_internal("quux/sub/thing").split(quux_sub_thing);
889 file_path_internal("foo/sub/deep").split(foo_sub_deep);
890 file_path_internal("foo/subsub").split(foo_subsub);
891 file_path_internal("foo/subsub/deep").split(foo_subsub_deep);
892
893 { // build a tree
894 cset cs; MM(cs);
895 cs.dirs_added.insert(quux);
896 cs.dirs_added.insert(quux_sub);
897 cs.dirs_added.insert(foo_sub);
898 cs.files_added.insert(make_pair(foo_sub_deep, f2));
899 cs.files_added.insert(make_pair(quux_sub_thing, f1));
900 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
901 BOOST_CHECK(r.all_nodes().size() == 8);
902 }
903
904 { // some renames
905 cset cs; MM(cs);
906 cs.nodes_renamed.insert(make_pair(foo, quux));
907 cs.nodes_renamed.insert(make_pair(quux, foo));
908 cs.nodes_renamed.insert(make_pair(foo_sub, foo_subsub));
909 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
910 }
911
912 BOOST_CHECK(r.all_nodes().size() == 8);
913 // /foo/bar -> /quux/bar
914 BOOST_CHECK(is_file_t(r.get_node(quux_bar)));
915 BOOST_CHECK(!(r.has_node(foo_bar)));
916 // /foo/sub/deep -> /foo/subsub/deep
917 BOOST_CHECK(is_file_t(r.get_node(foo_subsub_deep)));
918 BOOST_CHECK(!(r.has_node(foo_sub_deep)));
919 // /quux/sub -> /foo/sub
920 BOOST_CHECK(is_dir_t(r.get_node(foo_sub)));
921 BOOST_CHECK(!(r.has_node(quux_sub)));
922 // /quux/sub/thing -> /foo/sub/thing
923 BOOST_CHECK(is_file_t(r.get_node(foo_sub_thing)));
924 }
925
926 {
927 L(FL("delete targets pre-renamed nodes"));
928 setup_roster(r, f1, nis);
929 cset cs; MM(cs);
930 cs.nodes_renamed.insert(make_pair(foo_bar, foo));
931 cs.nodes_deleted.insert(foo);
932 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
933 BOOST_CHECK(r.all_nodes().size() == 2);
934 BOOST_CHECK(is_file_t(r.get_node(foo)));
935 }
936}
937
938UNIT_TEST(cset, invalid_csets)
939{
940 temp_node_id_source nis;
941 roster_t r;
942 MM(r);
943 editable_roster_base tree(r, nis);
944
945 file_id f1(string("0000000000000000000000000000000000000001"));
946 file_id f2(string("0000000000000000000000000000000000000002"));
947
948 split_path root, foo, foo_bar, baz, quux;
949 file_path().split(root);
950 file_path_internal("foo").split(foo);
951 file_path_internal("foo/bar").split(foo_bar);
952 file_path_internal("baz").split(baz);
953 file_path_internal("quux").split(quux);
954
955 {
956 L(FL("TEST: can't double-delete"));
957 setup_roster(r, f1, nis);
958 cset cs; MM(cs);
959 cs.nodes_deleted.insert(foo_bar);
960 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
961 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
962 }
963 {
964 L(FL("TEST: can't double-add file"));
965 setup_roster(r, f1, nis);
966 cset cs; MM(cs);
967 cs.files_added.insert(make_pair(baz, f2));
968 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
969 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
970 }
971 {
972 L(FL("TEST: can't add file on top of dir"));
973 setup_roster(r, f1, nis);
974 cset cs; MM(cs);
975 cs.files_added.insert(make_pair(foo, f2));
976 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
977 }
978 {
979 L(FL("TEST: can't delete+rename"));
980 setup_roster(r, f1, nis);
981 cset cs; MM(cs);
982 cs.nodes_deleted.insert(foo_bar);
983 cs.nodes_renamed.insert(make_pair(foo_bar, baz));
984 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
985 }
986 {
987 L(FL("TEST: can't add+rename"));
988 setup_roster(r, f1, nis);
989 cset cs; MM(cs);
990 cs.dirs_added.insert(baz);
991 cs.nodes_renamed.insert(make_pair(baz, quux));
992 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
993 }
994 {
995 L(FL("TEST: can't add on top of root dir"));
996 setup_roster(r, f1, nis);
997 cset cs; MM(cs);
998 cs.dirs_added.insert(root);
999 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1000 }
1001 {
1002 L(FL("TEST: can't rename on top of root dir"));
1003 setup_roster(r, f1, nis);
1004 cset cs; MM(cs);
1005 cs.nodes_renamed.insert(make_pair(foo, root));
1006 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1007 }
1008 {
1009 L(FL("TEST: can't rename 'a' 'a'"));
1010 setup_roster(r, f1, nis);
1011 cset cs; MM(cs);
1012 cs.nodes_renamed.insert(make_pair(foo_bar, foo_bar));
1013 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1014 }
1015 {
1016 L(FL("TEST: can't rename 'a' 'b'; rename 'a/foo' 'b/foo'"));
1017 setup_roster(r, f1, nis);
1018 cset cs; MM(cs);
1019 split_path baz_bar;
1020 file_path_internal("baz/bar").split(baz_bar);
1021 cs.nodes_renamed.insert(make_pair(foo, baz));
1022 cs.nodes_renamed.insert(make_pair(foo_bar, baz_bar));
1023 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1024 }
1025 {
1026 L(FL("TEST: can't attr_set + attr_cleared"));
1027 setup_roster(r, f1, nis);
1028 cset cs; MM(cs);
1029 cs.attrs_set.insert(make_pair(make_pair(foo_bar, attr_key("blah")),
1030 attr_value("blahblah")));
1031 cs.attrs_cleared.insert(make_pair(foo_bar, attr_key("blah")));
1032 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1033 }
1034 {
1035 L(FL("TEST: can't no-op attr_set"));
1036 setup_roster(r, f1, nis);
1037 cset cs; MM(cs);
1038 cs.attrs_set.insert(make_pair(make_pair(foo_bar, attr_key("attr_file")),
1039 attr_value("value_file")));
1040 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1041 }
1042 {
1043 L(FL("TEST: can't clear non-existent attr"));
1044 setup_roster(r, f1, nis);
1045 cset cs; MM(cs);
1046 cs.attrs_cleared.insert(make_pair(foo_bar, attr_key("blah")));
1047 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1048 }
1049 {
1050 L(FL("TEST: can't clear non-existent attr that once existed"));
1051 setup_roster(r, f1, nis);
1052 cset cs; MM(cs);
1053 cs.attrs_cleared.insert(make_pair(foo_bar, attr_key("attr_file")));
1054 // exists now, so should be fine
1055 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
1056 // but last time killed it, so can't be killed again
1057 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1058 }
1059 {
1060 L(FL("TEST: can't have no-op deltas"));
1061 setup_roster(r, f1, nis);
1062 cset cs; MM(cs);
1063 cs.deltas_applied.insert(make_pair(foo_bar,
1064 make_pair(f1, f1)));
1065 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1066 }
1067 {
1068 L(FL("TEST: can't have add+delta"));
1069 setup_roster(r, f1, nis);
1070 cset cs; MM(cs);
1071 cs.files_added.insert(make_pair(baz, f1));
1072 cs.deltas_applied.insert(make_pair(baz,
1073 make_pair(f1, f2)));
1074 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1075 }
1076 {
1077 L(FL("TEST: can't delta a directory"));
1078 setup_roster(r, f1, nis);
1079 cset cs; MM(cs);
1080 cs.deltas_applied.insert(make_pair(foo,
1081 make_pair(f1, f2)));
1082 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1083 }
1084 {
1085 L(FL("TEST: can't delete non-empty directory"));
1086 setup_roster(r, f1, nis);
1087 cset cs; MM(cs);
1088 cs.nodes_deleted.insert(foo);
1089 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1090 }
1091 {
1092 L(FL("TEST: attach node with no root directory present"));
1093 // for this test, make sure original roster has no contents
1094 r = roster_t();
1095 cset cs; MM(cs);
1096 split_path sp;
1097 file_path_internal("blah/blah/blah").split(sp);
1098 cs.dirs_added.insert(sp);
1099 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1100 }
1101 {
1102 L(FL("TEST: can't move a directory underneath itself"));
1103 setup_roster(r, f1, nis);
1104 cset cs; MM(cs);
1105 split_path foo_blah;
1106 file_path_internal("foo/blah").split(foo_blah);
1107 cs.nodes_renamed.insert(make_pair(foo, foo_blah));
1108 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1109 }
1110}
1111
1112UNIT_TEST(cset, root_dir)
1113{
1114 temp_node_id_source nis;
1115 roster_t r;
1116 MM(r);
1117 editable_roster_base tree(r, nis);
1118
1119 file_id f1(string("0000000000000000000000000000000000000001"));
1120
1121 split_path root, baz;
1122 file_path().split(root);
1123 file_path_internal("baz").split(baz);
1124
1125 {
1126 L(FL("TEST: can rename root"));
1127 setup_roster(r, f1, nis);
1128 cset cs; MM(cs);
1129 split_path sp1, sp2;
1130 cs.dirs_added.insert(root);
1131 cs.nodes_renamed.insert(make_pair(root, baz));
1132 cs.apply_to(tree);
1133 r.check_sane(true);
1134 }
1135 {
1136 L(FL("TEST: can delete root (but it makes us insane)"));
1137 // for this test, make sure root has no contents
1138 r = roster_t();
1139 r.attach_node(r.create_dir_node(nis), root);
1140 cset cs; MM(cs);
1141 cs.nodes_deleted.insert(root);
1142 cs.apply_to(tree);
1143 BOOST_CHECK_THROW(r.check_sane(true), logic_error);
1144 }
1145 {
1146 L(FL("TEST: can delete and replace root"));
1147 r = roster_t();
1148 r.attach_node(r.create_dir_node(nis), root);
1149 cset cs; MM(cs);
1150 cs.nodes_deleted.insert(root);
1151 cs.dirs_added.insert(root);
1152 cs.apply_to(tree);
1153 r.check_sane(true);
1154 }
1155}
1156
1157#endif // BUILD_UNIT_TESTS
1158
1159
1160
1161// Local Variables:
1162// mode: C++
1163// fill-column: 76
1164// c-file-style: "gnu"
1165// indent-tabs-mode: nil
1166// End:
1167// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

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