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.