~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-06-16 14:33:42 UTC
  • mfrom: (1770.2.1 config)
  • Revision ID: pqm@pqm.ubuntu.com-20060616143342-8f7f4a4f77c1e4c8
Use create_signature for signing policy, deprecate check_signatures for this

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
18
 
import re
19
18
import sys
20
19
 
21
 
import bzrlib
22
 
from bzrlib import (
23
 
    bzrdir,
24
 
    errors,
25
 
    merge,
26
 
    repository,
27
 
    versionedfile,
28
 
    )
29
20
from bzrlib.branch import Branch
30
21
from bzrlib.bzrdir import BzrDir
31
 
from bzrlib.repofmt import knitrepo
 
22
from bzrlib.builtins import merge
 
23
import bzrlib.errors
32
24
from bzrlib.tests import TestCaseWithTransport
33
 
from bzrlib.tests.http_utils import TestCaseWithWebserver
 
25
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
34
26
from bzrlib.tests.test_revision import make_branches
35
27
from bzrlib.trace import mutter
36
 
from bzrlib.upgrade import Convert
37
28
from bzrlib.workingtree import WorkingTree
38
29
 
39
 
# These tests are a bit old; please instead add new tests into
40
 
# interrepository_implementations/ so they'll run on all relevant
41
 
# combinations.
42
 
 
43
30
 
44
31
def has_revision(branch, revision_id):
45
32
    return branch.repository.has_revision(revision_id)
99
86
    self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
100
87
    # InstallFailed should be raised if the branch is missing the revision
101
88
    # that was requested.
102
 
    self.assertRaises(errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
 
89
    self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
 
90
    # InstallFailed should be raised if the branch is missing a revision
 
91
    # from its own revision history
 
92
    br_a2.append_revision('a-b-c')
 
93
    self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
103
94
 
104
 
    # TODO: Test trying to fetch from a branch that points to a revision not
105
 
    # actually present in its repository.  Not every branch format allows you
106
 
    # to directly point to such revisions, so it's a bit complicated to
107
 
    # construct.  One way would be to uncommit and gc the revision, but not
108
 
    # every branch supports that.  -- mbp 20070814
 
95
    # TODO: jam 20051218 Branch should no longer allow append_revision for revisions
 
96
    #       which don't exist. So this test needs to be rewritten
 
97
    #       RBC 20060403 the way to do this is to uncommit the revision from the
 
98
    #           repository after the commit
109
99
 
110
100
    #TODO: test that fetch correctly does reweaving when needed. RBC 20051008
111
101
    # Note that this means - updating the weave when ghosts are filled in to 
116
106
 
117
107
    def test_fetch(self):
118
108
        #highest indices a: 5, b: 7
119
 
        br_a, br_b = make_branches(self, format='dirstate-tags')
 
109
        br_a, br_b = make_branches(self)
120
110
        fetch_steps(self, br_a, br_b, br_a)
121
111
 
122
112
    def test_fetch_self(self):
123
113
        wt = self.make_branch_and_tree('br')
124
114
        self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
125
115
 
126
 
    def test_fetch_root_knit(self):
127
 
        """Ensure that knit2.fetch() updates the root knit
128
 
        
129
 
        This tests the case where the root has a new revision, but there are no
130
 
        corresponding filename, parent, contents or other changes.
131
 
        """
132
 
        knit1_format = bzrdir.BzrDirMetaFormat1()
133
 
        knit1_format.repository_format = knitrepo.RepositoryFormatKnit1()
134
 
        knit2_format = bzrdir.BzrDirMetaFormat1()
135
 
        knit2_format.repository_format = knitrepo.RepositoryFormatKnit3()
136
 
        # we start with a knit1 repository because that causes the
137
 
        # root revision to change for each commit, even though the content,
138
 
        # parent, name, and other attributes are unchanged.
139
 
        tree = self.make_branch_and_tree('tree', knit1_format)
140
 
        tree.set_root_id('tree-root')
141
 
        tree.commit('rev1', rev_id='rev1')
142
 
        tree.commit('rev2', rev_id='rev2')
143
 
 
144
 
        # Now we convert it to a knit2 repository so that it has a root knit
145
 
        Convert(tree.basedir, knit2_format)
146
 
        tree = WorkingTree.open(tree.basedir)
147
 
        branch = self.make_branch('branch', format=knit2_format)
148
 
        branch.pull(tree.branch, stop_revision='rev1')
149
 
        repo = branch.repository
150
 
        repo.lock_read()
151
 
        try:
152
 
            # Make sure fetch retrieved only what we requested
153
 
            self.assertEqual({('tree-root', 'rev1'):()},
154
 
                repo.texts.get_parent_map(
155
 
                    [('tree-root', 'rev1'), ('tree-root', 'rev2')]))
156
 
        finally:
157
 
            repo.unlock()
158
 
        branch.pull(tree.branch)
159
 
        # Make sure that the next revision in the root knit was retrieved,
160
 
        # even though the text, name, parent_id, etc., were unchanged.
161
 
        repo.lock_read()
162
 
        try:
163
 
            # Make sure fetch retrieved only what we requested
164
 
            self.assertEqual({('tree-root', 'rev2'):(('tree-root', 'rev1'),)},
165
 
                repo.texts.get_parent_map([('tree-root', 'rev2')]))
166
 
        finally:
167
 
            repo.unlock()
168
 
 
169
 
    def test_fetch_incompatible(self):
170
 
        knit_tree = self.make_branch_and_tree('knit', format='knit')
171
 
        knit3_tree = self.make_branch_and_tree('knit3',
172
 
            format='dirstate-with-subtree')
173
 
        knit3_tree.commit('blah')
174
 
        e = self.assertRaises(errors.IncompatibleRepositories,
175
 
                              knit_tree.branch.fetch, knit3_tree.branch)
176
 
        self.assertContainsRe(str(e),
177
 
            r"(?m).*/knit.*\nis not compatible with\n.*/knit3/.*\n"
178
 
            r"different rich-root support")
179
 
 
180
116
 
181
117
class TestMergeFetch(TestCaseWithTransport):
182
118
 
189
125
        wt2 = self.make_branch_and_tree('br2')
190
126
        br2 = wt2.branch
191
127
        wt2.commit(message='rev 2-1', rev_id='2-1')
192
 
        wt2.merge_from_branch(br1, from_revision='null:')
 
128
        merge(other_revision=['br1', -1], base_revision=['br1', 0],
 
129
              this_dir='br2')
193
130
        self._check_revs_present(br2)
194
131
 
195
132
    def test_merge_fetches(self):
200
137
        dir_2 = br1.bzrdir.sprout('br2')
201
138
        br2 = dir_2.open_branch()
202
139
        wt1.commit(message='rev 1-2', rev_id='1-2')
203
 
        wt2 = dir_2.open_workingtree()
204
 
        wt2.commit(message='rev 2-1', rev_id='2-1')
205
 
        wt2.merge_from_branch(br1)
 
140
        dir_2.open_workingtree().commit(message='rev 2-1', rev_id='2-1')
 
141
        merge(other_revision=['br1', -1], base_revision=[None, None], 
 
142
              this_dir='br2')
206
143
        self._check_revs_present(br2)
207
144
 
208
145
    def _check_revs_present(self, br2):
237
174
    def test_merge_fetches_file_history(self):
238
175
        """Merge brings across file histories"""
239
176
        br2 = Branch.open('br2')
240
 
        br1 = Branch.open('br1')
241
 
        wt2 = WorkingTree.open('br2').merge_from_branch(br1)
242
 
        br2.lock_read()
243
 
        self.addCleanup(br2.unlock)
 
177
        merge(other_revision=['br1', -1], base_revision=[None, None], 
 
178
              this_dir='br2')
244
179
        for rev_id, text in [('1-2', 'original from 1\n'),
245
180
                             ('1-3', 'agreement\n'),
246
181
                             ('2-1', 'contents in 2\n'),
263
198
 
264
199
    def _count_log_matches(self, target, logs):
265
200
        """Count the number of times the target file pattern was fetched in an http log"""
266
 
        get_succeeds_re = re.compile(
267
 
            '.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
268
 
            (     target,                    bzrlib.__version__))
 
201
        log_pattern = '%s HTTP/1.1" 200 - "-" "bzr/%s' % \
 
202
            (target, bzrlib.__version__)
269
203
        c = 0
270
204
        for line in logs:
271
 
            if get_succeeds_re.match(line):
 
205
            # TODO: perhaps use a regexp instead so we can match more
 
206
            # precisely?
 
207
            if line.find(log_pattern) > -1:
272
208
                c += 1
273
209
        return c
274
210
 
275
211
    def test_weaves_are_retrieved_once(self):
276
212
        self.build_tree(("source/", "source/file", "target/"))
277
 
        # This test depends on knit dasta storage.
278
 
        wt = self.make_branch_and_tree('source', format='dirstate-tags')
 
213
        wt = self.make_branch_and_tree('source')
279
214
        branch = wt.branch
280
215
        wt.add(["file"], ["id"])
281
216
        wt.commit("added file")
282
 
        open("source/file", 'w').write("blah\n")
 
217
        print >>open("source/file", 'w'), "blah"
283
218
        wt.commit("changed file")
284
219
        target = BzrDir.create_branch_and_repo("target/")
285
220
        source = Branch.open(self.get_readonly_url("source/"))
286
221
        self.assertEqual(target.fetch(source), (2, []))
 
222
        log_pattern = '%%s HTTP/1.1" 200 - "-" "bzr/%s' % bzrlib.__version__
287
223
        # this is the path to the literal file. As format changes 
288
224
        # occur it needs to be updated. FIXME: ask the store for the
289
225
        # path.
293
229
        # unfortunately this log entry is branch format specific. We could 
294
230
        # factor out the 'what files does this format use' to a method on the 
295
231
        # repository, which would let us to this generically. RBC 20060419
296
 
        # RBC 20080408: Or perhaps we can assert that no files are fully read
297
 
        # twice?
298
232
        self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
299
233
        self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
300
234
        self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
 
235
        self.assertEqual(1, self._count_log_matches('inventory.knit', http_logs))
301
236
        # this r-h check test will prevent regressions, but it currently already 
302
237
        # passes, before the patch to cache-rh is applied :[
303
 
        self.assertTrue(1 >= self._count_log_matches('revision-history',
304
 
                                                     http_logs))
305
 
        self.assertTrue(1 >= self._count_log_matches('last-revision',
306
 
                                                     http_logs))
 
238
        self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
307
239
        # FIXME naughty poking in there.
308
240
        self.get_readonly_server().logs = []
309
 
        # check there is nothing more to fetch.  We take care to re-use the
310
 
        # existing transport so that the request logs we're about to examine
311
 
        # aren't cluttered with redundant probes for a smart server.
312
 
        # XXX: Perhaps this further parameterisation: test http with smart
313
 
        # server, and test http without smart server?
314
 
        source = Branch.open(
315
 
            self.get_readonly_url("source/"),
316
 
            possible_transports=[source.bzrdir.root_transport])
 
241
        # check there is nothing more to fetch
 
242
        source = Branch.open(self.get_readonly_url("source/"))
317
243
        self.assertEqual(target.fetch(source), (0, []))
318
244
        # should make just two requests
319
245
        http_logs = self.get_readonly_server().logs
321
247
        self.log('\n'.join(http_logs))
322
248
        self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
323
249
        self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
324
 
        self.assertEqual(1, self._count_log_matches('repository/format',
325
 
            http_logs))
326
 
        self.assertTrue(1 >= self._count_log_matches('revision-history',
327
 
                                                     http_logs))
328
 
        self.assertTrue(1 >= self._count_log_matches('last-revision',
329
 
                                                     http_logs))
 
250
        self.assertEqual(1, self._count_log_matches('repository/format', http_logs))
 
251
        self.assertEqual(1, self._count_log_matches('revision-history', http_logs))
330
252
        self.assertEqual(4, len(http_logs))
331
 
 
332
 
 
333
 
class TestKnitToPackFetch(TestCaseWithTransport):
334
 
 
335
 
    def find_get_record_stream(self, calls):
336
 
        """In a list of calls, find 'get_record_stream' calls.
337
 
 
338
 
        This also ensures that there is only one get_record_stream call.
339
 
        """
340
 
        get_record_call = None
341
 
        for call in calls:
342
 
            if call[0] == 'get_record_stream':
343
 
                self.assertIs(None, get_record_call,
344
 
                              "there should only be one call to"
345
 
                              " get_record_stream")
346
 
                get_record_call = call
347
 
        self.assertIsNot(None, get_record_call,
348
 
                         "there should be exactly one call to "
349
 
                         " get_record_stream")
350
 
        return get_record_call
351
 
 
352
 
    def test_fetch_with_deltas_no_delta_closure(self):
353
 
        tree = self.make_branch_and_tree('source', format='dirstate')
354
 
        target = self.make_repository('target', format='pack-0.92')
355
 
        self.build_tree(['source/file'])
356
 
        tree.set_root_id('root-id')
357
 
        tree.add('file', 'file-id')
358
 
        tree.commit('one', rev_id='rev-one')
359
 
        source = tree.branch.repository
360
 
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
361
 
                        source.texts)
362
 
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
363
 
                        source.signatures)
364
 
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
365
 
                        source.revisions)
366
 
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
367
 
                        source.inventories)
368
 
        # precondition
369
 
        self.assertTrue(target._fetch_uses_deltas)
370
 
        target.fetch(source, revision_id='rev-one')
371
 
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
372
 
                          target._fetch_order, False),
373
 
                         self.find_get_record_stream(source.texts.calls))
374
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
375
 
                          target._fetch_order, False),
376
 
                         self.find_get_record_stream(source.inventories.calls))
377
 
        # Because of bugs in the old fetch code, revisions could accidentally
378
 
        # have deltas present in knits. However, it was never intended, so we
379
 
        # always for include_delta_closure=True, to make sure we get fulltexts.
380
 
        # bug #261339
381
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
382
 
                          target._fetch_order, True),
383
 
                         self.find_get_record_stream(source.revisions.calls))
384
 
        # XXX: Signatures is special, and slightly broken. The
385
 
        # standard item_keys_introduced_by actually does a lookup for every
386
 
        # signature to see if it exists, rather than waiting to do them all at
387
 
        # once at the end. The fetch code then does an all-at-once and just
388
 
        # allows for some of them to be missing.
389
 
        # So we know there will be extra calls, but the *last* one is the one
390
 
        # we care about.
391
 
        signature_calls = source.signatures.calls[-1:]
392
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
393
 
                          target._fetch_order, True),
394
 
                         self.find_get_record_stream(signature_calls))
395
 
 
396
 
    def test_fetch_no_deltas_with_delta_closure(self):
397
 
        tree = self.make_branch_and_tree('source', format='dirstate')
398
 
        target = self.make_repository('target', format='pack-0.92')
399
 
        self.build_tree(['source/file'])
400
 
        tree.set_root_id('root-id')
401
 
        tree.add('file', 'file-id')
402
 
        tree.commit('one', rev_id='rev-one')
403
 
        source = tree.branch.repository
404
 
        source.texts = versionedfile.RecordingVersionedFilesDecorator(
405
 
                        source.texts)
406
 
        source.signatures = versionedfile.RecordingVersionedFilesDecorator(
407
 
                        source.signatures)
408
 
        source.revisions = versionedfile.RecordingVersionedFilesDecorator(
409
 
                        source.revisions)
410
 
        source.inventories = versionedfile.RecordingVersionedFilesDecorator(
411
 
                        source.inventories)
412
 
        target._fetch_uses_deltas = False
413
 
        target.fetch(source, revision_id='rev-one')
414
 
        self.assertEqual(('get_record_stream', [('file-id', 'rev-one')],
415
 
                          target._fetch_order, True),
416
 
                         self.find_get_record_stream(source.texts.calls))
417
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
418
 
                          target._fetch_order, True),
419
 
                         self.find_get_record_stream(source.inventories.calls))
420
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
421
 
                          target._fetch_order, True),
422
 
                         self.find_get_record_stream(source.revisions.calls))
423
 
        # XXX: Signatures is special, and slightly broken. The
424
 
        # standard item_keys_introduced_by actually does a lookup for every
425
 
        # signature to see if it exists, rather than waiting to do them all at
426
 
        # once at the end. The fetch code then does an all-at-once and just
427
 
        # allows for some of them to be missing.
428
 
        # So we know there will be extra calls, but the *last* one is the one
429
 
        # we care about.
430
 
        signature_calls = source.signatures.calls[-1:]
431
 
        self.assertEqual(('get_record_stream', [('rev-one',)],
432
 
                          target._fetch_order, True),
433
 
                         self.find_get_record_stream(signature_calls))
434
 
 
435
 
 
436
 
class Test1To2Fetch(TestCaseWithTransport):
437
 
    """Tests for Model1To2 failure modes"""
438
 
 
439
 
    def make_tree_and_repo(self):
440
 
        self.tree = self.make_branch_and_tree('tree', format='pack-0.92')
441
 
        self.repo = self.make_repository('rich-repo', format='rich-root-pack')
442
 
        self.repo.lock_write()
443
 
        self.addCleanup(self.repo.unlock)
444
 
 
445
 
    def do_fetch_order_test(self, first, second):
446
 
        """Test that fetch works no matter what the set order of revision is.
447
 
 
448
 
        This test depends on the order of items in a set, which is
449
 
        implementation-dependant, so we test A, B and then B, A.
450
 
        """
451
 
        self.make_tree_and_repo()
452
 
        self.tree.commit('Commit 1', rev_id=first)
453
 
        self.tree.commit('Commit 2', rev_id=second)
454
 
        self.repo.fetch(self.tree.branch.repository, second)
455
 
 
456
 
    def test_fetch_order_AB(self):
457
 
        """See do_fetch_order_test"""
458
 
        self.do_fetch_order_test('A', 'B')
459
 
 
460
 
    def test_fetch_order_BA(self):
461
 
        """See do_fetch_order_test"""
462
 
        self.do_fetch_order_test('B', 'A')
463
 
 
464
 
    def get_parents(self, file_id, revision_id):
465
 
        self.repo.lock_read()
466
 
        try:
467
 
            parent_map = self.repo.texts.get_parent_map([(file_id, revision_id)])
468
 
            return parent_map[(file_id, revision_id)]
469
 
        finally:
470
 
            self.repo.unlock()
471
 
 
472
 
    def test_fetch_ghosts(self):
473
 
        self.make_tree_and_repo()
474
 
        self.tree.commit('first commit', rev_id='left-parent')
475
 
        self.tree.add_parent_tree_id('ghost-parent')
476
 
        fork = self.tree.bzrdir.sprout('fork', 'null:').open_workingtree()
477
 
        fork.commit('not a ghost', rev_id='not-ghost-parent')
478
 
        self.tree.branch.repository.fetch(fork.branch.repository,
479
 
                                     'not-ghost-parent')
480
 
        self.tree.add_parent_tree_id('not-ghost-parent')
481
 
        self.tree.commit('second commit', rev_id='second-id')
482
 
        self.repo.fetch(self.tree.branch.repository, 'second-id')
483
 
        root_id = self.tree.get_root_id()
484
 
        self.assertEqual(
485
 
            ((root_id, 'left-parent'), (root_id, 'ghost-parent'),
486
 
             (root_id, 'not-ghost-parent')),
487
 
            self.get_parents(root_id, 'second-id'))
488
 
 
489
 
    def make_two_commits(self, change_root, fetch_twice):
490
 
        self.make_tree_and_repo()
491
 
        self.tree.commit('first commit', rev_id='first-id')
492
 
        if change_root:
493
 
            self.tree.set_root_id('unique-id')
494
 
        self.tree.commit('second commit', rev_id='second-id')
495
 
        if fetch_twice:
496
 
            self.repo.fetch(self.tree.branch.repository, 'first-id')
497
 
        self.repo.fetch(self.tree.branch.repository, 'second-id')
498
 
 
499
 
    def test_fetch_changed_root(self):
500
 
        self.make_two_commits(change_root=True, fetch_twice=False)
501
 
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
502
 
 
503
 
    def test_two_fetch_changed_root(self):
504
 
        self.make_two_commits(change_root=True, fetch_twice=True)
505
 
        self.assertEqual((), self.get_parents('unique-id', 'second-id'))
506
 
 
507
 
    def test_two_fetches(self):
508
 
        self.make_two_commits(change_root=False, fetch_twice=True)
509
 
        self.assertEqual((('TREE_ROOT', 'first-id'),),
510
 
            self.get_parents('TREE_ROOT', 'second-id'))