/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(),
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)' %
5200.3.1 by Robert Collins
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
72
            self.lockable_thing)
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),
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
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):
6973.3.5 by Jelmer Vernooij
Review comments.
179
        include_file_ids = self.entries and not isinstance(self.entries[0], (str, text_type))
6973.3.3 by Jelmer Vernooij
Don't require file ids in matchers.
180
        actual = list(self.get_tree_layout(tree, include_file_ids=include_file_ids))
6110.6.2 by Jelmer Vernooij
In HasLayout, take into consideration Tree.has_versioned_directories.
181
        if not tree.has_versioned_directories():
182
            entries = list(self._strip_unreferenced_directories(self.entries))
183
        else:
184
            entries = self.entries
6110.6.3 by Jelmer Vernooij
review feedback from mgz
185
        return Equals(entries).match(actual)
6228.3.1 by Jelmer Vernooij
Add RevisionHistoryMatches.
186
187
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
188
class HasPathRelations(Matcher):
189
    """Matcher verifies that paths have a relation to those in another tree.
190
191
    :ivar previous_tree: tree to compare to
192
    :ivar previous_entries: List of expected entries, as (path, previous_path) pairs.
193
    """
194
195
    def __init__(self, previous_tree, previous_entries):
196
        Matcher.__init__(self)
197
        self.previous_tree = previous_tree
198
        self.previous_entries = previous_entries
199
200
    def get_path_map(self, tree):
201
        """Get the (path, previous_path) pairs for the current tree."""
6883.5.5 by Jelmer Vernooij
Add HasPathRelations.
202
        with tree.lock_read(), self.previous_tree.lock_read():
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
203
            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.
204
                if tree.supports_rename_tracking():
205
                    previous_path = find_previous_path(tree, self.previous_tree, path)
206
                else:
207
                    if self.previous_tree.is_versioned(path):
208
                        previous_path = path
209
                    else:
210
                        previous_path = None
6883.5.15 by Jelmer Vernooij
Add kind characters.
211
                if previous_path:
212
                    kind = self.previous_tree.kind(previous_path)
213
                    if kind == 'directory':
214
                        previous_path += '/'
6973.3.3 by Jelmer Vernooij
Don't require file ids in matchers.
215
                if path == u'':
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
216
                    yield (u"", previous_path)
217
                else:
218
                    yield (path+ie.kind_character(), previous_path)
219
220
    @staticmethod
221
    def _strip_unreferenced_directories(entries):
222
        """Strip all directories that don't (in)directly contain any files.
223
224
        :param entries: List of path strings or (path, previous_path) tuples to process
225
        """
6913.5.3 by Jelmer Vernooij
Simplify HasPathRelations behaviour; always require previous paths.
226
        directory_used = set()
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
227
        directories = []
6913.5.3 by Jelmer Vernooij
Simplify HasPathRelations behaviour; always require previous paths.
228
        for (path, previous_path) in entries:
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
229
            if not path or path[-1] == "/":
230
                # directory
6913.5.3 by Jelmer Vernooij
Simplify HasPathRelations behaviour; always require previous paths.
231
                directories.append((path, previous_path))
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
232
            else:
233
                # Yield the referenced parent directories
6913.5.3 by Jelmer Vernooij
Simplify HasPathRelations behaviour; always require previous paths.
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)
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
240
241
    def __str__(self):
242
        return 'HasPathRelations(%r, %r)' % (self.previous_tree, self.previous_entries)
243
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))
248
        else:
249
            entries = self.previous_entries
6883.5.17 by Jelmer Vernooij
Add Tree.supports_rename_tracking().
250
        if not tree.supports_rename_tracking():
251
            entries = [
6883.5.18 by Jelmer Vernooij
Fix test for rename tracking.
252
                (path, path if self.previous_tree.is_versioned(path) else None)
6883.5.17 by Jelmer Vernooij
Add Tree.supports_rename_tracking().
253
                for (path, previous_path) in entries]
6883.5.4 by Jelmer Vernooij
Add HasPathRelations.
254
        return Equals(entries).match(actual)
255
256
6228.3.1 by Jelmer Vernooij
Add RevisionHistoryMatches.
257
class RevisionHistoryMatches(Matcher):
258
    """A matcher that checks if a branch has a specific revision history.
259
260
    :ivar history: Revision history, as list of revisions. Oldest first.
261
    """
262
263
    def __init__(self, history):
264
        Matcher.__init__(self)
265
        self.expected = history
266
267
    def __str__(self):
268
        return 'RevisionHistoryMatches(%r)' % self.expected
269
270
    def match(self, branch):
6754.8.4 by Jelmer Vernooij
Use new context stuff.
271
        with branch.lock_read():
6228.3.1 by Jelmer Vernooij
Add RevisionHistoryMatches.
272
            graph = branch.repository.get_graph()
273
            history = list(graph.iter_lefthand_ancestry(
274
                branch.last_revision(), [_mod_revision.NULL_REVISION]))
275
            history.reverse()
276
        return Equals(self.expected).match(history)
6228.3.4 by Jelmer Vernooij
Merge bzr.dev.
277
278
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
279
class _NoVfsCallsMismatch(Mismatch):
280
    """Mismatch describing a list of HPSS calls which includes VFS requests."""
281
282
    def __init__(self, vfs_calls):
283
        self.vfs_calls = vfs_calls
284
285
    def describe(self):
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])
289
290
6352.2.3 by Jelmer Vernooij
s/NoVfsCalls/ContainsNoVfsCalls/.
291
class ContainsNoVfsCalls(Matcher):
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
292
    """Ensure that none of the specified calls are HPSS calls."""
293
294
    def __str__(self):
6352.2.3 by Jelmer Vernooij
s/NoVfsCalls/ContainsNoVfsCalls/.
295
        return 'ContainsNoVfsCalls()'
6352.2.1 by Jelmer Vernooij
Add matcher for NoVfsCalls.
296
297
    @classmethod
298
    def match(cls, hpss_calls):
299
        vfs_calls = []
300
        for call in hpss_calls:
301
            try:
302
                request_method = smart_request_handlers.get(call.call.method)
303
            except KeyError:
304
                # A method we don't know about doesn't count as a VFS method.
305
                continue
306
            if issubclass(request_method, vfs.VfsRequest):
307
                vfs_calls.append(call.call)
308
        if len(vfs_calls) == 0:
309
            return None
310
        return _NoVfsCallsMismatch(vfs_calls)