14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Matchers for breezy.
17
"""Matchers for bzrlib.
19
Primarily test support, Matchers are used by self.assertThat in the breezy
19
Primarily test support, Matchers are used by self.assertThat in the bzrlib
20
20
test suite. A matcher is a stateful test helper which can be used to determine
21
21
if a passed object 'matches', much like a regex. If the object does not match
22
22
the mismatch can be described in a human readable fashion. assertThat then
33
33
'ReturnsUnlockable',
34
34
'RevisionHistoryMatches',
39
39
revision as _mod_revision,
42
from ..tree import InterTree
41
from bzrlib import lazy_import
42
lazy_import.lazy_import(globals(),
44
from bzrlib.smart.request import request_handlers as smart_request_handlers
45
from bzrlib.smart import vfs
44
48
from testtools.matchers import Equals, Mismatch, Matcher
109
113
self.repository, self.revision_id))
111
115
def match(self, expected):
112
with self.repository.lock_read():
116
self.repository.lock_read()
113
118
graph = self.repository.get_graph()
114
119
got = [r for r, p in graph.iter_ancestry([self.revision_id])]
115
120
if _mod_revision.NULL_REVISION in got:
116
121
got.remove(_mod_revision.NULL_REVISION)
123
self.repository.unlock()
117
124
if sorted(got) != sorted(expected):
118
125
return _AncestryMismatch(self.revision_id, sorted(got),
122
129
class HasLayout(Matcher):
129
136
Matcher.__init__(self)
130
137
self.entries = entries
132
def get_tree_layout(self, tree, include_file_ids):
139
def get_tree_layout(self, tree):
133
140
"""Get the (path, file_id) pairs for the current tree."""
134
with tree.lock_read():
135
143
for path, ie in tree.iter_entries_by_dir():
137
path += ie.kind_character()
139
yield (path, ie.file_id)
144
if ie.parent_id is None:
145
yield (u"", ie.file_id)
147
yield (path+ie.kind_character(), ie.file_id)
144
152
def _strip_unreferenced_directories(entries):
167
175
return 'HasLayout(%r)' % self.entries
169
177
def match(self, tree):
170
include_file_ids = self.entries and not isinstance(
171
self.entries[0], str)
172
actual = list(self.get_tree_layout(
173
tree, include_file_ids=include_file_ids))
178
actual = list(self.get_tree_layout(tree))
179
if self.entries and isinstance(self.entries[0], basestring):
180
actual = [path for (path, fileid) in actual]
174
181
if not tree.has_versioned_directories():
175
182
entries = list(self._strip_unreferenced_directories(self.entries))
178
185
return Equals(entries).match(actual)
181
class HasPathRelations(Matcher):
182
"""Matcher verifies that paths have a relation to those in another tree.
184
:ivar previous_tree: tree to compare to
185
:ivar previous_entries: List of expected entries, as (path, previous_path) pairs.
188
def __init__(self, previous_tree, previous_entries):
189
Matcher.__init__(self)
190
self.previous_tree = previous_tree
191
self.previous_entries = previous_entries
193
def get_path_map(self, tree):
194
"""Get the (path, previous_path) pairs for the current tree."""
195
previous_intertree = InterTree.get(self.previous_tree, tree)
196
with tree.lock_read(), self.previous_tree.lock_read():
197
for path, ie in tree.iter_entries_by_dir():
198
if tree.supports_rename_tracking():
199
previous_path = previous_intertree.find_source_path(path)
201
if self.previous_tree.is_versioned(path):
206
kind = self.previous_tree.kind(previous_path)
207
if kind == 'directory':
210
yield (u"", previous_path)
212
yield (path + ie.kind_character(), previous_path)
215
def _strip_unreferenced_directories(entries):
216
"""Strip all directories that don't (in)directly contain any files.
218
:param entries: List of path strings or (path, previous_path) tuples to process
220
directory_used = set()
222
for (path, previous_path) in entries:
223
if not path or path[-1] == "/":
225
directories.append((path, previous_path))
227
# Yield the referenced parent directories
228
for direntry in directories:
229
if osutils.is_inside(direntry[0], path):
230
directory_used.add(direntry[0])
231
for (path, previous_path) in entries:
232
if (not path.endswith("/")) or path in directory_used:
233
yield (path, previous_path)
236
return 'HasPathRelations(%r, %r)' % (self.previous_tree, self.previous_entries)
238
def match(self, tree):
239
actual = list(self.get_path_map(tree))
240
if not tree.has_versioned_directories():
241
entries = list(self._strip_unreferenced_directories(
242
self.previous_entries))
244
entries = self.previous_entries
245
if not tree.supports_rename_tracking():
247
(path, path if self.previous_tree.is_versioned(path) else None)
248
for (path, previous_path) in entries]
249
return Equals(entries).match(actual)
252
188
class RevisionHistoryMatches(Matcher):
253
189
"""A matcher that checks if a branch has a specific revision history.
263
199
return 'RevisionHistoryMatches(%r)' % self.expected
265
201
def match(self, branch):
266
with branch.lock_read():
267
204
graph = branch.repository.get_graph()
268
205
history = list(graph.iter_lefthand_ancestry(
269
206
branch.last_revision(), [_mod_revision.NULL_REVISION]))
270
207
history.reverse()
271
210
return Equals(self.expected).match(history)
213
class _NoVfsCallsMismatch(Mismatch):
214
"""Mismatch describing a list of HPSS calls which includes VFS requests."""
216
def __init__(self, vfs_calls):
217
self.vfs_calls = vfs_calls
220
return "no VFS calls expected, got: %s" % ",".join([
221
"%s(%s)" % (c.method,
222
", ".join([repr(a) for a in c.args])) for c in self.vfs_calls])
225
class ContainsNoVfsCalls(Matcher):
226
"""Ensure that none of the specified calls are HPSS calls."""
229
return 'ContainsNoVfsCalls()'
232
def match(cls, hpss_calls):
234
for call in hpss_calls:
236
request_method = smart_request_handlers.get(call.call.method)
238
# A method we don't know about doesn't count as a VFS method.
240
if issubclass(request_method, vfs.VfsRequest):
241
vfs_calls.append(call.call)
242
if len(vfs_calls) == 0:
244
return _NoVfsCallsMismatch(vfs_calls)