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