1
# Copyright (C) 2007 Canonical Ltd
1
# Copyright (C) 2007-2010 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
"""Tests that branch classes implement hook callouts correctly."""
19
from bzrlib.branch import Branch, ChangeBranchTipParams
20
from bzrlib.errors import HookFailed, TipChangeRejected
21
from bzrlib.remote import RemoteBranch
22
from bzrlib.revision import NULL_REVISION
20
branch as _mod_branch,
23
26
from bzrlib.smart import server
24
from bzrlib.tests import TestCaseWithMemoryTransport
27
class ChangeBranchTipTestCase(TestCaseWithMemoryTransport):
29
class ChangeBranchTipTestCase(tests.TestCaseWithMemoryTransport):
28
30
"""Base TestCase for testing pre/post_change_branch_tip hooks."""
30
32
def install_logging_hook(self, prefix):
33
35
:returns: the list that the calls will be appended to.
36
Branch.hooks.install_named_hook(
38
_mod_branch.Branch.hooks.install_named_hook(
37
39
prefix + '_change_branch_tip', hook_calls.append, None)
54
56
if hook_calls is None:
55
57
hook_calls = self.hook_calls
56
if isinstance(branch, RemoteBranch):
58
if isinstance(branch, remote.RemoteBranch):
57
59
# For a remote branch, both the server and the client will raise
58
60
# this hook, and we see both in the test environment. The remote
59
61
# instance comes in between the clients - the client doe pre, the
74
76
self.hook_calls = []
75
TestCaseWithMemoryTransport.setUp(self)
77
super(TestSetRevisionHistoryHook, self).setUp()
77
79
def capture_set_rh_hook(self, branch, rev_history):
78
80
"""Capture post set-rh hook calls to self.hook_calls.
85
87
def test_set_rh_empty_history(self):
86
88
branch = self.make_branch('source')
87
Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
89
_mod_branch.Branch.hooks.install_named_hook(
90
'set_rh', self.capture_set_rh_hook, None)
89
91
branch.set_revision_history([])
90
92
expected_params = ('set_rh', branch, [], True)
91
93
self.assertHookCalls(expected_params, branch)
98
100
tree.commit('empty commit', rev_id='foo')
100
102
branch = tree.branch
101
Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
103
_mod_branch.Branch.hooks.install_named_hook(
104
'set_rh', self.capture_set_rh_hook, None)
103
105
# some branches require that their history be set to a revision in the
105
107
branch.set_revision_history(['f\xc2\xb5'])
109
111
def test_set_rh_branch_is_locked(self):
110
112
branch = self.make_branch('source')
111
Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
113
_mod_branch.Branch.hooks.install_named_hook(
114
'set_rh', self.capture_set_rh_hook, None)
113
115
branch.set_revision_history([])
114
116
expected_params = ('set_rh', branch, [], True)
115
117
self.assertHookCalls(expected_params, branch)
117
119
def test_set_rh_calls_all_hooks_no_errors(self):
118
120
branch = self.make_branch('source')
119
Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
121
Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
121
_mod_branch.Branch.hooks.install_named_hook(
122
'set_rh', self.capture_set_rh_hook, None)
123
_mod_branch.Branch.hooks.install_named_hook(
124
'set_rh', self.capture_set_rh_hook, None)
123
125
branch.set_revision_history([])
124
126
expected_calls = [('set_rh', branch, [], True),
125
127
('set_rh', branch, [], True),
127
if isinstance(branch, RemoteBranch):
129
if isinstance(branch, remote.RemoteBranch):
128
130
# For a remote branch, both the server and the client will raise
129
131
# set_rh, and the server will do so first because that is where
130
132
# the change takes place.
134
136
self.assertEqual(expected_calls, self.hook_calls)
137
class TestOpen(TestCaseWithMemoryTransport):
139
class TestOpen(tests.TestCaseWithMemoryTransport):
139
141
def capture_hook(self, branch):
140
142
self.hook_calls.append(branch)
142
144
def install_hook(self):
143
145
self.hook_calls = []
144
Branch.hooks.install_named_hook('open', self.capture_hook, None)
146
_mod_branch.Branch.hooks.install_named_hook(
147
'open', self.capture_hook, None)
146
149
def test_create(self):
147
150
self.install_hook()
148
151
b = self.make_branch('.')
149
if isinstance(b, RemoteBranch):
152
if isinstance(b, remote.RemoteBranch):
150
153
# RemoteBranch creation:
151
154
if (self.transport_readonly_server ==
152
155
server.ReadonlySmartTCPServer_for_testing_v2_only):
170
173
def test_open(self):
171
174
branch_url = self.make_branch('.').bzrdir.root_transport.base
172
175
self.install_hook()
173
b = Branch.open(branch_url)
174
if isinstance(b, RemoteBranch):
176
b = _mod_branch.Branch.open(branch_url)
177
if isinstance(b, remote.RemoteBranch):
175
178
self.assertEqual(3, len(self.hook_calls))
176
179
# open_branchV2 RPC
177
180
self.assertRealBranch(self.hook_calls[0])
185
188
def assertRealBranch(self, b):
186
189
# Branches opened on the server don't have comparable URLs, so we just
187
190
# assert that it is not a RemoteBranch.
188
self.assertIsInstance(b, Branch)
189
self.assertFalse(isinstance(b, RemoteBranch))
191
self.assertIsInstance(b, _mod_branch.Branch)
192
self.assertFalse(isinstance(b, remote.RemoteBranch))
192
195
class TestPreChangeBranchTip(ChangeBranchTipTestCase):
203
206
def assertBranchAtRevision1(params):
204
207
self.assertEquals(
205
208
(1, 'revid-one'), params.branch.last_revision_info())
206
Branch.hooks.install_named_hook(
209
_mod_branch.Branch.hooks.install_named_hook(
207
210
'pre_change_branch_tip', assertBranchAtRevision1, None)
208
branch.set_last_revision_info(0, NULL_REVISION)
211
branch.set_last_revision_info(0, revision.NULL_REVISION)
210
213
def test_hook_failure_prevents_change(self):
211
214
"""If a hook raises an exception, the change does not take effect."""
216
219
def hook_that_raises(params):
217
220
raise PearShapedError()
218
Branch.hooks.install_named_hook(
221
_mod_branch.Branch.hooks.install_named_hook(
219
222
'pre_change_branch_tip', hook_that_raises, None)
220
223
hook_failed_exc = self.assertRaises(
221
PearShapedError, branch.set_last_revision_info, 0, NULL_REVISION)
225
branch.set_last_revision_info, 0, revision.NULL_REVISION)
222
226
# The revision info is unchanged.
223
227
self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
225
229
def test_empty_history(self):
226
230
branch = self.make_branch('source')
227
231
hook_calls = self.install_logging_hook('pre')
228
branch.set_last_revision_info(0, NULL_REVISION)
229
expected_params = ChangeBranchTipParams(
230
branch, 0, 0, NULL_REVISION, NULL_REVISION)
232
branch.set_last_revision_info(0, revision.NULL_REVISION)
233
expected_params = _mod_branch.ChangeBranchTipParams(
234
branch, 0, 0, revision.NULL_REVISION, revision.NULL_REVISION)
231
235
self.assertHookCalls(expected_params, branch, hook_calls, pre=True)
233
237
def test_nonempty_history(self):
238
242
'one-\xc2\xb5', 'two-\xc2\xb5')
239
243
hook_calls = self.install_logging_hook('pre')
240
244
branch.set_last_revision_info(1, 'one-\xc2\xb5')
241
expected_params = ChangeBranchTipParams(
245
expected_params = _mod_branch.ChangeBranchTipParams(
242
246
branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
243
247
self.assertHookCalls(expected_params, branch, hook_calls, pre=True)
246
250
branch = self.make_branch('source')
247
251
def assertBranchIsLocked(params):
248
252
self.assertTrue(params.branch.is_locked())
249
Branch.hooks.install_named_hook(
253
_mod_branch.Branch.hooks.install_named_hook(
250
254
'pre_change_branch_tip', assertBranchIsLocked, None)
251
branch.set_last_revision_info(0, NULL_REVISION)
255
branch.set_last_revision_info(0, revision.NULL_REVISION)
253
257
def test_calls_all_hooks_no_errors(self):
254
258
"""If multiple hooks are registered, all are called (if none raise
258
262
hook_calls_1 = self.install_logging_hook('pre')
259
263
hook_calls_2 = self.install_logging_hook('pre')
260
264
self.assertIsNot(hook_calls_1, hook_calls_2)
261
branch.set_last_revision_info(0, NULL_REVISION)
265
branch.set_last_revision_info(0, revision.NULL_REVISION)
262
266
# Both hooks are called.
263
if isinstance(branch, RemoteBranch):
267
if isinstance(branch, remote.RemoteBranch):
275
279
branch = self.make_branch_with_revision_ids(
276
280
'one-\xc2\xb5', 'two-\xc2\xb5')
277
281
def hook_that_rejects(params):
278
raise TipChangeRejected('rejection message')
279
Branch.hooks.install_named_hook(
282
raise errors.TipChangeRejected('rejection message')
283
_mod_branch.Branch.hooks.install_named_hook(
280
284
'pre_change_branch_tip', hook_that_rejects, None)
281
285
self.assertRaises(
282
TipChangeRejected, branch.set_last_revision_info, 0, NULL_REVISION)
286
errors.TipChangeRejected,
287
branch.set_last_revision_info, 0, revision.NULL_REVISION)
283
288
# The revision info is unchanged.
284
289
self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
297
302
branch = self.make_branch_with_revision_ids('revid-one')
298
303
def assertBranchAtRevision1(params):
299
304
self.assertEquals(
300
(0, NULL_REVISION), params.branch.last_revision_info())
301
Branch.hooks.install_named_hook(
305
(0, revision.NULL_REVISION), params.branch.last_revision_info())
306
_mod_branch.Branch.hooks.install_named_hook(
302
307
'post_change_branch_tip', assertBranchAtRevision1, None)
303
branch.set_last_revision_info(0, NULL_REVISION)
308
branch.set_last_revision_info(0, revision.NULL_REVISION)
305
310
def test_empty_history(self):
306
311
branch = self.make_branch('source')
307
312
hook_calls = self.install_logging_hook('post')
308
branch.set_last_revision_info(0, NULL_REVISION)
309
expected_params = ChangeBranchTipParams(
310
branch, 0, 0, NULL_REVISION, NULL_REVISION)
313
branch.set_last_revision_info(0, revision.NULL_REVISION)
314
expected_params = _mod_branch.ChangeBranchTipParams(
315
branch, 0, 0, revision.NULL_REVISION, revision.NULL_REVISION)
311
316
self.assertHookCalls(expected_params, branch, hook_calls)
313
318
def test_nonempty_history(self):
318
323
'one-\xc2\xb5', 'two-\xc2\xb5')
319
324
hook_calls = self.install_logging_hook('post')
320
325
branch.set_last_revision_info(1, 'one-\xc2\xb5')
321
expected_params = ChangeBranchTipParams(
326
expected_params = _mod_branch.ChangeBranchTipParams(
322
327
branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
323
328
self.assertHookCalls(expected_params, branch, hook_calls)
327
332
branch = self.make_branch('source')
328
333
def assertBranchIsLocked(params):
329
334
self.assertTrue(params.branch.is_locked())
330
Branch.hooks.install_named_hook(
335
_mod_branch.Branch.hooks.install_named_hook(
331
336
'post_change_branch_tip', assertBranchIsLocked, None)
332
branch.set_last_revision_info(0, NULL_REVISION)
337
branch.set_last_revision_info(0, revision.NULL_REVISION)
334
339
def test_calls_all_hooks_no_errors(self):
335
340
"""If multiple hooks are registered, all are called (if none raise
339
344
hook_calls_1 = self.install_logging_hook('post')
340
345
hook_calls_2 = self.install_logging_hook('post')
341
346
self.assertIsNot(hook_calls_1, hook_calls_2)
342
branch.set_last_revision_info(0, NULL_REVISION)
347
branch.set_last_revision_info(0, revision.NULL_REVISION)
343
348
# Both hooks are called.
344
if isinstance(branch, RemoteBranch):
349
if isinstance(branch, remote.RemoteBranch):
374
379
# Check for the number of invocations expected. One invocation is
375
380
# local, one is remote (if the branch is remote).
376
if smart_enabled and isinstance(branch, RemoteBranch):
381
if smart_enabled and isinstance(branch, remote.RemoteBranch):
388
393
def test_set_last_revision_info(self):
389
394
branch = self.make_branch('')
390
branch.set_last_revision_info(0, NULL_REVISION)
395
branch.set_last_revision_info(0, revision.NULL_REVISION)
391
396
self.assertPreAndPostHooksWereInvoked(branch, True)
393
398
def test_generate_revision_history(self):
394
399
branch = self.make_branch('')
395
branch.generate_revision_history(NULL_REVISION)
400
branch.generate_revision_history(revision.NULL_REVISION)
396
401
# NB: for HPSS protocols < v3, the server does not invoke branch tip
397
402
# change events on generate_revision_history, as the change is done
398
403
# directly by the client over the VFS.