/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/matchers.py

  • Committer: Jelmer Vernooij
  • Date: 2020-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
16
16
 
17
 
"""Matchers for bzrlib.
 
17
"""Matchers for breezy.
18
18
 
19
 
Primarily test support, Matchers are used by self.assertThat in the bzrlib
 
19
Primarily test support, Matchers are used by self.assertThat in the breezy
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
27
27
"""
28
28
 
29
29
__all__ = [
 
30
    'HasLayout',
 
31
    'HasPathRelations',
 
32
    'MatchesAncestry',
 
33
    'ContainsNoVfsCalls',
30
34
    'ReturnsUnlockable',
 
35
    'RevisionHistoryMatches',
31
36
    ]
32
37
 
33
 
from testtools.matchers import Mismatch, Matcher
 
38
from .. import (
 
39
    osutils,
 
40
    revision as _mod_revision,
 
41
    )
 
42
from .. import lazy_import
 
43
lazy_import.lazy_import(globals(),
 
44
                        """
 
45
from breezy.bzr.smart.request import request_handlers as smart_request_handlers
 
46
from breezy.bzr.smart import vfs
 
47
""")
 
48
from ..sixish import (
 
49
    text_type,
 
50
    )
 
51
from ..tree import InterTree
 
52
 
 
53
from testtools.matchers import Equals, Mismatch, Matcher
34
54
 
35
55
 
36
56
class ReturnsUnlockable(Matcher):
48
68
        self.lockable_thing = lockable_thing
49
69
 
50
70
    def __str__(self):
51
 
        return ('ReturnsUnlockable(lockable_thing=%s)' % 
52
 
            self.lockable_thing)
 
71
        return ('ReturnsUnlockable(lockable_thing=%s)' %
 
72
                self.lockable_thing)
53
73
 
54
74
    def match(self, lock_method):
55
75
        lock_method().unlock()
66
86
 
67
87
    def describe(self):
68
88
        return "%s is locked" % self.lockable_thing
 
89
 
 
90
 
 
91
class _AncestryMismatch(Mismatch):
 
92
    """Ancestry matching mismatch."""
 
93
 
 
94
    def __init__(self, tip_revision, got, expected):
 
95
        self.tip_revision = tip_revision
 
96
        self.got = got
 
97
        self.expected = expected
 
98
 
 
99
    def describe(self):
 
100
        return "mismatched ancestry for revision %r was %r, expected %r" % (
 
101
            self.tip_revision, self.got, self.expected)
 
102
 
 
103
 
 
104
class MatchesAncestry(Matcher):
 
105
    """A matcher that checks the ancestry of a particular revision.
 
106
 
 
107
    :ivar graph: Graph in which to check the ancestry
 
108
    :ivar revision_id: Revision id of the revision
 
109
    """
 
110
 
 
111
    def __init__(self, repository, revision_id):
 
112
        Matcher.__init__(self)
 
113
        self.repository = repository
 
114
        self.revision_id = revision_id
 
115
 
 
116
    def __str__(self):
 
117
        return ('MatchesAncestry(repository=%r, revision_id=%r)' % (
 
118
            self.repository, self.revision_id))
 
119
 
 
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),
 
128
                                     sorted(expected))
 
129
 
 
130
 
 
131
class HasLayout(Matcher):
 
132
    """A matcher that checks if a tree has a specific layout.
 
133
 
 
134
    :ivar entries: List of expected entries, as (path, file_id) pairs.
 
135
    """
 
136
 
 
137
    def __init__(self, entries):
 
138
        Matcher.__init__(self)
 
139
        self.entries = entries
 
140
 
 
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():
 
145
                if path != u'':
 
146
                    path += ie.kind_character()
 
147
                if include_file_ids:
 
148
                    yield (path, ie.file_id)
 
149
                else:
 
150
                    yield path
 
151
 
 
152
    @staticmethod
 
153
    def _strip_unreferenced_directories(entries):
 
154
        """Strip all directories that don't (in)directly contain any files.
 
155
 
 
156
        :param entries: List of path strings or (path, ie) tuples to process
 
157
        """
 
158
        directories = []
 
159
        for entry in entries:
 
160
            if isinstance(entry, (str, text_type)):
 
161
                path = entry
 
162
            else:
 
163
                path = entry[0]
 
164
            if not path or path[-1] == "/":
 
165
                # directory
 
166
                directories.append((path, entry))
 
167
            else:
 
168
                # Yield the referenced parent directories
 
169
                for dirpath, direntry in directories:
 
170
                    if osutils.is_inside(dirpath, path):
 
171
                        yield direntry
 
172
                directories = []
 
173
                yield entry
 
174
 
 
175
    def __str__(self):
 
176
        return 'HasLayout(%r)' % self.entries
 
177
 
 
178
    def match(self, tree):
 
179
        include_file_ids = self.entries and not isinstance(
 
180
            self.entries[0], (str, text_type))
 
181
        actual = list(self.get_tree_layout(
 
182
            tree, include_file_ids=include_file_ids))
 
183
        if not tree.has_versioned_directories():
 
184
            entries = list(self._strip_unreferenced_directories(self.entries))
 
185
        else:
 
186
            entries = self.entries
 
187
        return Equals(entries).match(actual)
 
188
 
 
189
 
 
190
class HasPathRelations(Matcher):
 
191
    """Matcher verifies that paths have a relation to those in another tree.
 
192
 
 
193
    :ivar previous_tree: tree to compare to
 
194
    :ivar previous_entries: List of expected entries, as (path, previous_path) pairs.
 
195
    """
 
196
 
 
197
    def __init__(self, previous_tree, previous_entries):
 
198
        Matcher.__init__(self)
 
199
        self.previous_tree = previous_tree
 
200
        self.previous_entries = previous_entries
 
201
 
 
202
    def get_path_map(self, tree):
 
203
        """Get the (path, previous_path) pairs for the current tree."""
 
204
        previous_intertree = InterTree.get(self.previous_tree, tree)
 
205
        with tree.lock_read(), self.previous_tree.lock_read():
 
206
            for path, ie in tree.iter_entries_by_dir():
 
207
                if tree.supports_rename_tracking():
 
208
                    previous_path = previous_intertree.find_source_path(path)
 
209
                else:
 
210
                    if self.previous_tree.is_versioned(path):
 
211
                        previous_path = path
 
212
                    else:
 
213
                        previous_path = None
 
214
                if previous_path:
 
215
                    kind = self.previous_tree.kind(previous_path)
 
216
                    if kind == 'directory':
 
217
                        previous_path += '/'
 
218
                if path == u'':
 
219
                    yield (u"", previous_path)
 
220
                else:
 
221
                    yield (path + ie.kind_character(), previous_path)
 
222
 
 
223
    @staticmethod
 
224
    def _strip_unreferenced_directories(entries):
 
225
        """Strip all directories that don't (in)directly contain any files.
 
226
 
 
227
        :param entries: List of path strings or (path, previous_path) tuples to process
 
228
        """
 
229
        directory_used = set()
 
230
        directories = []
 
231
        for (path, previous_path) in entries:
 
232
            if not path or path[-1] == "/":
 
233
                # directory
 
234
                directories.append((path, previous_path))
 
235
            else:
 
236
                # Yield the referenced parent directories
 
237
                for direntry in directories:
 
238
                    if osutils.is_inside(direntry[0], path):
 
239
                        directory_used.add(direntry[0])
 
240
        for (path, previous_path) in entries:
 
241
            if (not path.endswith("/")) or path in directory_used:
 
242
                yield (path, previous_path)
 
243
 
 
244
    def __str__(self):
 
245
        return 'HasPathRelations(%r, %r)' % (self.previous_tree, self.previous_entries)
 
246
 
 
247
    def match(self, tree):
 
248
        actual = list(self.get_path_map(tree))
 
249
        if not tree.has_versioned_directories():
 
250
            entries = list(self._strip_unreferenced_directories(
 
251
                self.previous_entries))
 
252
        else:
 
253
            entries = self.previous_entries
 
254
        if not tree.supports_rename_tracking():
 
255
            entries = [
 
256
                (path, path if self.previous_tree.is_versioned(path) else None)
 
257
                for (path, previous_path) in entries]
 
258
        return Equals(entries).match(actual)
 
259
 
 
260
 
 
261
class RevisionHistoryMatches(Matcher):
 
262
    """A matcher that checks if a branch has a specific revision history.
 
263
 
 
264
    :ivar history: Revision history, as list of revisions. Oldest first.
 
265
    """
 
266
 
 
267
    def __init__(self, history):
 
268
        Matcher.__init__(self)
 
269
        self.expected = history
 
270
 
 
271
    def __str__(self):
 
272
        return 'RevisionHistoryMatches(%r)' % self.expected
 
273
 
 
274
    def match(self, branch):
 
275
        with branch.lock_read():
 
276
            graph = branch.repository.get_graph()
 
277
            history = list(graph.iter_lefthand_ancestry(
 
278
                branch.last_revision(), [_mod_revision.NULL_REVISION]))
 
279
            history.reverse()
 
280
        return Equals(self.expected).match(history)
 
281
 
 
282
 
 
283
class _NoVfsCallsMismatch(Mismatch):
 
284
    """Mismatch describing a list of HPSS calls which includes VFS requests."""
 
285
 
 
286
    def __init__(self, vfs_calls):
 
287
        self.vfs_calls = vfs_calls
 
288
 
 
289
    def describe(self):
 
290
        return "no VFS calls expected, got: %s" % ",".join([
 
291
            "%s(%s)" % (c.method,
 
292
                        ", ".join([repr(a) for a in c.args])) for c in self.vfs_calls])
 
293
 
 
294
 
 
295
class ContainsNoVfsCalls(Matcher):
 
296
    """Ensure that none of the specified calls are HPSS calls."""
 
297
 
 
298
    def __str__(self):
 
299
        return 'ContainsNoVfsCalls()'
 
300
 
 
301
    @classmethod
 
302
    def match(cls, hpss_calls):
 
303
        vfs_calls = []
 
304
        for call in hpss_calls:
 
305
            try:
 
306
                request_method = smart_request_handlers.get(call.call.method)
 
307
            except KeyError:
 
308
                # A method we don't know about doesn't count as a VFS method.
 
309
                continue
 
310
            if issubclass(request_method, vfs.VfsRequest):
 
311
                vfs_calls.append(call.call)
 
312
        if len(vfs_calls) == 0:
 
313
            return None
 
314
        return _NoVfsCallsMismatch(vfs_calls)