~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Andrew Bennetts
  • Date: 2010-01-12 03:53:21 UTC
  • mfrom: (4948 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4964.
  • Revision ID: andrew.bennetts@canonical.com-20100112035321-hofpz5p10224ryj3
Merge lp:bzr, resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests that branch classes implement hook callouts correctly."""
18
18
 
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
            if (self.transport_readonly_server ==
 
152
                server.ReadonlySmartTCPServer_for_testing_v2_only):
 
153
                # Older servers:
 
154
                self.assertEqual(3, len(self.hook_calls))
 
155
                # creates the branch via the VFS (for older servers)
 
156
                self.assertEqual(b._real_branch, self.hook_calls[0])
 
157
                # creates a RemoteBranch object
 
158
                self.assertEqual(b, self.hook_calls[1])
 
159
                # get_stacked_on_url RPC
 
160
                self.assertRealBranch(self.hook_calls[2])
 
161
            else:
 
162
                self.assertEqual(2, len(self.hook_calls))
 
163
                # create_branch RPC
 
164
                self.assertRealBranch(self.hook_calls[0])
 
165
                # create RemoteBranch locally
 
166
                self.assertEqual(b, self.hook_calls[1])
 
167
        else:
 
168
            self.assertEqual([b], self.hook_calls)
124
169
 
125
170
    def test_open(self):
126
171
        branch_url = self.make_branch('.').bzrdir.root_transport.base
127
172
        self.install_hook()
128
173
        b = Branch.open(branch_url)
129
174
        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))
 
175
            self.assertEqual(3, len(self.hook_calls))
 
176
            # open_branchV2 RPC
 
177
            self.assertRealBranch(self.hook_calls[0])
 
178
            # create RemoteBranch locally
 
179
            self.assertEqual(b, self.hook_calls[1])
 
180
            # get_stacked_on_url RPC
 
181
            self.assertRealBranch(self.hook_calls[2])
139
182
        else:
140
183
            self.assertEqual([b], self.hook_calls)
141
184
 
 
185
    def assertRealBranch(self, b):
 
186
        # Branches opened on the server don't have comparable URLs, so we just
 
187
        # assert that it is not a RemoteBranch.
 
188
        self.assertIsInstance(b, Branch)
 
189
        self.assertFalse(isinstance(b, RemoteBranch))
 
190
 
142
191
 
143
192
class TestPreChangeBranchTip(ChangeBranchTipTestCase):
144
193
    """Tests for pre_change_branch_tip hook.
145
 
    
 
194
 
146
195
    Most of these tests are very similar to the tests in
147
196
    TestPostChangeBranchTip.
148
197
    """
159
208
        branch.set_last_revision_info(0, NULL_REVISION)
160
209
 
161
210
    def test_hook_failure_prevents_change(self):
162
 
        """If a hook raises an exception, the change does not take effect.
163
 
        
164
 
        Also, a HookFailed exception will be raised.
165
 
        """
 
211
        """If a hook raises an exception, the change does not take effect."""
166
212
        branch = self.make_branch_with_revision_ids(
167
213
            'one-\xc2\xb5', 'two-\xc2\xb5')
168
214
        class PearShapedError(Exception):
172
218
        Branch.hooks.install_named_hook(
173
219
            'pre_change_branch_tip', hook_that_raises, None)
174
220
        hook_failed_exc = self.assertRaises(
175
 
            HookFailed, branch.set_last_revision_info, 0, NULL_REVISION)
176
 
        self.assertIsInstance(hook_failed_exc.exc_value, PearShapedError)
 
221
            PearShapedError, branch.set_last_revision_info, 0, NULL_REVISION)
177
222
        # The revision info is unchanged.
178
223
        self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
179
 
        
 
224
 
180
225
    def test_empty_history(self):
181
226
        branch = self.make_branch('source')
182
227
        hook_calls = self.install_logging_hook('pre')
183
228
        branch.set_last_revision_info(0, NULL_REVISION)
184
229
        expected_params = ChangeBranchTipParams(
185
230
            branch, 0, 0, NULL_REVISION, NULL_REVISION)
186
 
        self.assertEqual([expected_params], hook_calls)
 
231
        self.assertHookCalls(expected_params, branch, hook_calls, pre=True)
187
232
 
188
233
    def test_nonempty_history(self):
189
234
        # some branches require that their history be set to a revision in the
195
240
        branch.set_last_revision_info(1, 'one-\xc2\xb5')
196
241
        expected_params = ChangeBranchTipParams(
197
242
            branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
198
 
        self.assertEqual([expected_params], hook_calls)
 
243
        self.assertHookCalls(expected_params, branch, hook_calls, pre=True)
199
244
 
200
245
    def test_branch_is_locked(self):
201
246
        branch = self.make_branch('source')
215
260
        self.assertIsNot(hook_calls_1, hook_calls_2)
216
261
        branch.set_last_revision_info(0, NULL_REVISION)
217
262
        # Both hooks are called.
218
 
        self.assertEqual(len(hook_calls_1), 1)
219
 
        self.assertEqual(len(hook_calls_2), 1)
 
263
        if isinstance(branch, RemoteBranch):
 
264
            count = 2
 
265
        else:
 
266
            count = 1
 
267
        self.assertEqual(len(hook_calls_1), count)
 
268
        self.assertEqual(len(hook_calls_2), count)
220
269
 
221
270
    def test_explicit_reject_by_hook(self):
222
271
        """If a hook raises TipChangeRejected, the change does not take effect.
223
 
        
 
272
 
224
273
        TipChangeRejected exceptions are propagated, not wrapped in HookFailed.
225
274
        """
226
275
        branch = self.make_branch_with_revision_ids(
233
282
            TipChangeRejected, branch.set_last_revision_info, 0, NULL_REVISION)
234
283
        # The revision info is unchanged.
235
284
        self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
236
 
        
 
285
 
237
286
 
238
287
class TestPostChangeBranchTip(ChangeBranchTipTestCase):
239
288
    """Tests for post_change_branch_tip hook.
259
308
        branch.set_last_revision_info(0, NULL_REVISION)
260
309
        expected_params = ChangeBranchTipParams(
261
310
            branch, 0, 0, NULL_REVISION, NULL_REVISION)
262
 
        self.assertEqual([expected_params], hook_calls)
 
311
        self.assertHookCalls(expected_params, branch, hook_calls)
263
312
 
264
313
    def test_nonempty_history(self):
265
314
        # some branches require that their history be set to a revision in the
271
320
        branch.set_last_revision_info(1, 'one-\xc2\xb5')
272
321
        expected_params = ChangeBranchTipParams(
273
322
            branch, 2, 1, 'two-\xc2\xb5', 'one-\xc2\xb5')
274
 
        self.assertEqual([expected_params], hook_calls)
 
323
        self.assertHookCalls(expected_params, branch, hook_calls)
275
324
 
276
325
    def test_branch_is_locked(self):
277
326
        """The branch passed to the hook is locked."""
292
341
        self.assertIsNot(hook_calls_1, hook_calls_2)
293
342
        branch.set_last_revision_info(0, NULL_REVISION)
294
343
        # Both hooks are called.
295
 
        self.assertEqual(len(hook_calls_1), 1)
296
 
        self.assertEqual(len(hook_calls_2), 1)
 
344
        if isinstance(branch, RemoteBranch):
 
345
            count = 2
 
346
        else:
 
347
            count = 1
 
348
        self.assertEqual(len(hook_calls_1), count)
 
349
        self.assertEqual(len(hook_calls_2), count)
297
350
 
298
351
 
299
352
class TestAllMethodsThatChangeTipWillRunHooks(ChangeBranchTipTestCase):
304
357
    def setUp(self):
305
358
        ChangeBranchTipTestCase.setUp(self)
306
359
        self.installPreAndPostHooks()
307
 
        
 
360
 
308
361
    def installPreAndPostHooks(self):
309
362
        self.pre_hook_calls = self.install_logging_hook('pre')
310
363
        self.post_hook_calls = self.install_logging_hook('post')
312
365
    def resetHookCalls(self):
313
366
        del self.pre_hook_calls[:], self.post_hook_calls[:]
314
367
 
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))
 
368
    def assertPreAndPostHooksWereInvoked(self, branch, smart_enabled):
 
369
        """assert that both pre and post hooks were called
 
370
 
 
371
        :param smart_enabled: The method invoked is one that should be
 
372
            smart server ready.
 
373
        """
 
374
        # Check for the number of invocations expected. One invocation is
 
375
        # local, one is remote (if the branch is remote).
 
376
        if smart_enabled and isinstance(branch, RemoteBranch):
 
377
            length = 2
 
378
        else:
 
379
            length = 1
 
380
        self.assertEqual(length, len(self.pre_hook_calls))
 
381
        self.assertEqual(length, len(self.post_hook_calls))
320
382
 
321
383
    def test_set_revision_history(self):
322
384
        branch = self.make_branch('')
323
385
        branch.set_revision_history([])
324
 
        self.assertPreAndPostHooksWereInvoked()
 
386
        self.assertPreAndPostHooksWereInvoked(branch, True)
325
387
 
326
388
    def test_set_last_revision_info(self):
327
389
        branch = self.make_branch('')
328
390
        branch.set_last_revision_info(0, NULL_REVISION)
329
 
        self.assertPreAndPostHooksWereInvoked()
 
391
        self.assertPreAndPostHooksWereInvoked(branch, True)
330
392
 
331
393
    def test_generate_revision_history(self):
332
394
        branch = self.make_branch('')
333
395
        branch.generate_revision_history(NULL_REVISION)
334
 
        self.assertPreAndPostHooksWereInvoked()
 
396
        # NB: for HPSS protocols < v3, the server does not invoke branch tip
 
397
        # change events on generate_revision_history, as the change is done
 
398
        # directly by the client over the VFS.
 
399
        self.assertPreAndPostHooksWereInvoked(branch, True)
335
400
 
336
401
    def test_pull(self):
337
402
        source_branch = self.make_branch_with_revision_ids('rev-1', 'rev-2')
338
403
        self.resetHookCalls()
339
404
        destination_branch = self.make_branch('destination')
340
405
        destination_branch.pull(source_branch)
341
 
        self.assertPreAndPostHooksWereInvoked()
 
406
        self.assertPreAndPostHooksWereInvoked(destination_branch, False)
342
407
 
343
408
    def test_push(self):
344
409
        source_branch = self.make_branch_with_revision_ids('rev-1', 'rev-2')
345
410
        self.resetHookCalls()
346
411
        destination_branch = self.make_branch('destination')
347
412
        source_branch.push(destination_branch)
348
 
        self.assertPreAndPostHooksWereInvoked()
349
 
 
350
 
        
 
413
        self.assertPreAndPostHooksWereInvoked(destination_branch, True)