~bzr-pqm/bzr/bzr.dev

4763.2.4 by John Arbash Meinel
merge bzr.2.1 in preparation for NEWS entry.
1
# Copyright (C) 2007-2010 Canonical Ltd
2553.1.1 by Robert Collins
Give Hooks names.
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2553.1.1 by Robert Collins
Give Hooks names.
16
17
"""Tests for the core Hooks logic."""
18
4781.1.1 by Vincent Ladeuil
Hooks daughter classes should always call the base constructor
19
from bzrlib import (
20
    branch,
21
    errors,
5691.2.3 by Jelmer Vernooij
Fix tests.
22
    hooks as _mod_hooks,
5622.2.5 by Jelmer Vernooij
Add more tests for lazy hooks.
23
    pyutils,
4781.1.1 by Vincent Ladeuil
Hooks daughter classes should always call the base constructor
24
    tests,
25
    )
2553.1.1 by Robert Collins
Give Hooks names.
26
from bzrlib.hooks import (
4098.2.2 by Robert Collins
Review feedback.
27
    HookPoint,
2553.1.1 by Robert Collins
Give Hooks names.
28
    Hooks,
5622.2.1 by Jelmer Vernooij
Some work on lazy hooks.
29
    install_lazy_named_hook,
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
30
    known_hooks,
31
    known_hooks_key_to_object,
32
    known_hooks_key_to_parent_and_attribute,
2553.1.1 by Robert Collins
Give Hooks names.
33
    )
5436.2.1 by Andrew Bennetts
Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.
34
from bzrlib.symbol_versioning import (
35
    deprecated_in,
36
    )
4781.1.1 by Vincent Ladeuil
Hooks daughter classes should always call the base constructor
37
38
39
class TestHooks(tests.TestCase):
2553.1.1 by Robert Collins
Give Hooks names.
40
4098.2.1 by Robert Collins
Allow self documenting hooks.
41
    def test_create_hook_first(self):
5622.3.7 by Jelmer Vernooij
Fix tests.
42
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
4098.2.1 by Robert Collins
Allow self documenting hooks.
43
        doc = ("Invoked after changing the tip of a branch object. Called with"
44
            "a bzrlib.branch.PostChangeBranchTipParams object")
4098.2.2 by Robert Collins
Review feedback.
45
        hook = HookPoint("post_tip_change", doc, (0, 15), None)
5622.3.13 by Jelmer Vernooij
Catch deprecation warnings.
46
        self.applyDeprecated(deprecated_in((2, 4)), hooks.create_hook, hook)
4098.2.1 by Robert Collins
Allow self documenting hooks.
47
        self.assertEqual(hook, hooks['post_tip_change'])
48
49
    def test_create_hook_name_collision_errors(self):
5622.3.7 by Jelmer Vernooij
Fix tests.
50
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
4098.2.1 by Robert Collins
Allow self documenting hooks.
51
        doc = ("Invoked after changing the tip of a branch object. Called with"
52
            "a bzrlib.branch.PostChangeBranchTipParams object")
4098.2.2 by Robert Collins
Review feedback.
53
        hook = HookPoint("post_tip_change", doc, (0, 15), None)
54
        hook2 = HookPoint("post_tip_change", None, None, None)
5622.3.13 by Jelmer Vernooij
Catch deprecation warnings.
55
        self.applyDeprecated(deprecated_in((2, 4)), hooks.create_hook, hook)
56
        self.assertRaises(errors.DuplicateKey, self.applyDeprecated,
57
            deprecated_in((2, 4, 0)), hooks.create_hook, hook2)
4098.2.1 by Robert Collins
Allow self documenting hooks.
58
        self.assertEqual(hook, hooks['post_tip_change'])
59
60
    def test_docs(self):
61
        """docs() should return something reasonable about the Hooks."""
4108.3.1 by Robert Collins
Include the Hooks class in the Hooks docs.
62
        class MyHooks(Hooks):
63
            pass
5622.3.7 by Jelmer Vernooij
Fix tests.
64
        hooks = MyHooks("bzrlib.tests.test_hooks", "some_hooks")
4098.2.1 by Robert Collins
Allow self documenting hooks.
65
        hooks['legacy'] = []
5622.3.13 by Jelmer Vernooij
Catch deprecation warnings.
66
        hooks.add_hook('post_tip_change',
4098.2.1 by Robert Collins
Allow self documenting hooks.
67
            "Invoked after the tip of a branch changes. Called with "
5622.3.13 by Jelmer Vernooij
Catch deprecation warnings.
68
            "a ChangeBranchTipParams object.", (1, 4))
69
        hooks.add_hook('pre_tip_change',
4098.2.1 by Robert Collins
Allow self documenting hooks.
70
            "Invoked before the tip of a branch changes. Called with "
71
            "a ChangeBranchTipParams object. Hooks should raise "
72
            "TipChangeRejected to signal that a tip change is not permitted.",
73
            (1, 6), None)
4108.3.1 by Robert Collins
Include the Hooks class in the Hooks docs.
74
        self.assertEqualDiff(
75
            "MyHooks\n"
4119.3.2 by Robert Collins
Migrate existing hooks over to the new HookPoint infrastructure.
76
            "-------\n"
4108.3.1 by Robert Collins
Include the Hooks class in the Hooks docs.
77
            "\n"
4098.2.1 by Robert Collins
Allow self documenting hooks.
78
            "legacy\n"
4119.3.2 by Robert Collins
Migrate existing hooks over to the new HookPoint infrastructure.
79
            "~~~~~~\n"
4098.2.1 by Robert Collins
Allow self documenting hooks.
80
            "\n"
4108.3.1 by Robert Collins
Include the Hooks class in the Hooks docs.
81
            "An old-style hook. For documentation see the __init__ method of 'MyHooks'\n"
4098.2.1 by Robert Collins
Allow self documenting hooks.
82
            "\n"
83
            "post_tip_change\n"
4119.3.2 by Robert Collins
Migrate existing hooks over to the new HookPoint infrastructure.
84
            "~~~~~~~~~~~~~~~\n"
4098.2.1 by Robert Collins
Allow self documenting hooks.
85
            "\n"
86
            "Introduced in: 1.4\n"
87
            "\n"
88
            "Invoked after the tip of a branch changes. Called with a\n"
89
            "ChangeBranchTipParams object.\n"
90
            "\n"
91
            "pre_tip_change\n"
4119.3.2 by Robert Collins
Migrate existing hooks over to the new HookPoint infrastructure.
92
            "~~~~~~~~~~~~~~\n"
4098.2.1 by Robert Collins
Allow self documenting hooks.
93
            "\n"
94
            "Introduced in: 1.6\n"
95
            "\n"
96
            "Invoked before the tip of a branch changes. Called with a\n"
97
            "ChangeBranchTipParams object. Hooks should raise TipChangeRejected to\n"
98
            "signal that a tip change is not permitted.\n", hooks.docs())
99
3256.2.4 by Daniel Watkins
Modified tests to reflect install_named_hook becoming a separate method.
100
    def test_install_named_hook_raises_unknown_hook(self):
5622.3.7 by Jelmer Vernooij
Fix tests.
101
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
4781.1.1 by Vincent Ladeuil
Hooks daughter classes should always call the base constructor
102
        self.assertRaises(errors.UnknownHook, hooks.install_named_hook, 'silly',
3256.2.4 by Daniel Watkins
Modified tests to reflect install_named_hook becoming a separate method.
103
                          None, "")
104
105
    def test_install_named_hook_appends_known_hook(self):
5622.3.7 by Jelmer Vernooij
Fix tests.
106
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
3256.2.32 by Ian Clatworthy
tweak hook tests to init the set_rh test when needed
107
        hooks['set_rh'] = []
3256.2.4 by Daniel Watkins
Modified tests to reflect install_named_hook becoming a separate method.
108
        hooks.install_named_hook('set_rh', None, "demo")
109
        self.assertEqual(hooks['set_rh'], [None])
110
111
    def test_install_named_hook_and_retrieve_name(self):
5622.3.7 by Jelmer Vernooij
Fix tests.
112
        hooks = Hooks("bzrlib.tests.test_hooks", "somehooks")
3256.2.32 by Ian Clatworthy
tweak hook tests to init the set_rh test when needed
113
        hooks['set_rh'] = []
3256.2.4 by Daniel Watkins
Modified tests to reflect install_named_hook becoming a separate method.
114
        hooks.install_named_hook('set_rh', None, "demo")
3256.2.1 by Daniel Watkins
Added tests from Hooks.install_hook with an optional name parameter.
115
        self.assertEqual("demo", hooks.get_hook_name(None))
116
5622.4.2 by Jelmer Vernooij
Allow uninstalling hooks.
117
    def test_uninstall_named_hook(self):
118
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
5622.4.3 by Jelmer Vernooij
Remove all callables with specified label, not just the first.
119
        hooks.add_hook('set_rh', "Set revision history", (2, 0))
5622.4.2 by Jelmer Vernooij
Allow uninstalling hooks.
120
        hooks.install_named_hook('set_rh', None, "demo")
121
        self.assertEqual(1, len(hooks["set_rh"]))
122
        hooks.uninstall_named_hook("set_rh", "demo")
123
        self.assertEqual(0, len(hooks["set_rh"]))
124
5622.4.3 by Jelmer Vernooij
Remove all callables with specified label, not just the first.
125
    def test_uninstall_multiple_named_hooks(self):
126
        # Multiple callbacks with the same label all get removed
127
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
128
        hooks.add_hook('set_rh', "Set revision history", (2, 0))
129
        hooks.install_named_hook('set_rh', 1, "demo")
130
        hooks.install_named_hook('set_rh', 2, "demo")
131
        hooks.install_named_hook('set_rh', 3, "othername")
132
        self.assertEqual(3, len(hooks["set_rh"]))
133
        hooks.uninstall_named_hook("set_rh", "demo")
134
        self.assertEqual(1, len(hooks["set_rh"]))
135
5622.4.2 by Jelmer Vernooij
Allow uninstalling hooks.
136
    def test_uninstall_named_hook_unknown_callable(self):
137
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
138
        hooks.add_hook('set_rh', "Set revision hsitory", (2, 0))
139
        self.assertRaises(KeyError, hooks.uninstall_named_hook, "set_rh",
140
            "demo")
141
142
    def test_uninstall_named_hook_raises_unknown_hook(self):
143
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
144
        self.assertRaises(errors.UnknownHook, hooks.uninstall_named_hook,
145
            'silly', "")
146
147
    def test_uninstall_named_hook_old_style(self):
148
        hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
149
        hooks["set_rh"] = []
150
        hooks.install_named_hook('set_rh', None, "demo")
151
        self.assertRaises(errors.UnsupportedOperation,
152
            hooks.uninstall_named_hook, "set_rh", "demo")
153
5622.2.6 by Jelmer Vernooij
Put module/member information on Hooks, not individual hook points.
154
    hooks = Hooks("bzrlib.tests.test_hooks", "TestHooks.hooks")
5622.2.1 by Jelmer Vernooij
Some work on lazy hooks.
155
156
    def test_install_lazy_named_hook(self):
157
        # When the hook points are not yet registered the hook is
158
        # added to the _lazy_hooks dictionary in bzrlib.hooks.
5622.2.6 by Jelmer Vernooij
Put module/member information on Hooks, not individual hook points.
159
        self.hooks.add_hook('set_rh', "doc", (0, 15))
5622.2.1 by Jelmer Vernooij
Some work on lazy hooks.
160
        set_rh = lambda: None
161
        install_lazy_named_hook('bzrlib.tests.test_hooks',
162
            'TestHooks.hooks', 'set_rh', set_rh, "demo")
5691.2.3 by Jelmer Vernooij
Fix tests.
163
        set_rh_lazy_hooks = _mod_hooks._lazy_hooks[
5622.2.4 by Jelmer Vernooij
Fix tests for lazy hooks.
164
            ('bzrlib.tests.test_hooks', 'TestHooks.hooks', 'set_rh')]
165
        self.assertEquals(1, len(set_rh_lazy_hooks))
166
        self.assertEquals(set_rh, set_rh_lazy_hooks[0][0].get_obj())
167
        self.assertEquals("demo", set_rh_lazy_hooks[0][1])
5622.2.5 by Jelmer Vernooij
Add more tests for lazy hooks.
168
        self.assertEqual(list(TestHooks.hooks['set_rh']), [set_rh])
5622.2.1 by Jelmer Vernooij
Some work on lazy hooks.
169
5622.1.1 by Jelmer Vernooij
Allow lazily loading hook callbacks.
170
    set_rh = lambda: None
171
172
    def test_install_named_hook_lazy(self):
5622.3.7 by Jelmer Vernooij
Fix tests.
173
        hooks = Hooks("bzrlib.tests.hooks", "some_hooks")
5622.1.1 by Jelmer Vernooij
Allow lazily loading hook callbacks.
174
        hooks['set_rh'] = HookPoint("set_rh", "doc", (0, 15), None)
175
        hooks.install_named_hook_lazy('set_rh', 'bzrlib.tests.test_hooks',
176
            'TestHooks.set_rh', "demo")
177
        self.assertEqual(list(hooks['set_rh']), [TestHooks.set_rh])
178
179
    def test_install_named_hook_lazy_old(self):
180
        # An exception is raised if a lazy hook is raised for
181
        # an old style hook point.
5622.3.7 by Jelmer Vernooij
Fix tests.
182
        hooks = Hooks("bzrlib.tests.hooks", "some_hooks")
5622.1.1 by Jelmer Vernooij
Allow lazily loading hook callbacks.
183
        hooks['set_rh'] = []
184
        self.assertRaises(errors.UnsupportedOperation,
185
            hooks.install_named_hook_lazy,
186
            'set_rh', 'bzrlib.tests.test_hooks', 'TestHooks.set_rh',
187
            "demo")
188
5622.2.5 by Jelmer Vernooij
Add more tests for lazy hooks.
189
    def test_valid_lazy_hooks(self):
190
        # Make sure that all the registered lazy hooks are referring to existing
191
        # hook points which allow lazy registration.
5691.2.3 by Jelmer Vernooij
Fix tests.
192
        for key, callbacks in _mod_hooks._lazy_hooks.iteritems():
5622.2.8 by Jelmer Vernooij
more tests.
193
            (module_name, member_name, hook_name) = key
5622.2.5 by Jelmer Vernooij
Add more tests for lazy hooks.
194
            obj = pyutils.get_named_object(module_name, member_name)
5622.2.6 by Jelmer Vernooij
Put module/member information on Hooks, not individual hook points.
195
            self.assertEquals(obj._module, module_name)
196
            self.assertEquals(obj._member_name, member_name)
197
            self.assertTrue(hook_name in obj)
5622.2.8 by Jelmer Vernooij
more tests.
198
            self.assertIs(callbacks, obj[hook_name]._callbacks)
5622.2.5 by Jelmer Vernooij
Add more tests for lazy hooks.
199
4098.2.1 by Robert Collins
Allow self documenting hooks.
200
4781.1.1 by Vincent Ladeuil
Hooks daughter classes should always call the base constructor
201
class TestHook(tests.TestCase):
4098.2.1 by Robert Collins
Allow self documenting hooks.
202
203
    def test___init__(self):
204
        doc = ("Invoked after changing the tip of a branch object. Called with"
205
            "a bzrlib.branch.PostChangeBranchTipParams object")
4098.2.2 by Robert Collins
Review feedback.
206
        hook = HookPoint("post_tip_change", doc, (0, 15), None)
4098.2.1 by Robert Collins
Allow self documenting hooks.
207
        self.assertEqual(doc, hook.__doc__)
208
        self.assertEqual("post_tip_change", hook.name)
209
        self.assertEqual((0, 15), hook.introduced)
210
        self.assertEqual(None, hook.deprecated)
211
        self.assertEqual([], list(hook))
212
213
    def test_docs(self):
214
        doc = ("Invoked after changing the tip of a branch object. Called with"
215
            " a bzrlib.branch.PostChangeBranchTipParams object")
4098.2.2 by Robert Collins
Review feedback.
216
        hook = HookPoint("post_tip_change", doc, (0, 15), None)
4098.2.1 by Robert Collins
Allow self documenting hooks.
217
        self.assertEqual("post_tip_change\n"
4119.3.2 by Robert Collins
Migrate existing hooks over to the new HookPoint infrastructure.
218
            "~~~~~~~~~~~~~~~\n"
4098.2.1 by Robert Collins
Allow self documenting hooks.
219
            "\n"
220
            "Introduced in: 0.15\n"
221
            "\n"
222
            "Invoked after changing the tip of a branch object. Called with a\n"
223
            "bzrlib.branch.PostChangeBranchTipParams object\n", hook.docs())
224
225
    def test_hook(self):
4098.2.2 by Robert Collins
Review feedback.
226
        hook = HookPoint("foo", "no docs", None, None)
4098.2.1 by Robert Collins
Allow self documenting hooks.
227
        def callback():
228
            pass
229
        hook.hook(callback, "my callback")
230
        self.assertEqual([callback], list(hook))
231
5622.1.1 by Jelmer Vernooij
Allow lazily loading hook callbacks.
232
    def lazy_callback():
233
        pass
234
235
    def test_lazy_hook(self):
236
        hook = HookPoint("foo", "no docs", None, None)
237
        hook.hook_lazy(
238
            "bzrlib.tests.test_hooks", "TestHook.lazy_callback",
239
            "my callback")
240
        self.assertEqual([TestHook.lazy_callback], list(hook))
241
5622.4.2 by Jelmer Vernooij
Allow uninstalling hooks.
242
    def test_uninstall(self):
243
        hook = HookPoint("foo", "no docs", None, None)
244
        hook.hook_lazy(
245
            "bzrlib.tests.test_hooks", "TestHook.lazy_callback",
246
            "my callback")
247
        self.assertEqual([TestHook.lazy_callback], list(hook))
248
        hook.uninstall("my callback")
249
        self.assertEqual([], list(hook))
250
251
    def test_uninstall_unknown(self):
252
        hook = HookPoint("foo", "no docs", None, None)
253
        self.assertRaises(KeyError, hook.uninstall, "my callback")
254
4098.2.1 by Robert Collins
Allow self documenting hooks.
255
    def test___repr(self):
256
        # The repr should list all the callbacks, with names.
4098.2.2 by Robert Collins
Review feedback.
257
        hook = HookPoint("foo", "no docs", None, None)
4098.2.1 by Robert Collins
Allow self documenting hooks.
258
        def callback():
259
            pass
260
        hook.hook(callback, "my callback")
261
        callback_repr = repr(callback)
262
        self.assertEqual(
4098.2.2 by Robert Collins
Review feedback.
263
            '<HookPoint(foo), callbacks=[%s(my callback)]>' %
4098.2.1 by Robert Collins
Allow self documenting hooks.
264
            callback_repr, repr(hook))
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
265
266
4781.1.1 by Vincent Ladeuil
Hooks daughter classes should always call the base constructor
267
class TestHookRegistry(tests.TestCase):
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
268
269
    def test_items_are_reasonable_keys(self):
270
        # All the items in the known_hooks registry need to map from
271
        # (module_name, member_name) tuples to the callable used to get an
4230.1.2 by James Westby
Update the comments based on those in the test.
272
        # empty Hooks for that attribute. This is used to support the test
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
273
        # suite which needs to generate empty hooks (and HookPoints) to ensure
274
        # isolation and prevent tests failing spuriously.
275
        for key, factory in known_hooks.items():
276
            self.assertTrue(callable(factory),
277
                "The factory(%r) for %r is not callable" % (factory, key))
278
            obj = known_hooks_key_to_object(key)
279
            self.assertIsInstance(obj, Hooks)
5622.3.11 by Jelmer Vernooij
Revert some unnecessary changes.
280
            new_hooks = factory()
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
281
            self.assertIsInstance(obj, Hooks)
282
            self.assertEqual(type(obj), type(new_hooks))
4781.1.1 by Vincent Ladeuil
Hooks daughter classes should always call the base constructor
283
            self.assertEqual("No hook name", new_hooks.get_hook_name(None))
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
284
285
    def test_known_hooks_key_to_object(self):
286
        self.assertIs(branch.Branch.hooks,
287
            known_hooks_key_to_object(('bzrlib.branch', 'Branch.hooks')))
288
5436.2.1 by Andrew Bennetts
Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.
289
    def test_known_hooks_key_to_parent_and_attribute_deprecated(self):
290
        self.assertEqual((branch.Branch, 'hooks'),
291
            self.applyDeprecated(deprecated_in((2,3)),
292
                known_hooks_key_to_parent_and_attribute,
293
                ('bzrlib.branch', 'Branch.hooks')))
294
        self.assertEqual((branch, 'Branch'),
295
            self.applyDeprecated(deprecated_in((2,3)),
296
                known_hooks_key_to_parent_and_attribute,
297
                ('bzrlib.branch', 'Branch')))
298
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
299
    def test_known_hooks_key_to_parent_and_attribute(self):
300
        self.assertEqual((branch.Branch, 'hooks'),
5436.2.1 by Andrew Bennetts
Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.
301
            known_hooks.key_to_parent_and_attribute(
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
302
            ('bzrlib.branch', 'Branch.hooks')))
303
        self.assertEqual((branch, 'Branch'),
5436.2.1 by Andrew Bennetts
Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.
304
            known_hooks.key_to_parent_and_attribute(
4119.3.1 by Robert Collins
Create a single registry of all Hooks classes, removing the test suite knowledge of such hooks and allowing plugins to sensibly and safely define new hooks.
305
            ('bzrlib.branch', 'Branch')))