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 file_path p(*i);
267 basic_io::stanza st;
268 st.push_file_pair(syms::delete_node, file_path(*i));
269 printer.print_stanza(st);
270 }
271
272 for (map<split_path, split_path>::const_iterator i = cs.nodes_renamed.begin();
273 i != cs.nodes_renamed.end(); ++i)
274 {
275 file_path p(i->first);
276 basic_io::stanza st;
277 st.push_file_pair(syms::rename_node, file_path(i->first));
278 st.push_file_pair(syms::to, file_path(i->second));
279 printer.print_stanza(st);
280 }
281
282 for (path_set::const_iterator i = cs.dirs_added.begin();
283 i != cs.dirs_added.end(); ++i)
284 {
285 file_path p(*i);
286 basic_io::stanza st;
287 st.push_file_pair(syms::add_dir, file_path(*i));
288 printer.print_stanza(st);
289 }
290
291 for (map<split_path, file_id>::const_iterator i = cs.files_added.begin();
292 i != cs.files_added.end(); ++i)
293 {
294 file_path p(i->first);
295 basic_io::stanza st;
296 st.push_file_pair(syms::add_file, file_path(i->first));
297 st.push_hex_pair(syms::content, i->second.inner());
298 printer.print_stanza(st);
299 }
300
301 for (map<split_path, pair<file_id, file_id> >::const_iterator i = cs.deltas_applied.begin();
302 i != cs.deltas_applied.end(); ++i)
303 {
304 file_path p(i->first);
305 basic_io::stanza st;
306 st.push_file_pair(syms::patch, file_path(i->first));
307 st.push_hex_pair(syms::from, i->second.first.inner());
308 st.push_hex_pair(syms::to, i->second.second.inner());
309 printer.print_stanza(st);
310 }
311
312 for (set<pair<split_path, attr_key> >::const_iterator i = cs.attrs_cleared.begin();
313 i != cs.attrs_cleared.end(); ++i)
314 {
315 basic_io::stanza st;
316 st.push_file_pair(syms::clear, file_path(i->first));
317 st.push_str_pair(syms::attr, i->second());
318 printer.print_stanza(st);
319 }
320
321 for (map<pair<split_path, attr_key>, attr_value>::const_iterator i = cs.attrs_set.begin();
322 i != cs.attrs_set.end(); ++i)
323 {
324 basic_io::stanza st;
325 st.push_file_pair(syms::set, file_path(i->first.first));
326 st.push_str_pair(syms::attr, i->first.second());
327 st.push_str_pair(syms::value, i->second());
328 printer.print_stanza(st);
329 }
330}
331
332
333static inline void
334parse_path(basic_io::parser & parser, split_path & sp)
335{
336 string s;
337 parser.str(s);
338 file_path_internal(s).split(sp);
339}
340
341void
342parse_cset(basic_io::parser & parser,
343 cset & cs)
344{
345 cs.clear();
346 string t1, t2;
347 MM(t1);
348 MM(t2);
349 split_path p1, p2;
350 MM(p1);
351 MM(p2);
352
353 split_path prev_path;
354 MM(prev_path);
355 pair<split_path, attr_key> prev_pair;
356 MM(prev_pair.first);
357 MM(prev_pair.second);
358
359 // we make use of the fact that a valid split_path is never empty
360 prev_path.clear();
361 while (parser.symp(syms::delete_node))
362 {
363 parser.sym();
364 parse_path(parser, p1);
365 I(prev_path.empty() || p1 > prev_path);
366 prev_path = p1;
367 safe_insert(cs.nodes_deleted, p1);
368 }
369
370 prev_path.clear();
371 while (parser.symp(syms::rename_node))
372 {
373 parser.sym();
374 parse_path(parser, p1);
375 I(prev_path.empty() || p1 > prev_path);
376 prev_path = p1;
377 parser.esym(syms::to);
378 parse_path(parser, p2);
379 safe_insert(cs.nodes_renamed, make_pair(p1, p2));
380 }
381
382 prev_path.clear();
383 while (parser.symp(syms::add_dir))
384 {
385 parser.sym();
386 parse_path(parser, p1);
387 I(prev_path.empty() || p1 > prev_path);
388 prev_path = p1;
389 safe_insert(cs.dirs_added, p1);
390 }
391
392 prev_path.clear();
393 while (parser.symp(syms::add_file))
394 {
395 parser.sym();
396 parse_path(parser, p1);
397 I(prev_path.empty() || p1 > prev_path);
398 prev_path = p1;
399 parser.esym(syms::content);
400 parser.hex(t1);
401 safe_insert(cs.files_added, make_pair(p1, file_id(t1)));
402 }
403
404 prev_path.clear();
405 while (parser.symp(syms::patch))
406 {
407 parser.sym();
408 parse_path(parser, p1);
409 I(prev_path.empty() || p1 > prev_path);
410 prev_path = p1;
411 parser.esym(syms::from);
412 parser.hex(t1);
413 parser.esym(syms::to);
414 parser.hex(t2);
415 safe_insert(cs.deltas_applied,
416 make_pair(p1, make_pair(file_id(t1), file_id(t2))));
417 }
418
419 prev_pair.first.clear();
420 while (parser.symp(syms::clear))
421 {
422 parser.sym();
423 parse_path(parser, p1);
424 parser.esym(syms::attr);
425 parser.str(t1);
426 pair<split_path, attr_key> new_pair(p1, t1);
427 I(prev_pair.first.empty() || new_pair > prev_pair);
428 prev_pair = new_pair;
429 safe_insert(cs.attrs_cleared, new_pair);
430 }
431
432 prev_pair.first.clear();
433 while (parser.symp(syms::set))
434 {
435 parser.sym();
436 parse_path(parser, p1);
437 parser.esym(syms::attr);
438 parser.str(t1);
439 pair<split_path, attr_key> new_pair(p1, t1);
440 I(prev_pair.first.empty() || new_pair > prev_pair);
441 prev_pair = new_pair;
442 parser.esym(syms::value);
443 parser.str(t2);
444 safe_insert(cs.attrs_set, make_pair(new_pair, attr_value(t2)));
445 }
446}
447
448void
449write_cset(cset const & cs, data & dat)
450{
451 basic_io::printer pr;
452 print_cset(pr, cs);
453 dat = data(pr.buf);
454}
455
456void
457read_cset(data const & dat, cset & cs)
458{
459 MM(dat);
460 MM(cs);
461 basic_io::input_source src(dat(), "cset");
462 basic_io::tokenizer tok(src);
463 basic_io::parser pars(tok);
464 parse_cset(pars, cs);
465 I(src.lookahead == EOF);
466}
467
468template <> void
469dump(cset const & cs, string & out)
470{
471 data dat;
472 write_cset(cs, dat);
473 out = dat();
474}
475
476#ifdef BUILD_UNIT_TESTS
477#include "unit_tests.hh"
478
479#include "roster.hh"
480
481using std::logic_error;
482
483static void
484setup_roster(roster_t & r, file_id const & fid, node_id_source & nis)
485{
486 // sets up r to have a root dir, a dir in it name "foo", and a file under
487 // that named "bar", and the file has the given id.
488 // the file has attr "attr_file=value_file", and the dir has
489 // "attr_dir=value_dir".
490 r = roster_t();
491
492 {
493 split_path sp;
494 file_path().split(sp);
495 r.attach_node(r.create_dir_node(nis), sp);
496 }
497 {
498 split_path sp;
499 file_path_internal("foo").split(sp);
500 r.attach_node(r.create_dir_node(nis), sp);
501 r.set_attr(sp, attr_key("attr_dir"), attr_value("value_dir"));
502 }
503 {
504 split_path sp;
505 file_path_internal("foo/bar").split(sp);
506 r.attach_node(r.create_file_node(fid, nis), sp);
507 r.set_attr(sp, attr_key("attr_file"), attr_value("value_file"));
508 }
509}
510
511static void
512cset_written_test()
513{
514 {
515 L(FL("TEST: cset reading - operation misordering"));
516 // bad cset, add_dir should be before add_file
517 string s("delete \"foo\"\n"
518 "\n"
519 "rename \"quux\"\n"
520 " to \"baz\"\n"
521 "\n"
522 "add_file \"bar\"\n"
523 " content [0000000000000000000000000000000000000000]\n"
524 "\n"
525 "add_dir \"pling\"\n");
526 data d1(s);
527 cset cs;
528 BOOST_CHECK_THROW(read_cset(d1, cs), logic_error);
529 // check that it still fails if there's extra stanzas past the
530 // mis-ordered entries
531 data d2(s + "\n"
532 " set \"bar\"\n"
533 " attr \"flavoursome\"\n"
534 "value \"mostly\"\n");
535 BOOST_CHECK_THROW(read_cset(d2, cs), logic_error);
536 }
537
538 {
539 L(FL("TEST: cset reading - misordered files in delete"));
540 // bad cset, bar should be before foo
541 data dat("delete \"foo\"\n"
542 "\n"
543 "delete \"bar\"\n");
544 cset cs;
545 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
546 }
547
548 {
549 L(FL("TEST: cset reading - misordered files in rename"));
550 // bad cset, bar should be before foo
551 data dat("rename \"foo\"\n"
552 " to \"foonew\"\n"
553 "\n"
554 "rename \"bar\"\n"
555 " to \"barnew\"\n");
556 cset cs;
557 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
558 }
559
560 {
561 L(FL("TEST: cset reading - misordered files in add_dir"));
562 // bad cset, bar should be before foo
563 data dat("add_dir \"foo\"\n"
564 "\n"
565 "add_dir \"bar\"\n");
566 cset cs;
567 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
568 }
569
570 {
571 L(FL("TEST: cset reading - misordered files in add_file"));
572 // bad cset, bar should be before foo
573 data dat("add_file \"foo\"\n"
574 " content [0000000000000000000000000000000000000000]\n"
575 "\n"
576 "add_file \"bar\"\n"
577 " content [0000000000000000000000000000000000000000]\n");
578 cset cs;
579 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
580 }
581
582 {
583 L(FL("TEST: cset reading - misordered files in add_file"));
584 // bad cset, bar should be before foo
585 data dat("add_file \"foo\"\n"
586 " content [0000000000000000000000000000000000000000]\n"
587 "\n"
588 "add_file \"bar\"\n"
589 " content [0000000000000000000000000000000000000000]\n");
590 cset cs;
591 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
592 }
593
594 {
595 L(FL("TEST: cset reading - misordered files in patch"));
596 // bad cset, bar should be before foo
597 data dat("patch \"foo\"\n"
598 " from [0000000000000000000000000000000000000000]\n"
599 " to [1000000000000000000000000000000000000000]\n"
600 "\n"
601 "patch \"bar\"\n"
602 " from [0000000000000000000000000000000000000000]\n"
603 " to [1000000000000000000000000000000000000000]\n");
604 cset cs;
605 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
606 }
607
608 {
609 L(FL("TEST: cset reading - misordered files in clear"));
610 // bad cset, bar should be before foo
611 data dat("clear \"foo\"\n"
612 " attr \"flavoursome\"\n"
613 "\n"
614 "clear \"bar\"\n"
615 " attr \"flavoursome\"\n");
616 cset cs;
617 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
618 }
619
620 {
621 L(FL("TEST: cset reading - misordered files in set"));
622 // bad cset, bar should be before foo
623 data dat(" set \"foo\"\n"
624 " attr \"flavoursome\"\n"
625 "value \"yes\"\n"
626 "\n"
627 " set \"bar\"\n"
628 " attr \"flavoursome\"\n"
629 "value \"yes\"\n");
630 cset cs;
631 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
632 }
633
634 {
635 L(FL("TEST: cset reading - duplicate entries"));
636 data dat("delete \"foo\"\n"
637 "\n"
638 "delete \"foo\"\n");
639 cset cs;
640 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
641 }
642
643 {
644 L(FL("TEST: cset reading - multiple different attrs"));
645 // should succeed
646 data dat( " set \"bar\"\n"
647 " attr \"flavoursome\"\n"
648 "value \"mostly\"\n"
649 "\n"
650 " set \"bar\"\n"
651 " attr \"smell\"\n"
652 "value \"socks\"\n");
653 cset cs;
654 BOOST_CHECK_NOT_THROW(read_cset(dat, cs), logic_error);
655 }
656
657 {
658 L(FL("TEST: cset reading - wrong attr ordering in clear"));
659 // fooish should be before quuxy
660 data dat( "clear \"bar\"\n"
661 " attr \"quuxy\"\n"
662 "\n"
663 "clear \"bar\"\n"
664 " attr \"fooish\"\n");
665 cset cs;
666 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
667 }
668
669 {
670 L(FL("TEST: cset reading - wrong attr ordering in set"));
671 // fooish should be before quuxy
672 data dat( " set \"bar\"\n"
673 " attr \"quuxy\"\n"
674 "value \"mostly\"\n"
675 "\n"
676 " set \"bar\"\n"
677 " attr \"fooish\"\n"
678 "value \"seldom\"\n");
679 cset cs;
680 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
681 }
682
683 {
684 L(FL("TEST: cset reading - duplicate attrs"));
685 // can't have dups.
686 data dat( " set \"bar\"\n"
687 " attr \"flavoursome\"\n"
688 "value \"mostly\"\n"
689 "\n"
690 " set \"bar\"\n"
691 " attr \"flavoursome\"\n"
692 "value \"sometimes\"\n");
693 cset cs;
694 BOOST_CHECK_THROW(read_cset(dat, cs), logic_error);
695 }
696
697 {
698 L(FL("TEST: cset writing - normalisation"));
699 cset cs; MM(cs);
700 split_path foo, bar, quux, foo_quux, idle, fish, womble, policeman;
701 file_id f1(string("1234567800000000000000000000000000000000"));
702 file_id f2(string("9876543212394657263900000000000000000000"));
703 file_id f3(string("0000000000011111111000000000000000000000"));
704 file_path_internal("foo").split(foo);
705 file_path_internal("foo/quux").split(foo_quux);
706 file_path_internal("bar").split(bar);
707 file_path_internal("quux").split(quux);
708 file_path_internal("idle").split(idle);
709 file_path_internal("fish").split(fish);
710 file_path_internal("womble").split(womble);
711 file_path_internal("policeman").split(policeman);
712
713 cs.dirs_added.insert(foo_quux);
714 cs.dirs_added.insert(foo);
715 cs.files_added.insert(make_pair(bar, f1));
716 cs.nodes_deleted.insert(quux);
717 cs.nodes_deleted.insert(idle);
718 cs.nodes_renamed.insert(make_pair(fish, womble));
719 cs.deltas_applied.insert(make_pair(womble, make_pair(f2, f3)));
720 cs.attrs_cleared.insert(make_pair(policeman, attr_key("yodel")));
721 cs.attrs_set.insert(make_pair(make_pair(policeman,
722 attr_key("axolotyl")), attr_value("fruitily")));
723 cs.attrs_set.insert(make_pair(make_pair(policeman,
724 attr_key("spin")), attr_value("capybara")));
725
726 data dat; MM(dat);
727 write_cset(cs, dat);
728 data expected("delete \"idle\"\n"
729 "\n"
730 "delete \"quux\"\n"
731 "\n"
732 "rename \"fish\"\n"
733 " to \"womble\"\n"
734 "\n"
735 "add_dir \"foo\"\n"
736 "\n"
737 "add_dir \"foo/quux\"\n"
738 "\n"
739 "add_file \"bar\"\n"
740 " content [1234567800000000000000000000000000000000]\n"
741 "\n"
742 "patch \"womble\"\n"
743 " from [9876543212394657263900000000000000000000]\n"
744 " to [0000000000011111111000000000000000000000]\n"
745 "\n"
746 "clear \"policeman\"\n"
747 " attr \"yodel\"\n"
748 "\n"
749 " set \"policeman\"\n"
750 " attr \"axolotyl\"\n"
751 "value \"fruitily\"\n"
752 "\n"
753 " set \"policeman\"\n"
754 " attr \"spin\"\n"
755 "value \"capybara\"\n"
756 );
757 MM(expected);
758 // I() so that it'll dump on failure
759 BOOST_CHECK_NOT_THROW(I(expected == dat), logic_error);
760 }
761}
762
763static void
764basic_csets_test()
765{
766
767 temp_node_id_source nis;
768 roster_t r;
769 MM(r);
770
771 editable_roster_base tree(r, nis);
772
773 file_id f1(string("0000000000000000000000000000000000000001"));
774 file_id f2(string("0000000000000000000000000000000000000002"));
775
776 split_path root, foo, foo_bar, baz, quux;
777 file_path().split(root);
778 file_path_internal("foo").split(foo);
779 file_path_internal("foo/bar").split(foo_bar);
780 file_path_internal("baz").split(baz);
781 file_path_internal("quux").split(quux);
782
783 // some basic tests that should succeed
784 {
785 L(FL("TEST: cset add file"));
786 setup_roster(r, f1, nis);
787 cset cs; MM(cs);
788 cs.files_added.insert(make_pair(baz, f2));
789 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
790 BOOST_CHECK(is_file_t(r.get_node(baz)));
791 BOOST_CHECK(downcast_to_file_t(r.get_node(baz))->content == f2);
792 BOOST_CHECK(r.all_nodes().size() == 4);
793 }
794
795 {
796 L(FL("TEST: cset add dir"));
797 setup_roster(r, f1, nis);
798 cset cs; MM(cs);
799 cs.dirs_added.insert(quux);
800 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
801 BOOST_CHECK(is_dir_t(r.get_node(quux)));
802 BOOST_CHECK(r.all_nodes().size() == 4);
803 }
804
805 {
806 L(FL("TEST: cset delete"));
807 setup_roster(r, f1, nis);
808 cset cs; MM(cs);
809 cs.nodes_deleted.insert(foo_bar);
810 cs.nodes_deleted.insert(foo);
811 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
812 BOOST_CHECK(r.all_nodes().size() == 1); // only the root left
813 }
814
815 {
816 L(FL("TEST: cset rename file"));
817 setup_roster(r, f1, nis);
818 cset cs; MM(cs);
819 cs.nodes_renamed.insert(make_pair(foo_bar, quux));
820 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
821 BOOST_CHECK(is_file_t(r.get_node(quux)));
822 BOOST_CHECK(is_dir_t(r.get_node(foo)));
823 BOOST_CHECK(!r.has_node(foo_bar));
824 BOOST_CHECK(r.all_nodes().size() == 3);
825 }
826
827 {
828 L(FL("TEST: cset rename dir"));
829 split_path quux_bar;
830 file_path_internal("quux/bar").split(quux_bar);
831 setup_roster(r, f1, nis);
832 cset cs; MM(cs);
833 cs.nodes_renamed.insert(make_pair(foo, quux));
834 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
835 BOOST_CHECK(is_dir_t(r.get_node(quux)));
836 BOOST_CHECK(is_file_t(r.get_node(quux_bar)));
837 BOOST_CHECK(!r.has_node(foo));
838 BOOST_CHECK(r.all_nodes().size() == 3);
839 }
840
841 {
842 L(FL("TEST: patch file"));
843 setup_roster(r, f1, nis);
844 cset cs; MM(cs);
845 cs.deltas_applied.insert(make_pair(foo_bar, make_pair(f1, f2)));
846 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
847 BOOST_CHECK(is_dir_t(r.get_node(foo)));
848 BOOST_CHECK(is_file_t(r.get_node(foo_bar)));
849 BOOST_CHECK(downcast_to_file_t(r.get_node(foo_bar))->content == f2);
850 BOOST_CHECK(r.all_nodes().size() == 3);
851 }
852
853 {
854 L(FL("TEST: set attr"));
855 setup_roster(r, f1, nis);
856 cset cs; MM(cs);
857 cs.attrs_set.insert(make_pair(make_pair(foo_bar, attr_key("ping")),
858 attr_value("klang")));
859 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
860
861 full_attr_map_t attrs = (r.get_node(foo_bar))->attrs;
862 BOOST_CHECK(attrs[attr_key("ping")] == make_pair(true, attr_value("klang")));
863
864 attrs = (r.get_node(foo))->attrs;
865 BOOST_CHECK(attrs[attr_key("attr_dir")] == make_pair(true, attr_value("value_dir")));
866
867 BOOST_CHECK(r.all_nodes().size() == 3);
868 }
869
870 {
871 L(FL("TEST: clear attr file"));
872 setup_roster(r, f1, nis);
873 cset cs; MM(cs);
874 cs.attrs_set.insert(make_pair(make_pair(foo_bar, attr_key("ping")),
875 attr_value("klang")));
876 cs.attrs_cleared.insert(make_pair(foo_bar, attr_key("attr_file")));
877 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
878 BOOST_CHECK((r.get_node(foo_bar))->attrs[attr_key("attr_file")]
879 == make_pair(false, attr_value("")));
880 BOOST_CHECK(r.all_nodes().size() == 3);
881 }
882
883 // some renaming tests
884 {
885 L(FL("TEST: renaming at different levels"));
886 setup_roster(r, f1, nis);
887 split_path quux_sub, foo_sub, foo_sub_deep, foo_subsub,
888 foo_subsub_deep, quux_bar, foo_bar,
889 quux_sub_thing, foo_sub_thing;
890 file_path_internal("quux/bar").split(quux_bar);
891 file_path_internal("foo/bar").split(foo_bar);
892 file_path_internal("quux/sub").split(quux_sub);
893 file_path_internal("foo/sub").split(foo_sub);
894 file_path_internal("foo/sub/thing").split(foo_sub_thing);
895 file_path_internal("quux/sub/thing").split(quux_sub_thing);
896 file_path_internal("foo/sub/deep").split(foo_sub_deep);
897 file_path_internal("foo/subsub").split(foo_subsub);
898 file_path_internal("foo/subsub/deep").split(foo_subsub_deep);
899
900 { // build a tree
901 cset cs; MM(cs);
902 cs.dirs_added.insert(quux);
903 cs.dirs_added.insert(quux_sub);
904 cs.dirs_added.insert(foo_sub);
905 cs.files_added.insert(make_pair(foo_sub_deep, f2));
906 cs.files_added.insert(make_pair(quux_sub_thing, f1));
907 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
908 BOOST_CHECK(r.all_nodes().size() == 8);
909 }
910
911 { // some renames
912 cset cs; MM(cs);
913 cs.nodes_renamed.insert(make_pair(foo, quux));
914 cs.nodes_renamed.insert(make_pair(quux, foo));
915 cs.nodes_renamed.insert(make_pair(foo_sub, foo_subsub));
916 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
917 }
918
919 BOOST_CHECK(r.all_nodes().size() == 8);
920 // /foo/bar -> /quux/bar
921 BOOST_CHECK(is_file_t(r.get_node(quux_bar)));
922 BOOST_CHECK(!(r.has_node(foo_bar)));
923 // /foo/sub/deep -> /foo/subsub/deep
924 BOOST_CHECK(is_file_t(r.get_node(foo_subsub_deep)));
925 BOOST_CHECK(!(r.has_node(foo_sub_deep)));
926 // /quux/sub -> /foo/sub
927 BOOST_CHECK(is_dir_t(r.get_node(foo_sub)));
928 BOOST_CHECK(!(r.has_node(quux_sub)));
929 // /quux/sub/thing -> /foo/sub/thing
930 BOOST_CHECK(is_file_t(r.get_node(foo_sub_thing)));
931 }
932
933 {
934 L(FL("delete targets pre-renamed nodes"));
935 setup_roster(r, f1, nis);
936 cset cs; MM(cs);
937 cs.nodes_renamed.insert(make_pair(foo_bar, foo));
938 cs.nodes_deleted.insert(foo);
939 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
940 BOOST_CHECK(r.all_nodes().size() == 2);
941 BOOST_CHECK(is_file_t(r.get_node(foo)));
942 }
943}
944
945static void
946invalid_csets_test()
947{
948 temp_node_id_source nis;
949 roster_t r;
950 MM(r);
951 editable_roster_base tree(r, nis);
952
953 file_id f1(string("0000000000000000000000000000000000000001"));
954 file_id f2(string("0000000000000000000000000000000000000002"));
955
956 split_path root, foo, foo_bar, baz, quux;
957 file_path().split(root);
958 file_path_internal("foo").split(foo);
959 file_path_internal("foo/bar").split(foo_bar);
960 file_path_internal("baz").split(baz);
961 file_path_internal("quux").split(quux);
962
963 {
964 L(FL("TEST: can't double-delete"));
965 setup_roster(r, f1, nis);
966 cset cs; MM(cs);
967 cs.nodes_deleted.insert(foo_bar);
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 double-add file"));
973 setup_roster(r, f1, nis);
974 cset cs; MM(cs);
975 cs.files_added.insert(make_pair(baz, f2));
976 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
977 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
978 }
979 {
980 L(FL("TEST: can't add file on top of dir"));
981 setup_roster(r, f1, nis);
982 cset cs; MM(cs);
983 cs.files_added.insert(make_pair(foo, f2));
984 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
985 }
986 {
987 L(FL("TEST: can't delete+rename"));
988 setup_roster(r, f1, nis);
989 cset cs; MM(cs);
990 cs.nodes_deleted.insert(foo_bar);
991 cs.nodes_renamed.insert(make_pair(foo_bar, baz));
992 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
993 }
994 {
995 L(FL("TEST: can't add+rename"));
996 setup_roster(r, f1, nis);
997 cset cs; MM(cs);
998 cs.dirs_added.insert(baz);
999 cs.nodes_renamed.insert(make_pair(baz, quux));
1000 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1001 }
1002 {
1003 L(FL("TEST: can't add on top of root dir"));
1004 setup_roster(r, f1, nis);
1005 cset cs; MM(cs);
1006 cs.dirs_added.insert(root);
1007 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1008 }
1009 {
1010 L(FL("TEST: can't rename on top of root dir"));
1011 setup_roster(r, f1, nis);
1012 cset cs; MM(cs);
1013 cs.nodes_renamed.insert(make_pair(foo, root));
1014 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1015 }
1016 {
1017 L(FL("TEST: can't rename 'a' 'a'"));
1018 setup_roster(r, f1, nis);
1019 cset cs; MM(cs);
1020 cs.nodes_renamed.insert(make_pair(foo_bar, foo_bar));
1021 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1022 }
1023 {
1024 L(FL("TEST: can't rename 'a' 'b'; rename 'a/foo' 'b/foo'"));
1025 setup_roster(r, f1, nis);
1026 cset cs; MM(cs);
1027 split_path baz_bar;
1028 file_path_internal("baz/bar").split(baz_bar);
1029 cs.nodes_renamed.insert(make_pair(foo, baz));
1030 cs.nodes_renamed.insert(make_pair(foo_bar, baz_bar));
1031 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1032 }
1033 {
1034 L(FL("TEST: can't attr_set + attr_cleared"));
1035 setup_roster(r, f1, nis);
1036 cset cs; MM(cs);
1037 cs.attrs_set.insert(make_pair(make_pair(foo_bar, attr_key("blah")),
1038 attr_value("blahblah")));
1039 cs.attrs_cleared.insert(make_pair(foo_bar, attr_key("blah")));
1040 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1041 }
1042 {
1043 L(FL("TEST: can't no-op attr_set"));
1044 setup_roster(r, f1, nis);
1045 cset cs; MM(cs);
1046 cs.attrs_set.insert(make_pair(make_pair(foo_bar, attr_key("attr_file")),
1047 attr_value("value_file")));
1048 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1049 }
1050 {
1051 L(FL("TEST: can't clear non-existent attr"));
1052 setup_roster(r, f1, nis);
1053 cset cs; MM(cs);
1054 cs.attrs_cleared.insert(make_pair(foo_bar, attr_key("blah")));
1055 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1056 }
1057 {
1058 L(FL("TEST: can't clear non-existent attr that once existed"));
1059 setup_roster(r, f1, nis);
1060 cset cs; MM(cs);
1061 cs.attrs_cleared.insert(make_pair(foo_bar, attr_key("attr_file")));
1062 // exists now, so should be fine
1063 BOOST_CHECK_NOT_THROW(cs.apply_to(tree), logic_error);
1064 // but last time killed it, so can't be killed again
1065 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1066 }
1067 {
1068 L(FL("TEST: can't have no-op deltas"));
1069 setup_roster(r, f1, nis);
1070 cset cs; MM(cs);
1071 cs.deltas_applied.insert(make_pair(foo_bar,
1072 make_pair(f1, f1)));
1073 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1074 }
1075 {
1076 L(FL("TEST: can't have add+delta"));
1077 setup_roster(r, f1, nis);
1078 cset cs; MM(cs);
1079 cs.files_added.insert(make_pair(baz, f1));
1080 cs.deltas_applied.insert(make_pair(baz,
1081 make_pair(f1, f2)));
1082 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1083 }
1084 {
1085 L(FL("TEST: can't delta a directory"));
1086 setup_roster(r, f1, nis);
1087 cset cs; MM(cs);
1088 cs.deltas_applied.insert(make_pair(foo,
1089 make_pair(f1, f2)));
1090 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1091 }
1092 {
1093 L(FL("TEST: can't delete non-empty directory"));
1094 setup_roster(r, f1, nis);
1095 cset cs; MM(cs);
1096 cs.nodes_deleted.insert(foo);
1097 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1098 }
1099 {
1100 L(FL("TEST: attach node with no root directory present"));
1101 // for this test, make sure original roster has no contents
1102 r = roster_t();
1103 cset cs; MM(cs);
1104 split_path sp;
1105 file_path_internal("blah/blah/blah").split(sp);
1106 cs.dirs_added.insert(sp);
1107 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1108 }
1109 {
1110 L(FL("TEST: can't move a directory underneath itself"));
1111 setup_roster(r, f1, nis);
1112 cset cs; MM(cs);
1113 split_path foo_blah;
1114 file_path_internal("foo/blah").split(foo_blah);
1115 cs.nodes_renamed.insert(make_pair(foo, foo_blah));
1116 BOOST_CHECK_THROW(cs.apply_to(tree), logic_error);
1117 }
1118}
1119
1120void
1121root_dir_test()
1122{
1123 temp_node_id_source nis;
1124 roster_t r;
1125 MM(r);
1126 editable_roster_base tree(r, nis);
1127
1128 file_id f1(string("0000000000000000000000000000000000000001"));
1129
1130 split_path root, baz;
1131 file_path().split(root);
1132 file_path_internal("baz").split(baz);
1133
1134 {
1135 L(FL("TEST: can rename root"));
1136 setup_roster(r, f1, nis);
1137 cset cs; MM(cs);
1138 split_path sp1, sp2;
1139 cs.dirs_added.insert(root);
1140 cs.nodes_renamed.insert(make_pair(root, baz));
1141 cs.apply_to(tree);
1142 r.check_sane(true);
1143 }
1144 {
1145 L(FL("TEST: can delete root (but it makes us insane)"));
1146 // for this test, make sure root has no contents
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.apply_to(tree);
1152 BOOST_CHECK_THROW(r.check_sane(true), logic_error);
1153 }
1154 {
1155 L(FL("TEST: can delete and replace root"));
1156 r = roster_t();
1157 r.attach_node(r.create_dir_node(nis), root);
1158 cset cs; MM(cs);
1159 cs.nodes_deleted.insert(root);
1160 cs.dirs_added.insert(root);
1161 cs.apply_to(tree);
1162 r.check_sane(true);
1163 }
1164}
1165
1166void
1167add_cset_tests(test_suite * suite)
1168{
1169 I(suite);
1170 suite->add(BOOST_TEST_CASE(&basic_csets_test));
1171 suite->add(BOOST_TEST_CASE(&invalid_csets_test));
1172 suite->add(BOOST_TEST_CASE(&cset_written_test));
1173 suite->add(BOOST_TEST_CASE(&root_dir_test));
1174}
1175
1176#endif // BUILD_UNIT_TESTS
1177
1178
1179
1180// Local Variables:
1181// mode: C++
1182// fill-column: 76
1183// c-file-style: "gnu"
1184// indent-tabs-mode: nil
1185// End:
1186// 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