~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_fetch.py

  • Committer: Andrew Bennetts
  • Date: 2007-06-14 11:23:38 UTC
  • mto: (2506.2.7 container-format)
  • mto: This revision was merged to the branch mainline in revision 2572.
  • Revision ID: andrew.bennetts@canonical.com-20070614112338-6u3900u6nkag66u8
Return a callable instead of a str from read, and add more validation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
import os
 
18
import re
 
19
import sys
 
20
 
 
21
from bzrlib import (
 
22
    bzrdir,
 
23
    errors,
 
24
    repository,
 
25
    )
 
26
from bzrlib.branch import Branch
 
27
from bzrlib.bzrdir import BzrDir
 
28
from bzrlib.builtins import merge
 
29
import bzrlib.errors
 
30
from bzrlib.repofmt import knitrepo
 
31
from bzrlib.tests import TestCaseWithTransport
 
32
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
 
33
from bzrlib.tests.test_revision import make_branches
 
34
from bzrlib.trace import mutter
 
35
from bzrlib.upgrade import Convert
 
36
from bzrlib.workingtree import WorkingTree
 
37
 
 
38
 
 
39
def has_revision(branch, revision_id):
 
40
    return branch.repository.has_revision(revision_id)
 
41
 
 
42
def fetch_steps(self, br_a, br_b, writable_a):
 
43
    """A foreign test method for testing fetch locally and remotely."""
 
44
     
 
45
    # TODO RBC 20060201 make this a repository test.
 
46
    repo_b = br_b.repository
 
47
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
 
48
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[2]))
 
49
    self.assertEquals(len(br_b.revision_history()), 7)
 
50
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[2])[0], 0)
 
51
    # branch.fetch is not supposed to alter the revision history
 
52
    self.assertEquals(len(br_b.revision_history()), 7)
 
53
    self.assertFalse(repo_b.has_revision(br_a.revision_history()[3]))
 
54
 
 
55
    # fetching the next revision up in sample data copies one revision
 
56
    self.assertEquals(br_b.fetch(br_a, br_a.revision_history()[3])[0], 1)
 
57
    self.assertTrue(repo_b.has_revision(br_a.revision_history()[3]))
 
58
    self.assertFalse(has_revision(br_a, br_b.revision_history()[6]))
 
59
    self.assertTrue(br_a.repository.has_revision(br_b.revision_history()[5]))
 
60
 
 
61
    # When a non-branch ancestor is missing, it should be unlisted...
 
62
    # as its not reference from the inventory weave.
 
63
    br_b4 = self.make_branch('br_4')
 
64
    count, failures = br_b4.fetch(br_b)
 
65
    self.assertEqual(count, 7)
 
66
    self.assertEqual(failures, [])
 
67
 
 
68
    self.assertEqual(writable_a.fetch(br_b)[0], 1)
 
69
    self.assertTrue(has_revision(br_a, br_b.revision_history()[3]))
 
70
    self.assertTrue(has_revision(br_a, br_b.revision_history()[4]))
 
71
        
 
72
    br_b2 = self.make_branch('br_b2')
 
73
    self.assertEquals(br_b2.fetch(br_b)[0], 7)
 
74
    self.assertTrue(has_revision(br_b2, br_b.revision_history()[4]))
 
75
    self.assertTrue(has_revision(br_b2, br_a.revision_history()[2]))
 
76
    self.assertFalse(has_revision(br_b2, br_a.revision_history()[3]))
 
77
 
 
78
    br_a2 = self.make_branch('br_a2')
 
79
    self.assertEquals(br_a2.fetch(br_a)[0], 9)
 
80
    self.assertTrue(has_revision(br_a2, br_b.revision_history()[4]))
 
81
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[3]))
 
82
    self.assertTrue(has_revision(br_a2, br_a.revision_history()[2]))
 
83
 
 
84
    br_a3 = self.make_branch('br_a3')
 
85
    # pulling a branch with no revisions grabs nothing, regardless of 
 
86
    # whats in the inventory.
 
87
    self.assertEquals(br_a3.fetch(br_a2)[0], 0)
 
88
    for revno in range(4):
 
89
        self.assertFalse(
 
90
            br_a3.repository.has_revision(br_a.revision_history()[revno]))
 
91
    self.assertEqual(br_a3.fetch(br_a2, br_a.revision_history()[2])[0], 3)
 
92
    # pull the 3 revisions introduced by a@u-0-3
 
93
    fetched = br_a3.fetch(br_a2, br_a.revision_history()[3])[0]
 
94
    self.assertEquals(fetched, 3, "fetched %d instead of 3" % fetched)
 
95
    # InstallFailed should be raised if the branch is missing the revision
 
96
    # that was requested.
 
97
    self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2, 'pizza')
 
98
    # InstallFailed should be raised if the branch is missing a revision
 
99
    # from its own revision history
 
100
    br_a2.append_revision('a-b-c')
 
101
    self.assertRaises(bzrlib.errors.InstallFailed, br_a3.fetch, br_a2)
 
102
 
 
103
    # TODO: ADHB 20070116 Perhaps set_last_revision shouldn't accept
 
104
    #       revisions which are not present?  In that case, this test
 
105
    #       must be rewritten.
 
106
    #
 
107
    #       RBC 20060403 the way to do this is to uncommit the revision from
 
108
    #       the repository after the commit
 
109
 
 
110
    #TODO: test that fetch correctly does reweaving when needed. RBC 20051008
 
111
    # Note that this means - updating the weave when ghosts are filled in to 
 
112
    # add the right parents.
 
113
 
 
114
 
 
115
class TestFetch(TestCaseWithTransport):
 
116
 
 
117
    def test_fetch(self):
 
118
        #highest indices a: 5, b: 7
 
119
        br_a, br_b = make_branches(self)
 
120
        fetch_steps(self, br_a, br_b, br_a)
 
121
 
 
122
    def test_fetch_self(self):
 
123
        wt = self.make_branch_and_tree('br')
 
124
        self.assertEqual(wt.branch.fetch(wt.branch), (0, []))
 
125
 
 
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
        root_knit = repo.weave_store.get_weave('tree-root',
 
151
                                                repo.get_transaction())
 
152
        # Make sure fetch retrieved only what we requested
 
153
        self.assertTrue('rev1' in root_knit)
 
154
        self.assertTrue('rev2' not in root_knit)
 
155
        branch.pull(tree.branch)
 
156
        root_knit = repo.weave_store.get_weave('tree-root',
 
157
                                                repo.get_transaction())
 
158
        # Make sure that the next revision in the root knit was retrieved,
 
159
        # even though the text, name, parent_id, etc., were unchanged.
 
160
        self.assertTrue('rev2' in root_knit)
 
161
 
 
162
    def test_fetch_incompatible(self):
 
163
        knit_tree = self.make_branch_and_tree('knit', format='knit')
 
164
        knit3_tree = self.make_branch_and_tree('knit3',
 
165
            format='dirstate-with-subtree')
 
166
        knit3_tree.commit('blah')
 
167
        self.assertRaises(errors.IncompatibleRepositories,
 
168
                          knit_tree.branch.fetch, knit3_tree.branch)
 
169
 
 
170
 
 
171
class TestMergeFetch(TestCaseWithTransport):
 
172
 
 
173
    def test_merge_fetches_unrelated(self):
 
174
        """Merge brings across history from unrelated source"""
 
175
        wt1 = self.make_branch_and_tree('br1')
 
176
        br1 = wt1.branch
 
177
        wt1.commit(message='rev 1-1', rev_id='1-1')
 
178
        wt1.commit(message='rev 1-2', rev_id='1-2')
 
179
        wt2 = self.make_branch_and_tree('br2')
 
180
        br2 = wt2.branch
 
181
        wt2.commit(message='rev 2-1', rev_id='2-1')
 
182
        merge(other_revision=['br1', -1], base_revision=['br1', 0],
 
183
              this_dir='br2')
 
184
        self._check_revs_present(br2)
 
185
 
 
186
    def test_merge_fetches(self):
 
187
        """Merge brings across history from source"""
 
188
        wt1 = self.make_branch_and_tree('br1')
 
189
        br1 = wt1.branch
 
190
        wt1.commit(message='rev 1-1', rev_id='1-1')
 
191
        dir_2 = br1.bzrdir.sprout('br2')
 
192
        br2 = dir_2.open_branch()
 
193
        wt1.commit(message='rev 1-2', rev_id='1-2')
 
194
        dir_2.open_workingtree().commit(message='rev 2-1', rev_id='2-1')
 
195
        merge(other_revision=['br1', -1], base_revision=[None, None], 
 
196
              this_dir='br2')
 
197
        self._check_revs_present(br2)
 
198
 
 
199
    def _check_revs_present(self, br2):
 
200
        for rev_id in '1-1', '1-2', '2-1':
 
201
            self.assertTrue(br2.repository.has_revision(rev_id))
 
202
            rev = br2.repository.get_revision(rev_id)
 
203
            self.assertEqual(rev.revision_id, rev_id)
 
204
            self.assertTrue(br2.repository.get_inventory(rev_id))
 
205
 
 
206
 
 
207
class TestMergeFileHistory(TestCaseWithTransport):
 
208
 
 
209
    def setUp(self):
 
210
        super(TestMergeFileHistory, self).setUp()
 
211
        wt1 = self.make_branch_and_tree('br1')
 
212
        br1 = wt1.branch
 
213
        self.build_tree_contents([('br1/file', 'original contents\n')])
 
214
        wt1.add('file', 'this-file-id')
 
215
        wt1.commit(message='rev 1-1', rev_id='1-1')
 
216
        dir_2 = br1.bzrdir.sprout('br2')
 
217
        br2 = dir_2.open_branch()
 
218
        wt2 = dir_2.open_workingtree()
 
219
        self.build_tree_contents([('br1/file', 'original from 1\n')])
 
220
        wt1.commit(message='rev 1-2', rev_id='1-2')
 
221
        self.build_tree_contents([('br1/file', 'agreement\n')])
 
222
        wt1.commit(message='rev 1-3', rev_id='1-3')
 
223
        self.build_tree_contents([('br2/file', 'contents in 2\n')])
 
224
        wt2.commit(message='rev 2-1', rev_id='2-1')
 
225
        self.build_tree_contents([('br2/file', 'agreement\n')])
 
226
        wt2.commit(message='rev 2-2', rev_id='2-2')
 
227
 
 
228
    def test_merge_fetches_file_history(self):
 
229
        """Merge brings across file histories"""
 
230
        br2 = Branch.open('br2')
 
231
        merge(other_revision=['br1', -1], base_revision=[None, None], 
 
232
              this_dir='br2')
 
233
        for rev_id, text in [('1-2', 'original from 1\n'),
 
234
                             ('1-3', 'agreement\n'),
 
235
                             ('2-1', 'contents in 2\n'),
 
236
                             ('2-2', 'agreement\n')]:
 
237
            self.assertEqualDiff(
 
238
                br2.repository.revision_tree(
 
239
                    rev_id).get_file_text('this-file-id'), text)
 
240
 
 
241
 
 
242
class TestHttpFetch(TestCaseWithWebserver):
 
243
    # FIXME RBC 20060124 this really isn't web specific, perhaps an
 
244
    # instrumented readonly transport? Can we do an instrumented
 
245
    # adapter and use self.get_readonly_url ?
 
246
 
 
247
    def test_fetch(self):
 
248
        #highest indices a: 5, b: 7
 
249
        br_a, br_b = make_branches(self)
 
250
        br_rem_a = Branch.open(self.get_readonly_url('branch1'))
 
251
        fetch_steps(self, br_rem_a, br_b, br_a)
 
252
 
 
253
    def _count_log_matches(self, target, logs):
 
254
        """Count the number of times the target file pattern was fetched in an http log"""
 
255
        get_succeeds_re = re.compile(
 
256
            '.*"GET .*%s HTTP/1.1" 20[06] - "-" "bzr/%s' %
 
257
            (     target,                    bzrlib.__version__))
 
258
        c = 0
 
259
        for line in logs:
 
260
            if get_succeeds_re.match(line):
 
261
                c += 1
 
262
        return c
 
263
 
 
264
    def test_weaves_are_retrieved_once(self):
 
265
        self.build_tree(("source/", "source/file", "target/"))
 
266
        wt = self.make_branch_and_tree('source')
 
267
        branch = wt.branch
 
268
        wt.add(["file"], ["id"])
 
269
        wt.commit("added file")
 
270
        print >>open("source/file", 'w'), "blah"
 
271
        wt.commit("changed file")
 
272
        target = BzrDir.create_branch_and_repo("target/")
 
273
        source = Branch.open(self.get_readonly_url("source/"))
 
274
        self.assertEqual(target.fetch(source), (2, []))
 
275
        # this is the path to the literal file. As format changes 
 
276
        # occur it needs to be updated. FIXME: ask the store for the
 
277
        # path.
 
278
        self.log("web server logs are:")
 
279
        http_logs = self.get_readonly_server().logs
 
280
        self.log('\n'.join(http_logs))
 
281
        # unfortunately this log entry is branch format specific. We could 
 
282
        # factor out the 'what files does this format use' to a method on the 
 
283
        # repository, which would let us to this generically. RBC 20060419
 
284
        self.assertEqual(1, self._count_log_matches('/ce/id.kndx', http_logs))
 
285
        self.assertEqual(1, self._count_log_matches('/ce/id.knit', http_logs))
 
286
        self.assertEqual(1, self._count_log_matches('inventory.kndx', http_logs))
 
287
        # this r-h check test will prevent regressions, but it currently already 
 
288
        # passes, before the patch to cache-rh is applied :[
 
289
        self.assertTrue(1 >= self._count_log_matches('revision-history',
 
290
                                                     http_logs))
 
291
        self.assertTrue(1 >= self._count_log_matches('last-revision',
 
292
                                                     http_logs))
 
293
        # FIXME naughty poking in there.
 
294
        self.get_readonly_server().logs = []
 
295
        # check there is nothing more to fetch
 
296
        source = Branch.open(self.get_readonly_url("source/"))
 
297
        self.assertEqual(target.fetch(source), (0, []))
 
298
        # should make just two requests
 
299
        http_logs = self.get_readonly_server().logs
 
300
        self.log("web server logs are:")
 
301
        self.log('\n'.join(http_logs))
 
302
        self.assertEqual(1, self._count_log_matches('branch-format', http_logs))
 
303
        self.assertEqual(1, self._count_log_matches('branch/format', http_logs))
 
304
        self.assertEqual(1, self._count_log_matches('repository/format', http_logs))
 
305
        self.assertTrue(1 >= self._count_log_matches('revision-history',
 
306
                                                     http_logs))
 
307
        self.assertTrue(1 >= self._count_log_matches('last-revision',
 
308
                                                     http_logs))
 
309
        self.assertEqual(4, len(http_logs))