~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/matchers.py

  • Committer: Max Bowsher
  • Date: 2011-08-23 09:29:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6104.
  • Revision ID: _@maxb.eu-20110823092927-c7fnueriuunvv9mh
Per jam's review comments, get rid of features.meliae_feature, which is new in
2.5 and so will not be missed, assigning the corrected behaviour to the
features.meliae name.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
17
"""Matchers for bzrlib.
 
18
 
 
19
Primarily test support, Matchers are used by self.assertThat in the bzrlib
 
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__ = [
 
30
    'HasLayout',
 
31
    'MatchesAncestry',
 
32
    'ReturnsUnlockable',
 
33
    ]
 
34
 
 
35
from bzrlib import (
 
36
    revision as _mod_revision,
 
37
    )
 
38
 
 
39
from testtools.matchers import Equals, Mismatch, Matcher
 
40
 
 
41
 
 
42
class ReturnsUnlockable(Matcher):
 
43
    """A matcher that checks for the pattern we want lock* methods to have:
 
44
 
 
45
    They should return an object with an unlock() method.
 
46
    Calling that method should unlock the original object.
 
47
 
 
48
    :ivar lockable_thing: The object which can be locked that will be
 
49
        inspected.
 
50
    """
 
51
 
 
52
    def __init__(self, lockable_thing):
 
53
        Matcher.__init__(self)
 
54
        self.lockable_thing = lockable_thing
 
55
 
 
56
    def __str__(self):
 
57
        return ('ReturnsUnlockable(lockable_thing=%s)' % 
 
58
            self.lockable_thing)
 
59
 
 
60
    def match(self, lock_method):
 
61
        lock_method().unlock()
 
62
        if self.lockable_thing.is_locked():
 
63
            return _IsLocked(self.lockable_thing)
 
64
        return None
 
65
 
 
66
 
 
67
class _IsLocked(Mismatch):
 
68
    """Something is locked."""
 
69
 
 
70
    def __init__(self, lockable_thing):
 
71
        self.lockable_thing = lockable_thing
 
72
 
 
73
    def describe(self):
 
74
        return "%s is locked" % self.lockable_thing
 
75
 
 
76
 
 
77
class _AncestryMismatch(Mismatch):
 
78
    """Ancestry matching mismatch."""
 
79
 
 
80
    def __init__(self, tip_revision, got, expected):
 
81
        self.tip_revision = tip_revision
 
82
        self.got = got
 
83
        self.expected = expected
 
84
 
 
85
    def describe(self):
 
86
        return "mismatched ancestry for revision %r was %r, expected %r" % (
 
87
            self.tip_revision, self.got, self.expected)
 
88
 
 
89
 
 
90
class MatchesAncestry(Matcher):
 
91
    """A matcher that checks the ancestry of a particular revision.
 
92
 
 
93
    :ivar graph: Graph in which to check the ancestry
 
94
    :ivar revision_id: Revision id of the revision
 
95
    """
 
96
 
 
97
    def __init__(self, repository, revision_id):
 
98
        Matcher.__init__(self)
 
99
        self.repository = repository
 
100
        self.revision_id = revision_id
 
101
 
 
102
    def __str__(self):
 
103
        return ('MatchesAncestry(repository=%r, revision_id=%r)' % (
 
104
            self.repository, self.revision_id))
 
105
 
 
106
    def match(self, expected):
 
107
        self.repository.lock_read()
 
108
        try:
 
109
            graph = self.repository.get_graph()
 
110
            got = [r for r, p in graph.iter_ancestry([self.revision_id])]
 
111
            if _mod_revision.NULL_REVISION in got:
 
112
                got.remove(_mod_revision.NULL_REVISION)
 
113
        finally:
 
114
            self.repository.unlock()
 
115
        if sorted(got) != sorted(expected):
 
116
            return _AncestryMismatch(self.revision_id, sorted(got),
 
117
                sorted(expected))
 
118
 
 
119
 
 
120
class HasLayout(Matcher):
 
121
    """A matcher that checks if a tree has a specific layout.
 
122
 
 
123
    :ivar entries: List of expected entries, as (path, file_id) pairs.
 
124
    """
 
125
 
 
126
    def __init__(self, entries):
 
127
        Matcher.__init__(self)
 
128
        self.entries = entries
 
129
 
 
130
    def get_tree_layout(self, tree):
 
131
        """Get the (path, file_id) pairs for the current tree."""
 
132
        tree.lock_read()
 
133
        try:
 
134
            return [(path, ie.file_id) for path, ie
 
135
                    in tree.iter_entries_by_dir()]
 
136
        finally:
 
137
            tree.unlock()
 
138
 
 
139
    def __str__(self):
 
140
        return ('HasLayout(%r)' % self.entries)
 
141
 
 
142
    def match(self, tree):
 
143
        actual = self.get_tree_layout(tree)
 
144
        if self.entries and isinstance(self.entries[0], basestring):
 
145
            actual = [path for (path, fileid) in actual]
 
146
        return Equals(actual).match(self.entries)