83
class TestPostChangeBranchTip(TestCaseWithMemoryTransport):
87
TestCaseWithMemoryTransport.setUp(self)
89
def capture_post_change_branch_tip_hook(self, params):
90
"""Capture post_change_branch_tip hook calls to self.hook_calls.
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."""
87
def install_logging_hook(self, prefix):
88
"""Add a hook that logs calls made to it.
90
:returns: the list that the calls will be appended to.
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))
98
def test_post_change_branch_tip_empty_history(self):
99
branch = self.make_branch('source')
100
93
Branch.hooks.install_named_hook(
101
'post_change_branch_tip',
102
self.capture_post_change_branch_tip_hook,
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)
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()
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'),
119
106
branch = tree.branch
120
Branch.hooks.install_named_hook(
121
'post_change_branch_tip',
122
self.capture_post_change_branch_tip_hook,
124
# some branches require that their history be set to a revision in the
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)
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,
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)
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,
151
Branch.hooks.install_named_hook(
152
'post_change_branch_tip',
153
self.capture_post_change_branch_tip_hook,
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)
110
class TestPreChangeBranchTip(ChangeBranchTipTestCase):
111
"""Tests for pre_change_branch_tip hook.
113
Most of these tests are very similar to the tests in
114
TestPostChangeBranchTip.
117
def test_hook_runs_before_change(self):
118
"""The hook runs *before* the branch's last_revision_info has changed.
120
branch = self.make_branch_with_revision_ids('revid-one')
121
def assertBranchAtRevision1(params):
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)
128
def test_hook_failure_prevents_change(self):
129
"""If a hook raises an exception, the change does not take effect.
131
Also, a HookFailed exception will be raised.
133
branch = self.make_branch_with_revision_ids(
134
'one-\xc2\xb5', 'two-\xc2\xb5')
135
class PearShapedError(Exception):
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())
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)
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
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)
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)
175
def test_calls_all_hooks_no_errors(self):
176
"""If multiple hooks are registered, all are called (if none raise
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)
188
def test_explicit_reject_by_hook(self):
189
"""If a hook raises TipChangeRejected, the change does not take effect.
191
TipChangeRejected exceptions are propagated, not wrapped in HookFailed.
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)
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())
205
class TestPostChangeBranchTip(ChangeBranchTipTestCase):
206
"""Tests for post_change_branch_tip hook.
208
Most of these tests are very similar to the tests in
209
TestPostChangeBranchTip.
212
def test_hook_runs_after_change(self):
213
"""The hook runs *after* the branch's last_revision_info has changed.
215
branch = self.make_branch_with_revision_ids('revid-one')
216
def assertBranchAtRevision1(params):
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)
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)
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
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)
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)
252
def test_calls_all_hooks_no_errors(self):
253
"""If multiple hooks are registered, all are called (if none raise
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)
266
class TestAllMethodsThatChangeTipWillRunHooks(ChangeBranchTipTestCase):
267
"""Every method of Branch that changes a branch tip will invoke the
268
pre/post_change_branch_tip hooks.
272
ChangeBranchTipTestCase.setUp(self)
273
self.installPreAndPostHooks()
275
def installPreAndPostHooks(self):
276
self.pre_hook_calls = self.install_logging_hook('pre')
277
self.post_hook_calls = self.install_logging_hook('post')
279
def resetHookCalls(self):
280
del self.pre_hook_calls[:], self.post_hook_calls[:]
282
def assertPreAndPostHooksWereInvoked(self):
283
# Check for len == 1, because the hooks should only be be invoked once
285
self.assertEqual(1, len(self.pre_hook_calls))
286
self.assertEqual(1, len(self.post_hook_calls))
288
def test_set_revision_history(self):
289
branch = self.make_branch('')
290
branch.set_revision_history([])
291
self.assertPreAndPostHooksWereInvoked()
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()
298
def test_generate_revision_history(self):
299
branch = self.make_branch('')
300
branch.generate_revision_history(NULL_REVISION)
301
self.assertPreAndPostHooksWereInvoked()
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()
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()