/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

  • Committer: Jelmer Vernooij
  • Date: 2009-02-25 15:36:48 UTC
  • mfrom: (4048 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4050.
  • Revision ID: jelmer@samba.org-20090225153648-7r5mk20nr9dttqbf
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
from bzrlib.errors import HookFailed, TipChangeRejected
21
21
from bzrlib.remote import RemoteBranch
22
22
from bzrlib.revision import NULL_REVISION
 
23
from bzrlib.smart import server
23
24
from bzrlib.tests import TestCaseWithMemoryTransport
24
25
 
25
26
 
26
 
class TestSetRevisionHistoryHook(TestCaseWithMemoryTransport):
 
27
class ChangeBranchTipTestCase(TestCaseWithMemoryTransport):
 
28
    """Base TestCase for testing pre/post_change_branch_tip hooks."""
 
29
 
 
30
    def install_logging_hook(self, prefix):
 
31
        """Add a hook that logs calls made to it.
 
32
 
 
33
        :returns: the list that the calls will be appended to.
 
34
        """
 
35
        hook_calls = []
 
36
        Branch.hooks.install_named_hook(
 
37
            prefix + '_change_branch_tip', hook_calls.append, None)
 
38
        return hook_calls
 
39
 
 
40
    def make_branch_with_revision_ids(self, *revision_ids):
 
41
        """Makes a branch with the given commits."""
 
42
        tree = self.make_branch_and_memory_tree('source')
 
43
        tree.lock_write()
 
44
        tree.add('')
 
45
        for revision_id in revision_ids:
 
46
            tree.commit(u'Message of ' + revision_id.decode('utf8'),
 
47
                        rev_id=revision_id)
 
48
        tree.unlock()
 
49
        branch = tree.branch
 
50
        return branch
 
51
 
 
52
    def assertHookCalls(self, expected_params, branch, hook_calls=None,
 
53
        pre=False):
 
54
        if hook_calls is None:
 
55
            hook_calls = self.hook_calls
 
56
        if isinstance(branch, RemoteBranch):
 
57
            # For a remote branch, both the server and the client will raise
 
58
            # this hook, and we see both in the test environment. The remote
 
59
            # instance comes in between the clients - the client doe pre, the
 
60
            # server does pre, the server does post, the client does post.
 
61
            if pre:
 
62
                offset = 0
 
63
            else:
 
64
                offset = 1
 
65
            self.assertEqual(expected_params, hook_calls[offset])
 
66
            self.assertEqual(2, len(hook_calls))
 
67
        else:
 
68
            self.assertEqual([expected_params], hook_calls)
 
69
 
 
70
 
 
71
class TestSetRevisionHistoryHook(ChangeBranchTipTestCase):
27
72
 
28
73
    def setUp(self):
29
74
        self.hook_calls = []
31
76
 
32
77
    def capture_set_rh_hook(self, branch, rev_history):
33
78
        """Capture post set-rh hook calls to self.hook_calls.
34
 
        
 
79
 
35
80
        The call is logged, as is some state of the branch.
36
81
        """
37
82
        self.hook_calls.append(
42
87
        Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
43
88
                                        None)
44
89
        branch.set_revision_history([])
45
 
        self.assertEqual(self.hook_calls,
46
 
            [('set_rh', branch, [], True)])
 
90
        expected_params = ('set_rh', branch, [], True)
 
91
        self.assertHookCalls(expected_params, branch)
47
92
 
48
93
    def test_set_rh_nonempty_history(self):
49
94
        tree = self.make_branch_and_memory_tree('source')
58
103
        # some branches require that their history be set to a revision in the
59
104
        # repository
60
105
        branch.set_revision_history(['f\xc2\xb5'])
61
 
        self.assertEqual(self.hook_calls,
62
 
            [('set_rh', branch, ['f\xc2\xb5'], True)])
 
106
        expected_params =('set_rh', branch, ['f\xc2\xb5'], True)
 
107
        self.assertHookCalls(expected_params, branch)
63
108
 
64
109
    def test_set_rh_branch_is_locked(self):
65
110
        branch = self.make_branch('source')
66
111
        Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
67
112
                                        None)
68
113
        branch.set_revision_history([])
69
 
        self.assertEqual(self.hook_calls,
70
 
            [('set_rh', branch, [], True)])
 
114
        expected_params = ('set_rh', branch, [], True)
 
115
        self.assertHookCalls(expected_params, branch)
71
116
 
72
117
    def test_set_rh_calls_all_hooks_no_errors(self):
73
118
        branch = self.make_branch('source')
76
121
        Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
77
122
                                        None)
78
123
        branch.set_revision_history([])
79
 
        self.assertEqual(self.hook_calls,
80
 
            [('set_rh', branch, [], True),
81
 
             ('set_rh', branch, [], True),
82
 
            ])
83
 
 
84
 
 
85
 
class ChangeBranchTipTestCase(TestCaseWithMemoryTransport):
86
 
    """Base TestCase for testing pre/post_change_branch_tip hooks."""
87
 
 
88
 
    def install_logging_hook(self, prefix):
89
 
        """Add a hook that logs calls made to it.
90
 
        
91
 
        :returns: the list that the calls will be appended to.
92
 
        """
93
 
        hook_calls = []
94
 
        Branch.hooks.install_named_hook(
95
 
            prefix + '_change_branch_tip', hook_calls.append, None)
96
 
        return hook_calls
97
 
 
98
 
    def make_branch_with_revision_ids(self, *revision_ids):
99
 
        """Makes a branch with the given commits."""
100
 
        tree = self.make_branch_and_memory_tree('source')
101
 
        tree.lock_write()
102
 
        tree.add('')
103
 
        for revision_id in revision_ids:
104
 
            tree.commit(u'Message of ' + revision_id.decode('utf8'),
105
 
                        rev_id=revision_id)
106
 
        tree.unlock()
107
 
        branch = tree.branch
108
 
        return branch
 
124
        expected_calls = [('set_rh', branch, [], True),
 
125
            ('set_rh', branch, [], True),
 
126
            ]
 
127
        if isinstance(branch, RemoteBranch):
 
128
            # For a remote branch, both the server and the client will raise
 
129
            # set_rh, and the server will do so first because that is where
 
130
            # the change takes place.
 
131
            self.assertEqual(expected_calls, self.hook_calls[2:])
 
132
            self.assertEqual(4, len(self.hook_calls))
 
133
        else:
 
134
            self.assertEqual(expected_calls, self.hook_calls)
109
135
 
110
136
 
111
137
class TestOpen(TestCaseWithMemoryTransport):
120
146
    def test_create(self):
121
147
        self.install_hook()
122
148
        b = self.make_branch('.')
123
 
        self.assertEqual([b], self.hook_calls)
 
149
        if isinstance(b, RemoteBranch):
 
150
            # RemoteBranch creation:
 
151
            # - creates the branch via the VFS (for older servers)
 
152
            # - does a branch open (by creating a RemoteBranch object)
 
153
            # - this has the nearly the same behaviour as simple branch opening
 
154
            if (self.transport_readonly_server ==
 
155
                server.ReadonlySmartTCPServer_for_testing_v2_only):
 
156
                # Older servers:
 
157
                self.assertEqual(b._real_branch, self.hook_calls[0])
 
158
                self.assertOpenedRemoteBranch(self.hook_calls[1:], b)
 
159
            else:
 
160
                self.assertOpenedRemoteBranch(self.hook_calls, b,
 
161
                    remote_first=True)
 
162
        else:
 
163
            self.assertEqual([b], self.hook_calls)
124
164
 
125
165
    def test_open(self):
126
166
        branch_url = self.make_branch('.').bzrdir.root_transport.base
127
167
        self.install_hook()
128
168
        b = Branch.open(branch_url)
129
169
        if isinstance(b, RemoteBranch):
130
 
            # RemoteBranch open always opens the backing branch to get stacking
131
 
            # details. As that is done remotely we can't see the branch object
132
 
            # nor even compare base url's etc. So we just assert that the first
133
 
            # branch returned is the RemoteBranch, and that the second is a
134
 
            # Branch but not a RemoteBranch.
135
 
            self.assertEqual(2, len(self.hook_calls))
136
 
            self.assertEqual(b, self.hook_calls[0])
137
 
            self.assertIsInstance(self.hook_calls[1], Branch)
138
 
            self.assertFalse(isinstance(self.hook_calls[1], RemoteBranch))
 
170
            self.assertOpenedRemoteBranch(self.hook_calls, b)
139
171
        else:
140
172
            self.assertEqual([b], self.hook_calls)
141
173
 
 
174
    def assertOpenedRemoteBranch(self, hook_calls, b, remote_first=False):
 
175
        """Assert that the expected calls were recorded for opening 'b'.
 
176
 
 
177
        :param remote_first: If True expect the server side operation to open
 
178
            the branch object first.
 
179
        """
 
180
        # RemoteBranch open always opens the backing branch to get stacking
 
181
        # details. As that is done remotely we can't see the branch object
 
182
        # nor even compare base url's etc. So we just assert that the first
 
183
        # branch returned is the RemoteBranch, and that the second is a
 
184
        # Branch but not a RemoteBranch.
 
185
        #
 
186
        # RemoteBranch *creation* on the other hand creates the branch object
 
187
        # on the server, and then creates the local proxy object in the client,
 
188
        # so it sees the reverse order.
 
189
        self.assertEqual(2, len(hook_calls))
 
190
        if remote_first:
 
191
            real_index = 0
 
192
            remote_index = 1
 
193
        else:
 
194
            real_index = 1
 
195
            remote_index = 0
 
196
        self.assertEqual(b, hook_calls[remote_index])
 
197
        self.assertIsInstance(hook_calls[real_index], Branch)
 
198
        self.assertFalse(isinstance(hook_calls[real_index], RemoteBranch))
 
199
 
142
200
 
143
201
class TestPreChangeBranchTip(ChangeBranchTipTestCase):
144
202
    """Tests for pre_change_branch_tip hook.
145
 
    
 
203
 
146
204
    Most of these tests are very similar to the tests in
147
205
    TestPostChangeBranchTip.
148
206
    """
160
218
 
161
219
    def test_hook_failure_prevents_change(self):
162
220
        """If a hook raises an exception, the change does not take effect.
163
 
        
 
221
 
164
222
        Also, a HookFailed exception will be raised.
165
223
        """
166
224
        branch = self.make_branch_with_revision_ids(
176
234
        self.assertIsInstance(hook_failed_exc.exc_value, PearShapedError)
177
235
        # The revision info is unchanged.
178
236
        self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
179
 
        
 
237
 
180
238
    def test_empty_history(self):
181
239
        branch = self.make_branch('source')
182
240
        hook_calls = self.install_logging_hook('pre')
183
241
        branch.set_last_revision_info(0, NULL_REVISION)
184
242
        expected_params = ChangeBranchTipParams(
185
243
            branch, 0, 0, NULL_REVISION, NULL_REVISION)
186
 
        self.assertEqual([expected_params], hook_calls)
 
244
        self.assertHookCalls(expected_params, branch, hook_calls, pre=True)
187
245
 
188
246
    def test_nonempty_history(self):
189
247
        # some branches require that their history be set to a revision in the
195
253
        branch.set_last_revision_info(1, 'one-\xc2\xb5')
196
254
        expected_params = ChangeBranchTipParams(
197
255
            branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
198
 
        self.assertEqual([expected_params], hook_calls)
 
256
        self.assertHookCalls(expected_params, branch, hook_calls, pre=True)
199
257
 
200
258
    def test_branch_is_locked(self):
201
259
        branch = self.make_branch('source')
215
273
        self.assertIsNot(hook_calls_1, hook_calls_2)
216
274
        branch.set_last_revision_info(0, NULL_REVISION)
217
275
        # Both hooks are called.
218
 
        self.assertEqual(len(hook_calls_1), 1)
219
 
        self.assertEqual(len(hook_calls_2), 1)
 
276
        if isinstance(branch, RemoteBranch):
 
277
            count = 2
 
278
        else:
 
279
            count = 1
 
280
        self.assertEqual(len(hook_calls_1), count)
 
281
        self.assertEqual(len(hook_calls_2), count)
220
282
 
221
283
    def test_explicit_reject_by_hook(self):
222
284
        """If a hook raises TipChangeRejected, the change does not take effect.
223
 
        
 
285
 
224
286
        TipChangeRejected exceptions are propagated, not wrapped in HookFailed.
225
287
        """
226
288
        branch = self.make_branch_with_revision_ids(
233
295
            TipChangeRejected, branch.set_last_revision_info, 0, NULL_REVISION)
234
296
        # The revision info is unchanged.
235
297
        self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
236
 
        
 
298
 
237
299
 
238
300
class TestPostChangeBranchTip(ChangeBranchTipTestCase):
239
301
    """Tests for post_change_branch_tip hook.
259
321
        branch.set_last_revision_info(0, NULL_REVISION)
260
322
        expected_params = ChangeBranchTipParams(
261
323
            branch, 0, 0, NULL_REVISION, NULL_REVISION)
262
 
        self.assertEqual([expected_params], hook_calls)
 
324
        self.assertHookCalls(expected_params, branch, hook_calls)
263
325
 
264
326
    def test_nonempty_history(self):
265
327
        # some branches require that their history be set to a revision in the
271
333
        branch.set_last_revision_info(1, 'one-\xc2\xb5')
272
334
        expected_params = ChangeBranchTipParams(
273
335
            branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
274
 
        self.assertEqual([expected_params], hook_calls)
 
336
        self.assertHookCalls(expected_params, branch, hook_calls)
275
337
 
276
338
    def test_branch_is_locked(self):
277
339
        """The branch passed to the hook is locked."""
292
354
        self.assertIsNot(hook_calls_1, hook_calls_2)
293
355
        branch.set_last_revision_info(0, NULL_REVISION)
294
356
        # Both hooks are called.
295
 
        self.assertEqual(len(hook_calls_1), 1)
296
 
        self.assertEqual(len(hook_calls_2), 1)
 
357
        if isinstance(branch, RemoteBranch):
 
358
            count = 2
 
359
        else:
 
360
            count = 1
 
361
        self.assertEqual(len(hook_calls_1), count)
 
362
        self.assertEqual(len(hook_calls_2), count)
297
363
 
298
364
 
299
365
class TestAllMethodsThatChangeTipWillRunHooks(ChangeBranchTipTestCase):
304
370
    def setUp(self):
305
371
        ChangeBranchTipTestCase.setUp(self)
306
372
        self.installPreAndPostHooks()
307
 
        
 
373
 
308
374
    def installPreAndPostHooks(self):
309
375
        self.pre_hook_calls = self.install_logging_hook('pre')
310
376
        self.post_hook_calls = self.install_logging_hook('post')
312
378
    def resetHookCalls(self):
313
379
        del self.pre_hook_calls[:], self.post_hook_calls[:]
314
380
 
315
 
    def assertPreAndPostHooksWereInvoked(self):
316
 
        # Check for len == 1, because the hooks should only be be invoked once
317
 
        # by an operation.
318
 
        self.assertEqual(1, len(self.pre_hook_calls))
319
 
        self.assertEqual(1, len(self.post_hook_calls))
 
381
    def assertPreAndPostHooksWereInvoked(self, branch, smart_enabled):
 
382
        """assert that both pre and post hooks were called
 
383
 
 
384
        :param smart_enabled: The method invoked is one that should be
 
385
            smart server ready.
 
386
        """
 
387
        # Check for the number of invocations expected. One invocation is
 
388
        # local, one is remote (if the branch is remote).
 
389
        if smart_enabled and isinstance(branch, RemoteBranch):
 
390
            length = 2
 
391
        else:
 
392
            length = 1
 
393
        self.assertEqual(length, len(self.pre_hook_calls))
 
394
        self.assertEqual(length, len(self.post_hook_calls))
320
395
 
321
396
    def test_set_revision_history(self):
322
397
        branch = self.make_branch('')
323
398
        branch.set_revision_history([])
324
 
        self.assertPreAndPostHooksWereInvoked()
 
399
        self.assertPreAndPostHooksWereInvoked(branch, True)
325
400
 
326
401
    def test_set_last_revision_info(self):
327
402
        branch = self.make_branch('')
328
403
        branch.set_last_revision_info(0, NULL_REVISION)
329
 
        self.assertPreAndPostHooksWereInvoked()
 
404
        self.assertPreAndPostHooksWereInvoked(branch, True)
330
405
 
331
406
    def test_generate_revision_history(self):
332
407
        branch = self.make_branch('')
333
408
        branch.generate_revision_history(NULL_REVISION)
334
 
        self.assertPreAndPostHooksWereInvoked()
 
409
        # NB: for HPSS protocols < v3, the server does not invoke branch tip
 
410
        # change events on generate_revision_history, as the change is done
 
411
        # directly by the client over the VFS.
 
412
        self.assertPreAndPostHooksWereInvoked(branch, True)
335
413
 
336
414
    def test_pull(self):
337
415
        source_branch = self.make_branch_with_revision_ids('rev-1', 'rev-2')
338
416
        self.resetHookCalls()
339
417
        destination_branch = self.make_branch('destination')
340
418
        destination_branch.pull(source_branch)
341
 
        self.assertPreAndPostHooksWereInvoked()
 
419
        self.assertPreAndPostHooksWereInvoked(destination_branch, False)
342
420
 
343
421
    def test_push(self):
344
422
        source_branch = self.make_branch_with_revision_ids('rev-1', 'rev-2')
345
423
        self.resetHookCalls()
346
424
        destination_branch = self.make_branch('destination')
347
425
        source_branch.push(destination_branch)
348
 
        self.assertPreAndPostHooksWereInvoked()
349
 
 
350
 
        
 
426
        self.assertPreAndPostHooksWereInvoked(destination_branch, True)