# # patch "cset.cc" # from [eb4763330e53596f2f751ddf71ad70da1e4b295a] # to [5d6af2c05c81369374962dae8359d1ae3784dc41] # # patch "cset.hh" # from [27a33d01fa88feb466ef9a7e911e43cf7478fa3f] # to [6f48b1cfbc5d34bafe123dab002a92e36c7d5353] # # patch "paths.cc" # from [65e0f4d000bc1eb76582d0b07213094b5746d564] # to [c32ea754e4be001dc27f4dfc37a197c2c8b6ca6d] # # patch "roster4.cc" # from [765cf00286c85d87fde25029a3cb15cc26b62771] # to [e2f34c430686f2b4c886b5cd2bfc211c827a1635] # # patch "roster4.hh" # from [d4c48d7ec8ea1c8092b520ee9d8b230c50a0786f] # to [e7936fb9cda08828fe263cb6215ae389ac628cee] # # patch "unit_tests.cc" # from [4fb42f5d274195a4c766d3f945dbacacef0bb183] # to [d633d9d5a2bab314251f86da95c3ef74621c2970] # # patch "unit_tests.hh" # from [b4771072e78a9d5490ec02109d82506ea454b54a] # to [85c98184709aa215922b6d08a32edab993073d89] # ======================================================================== --- cset.cc eb4763330e53596f2f751ddf71ad70da1e4b295a +++ cset.cc 5d6af2c05c81369374962dae8359d1ae3784dc41 @@ -70,7 +70,7 @@ // no file appears in both the "added" list and the "patched" list { - set::const_iterator a = cs.files_added.begin(); + map::const_iterator a = cs.files_added.begin(); map >::const_iterator d = cs.deltas_applied.begin(); while (a != cs.files_added.end() && d != cs.deltas_applied.end()) @@ -89,8 +89,8 @@ // no file+attr pair appears in both the "set" list and the "cleared" list { - set >::const_iterator c = cs.attrs_cleared.begin(); - map, attr_value>::const_iterator + set >::const_iterator c = cs.attrs_cleared.begin(); + map, attr_value>::const_iterator s = cs.attrs_set.begin(); while (c != cs.attrs_cleared.end() && s != cs.attrs_set.end()) { @@ -109,6 +109,19 @@ I(i->first != i->second); } +bool +cset::empty() +{ + return + nodes_deleted.empty() + && dirs_added.empty() + && files_added.empty() + && nodes_renamed.empty() + && deltas_applied.empty() + && attrs_cleared.empty() + && attrs_set.empty(); +} + void cset::apply_to(editable_tree & t) { @@ -179,11 +192,11 @@ i != deltas_applied.end(); ++i) t.apply_delta(i->first, i->second.first, i->second.second); - for (set >::const_iterator i = attrs_cleared.begin(); + for (set >::const_iterator i = attrs_cleared.begin(); i != attrs_cleared.end(); ++i) t.clear_attr(i->first, i->second); - for (map, attr_val>::const_iterator i = attrs_set.begin(); + for (map, attr_value>::const_iterator i = attrs_set.begin(); i != attrs_set.end(); ++i) t.set_attr(i->first.first, i->first.second, i->second); } ======================================================================== --- cset.hh 27a33d01fa88feb466ef9a7e911e43cf7478fa3f +++ cset.hh 6f48b1cfbc5d34bafe123dab002a92e36c7d5353 @@ -16,9 +16,7 @@ #include "paths.hh" #include "vocab.hh" -typedef std::string attr_name; -typedef std::string attr_val; -typedef std::map attr_map; +typedef std::map attr_map; typedef std::vector split_path; @@ -44,10 +42,10 @@ file_id const & old_id, file_id const & new_id) = 0; virtual void clear_attr(split_path const & pth, - attr_name const & name) = 0; + attr_key const & name) = 0; virtual void set_attr(split_path const & pth, - attr_name const & name, - attr_val const & val) = 0; + attr_key const & name, + attr_value const & val) = 0; virtual ~editable_tree() {} }; @@ -71,10 +69,11 @@ std::map > deltas_applied; // Attribute changes. - std::set > attrs_cleared; - std::map, attr_val> attrs_set; + std::set > attrs_cleared; + std::map, attr_value> attrs_set; void apply_to(editable_tree & t); + bool empty(); }; #endif // __CSET_HH__ ======================================================================== --- paths.cc 65e0f4d000bc1eb76582d0b07213094b5746d564 +++ paths.cc c32ea754e4be001dc27f4dfc37a197c2c8b6ca6d @@ -235,10 +235,14 @@ { std::vector::const_iterator i = pieces.begin(); I(i != pieces.end()); - if (pieces.size() > 1) - I(!null_name(*i)); + + // FIXME: this is disabled for the moment, as rosters make + // paths of the form ["", "foo", "bar"] all the time... + // if (pieces.size() > 1) + // I(!null_name(*i)); + std::string tmp = pc_interner.lookup(*i); - I(tmp != bookkeeping_root.as_internal()); + //I(tmp != bookkeeping_root.as_internal()); for (++i; i != pieces.end(); ++i) { I(!null_name(*i)); ======================================================================== --- roster4.cc 765cf00286c85d87fde25029a3cb15cc26b62771 +++ roster4.cc e2f34c430686f2b4c886b5cd2bfc211c827a1635 @@ -1,13 +1,15 @@ // copyright (C) 2005 nathaniel smith // copyright (C) 2005 graydon hoare // all rights reserved. // licensed to the public under the terms of the GNU GPL (>= 2) // see the file COPYING for details +#include +#include +#include #include "roster4.hh" #include "vocab.hh" -#include using std::inserter; using std::make_pair; @@ -16,6 +18,7 @@ using std::reverse; using std::set; using std::stack; +using std::vector; /////////////////////////////////////////////////////////////////// @@ -69,19 +72,11 @@ // - The only legal one-element split_path is [""], referring to the // root node. // - // We do this in order to support the notion of moving the root - // directory around, or applying attributes to the root directory. A - // roster is not considered "sane" for writing to disk unless it has a - // root node, but it can assume a transient state during editing in - // which there is no root node (if you detach it, to move it somewhere - // else in the tree). + // We do this in order to support the notion of moving the root directory + // around, or applying attributes to the root directory (though we will + // not support moving the root at this time, since we haven't worked out + // all the UI implications yet). // - // Note that this means that "adds" and "rename second-halfs" should - // be intermixed as top-down attaches, so that a "new" root node is - // added before a renamed root node is moved elsewhere; just as - // "deletes" and "rename first-halfs" should be intermixed as - // bottom-up detaches. - // const node_id the_null_node = 0; @@ -115,8 +110,8 @@ void dir_node::attach_child(path_component const & pc, node_t child) { - I(!null_node(child->parent)); - I(!null_name(child->name)); + I(null_node(child->parent)); + I(null_name(child->name)); safe_insert(children, make_pair(pc, child)); child->parent = this->self; child->name = pc; @@ -165,12 +160,15 @@ split_path & dirname, path_component & basename) { I(!sp.empty()); - split_path::const_iterator penultimate = sp.end(); - --penultimate; + // L(F("dirname_basename('%s' [%d components],...)\n") % file_path(sp) % sp.size()); + split_path::const_iterator penultimate = sp.begin() + (sp.size()-1); dirname = split_path(sp.begin(), penultimate); + basename = *penultimate; if (dirname.empty()) - I(null_name(basename)); - basename = *penultimate; + { + // L(F("basename %d vs. null component %d\n") % basename % the_null_component); + I(null_name(basename)); + } } @@ -195,8 +193,8 @@ } I(has_root()); - dir_t d = root_dir; - for (split_path::const_iterator i = dirname.begin(); i != dirname.end(); ++i) + dir_t d = root_dir; + for (split_path::const_iterator i = dirname.begin()+1; i != dirname.end(); ++i) d = downcast_to_dir_t(d->get_child(*i)); return d->get_child(basename); } @@ -212,6 +210,7 @@ void roster_t::get_name(node_id nid, split_path & sp) const { + I(!null_node(nid)); sp.clear(); while (!null_node(nid)) { @@ -226,6 +225,8 @@ void roster_t::replace_node_id(node_id from, node_id to) { + I(!null_node(from)); + I(!null_node(to)); node_t n = get_node(from); safe_erase(nodes, from); safe_insert(nodes, make_pair(to, n)); @@ -261,7 +262,9 @@ } dir_t parent = downcast_to_dir_t(get_node(dirname)); - return parent->detach_child(basename)->self; + node_id n = parent->detach_child(basename)->self; + I(!null_node(n)); + return n; } @@ -280,7 +283,9 @@ roster_t::create_dir_node(node_id_source & nis) { node_id nid = nis.next(); - safe_insert(nodes, make_pair(nid, dir_t(new dir_node()))); + dir_t d = dir_t(new dir_node()); + d->self = nid; + safe_insert(nodes, make_pair(nid, d)); return nid; } @@ -290,6 +295,7 @@ { node_id nid = nis.next(); file_t f = file_t(new file_node()); + f->self = nid; f->content = content; safe_insert(nodes, make_pair(nid, f)); return nid; @@ -308,6 +314,7 @@ // ensure the node is already detached (as best one can) I(null_node(n->parent)); I(null_name(n->name)); + I(!null_node(n->self)); if (dirname.empty()) { @@ -318,6 +325,7 @@ } else { + // L(F("attaching into dir '%s'\n") % file_path(dirname)); dir_t parent = downcast_to_dir_t(get_node(dirname)); parent->attach_child(basename, n); } @@ -331,6 +339,7 @@ { file_t f = downcast_to_file_t(get_node(pth)); I(f->content == old_id); + I(!null_node(f->self)); I(!(f->content == new_id)); f->content = new_id; } @@ -338,16 +347,16 @@ void roster_t::clear_attr(split_path const & pth, - attr_name const & name) + attr_key const & name) { - set_attr(pth, name, make_pair(false, attr_val())); + set_attr(pth, name, make_pair(false, attr_value())); } void roster_t::set_attr(split_path const & pth, - attr_name const & name, - attr_val const & val) + attr_key const & name, + attr_value const & val) { set_attr(pth, name, make_pair(true, val)); } @@ -355,15 +364,16 @@ void roster_t::set_attr(split_path const & pth, - attr_name const & name, - pair const & val) + attr_key const & name, + pair const & val) { - I(val.first || val.second.empty()); + I(val.first || val.second().empty()); node_t n = get_node(pth); + I(!null_node(n->self)); full_attr_map::iterator i = n->attrs.find(name); if (i == n->attrs.end()) i = safe_insert(n->attrs, make_pair(name, - make_pair(false, attr_val()))); + make_pair(false, attr_value()))); I(i->second != val); i->second = val; } @@ -518,7 +528,7 @@ } I(!null_id(n->birth_revision)); for (full_attr_map::const_iterator i = n->attrs.begin(); i != n->attrs.end(); ++i) - I(i->second.first || !i->second.second.empty()); + I(i->second.first || !i->second.second().empty()); if (n != root_dir) I(downcast_to_dir_t(get_node(n->parent))->get_child(n->name) == n); } @@ -528,81 +538,65 @@ } -/// Remainder is not yet compiling, so commented out - - -/* - - namespace { - struct temp_node_id_source : public node_id_source - { - temp_node_id_source() : curr(first_temp_node) {} - virtual node_id next() - { - node_id n = curr++; - I(temp_node(n)); - return n; - } - node_id curr; - }; - struct true_node_id_source : public node_id_source - { - true_node_id_source(app_state & app) : app(app) {} - virtual node_id next() - { - node_id n = app.db.next_node_id(); - I(!temp_node(n)); - return n; - } - app_state & app; - }; - // adaptor class to enable cset application on rosters. - class editable_roster_base : public editable_tree + class editable_roster_base + : public editable_tree { public: - editable_roster(roster_t & r, node_id_source & nis) + editable_roster_base(roster_t & r, node_id_source & nis) : r(r), nis(nis) {} virtual node_id detach_node(split_path const & src) { - return r.detch_node(src); + // L(F("detach_node('%s')") % file_path(src)); + return r.detach_node(src); } virtual void drop_detached_node(node_id nid) { + // L(F("drop_detached_node(%d)") % nid); r.drop_detached_node(nid); } virtual node_id create_dir_node() { - return r.create_dir_node(nis); + // L(F("create_dir_node()\n")); + node_id n = r.create_dir_node(nis); + // L(F("create_dir_node() -> %d\n") % n); + return n; } virtual node_id create_file_node(file_id const & content) { - return r.create_file_node(content, nis); + // L(F("create_file_node('%s')\n") % content); + node_id n = r.create_file_node(content, nis); + // L(F("create_file_node('%s') -> %d\n") % content % n); + return n; } virtual void attach_node(node_id nid, split_path const & dst) { + // L(F("attach_node(%d, '%s')") % nid % file_path(dst)); r.attach_node(nid, dst); } virtual void apply_delta(split_path const & pth, file_id const & old_id, file_id const & new_id) { + // L(F("clear_attr('%s', '%s', '%s')") % file_path(pth) % old_id % new_id); r.apply_delta(pth, old_id, new_id); } virtual void clear_attr(split_path const & pth, - attr_name const & name) + attr_key const & name) { + // L(F("clear_attr('%s', '%s')") % file_path(pth) % name); r.clear_attr(pth, name); } virtual void set_attr(split_path const & pth, - attr_name const & name, - attr_val const & val) + attr_key const & name, + attr_value const & val) { + // L(F("set_attr('%s', '%s', '%s')") % file_path(pth) % name % val); r.set_attr(pth, name, val); } protected: @@ -610,6 +604,50 @@ node_id_source & nis; }; + struct testing_node_id_source : public node_id_source + { + testing_node_id_source() : curr(first_node) {} + virtual node_id next() + { + // L(F("creating node %x\n") % curr); + node_id n = curr++; + I(!temp_node(n)); + return n; + } + node_id curr; + }; + +} + +/// Remainder is not yet compiling, so commented out + +/* +namespace +{ + struct temp_node_id_source : public node_id_source + { + temp_node_id_source() : curr(first_temp_node) {} + virtual node_id next() + { + node_id n = curr++; + I(temp_node(n)); + return n; + } + node_id curr; + }; + + struct true_node_id_source : public node_id_source + { + true_node_id_source(app_state & app) : app(app) {} + virtual node_id next() + { + node_id n = app.db.next_node_id(); + I(!temp_node(n)); + return n; + } + app_state & app; + }; + class editable_roster_for_merge : editable_roster_base { public: @@ -975,7 +1013,7 @@ marks.file_content.clear(); marks.file_content.insert(rid); } - void handle_attr(split_path const & pth, attr_name const & name) + void handle_attr(split_path const & pth, attr_key const & name) { marking_t & marks = safe_get(marking, r.lookup(pth)); if (marks.attrs.find(name) == marks.attrs.end()) @@ -984,13 +1022,13 @@ markset.clear(); markset.insert(rid); } - virtual void clear_attr(split_path const & pth, attr_name const & name) + virtual void clear_attr(split_path const & pth, attr_key const & name) { this->editable_roster_base::clear_attr(pth, name); handle_attr(pth, name); } - virtual void set_attr(split_path const & pth, attr_name const & name, - attr_val const & val); + virtual void set_attr(split_path const & pth, attr_key const & name, + attr_value const & val); { this->editable_roster_base::set_attr(pth, name, val); handle_attr(pth, name); @@ -1039,3 +1077,259 @@ } */ + + + + +//////////////////////////////////////////////////////////////////// +// testing +//////////////////////////////////////////////////////////////////// + +#ifdef BUILD_UNIT_TESTS +#include "unit_tests.hh" +#include "sanity.hh" +#include "constants.hh" + +#include +#include + +using std::string; +using boost::lexical_cast; + +template +typename M::const_iterator +random_element(M const & m) +{ + size_t i = rand() % m.size(); + typename M::const_iterator j = m.begin(); + while (i > 0) + { + I(j != m.end()); + --i; + ++j; + } + return j; +} + +struct +change_automaton +{ + + change_automaton() + { + srand(0x12345678); + } + + string new_word() + { + static string wordchars = "abcdefghijlkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + static unsigned tick = 0; + string tmp; + do + { + tmp += wordchars[rand() % wordchars.size()]; + } + while (tmp.size() < 10 && !flip(10)); + return tmp + lexical_cast(tick++); + } + + file_id new_ident() + { + static string tab = "0123456789abcdef"; + string tmp; + tmp.reserve(constants::idlen); + for (unsigned i = 0; i < constants::idlen; ++i) + tmp += tab[rand() % tab.size()]; + return file_id(tmp); + } + + path_component new_component() + { + vector pieces; + file_path_internal(new_word()).split(pieces); + return pieces.back(); + } + + bool flip(unsigned n = 2) + { + return (rand() % n) == 0; + } + + attr_key pick_attr(full_attr_map const & attrs) + { + return random_element(attrs)->first; + } + + attr_key pick_attr(attr_map const & attrs) + { + return random_element(attrs)->first; + } + + bool parent_of(split_path const & p, + split_path const & c) + { + bool is_parent = false; + + if (p.size() <= c.size()) + { + split_path::const_iterator c_anchor = + search(c.begin(), c.end(), + p.begin(), p.end()); + + is_parent = (c_anchor == c.begin()); + } + + // L(F("path '%s' is%s parent of '%s'") + // % file_path(p) + // % (is_parent ? "" : " not") + // % file_path(c)); + + return is_parent; + } + + void perform_random_action(roster_t & r, node_id_source & nis) + { + cset c; + while (c.empty()) + { + if (r.all_nodes().empty()) + { + // Must add, couldn't find anything to work with + split_path root; + root.push_back(the_null_component); + c.dirs_added.insert(root); + } + else + { + node_t n = random_element(r.all_nodes())->second; + split_path pth; + r.get_name(n->self, pth); + // L(F("considering acting on '%s'\n") % file_path(pth)); + + switch (rand() % 7) + { + default: + case 0: + case 1: + case 2: + if (is_file_t(n) || (pth.size() > 1 && flip())) + // Add a sibling of an existing entry. + pth[pth.size() - 1] = new_component(); + + else + // Add a child of an existing entry. + pth.push_back(new_component()); + + if (flip()) + { + // L(F("adding dir '%s'\n") % file_path(pth)); + safe_insert(c.dirs_added, pth); + } + else + { + // L(F("adding file '%s'\n") % file_path(pth)); + safe_insert(c.files_added, make_pair(pth, new_ident())); + } + break; + + case 3: + if (is_file_t(n)) + { + safe_insert(c.deltas_applied, + make_pair + (pth, make_pair(downcast_to_file_t(n)->content, + new_ident()))); + } + break; + + case 4: + { + node_t n2 = random_element(r.all_nodes())->second; + split_path pth2; + r.get_name(n2->self, pth2); + + if (n == n2) + continue; + + if (is_file_t(n2) || (pth2.size() > 1 && flip())) + { + // L(F("renaming to a sibling of an existing entry '%s'\n") % file_path(pth2)); + // Move to a sibling of an existing entry. + pth2[pth2.size() - 1] = new_component(); + } + + else + { + // L(F("renaming to a child of an existing entry '%s'\n") % file_path(pth2)); + // Move to a child of an existing entry. + pth2.push_back(new_component()); + } + + if (!parent_of(pth, pth2)) + { + // L(F("renaming '%s' -> '%s\n") % file_path(pth) % file_path(pth2)); + safe_insert(c.nodes_renamed, make_pair(pth, pth2)); + } + } + break; + + case 5: + if (!null_node(n->parent) && + (is_file_t(n) || downcast_to_dir_t(n)->children.empty())) + { + // L(F("deleting '%s'\n") % file_path(pth)); + safe_insert(c.nodes_deleted, pth); + } + break; + + case 6: + if (!n->attrs.empty() && flip()) + { + attr_key k = pick_attr(n->attrs); + if (safe_get(n->attrs, k).first) + { + // L(F("clearing attr on '%s'\n") % file_path(pth)); + safe_insert(c.attrs_cleared, make_pair(pth, k)); + } + } + else + { + // L(F("setting attr on '%s'\n") % file_path(pth)); + safe_insert(c.attrs_set, make_pair(make_pair(pth, new_word()), new_word())); + } + break; + } + } + } + // now do it + editable_roster_base e = editable_roster_base(r, nis); + c.apply_to(e); + } +}; + + +static void +automaton_roster_test() +{ + roster_t r1; + change_automaton aut; + testing_node_id_source nis; + + for (int i = 0; i < 100000; ++i) + { + if (i < 500 || i % 500 == 0) + P(F("performing random action %d\n") % i); + aut.perform_random_action(r1, nis); + } +} + + +void +add_roster_tests(test_suite * suite) +{ + I(suite); + suite->add(BOOST_TEST_CASE(&automaton_roster_test)); +} + + +#endif // BUILD_UNIT_TESTS ======================================================================== --- roster4.hh d4c48d7ec8ea1c8092b520ee9d8b230c50a0786f +++ roster4.hh e7936fb9cda08828fe263cb6215ae389ac628cee @@ -33,9 +33,9 @@ typedef boost::shared_ptr dir_t; // (true, "val") or (false, "") are both valid attr values (for proper -// merging, we have to widen the attr_val type to include a first-class +// merging, we have to widen the attr_value type to include a first-class // "undefined" value). -typedef std::map > full_attr_map; +typedef std::map > full_attr_map; typedef std::map dir_map; typedef std::map node_map; @@ -145,13 +145,13 @@ file_id const & old_id, file_id const & new_new); void clear_attr(split_path const & pth, - attr_name const & name); + attr_key const & name); void set_attr(split_path const & pth, - attr_name const & name, - attr_val const & val); + attr_key const & name, + attr_value const & val); void set_attr(split_path const & pth, - attr_name const & name, - std::pair const & val); + attr_key const & name, + std::pair const & val); node_map const & all_nodes() const { ======================================================================== --- unit_tests.cc 4fb42f5d274195a4c766d3f945dbacacef0bb183 +++ unit_tests.cc d633d9d5a2bab314251f86da95c3ef74621c2970 @@ -85,6 +85,9 @@ if (t.empty() || t.find("paths") != t.end()) add_paths_tests(suite); + + if (t.empty() || t.find("roster") != t.end()) + add_roster_tests(suite); // all done, add our clean-shutdown-indicator suite->add(BOOST_TEST_CASE(&clean_shutdown_dummy_test)); ======================================================================== --- unit_tests.hh b4771072e78a9d5490ec02109d82506ea454b54a +++ unit_tests.hh 85c98184709aa215922b6d08a32edab993073d89 @@ -35,5 +35,6 @@ void add_crypto_tests(test_suite * suite); void add_string_queue_tests(test_suite * suite); void add_paths_tests(test_suite * suite); +void add_roster_tests(test_suite * suite); #endif