Rev 4508: Test infrastructure for testing all inventory delta applications and fix CHK inventories to reject repeated file ids in deltas. in http://people.ubuntu.com/~robertc/baz2.0/pending/apply-inventory-delta

Robert Collins robertc at robertcollins.net
Tue Jul 7 05:34:27 BST 2009


At http://people.ubuntu.com/~robertc/baz2.0/pending/apply-inventory-delta

------------------------------------------------------------
revno: 4508
revision-id: robertc at robertcollins.net-20090707043425-3cs9g40rei1siu1q
parent: robertc at robertcollins.net-20090707043213-4hjjhgr40iq7gk2d
committer: Robert Collins <robertc at robertcollins.net>
branch nick: apply-inventory-delta
timestamp: Tue 2009-07-07 14:34:25 +1000
message:
  Test infrastructure for testing all inventory delta applications and fix CHK inventories to reject repeated file ids in deltas.
=== modified file 'bzrlib/inventory.py'
--- a/bzrlib/inventory.py	2009-07-02 23:10:53 +0000
+++ b/bzrlib/inventory.py	2009-07-07 04:34:25 +0000
@@ -1130,8 +1130,8 @@
         # before starting to mutate the inventory.
         unique_file_ids = set([f for _, _, f, _ in delta])
         if len(unique_file_ids) != len(delta):
-            raise AssertionError("a file-id appears multiple times in %r"
-                    % (delta,))
+            raise errors.InconsistentDeltaDelta(delta,
+                "a file-id appears multiple times")
         del unique_file_ids
 
         children = {}
@@ -1619,7 +1619,9 @@
             result.parent_id_basename_to_file_id = None
         result.root_id = self.root_id
         id_to_entry_delta = []
+        id_set = set()
         for old_path, new_path, file_id, entry in inventory_delta:
+            id_set.add(file_id)
             # file id changes
             if new_path == '':
                 result.root_id = file_id
@@ -1661,6 +1663,9 @@
                     # If the two keys are the same, the value will be unchanged
                     # as its always the file id.
                     parent_id_basename_delta.append((old_key, new_key, new_value))
+        if len(id_set) != len(inventory_delta):
+            raise errors.InconsistentDeltaDelta(inventory_delta,
+                'repeated file id')
         result.id_to_entry.apply_delta(id_to_entry_delta)
         if parent_id_basename_delta:
             result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)

=== modified file 'bzrlib/tests/test_inv.py'
--- a/bzrlib/tests/test_inv.py	2009-06-17 18:41:26 +0000
+++ b/bzrlib/tests/test_inv.py	2009-07-07 04:34:25 +0000
@@ -15,10 +15,208 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
 
-from bzrlib import errors, chk_map, inventory, osutils
+from bzrlib import (
+    chk_map,
+    bzrdir,
+    errors,
+    inventory,
+    osutils,
+    repository,
+    revision,
+    )
 from bzrlib.inventory import (CHKInventory, Inventory, ROOT_ID, InventoryFile,
     InventoryDirectory, InventoryEntry, TreeReference)
-from bzrlib.tests import TestCase, TestCaseWithTransport
+from bzrlib.tests import (
+    TestCase,
+    TestCaseWithTransport,
+    condition_isinstance,
+    multiply_tests,
+    split_suite_by_condition,
+    )
+from bzrlib.tests.workingtree_implementations import workingtree_formats
+
+
+def load_tests(standard_tests, module, loader):
+    """Parameterise some inventory tests."""
+    to_adapt, result = split_suite_by_condition(standard_tests,
+        condition_isinstance(TestDeltaApplication))
+    scenarios = [
+        ('Inventory', {'apply_delta':apply_inventory_Inventory}),
+        ]
+    # Working tree basis delta application
+    # Repository add_inv_by_delta.
+    # Reduce form of the per_repository test logic - that logic needs to be
+    # be able to get /just/ repositories whereas these tests are fine with
+    # just creating trees.
+    formats = set()
+    for _, format in repository.format_registry.iteritems():
+        scenarios.append((str(format.__name__), {
+            'apply_delta':apply_inventory_Repository_add_inventory_by_delta,
+            'format':format}))
+    for format in workingtree_formats():
+        scenarios.append((str(format.__class__.__name__), {
+            'apply_delta':apply_inventory_WT_basis,
+            'format':format}))
+    return multiply_tests(to_adapt, scenarios, result)
+
+
+def apply_inventory_Inventory(self, basis, delta):
+    """Apply delta to basis and return the result.
+    
+    :param basis: An inventory to be used as the basis.
+    :param delta: The inventory delta to apply:
+    :return: An inventory resulting from the application.
+    """
+    basis.apply_delta(delta)
+    return basis
+
+
+def apply_inventory_WT_basis(self, basis, delta):
+    """Apply delta to basis and return the result.
+
+    This sets the parent and then calls update_basis_by_delta.
+    
+    :param basis: An inventory to be used as the basis.
+    :param delta: The inventory delta to apply:
+    :return: An inventory resulting from the application.
+    """
+    control = self.make_bzrdir('tree', format=self.format._matchingbzrdir)
+    control.create_repository()
+    control.create_branch()
+    tree = self.format.initialize(control)
+    tree.lock_write()
+    try:
+        repo = tree.branch.repository
+        repo.start_write_group()
+        try:
+            rev = revision.Revision('basis', timestamp=0, timezone=None,
+                message="", committer="foo at example.com")
+            basis.revision_id = 'basis'
+            repo.add_revision('basis', rev, basis)
+            # Add a revision for the result, with the basis content - 
+            # update_basis_by_delta doesn't check that the delta results in
+            # result, and we want inconsistent deltas to get called on the
+            # tree, or else the code isn't actually checked.
+            rev = revision.Revision('result', timestamp=0, timezone=None,
+                message="", committer="foo at example.com")
+            basis.revision_id = 'result'
+            repo.add_revision('result', rev, basis)
+        except:
+            repo.abort_write_group()
+            raise
+        else:
+            repo.commit_write_group()
+        # This reads basis from the repo and puts it into the tree's local
+        # cache, if it has one.
+        tree.set_parent_ids(['basis'])
+    finally:
+        tree.unlock()
+    # Fresh lock, reads disk again.
+    tree.lock_write()
+    try:
+        tree.update_basis_by_delta('result', delta)
+    finally:
+        tree.unlock()
+    # reload tree - ensure we get what was written.
+    tree = tree.bzrdir.open_workingtree()
+    basis_tree = tree.basis_tree()
+    basis_tree.lock_read()
+    self.addCleanup(basis_tree.unlock)
+    # Note, that if the tree does not have a local cache, the trick above of
+    # setting the result as the basis, will come back to bite us. That said,
+    # all the implementations in bzr do have a local cache.
+    return basis_tree.inventory
+
+
+def apply_inventory_Repository_add_inventory_by_delta(self, basis, delta):
+    """Apply delta to basis and return the result.
+    
+    This inserts basis as a whole inventory and then uses
+    add_inventory_by_delta to add delta.
+
+    :param basis: An inventory to be used as the basis.
+    :param delta: The inventory delta to apply:
+    :return: An inventory resulting from the application.
+    """
+    format = self.format()
+    control = self.make_bzrdir('tree', format=format._matchingbzrdir)
+    repo = format.initialize(control)
+    repo.lock_write()
+    try:
+        repo.start_write_group()
+        try:
+            rev = revision.Revision('basis', timestamp=0, timezone=None,
+                message="", committer="foo at example.com")
+            basis.revision_id = 'basis'
+            repo.add_revision('basis', rev, basis)
+        except:
+            repo.abort_write_group()
+            raise
+        else:
+            repo.commit_write_group()
+    finally:
+        repo.unlock()
+    repo.lock_write()
+    try:
+        repo.start_write_group()
+        try:
+            inv_sha1 = repo.add_inventory_by_delta('basis', delta,
+                'result', ['basis'])
+        except:
+            repo.abort_write_group()
+            raise
+        else:
+            repo.commit_write_group()
+    finally:
+        repo.unlock()
+    # Fresh lock, reads disk again.
+    repo = repo.bzrdir.open_repository()
+    repo.lock_read()
+    self.addCleanup(repo.unlock)
+    return repo.get_inventory('result')
+
+
+class TestDeltaApplication(TestCaseWithTransport):
+ 
+    def get_empty_inventory(self, reference_inv=None):
+        """Get an empty inventory.
+
+        Note that tests should not depend on the revision of the root for
+        setting up test conditions, as it has to be flexible to accomodate non
+        rich root repositories.
+
+        :param reference_inv: If not None, get the revision for the root from
+            this inventory. This is useful for dealing with older repositories
+            that routinely discarded the root entry data. If None, the root's
+            revision is set to 'basis'.
+        """
+        inv = inventory.Inventory()
+        if reference_inv is not None:
+            inv.root.revision = reference_inv.root.revision
+        else:
+            inv.root.revision = 'basis'
+        return inv
+
+    def test_empty_delta(self):
+        inv = self.get_empty_inventory()
+        delta = []
+        inv = self.apply_delta(self, inv, delta)
+        inv2 = self.get_empty_inventory(inv)
+        self.assertEqual([], inv2._make_delta(inv))
+
+    def test_repeated_file_id(self):
+        inv = self.get_empty_inventory()
+        file1 = inventory.InventoryFile('id', 'path1', inv.root.file_id)
+        file1.revision = 'result'
+        file1.text_size = 0
+        file1.text_sha1 = ""
+        file2 = inventory.InventoryFile('id', 'path2', inv.root.file_id)
+        file2.revision = 'result'
+        file2.text_size = 0
+        file2.text_sha1 = ""
+        delta = [(None, 'path1', 'id', file1), (None, 'path2', 'id', file2)]
+        self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
+            inv, delta)
 
 
 class TestInventoryEntry(TestCase):

=== modified file 'bzrlib/tests/workingtree_implementations/__init__.py'
--- a/bzrlib/tests/workingtree_implementations/__init__.py	2009-06-10 03:56:49 +0000
+++ b/bzrlib/tests/workingtree_implementations/__init__.py	2009-07-07 04:34:25 +0000
@@ -59,6 +59,12 @@
         return self.workingtree_format.initialize(made_control)
 
 
+def workingtree_formats():
+    """The known working tree formats."""
+    return (workingtree.WorkingTreeFormat._formats.values() +
+        workingtree._legacy_formats)
+
+
 def load_tests(standard_tests, module, loader):
     test_workingtree_implementations = [
         'bzrlib.tests.workingtree_implementations.test_add_reference',
@@ -105,8 +111,8 @@
         # None here will cause a readonly decorator to be created
         # by the TestCaseWithTransport.get_readonly_transport method.
         None,
-        workingtree.WorkingTreeFormat._formats.values()
-        + workingtree._legacy_formats)
+        workingtree_formats()
+        )
 
     # add the tests for the sub modules
     return tests.multiply_tests(




More information about the bazaar-commits mailing list