Rev 4524: (robertc) First tranche of inventory-delta application validation. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/
Canonical.com Patch Queue Manager
pqm at pqm.ubuntu.com
Fri Jul 10 07:35:42 BST 2009
At file:///home/pqm/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 4524 [merge]
revision-id: pqm at pqm.ubuntu.com-20090710063538-2hap9pxafqfe6r20
parent: pqm at pqm.ubuntu.com-20090709014400-y2e7prh8mejs2j26
parent: robertc at robertcollins.net-20090710053307-kf31or414hu20no8
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2009-07-10 07:35:38 +0100
message:
(robertc) First tranche of inventory-delta application validation.
(Robert Collins)
modified:
bzrlib/dirstate.py dirstate.py-20060728012006-d6mvoihjb3je9peu-1
bzrlib/errors.py errors.py-20050309040759-20512168c4e14fbd
bzrlib/inventory.py inventory.py-20050309040759-6648b84ca2005b37
bzrlib/tests/inventory_implementations/basics.py basics.py-20070903044446-kdjwbiu1p1zi9phs-1
bzrlib/tests/test_errors.py test_errors.py-20060210110251-41aba2deddf936a8
bzrlib/tests/test_inv.py testinv.py-20050722220913-1dc326138d1a5892
bzrlib/tests/workingtree_implementations/__init__.py __init__.py-20060203003124-b2aa5aca21a8bfad
bzrlib/xml8.py xml5.py-20050907032657-aac8f960815b66b1
=== modified file 'bzrlib/dirstate.py'
--- a/bzrlib/dirstate.py 2009-06-22 14:32:48 +0000
+++ b/bzrlib/dirstate.py 2009-07-10 05:33:07 +0000
@@ -1285,7 +1285,8 @@
removals = {}
for old_path, new_path, file_id, inv_entry in sorted(delta, reverse=True):
if (file_id in insertions) or (file_id in removals):
- raise AssertionError("repeated file id in delta %r" % (file_id,))
+ raise errors.InconsistentDelta(old_path or new_path, file_id,
+ "repeated file_id")
if old_path is not None:
old_path = old_path.encode('utf-8')
removals[file_id] = old_path
@@ -1399,7 +1400,21 @@
# At the same time, to reduce interface friction we convert the input
# inventory entries to dirstate.
root_only = ('', '')
+ # Accumulate parent references (path and id), to check for parentless
+ # items or items placed under files/links/tree-references. We get
+ # references from every item in the delta that is not a deletion and
+ # is not itself the root.
+ parents = set()
for old_path, new_path, file_id, inv_entry in delta:
+ if inv_entry is not None and file_id != inv_entry.file_id:
+ raise errors.InconsistentDelta(new_path, file_id,
+ "mismatched entry file_id %r" % inv_entry)
+ if new_path is not None:
+ new_path_utf8 = encode(new_path)
+ # note the parent for validation
+ dirname_utf8, basename_utf8 = osutils.split(new_path_utf8)
+ if basename_utf8:
+ parents.add((dirname_utf8, inv_entry.parent_id))
if old_path is None:
adds.append((None, encode(new_path), file_id,
inv_to_entry(inv_entry), True))
@@ -1420,7 +1435,6 @@
# for 'r' items on every pass.
self._update_basis_apply_deletes(deletes)
deletes = []
- new_path_utf8 = encode(new_path)
# Split into an add/delete pair recursively.
adds.append((None, new_path_utf8, file_id,
inv_to_entry(inv_entry), False))
@@ -1453,12 +1467,25 @@
changes.append((encode(old_path), encode(new_path), file_id,
inv_to_entry(inv_entry)))
- # Finish expunging deletes/first half of renames.
- self._update_basis_apply_deletes(deletes)
- # Reinstate second half of renames and new paths.
- self._update_basis_apply_adds(adds)
- # Apply in-situ changes.
- self._update_basis_apply_changes(changes)
+ try:
+ # Finish expunging deletes/first half of renames.
+ self._update_basis_apply_deletes(deletes)
+ # Reinstate second half of renames and new paths.
+ self._update_basis_apply_adds(adds)
+ # Apply in-situ changes.
+ self._update_basis_apply_changes(changes)
+ # Validate parents
+ self._update_basis_check_parents(parents)
+ except errors.BzrError, e:
+ if 'integrity error' not in str(e):
+ raise
+ # _get_entry raises BzrError when a request is inconsistent; we
+ # want such errors to be shown as InconsistentDelta - and that
+ # fits the behaviour we trigger. Partof this is driven by dirstate
+ # only supporting deltas that turn the basis into a closer fit to
+ # the active tree.
+ self._changes_aborted = True
+ raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
self._header_state = DirState.IN_MEMORY_MODIFIED
@@ -1573,6 +1600,22 @@
# it is being resurrected here, so blank it out temporarily.
self._dirblocks[block_index][1][entry_index][1][1] = null
+ def _update_basis_check_parents(self, parents):
+ """Check that parents required by the delta are all intact."""
+ for dirname_utf8, file_id in parents:
+ # Get the entry - the ensures that file_id, dirname_utf8 exists and
+ # has the right file id.
+ entry = self._get_entry(1, file_id, dirname_utf8)
+ if entry[1] is None:
+ self._changes_aborted = True
+ raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
+ file_id, "This parent is not present.")
+ # Parents of things must be directories
+ if entry[1][1][0] != 'd':
+ self._changes_aborted = True
+ raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
+ file_id, "This parent is not a directory.")
+
def _observed_sha1(self, entry, sha1, stat_value,
_stat_to_minikind=_stat_to_minikind, _pack_stat=pack_stat):
"""Note the sha1 of a file.
@@ -1821,7 +1864,7 @@
self._read_dirblocks_if_needed()
if path_utf8 is not None:
if type(path_utf8) is not str:
- raise AssertionError('path_utf8 is not a str: %s %s'
+ raise errors.BzrError('path_utf8 is not a str: %s %r'
% (type(path_utf8), path_utf8))
# path lookups are faster
dirname, basename = osutils.split(path_utf8)
=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py 2009-06-30 15:54:23 +0000
+++ b/bzrlib/errors.py 2009-07-07 03:40:29 +0000
@@ -2162,6 +2162,18 @@
self.reason = reason
+class InconsistentDeltaDelta(InconsistentDelta):
+ """Used when we get a delta that is not valid."""
+
+ _fmt = ("An inconsistent delta was supplied: %(delta)r"
+ "\nreason: %(reason)s")
+
+ def __init__(self, delta, reason):
+ BzrError.__init__(self)
+ self.delta = delta
+ self.reason = reason
+
+
class UpgradeRequired(BzrError):
_fmt = "To use this feature you must upgrade your branch at %(path)s."
=== modified file 'bzrlib/inventory.py'
--- a/bzrlib/inventory.py 2009-07-02 23:10:53 +0000
+++ b/bzrlib/inventory.py 2009-07-10 05:18:29 +0000
@@ -712,7 +712,19 @@
class CommonInventory(object):
- """Basic inventory logic, defined in terms of primitives like has_id."""
+ """Basic inventory logic, defined in terms of primitives like has_id.
+
+ An inventory is the metadata about the contents of a tree.
+
+ This is broadly a map from file_id to entries such as directories, files,
+ symlinks and tree references. Each entry maintains its own metadata like
+ SHA1 and length for files, or children for a directory.
+
+ Entries can be looked up either by path or by file_id.
+
+ InventoryEntry objects must not be modified after they are
+ inserted, other than through the Inventory API.
+ """
def __contains__(self, file_id):
"""True if this entry contains a file with given id.
@@ -1019,46 +1031,32 @@
class Inventory(CommonInventory):
- """Inventory of versioned files in a tree.
-
- This describes which file_id is present at each point in the tree,
- and possibly the SHA-1 or other information about the file.
- Entries can be looked up either by path or by file_id.
-
- The inventory represents a typical unix file tree, with
- directories containing files and subdirectories. We never store
- the full path to a file, because renaming a directory implicitly
- moves all of its contents. This class internally maintains a
+ """Mutable dict based in-memory inventory.
+
+ We never store the full path to a file, because renaming a directory
+ implicitly moves all of its contents. This class internally maintains a
lookup tree that allows the children under a directory to be
returned quickly.
- InventoryEntry objects must not be modified after they are
- inserted, other than through the Inventory API.
-
>>> inv = Inventory()
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
>>> inv['123-123'].name
'hello.c'
- May be treated as an iterator or set to look up file ids:
+ Id's may be looked up from paths:
- >>> bool(inv.path2id('hello.c'))
- True
+ >>> inv.path2id('hello.c')
+ '123-123'
>>> '123-123' in inv
True
- May also look up by name:
+ There are iterators over the contents:
- >>> [x[0] for x in inv.iter_entries()]
+ >>> [entry[0] for entry in inv.iter_entries()]
['', u'hello.c']
- >>> inv = Inventory('TREE_ROOT-12345678-12345678')
- >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
- Traceback (most recent call last):
- BzrError: parent_id {TREE_ROOT} not in inventory
- >>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
- InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None, revision=None)
"""
+
def __init__(self, root_id=ROOT_ID, revision_id=None):
"""Create or read an inventory.
@@ -1127,12 +1125,11 @@
"""
# Check that the delta is legal. It would be nice if this could be
# done within the loops below but it's safer to validate the delta
- # 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,))
- del unique_file_ids
+ # before starting to mutate the inventory, as there isn't a rollback
+ # facility.
+ list(_check_delta_unique_ids(_check_delta_unique_new_paths(
+ _check_delta_unique_old_paths(_check_delta_ids_match_entry(
+ delta)))))
children = {}
# Remove all affected items which were in the original inventory,
@@ -1167,7 +1164,11 @@
replacement.revision = new_entry.revision
replacement.children = children.pop(replacement.file_id, {})
new_entry = replacement
- self.add(new_entry)
+ try:
+ self.add(new_entry)
+ except AttributeError:
+ raise errors.InconsistentDelta(new_path, new_entry.file_id,
+ "Parent is not a directory.")
if len(children):
# Get the parent id that was deleted
parent_id, children = children.popitem()
@@ -1265,13 +1266,13 @@
try:
parent = self._byid[entry.parent_id]
except KeyError:
- raise BzrError("parent_id {%s} not in inventory" %
- entry.parent_id)
-
+ raise errors.InconsistentDelta("<unknown>", entry.parent_id,
+ "Parent not in inventory.")
if entry.name in parent.children:
- raise BzrError("%s is already versioned" %
- osutils.pathjoin(self.id2path(parent.file_id),
- entry.name).encode('utf-8'))
+ raise errors.InconsistentDelta(
+ self.id2path(parent.children[entry.name].file_id),
+ entry.file_id,
+ "Path already versioned")
parent.children[entry.name] = entry
return self._add_child(entry)
@@ -1619,6 +1620,18 @@
result.parent_id_basename_to_file_id = None
result.root_id = self.root_id
id_to_entry_delta = []
+ # inventory_delta is only traversed once, so we just update the
+ # variable.
+ # Check for repeated file ids
+ inventory_delta = _check_delta_unique_ids(inventory_delta)
+ # Repeated old paths
+ inventory_delta = _check_delta_unique_old_paths(inventory_delta)
+ # Check for repeated new paths
+ inventory_delta = _check_delta_unique_new_paths(inventory_delta)
+ # Check for entries that don't match the fileid
+ inventory_delta = _check_delta_ids_match_entry(inventory_delta)
+ # All changed entries need to have their parents be directories.
+ parents = set()
for old_path, new_path, file_id, entry in inventory_delta:
# file id changes
if new_path == '':
@@ -1639,6 +1652,7 @@
# Update caches. It's worth doing this whether
# we're propagating the old caches or not.
result._path_to_fileid_cache[new_path] = file_id
+ parents.add(entry.parent_id)
if old_path is None:
old_key = None
else:
@@ -1664,6 +1678,15 @@
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)
+ parents.discard(None)
+ for parent in parents:
+ try:
+ if result[parent].kind != 'directory':
+ raise errors.InconsistentDelta(result.id2path(parent), parent,
+ 'Not a directory, but given children')
+ except errors.NoSuchId:
+ raise errors.InconsistentDelta("<unknown>", parent,
+ "Parent is not present in resulting inventory.")
return result
@classmethod
@@ -2072,3 +2095,64 @@
_NAME_RE = re.compile(r'^[^/\\]+$')
return bool(_NAME_RE.match(name))
+
+
+def _check_delta_unique_ids(delta):
+ """Decorate a delta and check that the file ids in it are unique.
+
+ :return: A generator over delta.
+ """
+ ids = set()
+ for item in delta:
+ length = len(ids) + 1
+ ids.add(item[2])
+ if len(ids) != length:
+ raise errors.InconsistentDelta(item[0] or item[1], item[2],
+ "repeated file_id")
+ yield item
+
+
+def _check_delta_unique_new_paths(delta):
+ """Decorate a delta and check that the new paths in it are unique.
+
+ :return: A generator over delta.
+ """
+ paths = set()
+ for item in delta:
+ length = len(paths) + 1
+ path = item[1]
+ if path is not None:
+ paths.add(path)
+ if len(paths) != length:
+ raise errors.InconsistentDelta(path, item[2], "repeated path")
+ yield item
+
+
+def _check_delta_unique_old_paths(delta):
+ """Decorate a delta and check that the old paths in it are unique.
+
+ :return: A generator over delta.
+ """
+ paths = set()
+ for item in delta:
+ length = len(paths) + 1
+ path = item[0]
+ if path is not None:
+ paths.add(path)
+ if len(paths) != length:
+ raise errors.InconsistentDelta(path, item[2], "repeated path")
+ yield item
+
+
+def _check_delta_ids_match_entry(delta):
+ """Decorate a delta and check that the ids in it match the entry.file_id.
+
+ :return: A generator over delta.
+ """
+ for item in delta:
+ entry = item[3]
+ if entry is not None:
+ if entry.file_id != item[2]:
+ raise errors.InconsistentDelta(item[0] or item[1], item[2],
+ "mismatched id with %r" % entry)
+ yield item
=== modified file 'bzrlib/tests/inventory_implementations/basics.py'
--- a/bzrlib/tests/inventory_implementations/basics.py 2009-06-10 03:56:49 +0000
+++ b/bzrlib/tests/inventory_implementations/basics.py 2009-07-10 05:18:29 +0000
@@ -112,12 +112,9 @@
def test_error_encoding(self):
inv = self.make_inventory('tree-root')
inv.add(InventoryFile('a-id', u'\u1234', 'tree-root'))
- try:
- inv.add(InventoryFile('b-id', u'\u1234', 'tree-root'))
- except errors.BzrError, e:
- self.assertContainsRe(str(e), u'\u1234'.encode('utf-8'))
- else:
- self.fail('BzrError not raised')
+ e = self.assertRaises(errors.InconsistentDelta, inv.add,
+ InventoryFile('b-id', u'\u1234', 'tree-root'))
+ self.assertContainsRe(str(e), r'\\u1234')
def test_add_recursive(self):
parent = InventoryDirectory('src-id', 'src', 'tree-root')
@@ -129,6 +126,13 @@
class TestInventoryApplyDelta(TestInventory):
+ """A subset of the inventory delta application tests.
+
+ See test_inv which has comprehensive delta application tests for
+ inventories, dirstate, and repository based inventories, unlike the tests
+ here which only test in-memory implementations that can support a plain
+ 'apply_delta'.
+ """
def test_apply_delta_add(self):
inv = self.make_inventory('tree-root')
@@ -161,7 +165,7 @@
def test_apply_delta_illegal(self):
# A file-id cannot appear in a delta more than once
inv = self.make_inventory('tree-root')
- self.assertRaises(AssertionError, inv.apply_delta, [
+ self.assertRaises(errors.InconsistentDelta, inv.apply_delta, [
("a", "a", "id-1", InventoryFile('id-1', 'a', 'tree-root')),
("a", "b", "id-1", InventoryFile('id-1', 'b', 'tree-root')),
])
=== modified file 'bzrlib/tests/test_errors.py'
--- a/bzrlib/tests/test_errors.py 2009-06-19 00:33:36 +0000
+++ b/bzrlib/tests/test_errors.py 2009-07-07 03:40:29 +0000
@@ -87,6 +87,12 @@
"reason: reason for foo",
str(error))
+ def test_inconsistent_delta_delta(self):
+ error = errors.InconsistentDeltaDelta([], 'reason')
+ self.assertEqualDiff(
+ "An inconsistent delta was supplied: []\nreason: reason",
+ str(error))
+
def test_in_process_transport(self):
error = errors.InProcessTransport('fpp')
self.assertEqualDiff(
=== 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-10 02:33:46 +0000
@@ -15,10 +15,311 @@
# 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.
+ It also puts the basis in the repository under both 'basis' and 'result' to
+ allow safety checks made by the WT to succeed, and finally ensures that all
+ items in the delta with a new path are present in the WT before calling
+ 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()
+ # Set the basis state as the trees current state
+ tree._write_inventory(basis)
+ # This reads basis from the repo and puts it into the tree's local
+ # cache, if it has one.
+ tree.set_parent_ids(['basis'])
+ paths = {}
+ parents = set()
+ for old, new, id, entry in delta:
+ if entry is None:
+ continue
+ paths[new] = (entry.file_id, entry.kind)
+ parents.add(osutils.dirname(new))
+ parents = osutils.minimum_path_selection(parents)
+ parents.discard('')
+ # Put place holders in the tree to permit adding the other entries.
+ for pos, parent in enumerate(parents):
+ if not tree.path2id(parent):
+ # add a synthetic directory in the tree so we can can put the
+ # tree0 entries in place for dirstate.
+ tree.add([parent], ["id%d" % pos], ["directory"])
+ if paths:
+ # Many deltas may cause this mini-apply to fail, but we want to see what
+ # the delta application code says, not the prep that we do to deal with
+ # limitations of dirstate's update_basis code.
+ for path, (file_id, kind) in sorted(paths.items()):
+ try:
+ tree.add([path], [file_id], [kind])
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ pass
+ 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, u'path1', 'id', file1), (None, u'path2', 'id', file2)]
+ self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
+ inv, delta)
+
+ def test_repeated_new_path(self):
+ inv = self.get_empty_inventory()
+ file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
+ file1.revision = 'result'
+ file1.text_size = 0
+ file1.text_sha1 = ""
+ file2 = inventory.InventoryFile('id2', 'path', inv.root.file_id)
+ file2.revision = 'result'
+ file2.text_size = 0
+ file2.text_sha1 = ""
+ delta = [(None, u'path', 'id1', file1), (None, u'path', 'id2', file2)]
+ self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
+ inv, delta)
+
+ def test_repeated_old_path(self):
+ inv = self.get_empty_inventory()
+ file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
+ file1.revision = 'result'
+ file1.text_size = 0
+ file1.text_sha1 = ""
+ # We can't *create* a source inventory with the same path, but
+ # a badly generated partial delta might claim the same source twice.
+ # This would be buggy in two ways: the path is repeated in the delta,
+ # And the path for one of the file ids doesn't match the source
+ # location. Alternatively, we could have a repeated fileid, but that
+ # is separately checked for.
+ file2 = inventory.InventoryFile('id2', 'path2', inv.root.file_id)
+ file2.revision = 'result'
+ file2.text_size = 0
+ file2.text_sha1 = ""
+ inv.add(file1)
+ inv.add(file2)
+ delta = [(u'path', None, 'id1', None), (u'path', None, 'id2', None)]
+ self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
+ inv, delta)
+
+ def test_mismatched_id_entry_id(self):
+ inv = self.get_empty_inventory()
+ file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
+ file1.revision = 'result'
+ file1.text_size = 0
+ file1.text_sha1 = ""
+ delta = [(None, u'path', 'id', file1)]
+ self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
+ inv, delta)
+
+ def test_parent_is_not_directory(self):
+ inv = self.get_empty_inventory()
+ file1 = inventory.InventoryFile('id1', 'path', inv.root.file_id)
+ file1.revision = 'result'
+ file1.text_size = 0
+ file1.text_sha1 = ""
+ file2 = inventory.InventoryFile('id2', 'path2', 'id1')
+ file2.revision = 'result'
+ file2.text_size = 0
+ file2.text_sha1 = ""
+ inv.add(file1)
+ delta = [(None, u'path/path2', 'id2', file2)]
+ self.assertRaises(errors.InconsistentDelta, self.apply_delta, self,
+ inv, delta)
+
+ def test_parent_is_missing(self):
+ inv = self.get_empty_inventory()
+ file2 = inventory.InventoryFile('id2', 'path2', 'missingparent')
+ file2.revision = 'result'
+ file2.text_size = 0
+ file2.text_sha1 = ""
+ delta = [(None, u'path/path2', 'id2', 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-07-06 21:13:30 +0000
+++ b/bzrlib/tests/workingtree_implementations/__init__.py 2009-07-10 06:35:38 +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',
@@ -106,8 +112,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(
=== modified file 'bzrlib/xml8.py'
--- a/bzrlib/xml8.py 2009-06-09 00:59:51 +0000
+++ b/bzrlib/xml8.py 2009-07-07 04:32:13 +0000
@@ -168,9 +168,9 @@
:raises: AssertionError if an error has occurred.
"""
if inv.revision_id is None:
- raise AssertionError()
+ raise AssertionError("inv.revision_id is None")
if inv.root.revision is None:
- raise AssertionError()
+ raise AssertionError("inv.root.revision is None")
def _check_cache_size(self, inv_size, entry_cache):
"""Check that the entry_cache is large enough.
More information about the bazaar-commits
mailing list