/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
1
# Copyright (C) 2010 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
17
"""Matchers for breezy.
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
18
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
19
Primarily test support, Matchers are used by self.assertThat in the breezy
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
20
test suite. A matcher is a stateful test helper which can be used to determine
21
if a passed object 'matches', much like a regex. If the object does not match
22
the mismatch can be described in a human readable fashion. assertThat then
23
raises if a mismatch occurs, showing the description as the assertion error.
24
25
Matchers are designed to be more reusable and composable than layered
26
assertions in Test Case objects, so they are recommended for new testing work.
27
"""
28
29
__all__ = [
6072.2.4 by Jelmer Vernooij
tests for matcher
30
    'HasLayout',
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
31
    'HasPathRelations',
5972.3.13 by Jelmer Vernooij
Add matcher for ancestry.
32
    'MatchesAncestry',
6352.2.3 by Jelmer Vernooij
s/NoVfsCalls/ContainsNoVfsCalls/.
33
    'ContainsNoVfsCalls',
5200.3.2 by Robert Collins
Cleaner matcher matching revised unlocking protocol.
34
    'ReturnsUnlockable',
6228.3.1 by Jelmer Vernooij
Add RevisionHistoryMatches.
35
    'RevisionHistoryMatches',
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
36
    ]
37
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
38
from .. import (
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
39
    osutils,
5972.3.13 by Jelmer Vernooij
Add matcher for ancestry.
40
    revision as _mod_revision,
41
    )
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
42
from .. import lazy_import
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
43
lazy_import.lazy_import(globals(),
7143.15.2 by Jelmer Vernooij
Run autopep8.
44
                        """
6670.4.16 by Jelmer Vernooij
Move smart to breezy.bzr.
45
from breezy.bzr.smart.request import request_handlers as smart_request_handlers
46
from breezy.bzr.smart import vfs
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
47
""")
6695.3.1 by Martin
Remove remaining uses of basestring from the codebase
48
from ..sixish import (
49
    text_type,
50
    )
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
51
from ..tree import find_previous_path
5972.3.13 by Jelmer Vernooij
Add matcher for ancestry.
52
6072.2.1 by Jelmer Vernooij
Add HasLayout matcher.
53
from testtools.matchers import Equals, Mismatch, Matcher
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
54
55
5200.3.2 by Robert Collins
Cleaner matcher matching revised unlocking protocol.
56
class ReturnsUnlockable(Matcher):
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
57
    """A matcher that checks for the pattern we want lock* methods to have:
58
5200.3.2 by Robert Collins
Cleaner matcher matching revised unlocking protocol.
59
    They should return an object with an unlock() method.
60
    Calling that method should unlock the original object.
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
61
62
    :ivar lockable_thing: The object which can be locked that will be
63
        inspected.
64
    """
65
66
    def __init__(self, lockable_thing):
67
        Matcher.__init__(self)
68
        self.lockable_thing = lockable_thing
69
70
    def __str__(self):
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
71
        return ('ReturnsUnlockable(lockable_thing=%s)' %
7143.15.2 by Jelmer Vernooij
Run autopep8.
72
                self.lockable_thing)
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
73
74
    def match(self, lock_method):
5200.3.2 by Robert Collins
Cleaner matcher matching revised unlocking protocol.
75
        lock_method().unlock()
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
76
        if self.lockable_thing.is_locked():
77
            return _IsLocked(self.lockable_thing)
78
        return None
79
80
81
class _IsLocked(Mismatch):
82
    """Something is locked."""
83
84
    def __init__(self, lockable_thing):
85
        self.lockable_thing = lockable_thing
86
87
    def describe(self):
88
        return "%s is locked" % self.lockable_thing
5972.3.13 by Jelmer Vernooij
Add matcher for ancestry.
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):
6754.8.4 by Jelmer Vernooij
Use new context stuff.
121
        with self.repository.lock_read():
5972.3.13 by Jelmer Vernooij
Add matcher for ancestry.
122
            graph = self.repository.get_graph()
123
            got = [r for r, p in graph.iter_ancestry([self.revision_id])]
5972.3.20 by Jelmer Vernooij
fix test.
124
            if _mod_revision.NULL_REVISION in got:
125
                got.remove(_mod_revision.NULL_REVISION)
5972.3.13 by Jelmer Vernooij
Add matcher for ancestry.
126
        if sorted(got) != sorted(expected):
6072.2.1 by Jelmer Vernooij
Add HasLayout matcher.
127
            return _AncestryMismatch(self.revision_id, sorted(got),
7143.15.2 by Jelmer Vernooij
Run autopep8.
128
                                     sorted(expected))
6072.2.1 by Jelmer Vernooij
Add HasLayout matcher.
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
6973.3.3 by Jelmer Vernooij
Don't require file ids in matchers.
141
    def get_tree_layout(self, tree, include_file_ids):
6072.2.1 by Jelmer Vernooij
Add HasLayout matcher.
142
        """Get the (path, file_id) pairs for the current tree."""
6754.8.4 by Jelmer Vernooij
Use new context stuff.
143
        with tree.lock_read():
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
144
            for path, ie in tree.iter_entries_by_dir():
6973.3.3 by Jelmer Vernooij
Don't require file ids in matchers.
145
                if path != u'':
146
                    path += ie.kind_character()
147
                if include_file_ids:
148
                    yield (path, ie.file_id)
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
149
                else:
6973.3.3 by Jelmer Vernooij
Don't require file ids in matchers.
150
                    yield path
6072.2.1 by Jelmer Vernooij
Add HasLayout matcher.
151
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
152
    @staticmethod
153
    def _strip_unreferenced_directories(entries):
6110.6.3 by Jelmer Vernooij
review feedback from mgz
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
        """
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
158
        directories = []
159
        for entry in entries:
6695.3.1 by Martin
Remove remaining uses of basestring from the codebase
160
            if isinstance(entry, (str, text_type)):
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
161
                path = entry
162
            else:
163
                path = entry[0]
6110.6.3 by Jelmer Vernooij
review feedback from mgz
164
            if not path or path[-1] == "/":
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
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
6072.2.1 by Jelmer Vernooij
Add HasLayout matcher.
175
    def __str__(self):
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
176
        return 'HasLayout(%r)' % self.entries
6072.2.1 by Jelmer Vernooij
Add HasLayout matcher.
177
178
    def match(self, tree):
7143.15.2 by Jelmer Vernooij
Run autopep8.
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))
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
183
        if not tree.has_versioned_directories():
184
            entries = list(self._strip_unreferenced_directories(self.entries))
185
        else:
186
            entries = self.entries
6110.6.3 by Jelmer Vernooij
review feedback from mgz
187
        return Equals(entries).match(actual)
6228.3.1 by Jelmer Vernooij
Add RevisionHistoryMatches.
188
189
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
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."""
6883.5.5 by Jelmer Vernooij
Add HasPathRelations.
204
        with tree.lock_read(), self.previous_tree.lock_read():
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
205
            for path, ie in tree.iter_entries_by_dir():
6883.5.19 by Jelmer Vernooij
Don't look for previous path if rename tracking is not supported.
206
                if tree.supports_rename_tracking():
7143.15.2 by Jelmer Vernooij
Run autopep8.
207
                    previous_path = find_previous_path(
208
                        tree, self.previous_tree, path)
6883.5.19 by Jelmer Vernooij
Don't look for previous path if rename tracking is not supported.
209
                else:
210
                    if self.previous_tree.is_versioned(path):
211
                        previous_path = path
212
                    else:
213
                        previous_path = None
6883.5.15 by Jelmer Vernooij
Add kind characters.
214
                if previous_path:
215
                    kind = self.previous_tree.kind(previous_path)
216
                    if kind == 'directory':
217
                        previous_path += '/'
6973.3.3 by Jelmer Vernooij
Don't require file ids in matchers.
218
                if path == u'':
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
219
                    yield (u"", previous_path)
220
                else:
7143.15.2 by Jelmer Vernooij
Run autopep8.
221
                    yield (path + ie.kind_character(), previous_path)
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
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
        """
6913.5.3 by Jelmer Vernooij
Simplify HasPathRelations behaviour; always require previous paths.
229
        directory_used = set()
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
230
        directories = []
6913.5.3 by Jelmer Vernooij
Simplify HasPathRelations behaviour; always require previous paths.
231
        for (path, previous_path) in entries:
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
232
            if not path or path[-1] == "/":
233
                # directory
6913.5.3 by Jelmer Vernooij
Simplify HasPathRelations behaviour; always require previous paths.
234
                directories.append((path, previous_path))
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
235
            else:
236
                # Yield the referenced parent directories
6913.5.3 by Jelmer Vernooij
Simplify HasPathRelations behaviour; always require previous paths.
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)
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
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():
7143.15.2 by Jelmer Vernooij
Run autopep8.
250
            entries = list(self._strip_unreferenced_directories(
251
                self.previous_entries))
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
252
        else:
253
            entries = self.previous_entries
6883.5.17 by Jelmer Vernooij
Add Tree.supports_rename_tracking().
254
        if not tree.supports_rename_tracking():
255
            entries = [
6883.5.18 by Jelmer Vernooij
Fix test for rename tracking.
256
                (path, path if self.previous_tree.is_versioned(path) else None)
6883.5.17 by Jelmer Vernooij
Add Tree.supports_rename_tracking().
257
                for (path, previous_path) in entries]
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
258
        return Equals(entries).match(actual)
259
260
6228.3.1 by Jelmer Vernooij
Add RevisionHistoryMatches.
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):
6754.8.4 by Jelmer Vernooij
Use new context stuff.
275
        with branch.lock_read():
6228.3.1 by Jelmer Vernooij
Add RevisionHistoryMatches.
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)
6228.3.4 by Jelmer Vernooij
Merge bzr.dev.
281
282
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
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,
7143.15.2 by Jelmer Vernooij
Run autopep8.
292
                        ", ".join([repr(a) for a in c.args])) for c in self.vfs_calls])
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
293
294
6352.2.3 by Jelmer Vernooij
s/NoVfsCalls/ContainsNoVfsCalls/.
295
class ContainsNoVfsCalls(Matcher):
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
296
    """Ensure that none of the specified calls are HPSS calls."""
297
298
    def __str__(self):
6352.2.3 by Jelmer Vernooij
s/NoVfsCalls/ContainsNoVfsCalls/.
299
        return 'ContainsNoVfsCalls()'
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
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)