67
87
def describe(self):
68
88
return "%s is locked" % self.lockable_thing
91
class _AncestryMismatch(Mismatch):
92
"""Ancestry matching mismatch."""
94
def __init__(self, tip_revision, got, expected):
95
self.tip_revision = tip_revision
97
self.expected = expected
100
return "mismatched ancestry for revision %r was %r, expected %r" % (
101
self.tip_revision, self.got, self.expected)
104
class MatchesAncestry(Matcher):
105
"""A matcher that checks the ancestry of a particular revision.
107
:ivar graph: Graph in which to check the ancestry
108
:ivar revision_id: Revision id of the revision
111
def __init__(self, repository, revision_id):
112
Matcher.__init__(self)
113
self.repository = repository
114
self.revision_id = revision_id
117
return ('MatchesAncestry(repository=%r, revision_id=%r)' % (
118
self.repository, self.revision_id))
120
def match(self, expected):
121
with self.repository.lock_read():
122
graph = self.repository.get_graph()
123
got = [r for r, p in graph.iter_ancestry([self.revision_id])]
124
if _mod_revision.NULL_REVISION in got:
125
got.remove(_mod_revision.NULL_REVISION)
126
if sorted(got) != sorted(expected):
127
return _AncestryMismatch(self.revision_id, sorted(got),
131
class HasLayout(Matcher):
132
"""A matcher that checks if a tree has a specific layout.
134
:ivar entries: List of expected entries, as (path, file_id) pairs.
137
def __init__(self, entries):
138
Matcher.__init__(self)
139
self.entries = entries
141
def get_tree_layout(self, tree, include_file_ids):
142
"""Get the (path, file_id) pairs for the current tree."""
143
with tree.lock_read():
144
for path, ie in tree.iter_entries_by_dir():
146
path += ie.kind_character()
148
yield (path, ie.file_id)
153
def _strip_unreferenced_directories(entries):
154
"""Strip all directories that don't (in)directly contain any files.
156
:param entries: List of path strings or (path, ie) tuples to process
159
for entry in entries:
160
if isinstance(entry, (str, text_type)):
164
if not path or path[-1] == "/":
166
directories.append((path, entry))
168
# Yield the referenced parent directories
169
for dirpath, direntry in directories:
170
if osutils.is_inside(dirpath, path):
176
return 'HasLayout(%r)' % self.entries
178
def match(self, tree):
179
include_file_ids = self.entries and not isinstance(self.entries[0], (str, text_type))
180
actual = list(self.get_tree_layout(tree, include_file_ids=include_file_ids))
181
if not tree.has_versioned_directories():
182
entries = list(self._strip_unreferenced_directories(self.entries))
184
entries = self.entries
185
return Equals(entries).match(actual)
188
class HasPathRelations(Matcher):
189
"""Matcher verifies that paths have a relation to those in another tree.
191
:ivar previous_tree: tree to compare to
192
:ivar previous_entries: List of expected entries, as (path, previous_path) pairs.
195
def __init__(self, previous_tree, previous_entries):
196
Matcher.__init__(self)
197
self.previous_tree = previous_tree
198
self.previous_entries = previous_entries
200
def get_path_map(self, tree):
201
"""Get the (path, previous_path) pairs for the current tree."""
202
with tree.lock_read(), self.previous_tree.lock_read():
203
for path, ie in tree.iter_entries_by_dir():
204
if tree.supports_rename_tracking():
205
previous_path = find_previous_path(tree, self.previous_tree, path)
207
if self.previous_tree.is_versioned(path):
212
kind = self.previous_tree.kind(previous_path)
213
if kind == 'directory':
216
yield (u"", previous_path)
218
yield (path+ie.kind_character(), previous_path)
221
def _strip_unreferenced_directories(entries):
222
"""Strip all directories that don't (in)directly contain any files.
224
:param entries: List of path strings or (path, previous_path) tuples to process
226
directory_used = set()
228
for (path, previous_path) in entries:
229
if not path or path[-1] == "/":
231
directories.append((path, previous_path))
233
# Yield the referenced parent directories
234
for direntry in directories:
235
if osutils.is_inside(direntry[0], path):
236
directory_used.add(direntry[0])
237
for (path, previous_path) in entries:
238
if (not path.endswith("/")) or path in directory_used:
239
yield (path, previous_path)
242
return 'HasPathRelations(%r, %r)' % (self.previous_tree, self.previous_entries)
244
def match(self, tree):
245
actual = list(self.get_path_map(tree))
246
if not tree.has_versioned_directories():
247
entries = list(self._strip_unreferenced_directories(self.previous_entries))
249
entries = self.previous_entries
250
if not tree.supports_rename_tracking():
252
(path, path if self.previous_tree.is_versioned(path) else None)
253
for (path, previous_path) in entries]
254
return Equals(entries).match(actual)
257
class RevisionHistoryMatches(Matcher):
258
"""A matcher that checks if a branch has a specific revision history.
260
:ivar history: Revision history, as list of revisions. Oldest first.
263
def __init__(self, history):
264
Matcher.__init__(self)
265
self.expected = history
268
return 'RevisionHistoryMatches(%r)' % self.expected
270
def match(self, branch):
271
with branch.lock_read():
272
graph = branch.repository.get_graph()
273
history = list(graph.iter_lefthand_ancestry(
274
branch.last_revision(), [_mod_revision.NULL_REVISION]))
276
return Equals(self.expected).match(history)
279
class _NoVfsCallsMismatch(Mismatch):
280
"""Mismatch describing a list of HPSS calls which includes VFS requests."""
282
def __init__(self, vfs_calls):
283
self.vfs_calls = vfs_calls
286
return "no VFS calls expected, got: %s" % ",".join([
287
"%s(%s)" % (c.method,
288
", ".join([repr(a) for a in c.args])) for c in self.vfs_calls])
291
class ContainsNoVfsCalls(Matcher):
292
"""Ensure that none of the specified calls are HPSS calls."""
295
return 'ContainsNoVfsCalls()'
298
def match(cls, hpss_calls):
300
for call in hpss_calls:
302
request_method = smart_request_handlers.get(call.call.method)
304
# A method we don't know about doesn't count as a VFS method.
306
if issubclass(request_method, vfs.VfsRequest):
307
vfs_calls.append(call.call)
308
if len(vfs_calls) == 0:
310
return _NoVfsCallsMismatch(vfs_calls)