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
26
class TestSetRevisionHistoryHook(TestCaseWithMemoryTransport):
27
class ChangeBranchTipTestCase(TestCaseWithMemoryTransport):
28
"""Base TestCase for testing pre/post_change_branch_tip hooks."""
30
def install_logging_hook(self, prefix):
31
"""Add a hook that logs calls made to it.
33
:returns: the list that the calls will be appended to.
36
Branch.hooks.install_named_hook(
37
prefix + '_change_branch_tip', hook_calls.append, None)
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')
45
for revision_id in revision_ids:
46
tree.commit(u'Message of ' + revision_id.decode('utf8'),
52
def assertHookCalls(self, expected_params, branch, hook_calls=None,
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.
65
self.assertEqual(expected_params, hook_calls[offset])
66
self.assertEqual(2, len(hook_calls))
68
self.assertEqual([expected_params], hook_calls)
71
class TestSetRevisionHistoryHook(ChangeBranchTipTestCase):
29
74
self.hook_calls = []
42
87
Branch.hooks.install_named_hook('set_rh', self.capture_set_rh_hook,
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)
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
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)
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,
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)
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,
78
123
branch.set_revision_history([])
79
self.assertEqual(self.hook_calls,
80
[('set_rh', branch, [], True),
81
('set_rh', branch, [], True),
85
class ChangeBranchTipTestCase(TestCaseWithMemoryTransport):
86
"""Base TestCase for testing pre/post_change_branch_tip hooks."""
88
def install_logging_hook(self, prefix):
89
"""Add a hook that logs calls made to it.
91
:returns: the list that the calls will be appended to.
94
Branch.hooks.install_named_hook(
95
prefix + '_change_branch_tip', hook_calls.append, None)
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')
103
for revision_id in revision_ids:
104
tree.commit(u'Message of ' + revision_id.decode('utf8'),
124
expected_calls = [('set_rh', branch, [], True),
125
('set_rh', branch, [], True),
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))
134
self.assertEqual(expected_calls, self.hook_calls)
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):
157
self.assertEqual(b._real_branch, self.hook_calls[0])
158
self.assertOpenedRemoteBranch(self.hook_calls[1:], b)
160
self.assertOpenedRemoteBranch(self.hook_calls, b,
163
self.assertEqual([b], self.hook_calls)
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)
140
172
self.assertEqual([b], self.hook_calls)
174
def assertOpenedRemoteBranch(self, hook_calls, b, remote_first=False):
175
"""Assert that the expected calls were recorded for opening 'b'.
177
:param remote_first: If True expect the server side operation to open
178
the branch object first.
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.
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))
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))
143
201
class TestPreChangeBranchTip(ChangeBranchTipTestCase):
144
202
"""Tests for pre_change_branch_tip hook.
146
204
Most of these tests are very similar to the tests in
147
205
TestPostChangeBranchTip.
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())
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)
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)
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):
280
self.assertEqual(len(hook_calls_1), count)
281
self.assertEqual(len(hook_calls_2), count)
221
283
def test_explicit_reject_by_hook(self):
222
284
"""If a hook raises TipChangeRejected, the change does not take effect.
224
286
TipChangeRejected exceptions are propagated, not wrapped in HookFailed.
226
288
branch = self.make_branch_with_revision_ids(
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)
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)
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):
361
self.assertEqual(len(hook_calls_1), count)
362
self.assertEqual(len(hook_calls_2), count)
299
365
class TestAllMethodsThatChangeTipWillRunHooks(ChangeBranchTipTestCase):
312
378
def resetHookCalls(self):
313
379
del self.pre_hook_calls[:], self.post_hook_calls[:]
315
def assertPreAndPostHooksWereInvoked(self):
316
# Check for len == 1, because the hooks should only be be invoked once
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
384
:param smart_enabled: The method invoked is one that should be
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):
393
self.assertEqual(length, len(self.pre_hook_calls))
394
self.assertEqual(length, len(self.post_hook_calls))
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)
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)
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)
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)
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()
426
self.assertPreAndPostHooksWereInvoked(destination_branch, True)