~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/branch_implementations/test_hooks.py

  • Committer: Andrew Bennetts
  • Date: 2008-07-28 06:53:44 UTC
  • mfrom: (3581 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3583.
  • Revision ID: andrew.bennetts@canonical.com-20080728065344-ocndjoycs903q6fz
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Tests that branch classes implement hook callouts correctly."""
18
18
 
19
 
from bzrlib.branch import Branch
 
19
from bzrlib.errors import HookFailed, TipChangeRejected
 
20
from bzrlib.branch import Branch, ChangeBranchTipParams
20
21
from bzrlib.revision import NULL_REVISION
21
22
from bzrlib.tests import TestCaseWithMemoryTransport
22
23
 
80
81
            ])
81
82
 
82
83
 
83
 
class TestPostChangeBranchTip(TestCaseWithMemoryTransport):
84
 
 
85
 
    def setUp(self):
86
 
        self.hook_calls = []
87
 
        TestCaseWithMemoryTransport.setUp(self)
88
 
 
89
 
    def capture_post_change_branch_tip_hook(self, params):
90
 
        """Capture post_change_branch_tip hook calls to self.hook_calls.
91
 
 
92
 
        The call is logged, as is some state of the branch.
 
84
class ChangeBranchTipTestCase(TestCaseWithMemoryTransport):
 
85
    """Base TestCase for testing pre/post_change_branch_tip hooks."""
 
86
 
 
87
    def install_logging_hook(self, prefix):
 
88
        """Add a hook that logs calls made to it.
 
89
        
 
90
        :returns: the list that the calls will be appended to.
93
91
        """
94
 
        self.hook_calls.append((params, params.branch.is_locked()))
95
 
        self.assertEquals(params.branch.last_revision_info(),
96
 
                          (params.new_revno, params.new_revid))
97
 
 
98
 
    def test_post_change_branch_tip_empty_history(self):
99
 
        branch = self.make_branch('source')
 
92
        hook_calls = []
100
93
        Branch.hooks.install_named_hook(
101
 
            'post_change_branch_tip',
102
 
            self.capture_post_change_branch_tip_hook,
103
 
            None)
104
 
        branch.set_last_revision_info(0, NULL_REVISION)
105
 
        self.assertEqual(len(self.hook_calls), 1)
106
 
        self.assertEqual(self.hook_calls[0][0].branch, branch)
107
 
        self.assertEqual(self.hook_calls[0][0].old_revid, NULL_REVISION)
108
 
        self.assertEqual(self.hook_calls[0][0].old_revno, 0)
109
 
        self.assertEqual(self.hook_calls[0][0].new_revid, NULL_REVISION)
110
 
        self.assertEqual(self.hook_calls[0][0].new_revno, 0)
 
94
            prefix + '_change_branch_tip', hook_calls.append, None)
 
95
        return hook_calls
111
96
 
112
 
    def test_post_change_branch_tip_nonempty_history(self):
 
97
    def make_branch_with_revision_ids(self, *revision_ids):
 
98
        """Makes a branch with the given commits."""
113
99
        tree = self.make_branch_and_memory_tree('source')
114
100
        tree.lock_write()
115
101
        tree.add('')
116
 
        tree.commit('another commit', rev_id='f\xc2\xb5')
117
 
        tree.commit('empty commit', rev_id='foo')
 
102
        for revision_id in revision_ids:
 
103
            tree.commit(u'Message of ' + revision_id.decode('utf8'),
 
104
                        rev_id=revision_id)
118
105
        tree.unlock()
119
106
        branch = tree.branch
120
 
        Branch.hooks.install_named_hook(
121
 
            'post_change_branch_tip',
122
 
            self.capture_post_change_branch_tip_hook,
123
 
            None)
124
 
        # some branches require that their history be set to a revision in the
125
 
        # repository
126
 
        branch.set_last_revision_info(1, 'f\xc2\xb5')
127
 
        self.assertEqual(len(self.hook_calls), 1)
128
 
        self.assertEqual(self.hook_calls[0][0].branch, branch)
129
 
        self.assertEqual(self.hook_calls[0][0].old_revid, 'foo')
130
 
        self.assertEqual(self.hook_calls[0][0].old_revno, 2)
131
 
        self.assertEqual(self.hook_calls[0][0].new_revid, 'f\xc2\xb5')
132
 
        self.assertEqual(self.hook_calls[0][0].new_revno, 1)
133
 
 
134
 
    def test_post_change_branch_tip_branch_is_locked(self):
135
 
        branch = self.make_branch('source')
136
 
        Branch.hooks.install_named_hook(
137
 
            'post_change_branch_tip',
138
 
            self.capture_post_change_branch_tip_hook,
139
 
            None)
140
 
        branch.set_last_revision_info(0, NULL_REVISION)
141
 
        self.assertEqual(len(self.hook_calls), 1)
142
 
        self.assertEqual(self.hook_calls[0][0].branch, branch)
143
 
        self.assertEqual(self.hook_calls[0][1], True)
144
 
 
145
 
    def test_post_change_branch_tip_calls_all_hooks_no_errors(self):
146
 
        branch = self.make_branch('source')
147
 
        Branch.hooks.install_named_hook(
148
 
            'post_change_branch_tip',
149
 
            self.capture_post_change_branch_tip_hook,
150
 
            None)
151
 
        Branch.hooks.install_named_hook(
152
 
            'post_change_branch_tip',
153
 
            self.capture_post_change_branch_tip_hook,
154
 
            None)
155
 
        branch.set_last_revision_info(0, NULL_REVISION)
156
 
        self.assertEqual(len(self.hook_calls), 2)
157
 
        self.assertEqual(self.hook_calls[0][0].branch, branch)
158
 
        self.assertEqual(self.hook_calls[1][0].branch, branch)
 
107
        return branch
 
108
 
 
109
 
 
110
class TestPreChangeBranchTip(ChangeBranchTipTestCase):
 
111
    """Tests for pre_change_branch_tip hook.
 
112
    
 
113
    Most of these tests are very similar to the tests in
 
114
    TestPostChangeBranchTip.
 
115
    """
 
116
 
 
117
    def test_hook_runs_before_change(self):
 
118
        """The hook runs *before* the branch's last_revision_info has changed.
 
119
        """
 
120
        branch = self.make_branch_with_revision_ids('revid-one')
 
121
        def assertBranchAtRevision1(params):
 
122
            self.assertEquals(
 
123
                (1, 'revid-one'), params.branch.last_revision_info())
 
124
        Branch.hooks.install_named_hook(
 
125
            'pre_change_branch_tip', assertBranchAtRevision1, None)
 
126
        branch.set_last_revision_info(0, NULL_REVISION)
 
127
 
 
128
    def test_hook_failure_prevents_change(self):
 
129
        """If a hook raises an exception, the change does not take effect.
 
130
        
 
131
        Also, a HookFailed exception will be raised.
 
132
        """
 
133
        branch = self.make_branch_with_revision_ids(
 
134
            'one-\xc2\xb5', 'two-\xc2\xb5')
 
135
        class PearShapedError(Exception):
 
136
            pass
 
137
        def hook_that_raises(params):
 
138
            raise PearShapedError()
 
139
        Branch.hooks.install_named_hook(
 
140
            'pre_change_branch_tip', hook_that_raises, None)
 
141
        hook_failed_exc = self.assertRaises(
 
142
            HookFailed, branch.set_last_revision_info, 0, NULL_REVISION)
 
143
        self.assertIsInstance(hook_failed_exc.exc_value, PearShapedError)
 
144
        # The revision info is unchanged.
 
145
        self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
 
146
        
 
147
    def test_empty_history(self):
 
148
        branch = self.make_branch('source')
 
149
        hook_calls = self.install_logging_hook('pre')
 
150
        branch.set_last_revision_info(0, NULL_REVISION)
 
151
        expected_params = ChangeBranchTipParams(
 
152
            branch, 0, 0, NULL_REVISION, NULL_REVISION)
 
153
        self.assertEqual([expected_params], hook_calls)
 
154
 
 
155
    def test_nonempty_history(self):
 
156
        # some branches require that their history be set to a revision in the
 
157
        # repository, so we need to make a branch with non-empty history for
 
158
        # this test.
 
159
        branch = self.make_branch_with_revision_ids(
 
160
            'one-\xc2\xb5', 'two-\xc2\xb5')
 
161
        hook_calls = self.install_logging_hook('pre')
 
162
        branch.set_last_revision_info(1, 'one-\xc2\xb5')
 
163
        expected_params = ChangeBranchTipParams(
 
164
            branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
 
165
        self.assertEqual([expected_params], hook_calls)
 
166
 
 
167
    def test_branch_is_locked(self):
 
168
        branch = self.make_branch('source')
 
169
        def assertBranchIsLocked(params):
 
170
            self.assertTrue(params.branch.is_locked())
 
171
        Branch.hooks.install_named_hook(
 
172
            'pre_change_branch_tip', assertBranchIsLocked, None)
 
173
        branch.set_last_revision_info(0, NULL_REVISION)
 
174
 
 
175
    def test_calls_all_hooks_no_errors(self):
 
176
        """If multiple hooks are registered, all are called (if none raise
 
177
        errors).
 
178
        """
 
179
        branch = self.make_branch('source')
 
180
        hook_calls_1 = self.install_logging_hook('pre')
 
181
        hook_calls_2 = self.install_logging_hook('pre')
 
182
        self.assertIsNot(hook_calls_1, hook_calls_2)
 
183
        branch.set_last_revision_info(0, NULL_REVISION)
 
184
        # Both hooks are called.
 
185
        self.assertEqual(len(hook_calls_1), 1)
 
186
        self.assertEqual(len(hook_calls_2), 1)
 
187
 
 
188
    def test_explicit_reject_by_hook(self):
 
189
        """If a hook raises TipChangeRejected, the change does not take effect.
 
190
        
 
191
        TipChangeRejected exceptions are propagated, not wrapped in HookFailed.
 
192
        """
 
193
        branch = self.make_branch_with_revision_ids(
 
194
            'one-\xc2\xb5', 'two-\xc2\xb5')
 
195
        def hook_that_rejects(params):
 
196
            raise TipChangeRejected('rejection message')
 
197
        Branch.hooks.install_named_hook(
 
198
            'pre_change_branch_tip', hook_that_rejects, None)
 
199
        self.assertRaises(
 
200
            TipChangeRejected, branch.set_last_revision_info, 0, NULL_REVISION)
 
201
        # The revision info is unchanged.
 
202
        self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
 
203
        
 
204
 
 
205
class TestPostChangeBranchTip(ChangeBranchTipTestCase):
 
206
    """Tests for post_change_branch_tip hook.
 
207
 
 
208
    Most of these tests are very similar to the tests in
 
209
    TestPostChangeBranchTip.
 
210
    """
 
211
 
 
212
    def test_hook_runs_after_change(self):
 
213
        """The hook runs *after* the branch's last_revision_info has changed.
 
214
        """
 
215
        branch = self.make_branch_with_revision_ids('revid-one')
 
216
        def assertBranchAtRevision1(params):
 
217
            self.assertEquals(
 
218
                (0, NULL_REVISION), params.branch.last_revision_info())
 
219
        Branch.hooks.install_named_hook(
 
220
            'post_change_branch_tip', assertBranchAtRevision1, None)
 
221
        branch.set_last_revision_info(0, NULL_REVISION)
 
222
 
 
223
    def test_empty_history(self):
 
224
        branch = self.make_branch('source')
 
225
        hook_calls = self.install_logging_hook('post')
 
226
        branch.set_last_revision_info(0, NULL_REVISION)
 
227
        expected_params = ChangeBranchTipParams(
 
228
            branch, 0, 0, NULL_REVISION, NULL_REVISION)
 
229
        self.assertEqual([expected_params], hook_calls)
 
230
 
 
231
    def test_nonempty_history(self):
 
232
        # some branches require that their history be set to a revision in the
 
233
        # repository, so we need to make a branch with non-empty history for
 
234
        # this test.
 
235
        branch = self.make_branch_with_revision_ids(
 
236
            'one-\xc2\xb5', 'two-\xc2\xb5')
 
237
        hook_calls = self.install_logging_hook('post')
 
238
        branch.set_last_revision_info(1, 'one-\xc2\xb5')
 
239
        expected_params = ChangeBranchTipParams(
 
240
            branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
 
241
        self.assertEqual([expected_params], hook_calls)
 
242
 
 
243
    def test_branch_is_locked(self):
 
244
        """The branch passed to the hook is locked."""
 
245
        branch = self.make_branch('source')
 
246
        def assertBranchIsLocked(params):
 
247
            self.assertTrue(params.branch.is_locked())
 
248
        Branch.hooks.install_named_hook(
 
249
            'post_change_branch_tip', assertBranchIsLocked, None)
 
250
        branch.set_last_revision_info(0, NULL_REVISION)
 
251
 
 
252
    def test_calls_all_hooks_no_errors(self):
 
253
        """If multiple hooks are registered, all are called (if none raise
 
254
        errors).
 
255
        """
 
256
        branch = self.make_branch('source')
 
257
        hook_calls_1 = self.install_logging_hook('post')
 
258
        hook_calls_2 = self.install_logging_hook('post')
 
259
        self.assertIsNot(hook_calls_1, hook_calls_2)
 
260
        branch.set_last_revision_info(0, NULL_REVISION)
 
261
        # Both hooks are called.
 
262
        self.assertEqual(len(hook_calls_1), 1)
 
263
        self.assertEqual(len(hook_calls_2), 1)
 
264
 
 
265
 
 
266
class TestAllMethodsThatChangeTipWillRunHooks(ChangeBranchTipTestCase):
 
267
    """Every method of Branch that changes a branch tip will invoke the
 
268
    pre/post_change_branch_tip hooks.
 
269
    """
 
270
 
 
271
    def setUp(self):
 
272
        ChangeBranchTipTestCase.setUp(self)
 
273
        self.installPreAndPostHooks()
 
274
        
 
275
    def installPreAndPostHooks(self):
 
276
        self.pre_hook_calls = self.install_logging_hook('pre')
 
277
        self.post_hook_calls = self.install_logging_hook('post')
 
278
 
 
279
    def resetHookCalls(self):
 
280
        del self.pre_hook_calls[:], self.post_hook_calls[:]
 
281
 
 
282
    def assertPreAndPostHooksWereInvoked(self):
 
283
        # Check for len == 1, because the hooks should only be be invoked once
 
284
        # by an operation.
 
285
        self.assertEqual(1, len(self.pre_hook_calls))
 
286
        self.assertEqual(1, len(self.post_hook_calls))
 
287
 
 
288
    def test_set_revision_history(self):
 
289
        branch = self.make_branch('')
 
290
        branch.set_revision_history([])
 
291
        self.assertPreAndPostHooksWereInvoked()
 
292
 
 
293
    def test_set_last_revision_info(self):
 
294
        branch = self.make_branch('')
 
295
        branch.set_last_revision_info(0, NULL_REVISION)
 
296
        self.assertPreAndPostHooksWereInvoked()
 
297
 
 
298
    def test_generate_revision_history(self):
 
299
        branch = self.make_branch('')
 
300
        branch.generate_revision_history(NULL_REVISION)
 
301
        self.assertPreAndPostHooksWereInvoked()
 
302
 
 
303
    def test_pull(self):
 
304
        source_branch = self.make_branch_with_revision_ids('rev-1', 'rev-2')
 
305
        self.resetHookCalls()
 
306
        destination_branch = self.make_branch('destination')
 
307
        destination_branch.pull(source_branch)
 
308
        self.assertPreAndPostHooksWereInvoked()
 
309
 
 
310
    def test_push(self):
 
311
        source_branch = self.make_branch_with_revision_ids('rev-1', 'rev-2')
 
312
        self.resetHookCalls()
 
313
        destination_branch = self.make_branch('destination')
 
314
        source_branch.push(destination_branch)
 
315
        self.assertPreAndPostHooksWereInvoked()
 
316
 
 
317