/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: v.ladeuil+lp at free
  • Date: 2006-10-12 14:29:32 UTC
  • mto: (2145.1.1 keepalive)
  • mto: This revision was merged to the branch mainline in revision 2146.
  • Revision ID: v.ladeuil+lp@free.fr-20061012142932-7221fe16d2b48fa3
Shuffle http related test code. Hopefully it ends up at the right place :)

* bzrlib/tests/HttpServer.py: 
New file. bzrlib.tests.ChrootedTestCase use HttpServer. So the
class can't be defined in bzrlib.tests.HTTPUtils because it
creates a circular dependency (bzrlib.tests.HTTPUtils needs to
import bzrlib.tests).

* bzrlib/transport/http/_urllib.py: 
Transfer test server definition to bzrlib.tests.HttpServer. Clean
up imports.

* bzrlib/transport/http/_pycurl.py: 
Transfer test server definition to bzrlib.tests.HttpServer. Clean
up imports.

* bzrlib/transport/http/__init__.py: 
Transfer all test related code to either bzrlib.tests.HttpServer
and bzrlib.tests.HTTPUtils.
Fix all use of TransportNotPossible and InvalidURL by prefixing it
by 'errors.' (this seems to be the preferred way in the rest of
bzr).
Get rid of unused imports.

* bzrlib/tests/test_transport.py:
(ReadonlyDecoratorTransportTest.test_local_parameters,
FakeNFSDecoratorTests.test_http_parameters): Use HttpServer from
bzrlib.tests.HttpServer instead of bzrlib.transport.http.

* bzrlib/tests/test_sftp_transport.py:
(set_test_transport_to_sftp): Use HttpServer from
bzrlib.tests.HttpServer instead of bzrlib.transport.http.

* bzrlib/tests/test_selftest.py:
(TestTestCaseWithTransport.test_get_readonly_url_http): Use
HttpServer from bzrlib.tests.HttpServer instead of
bzrlib.transport.http.

* bzrlib/tests/test_repository.py: 
Does *not* use HttpServer.

* bzrlib/tests/test_http.py: 
Build on top of bzrlib.tests.HttpServer and bzrlib.tests.HTTPUtils
instead of bzrlib.transport.http.

* bzrlib/tests/test_bzrdir.py:
(ChrootedTests.setUp): Use HttpServer from bzrlib.tests.HttpServer
instead of bzrlib.transport.http.

* bzrlib/tests/branch_implementations/test_http.py:
(HTTPBranchTests.setUp): Use HttpServer from bzrlib.tests.HttpServer
instead of bzrlib.transport.http.

* bzrlib/tests/branch_implementations/test_branch.py:
(ChrootedTests.setUp): Use HttpServer from bzrlib.tests.HttpServer
instead of bzrlib.transport.http.

* bzrlib/tests/__init__.py:
(ChrootedTestCase.setUp): Use HttpServer from
bzrlib.tests.HttpServer instead of bzrlib.transport.http.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004-2006 by 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
from cStringIO import StringIO
 
18
import os
 
19
import sys
 
20
import tempfile
 
21
 
 
22
from bzrlib import (
 
23
    bzrdir,
 
24
    errors,
 
25
    inventory,
 
26
    repository,
 
27
    treebuilder,
 
28
    )
 
29
from bzrlib.builtins import _merge_helper
 
30
from bzrlib.bzrdir import BzrDir
 
31
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
 
32
from bzrlib.bundle.bundle_data import BundleTree
 
33
from bzrlib.bundle.serializer import write_bundle, read_bundle
 
34
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
 
35
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
 
36
from bzrlib.branch import Branch
 
37
from bzrlib.diff import internal_diff
 
38
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
39
                           NoSuchFile,)
 
40
from bzrlib.merge import Merge3Merger
 
41
from bzrlib.osutils import has_symlinks, sha_file
 
42
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
 
43
                          TestCase, TestSkipped)
 
44
from bzrlib.transform import TreeTransform
 
45
from bzrlib.workingtree import WorkingTree
 
46
 
 
47
 
 
48
class MockTree(object):
 
49
    def __init__(self):
 
50
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
 
51
        object.__init__(self)
 
52
        self.paths = {ROOT_ID: ""}
 
53
        self.ids = {"": ROOT_ID}
 
54
        self.contents = {}
 
55
        self.root = InventoryDirectory(ROOT_ID, '', None)
 
56
 
 
57
    inventory = property(lambda x:x)
 
58
 
 
59
    def __iter__(self):
 
60
        return self.paths.iterkeys()
 
61
 
 
62
    def __getitem__(self, file_id):
 
63
        if file_id == self.root.file_id:
 
64
            return self.root
 
65
        else:
 
66
            return self.make_entry(file_id, self.paths[file_id])
 
67
 
 
68
    def parent_id(self, file_id):
 
69
        parent_dir = os.path.dirname(self.paths[file_id])
 
70
        if parent_dir == "":
 
71
            return None
 
72
        return self.ids[parent_dir]
 
73
 
 
74
    def iter_entries(self):
 
75
        for path, file_id in self.ids.iteritems():
 
76
            yield path, self[file_id]
 
77
 
 
78
    def get_file_kind(self, file_id):
 
79
        if file_id in self.contents:
 
80
            kind = 'file'
 
81
        else:
 
82
            kind = 'directory'
 
83
        return kind
 
84
 
 
85
    def make_entry(self, file_id, path):
 
86
        from bzrlib.inventory import (InventoryEntry, InventoryFile
 
87
                                    , InventoryDirectory, InventoryLink)
 
88
        name = os.path.basename(path)
 
89
        kind = self.get_file_kind(file_id)
 
90
        parent_id = self.parent_id(file_id)
 
91
        text_sha_1, text_size = self.contents_stats(file_id)
 
92
        if kind == 'directory':
 
93
            ie = InventoryDirectory(file_id, name, parent_id)
 
94
        elif kind == 'file':
 
95
            ie = InventoryFile(file_id, name, parent_id)
 
96
        elif kind == 'symlink':
 
97
            ie = InventoryLink(file_id, name, parent_id)
 
98
        else:
 
99
            raise BzrError('unknown kind %r' % kind)
 
100
        ie.text_sha1 = text_sha_1
 
101
        ie.text_size = text_size
 
102
        return ie
 
103
 
 
104
    def add_dir(self, file_id, path):
 
105
        self.paths[file_id] = path
 
106
        self.ids[path] = file_id
 
107
    
 
108
    def add_file(self, file_id, path, contents):
 
109
        self.add_dir(file_id, path)
 
110
        self.contents[file_id] = contents
 
111
 
 
112
    def path2id(self, path):
 
113
        return self.ids.get(path)
 
114
 
 
115
    def id2path(self, file_id):
 
116
        return self.paths.get(file_id)
 
117
 
 
118
    def has_id(self, file_id):
 
119
        return self.id2path(file_id) is not None
 
120
 
 
121
    def get_file(self, file_id):
 
122
        result = StringIO()
 
123
        result.write(self.contents[file_id])
 
124
        result.seek(0,0)
 
125
        return result
 
126
 
 
127
    def contents_stats(self, file_id):
 
128
        if file_id not in self.contents:
 
129
            return None, None
 
130
        text_sha1 = sha_file(self.get_file(file_id))
 
131
        return text_sha1, len(self.contents[file_id])
 
132
 
 
133
 
 
134
class BTreeTester(TestCase):
 
135
    """A simple unittest tester for the BundleTree class."""
 
136
 
 
137
    def make_tree_1(self):
 
138
        mtree = MockTree()
 
139
        mtree.add_dir("a", "grandparent")
 
140
        mtree.add_dir("b", "grandparent/parent")
 
141
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
 
142
        mtree.add_dir("d", "grandparent/alt_parent")
 
143
        return BundleTree(mtree, ''), mtree
 
144
        
 
145
    def test_renames(self):
 
146
        """Ensure that file renames have the proper effect on children"""
 
147
        btree = self.make_tree_1()[0]
 
148
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
 
149
        self.assertEqual(btree.old_path("grandparent/parent"), 
 
150
                         "grandparent/parent")
 
151
        self.assertEqual(btree.old_path("grandparent/parent/file"),
 
152
                         "grandparent/parent/file")
 
153
 
 
154
        self.assertEqual(btree.id2path("a"), "grandparent")
 
155
        self.assertEqual(btree.id2path("b"), "grandparent/parent")
 
156
        self.assertEqual(btree.id2path("c"), "grandparent/parent/file")
 
157
 
 
158
        self.assertEqual(btree.path2id("grandparent"), "a")
 
159
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
 
160
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
 
161
 
 
162
        assert btree.path2id("grandparent2") is None
 
163
        assert btree.path2id("grandparent2/parent") is None
 
164
        assert btree.path2id("grandparent2/parent/file") is None
 
165
 
 
166
        btree.note_rename("grandparent", "grandparent2")
 
167
        assert btree.old_path("grandparent") is None
 
168
        assert btree.old_path("grandparent/parent") is None
 
169
        assert btree.old_path("grandparent/parent/file") is None
 
170
 
 
171
        self.assertEqual(btree.id2path("a"), "grandparent2")
 
172
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
 
173
        self.assertEqual(btree.id2path("c"), "grandparent2/parent/file")
 
174
 
 
175
        self.assertEqual(btree.path2id("grandparent2"), "a")
 
176
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
 
177
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
 
178
 
 
179
        assert btree.path2id("grandparent") is None
 
180
        assert btree.path2id("grandparent/parent") is None
 
181
        assert btree.path2id("grandparent/parent/file") is None
 
182
 
 
183
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
 
184
        self.assertEqual(btree.id2path("a"), "grandparent2")
 
185
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
 
186
        self.assertEqual(btree.id2path("c"), "grandparent2/parent2/file")
 
187
 
 
188
        self.assertEqual(btree.path2id("grandparent2"), "a")
 
189
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
 
190
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
 
191
 
 
192
        assert btree.path2id("grandparent2/parent") is None
 
193
        assert btree.path2id("grandparent2/parent/file") is None
 
194
 
 
195
        btree.note_rename("grandparent/parent/file", 
 
196
                          "grandparent2/parent2/file2")
 
197
        self.assertEqual(btree.id2path("a"), "grandparent2")
 
198
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
 
199
        self.assertEqual(btree.id2path("c"), "grandparent2/parent2/file2")
 
200
 
 
201
        self.assertEqual(btree.path2id("grandparent2"), "a")
 
202
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
 
203
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
 
204
 
 
205
        assert btree.path2id("grandparent2/parent2/file") is None
 
206
 
 
207
    def test_moves(self):
 
208
        """Ensure that file moves have the proper effect on children"""
 
209
        btree = self.make_tree_1()[0]
 
210
        btree.note_rename("grandparent/parent/file", 
 
211
                          "grandparent/alt_parent/file")
 
212
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
 
213
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
 
214
        assert btree.path2id("grandparent/parent/file") is None
 
215
 
 
216
    def unified_diff(self, old, new):
 
217
        out = StringIO()
 
218
        internal_diff("old", old, "new", new, out)
 
219
        out.seek(0,0)
 
220
        return out.read()
 
221
 
 
222
    def make_tree_2(self):
 
223
        btree = self.make_tree_1()[0]
 
224
        btree.note_rename("grandparent/parent/file", 
 
225
                          "grandparent/alt_parent/file")
 
226
        assert btree.id2path("e") is None
 
227
        assert btree.path2id("grandparent/parent/file") is None
 
228
        btree.note_id("e", "grandparent/parent/file")
 
229
        return btree
 
230
 
 
231
    def test_adds(self):
 
232
        """File/inventory adds"""
 
233
        btree = self.make_tree_2()
 
234
        add_patch = self.unified_diff([], ["Extra cheese\n"])
 
235
        btree.note_patch("grandparent/parent/file", add_patch)
 
236
        btree.note_id('f', 'grandparent/parent/symlink', kind='symlink')
 
237
        btree.note_target('grandparent/parent/symlink', 'venus')
 
238
        self.adds_test(btree)
 
239
 
 
240
    def adds_test(self, btree):
 
241
        self.assertEqual(btree.id2path("e"), "grandparent/parent/file")
 
242
        self.assertEqual(btree.path2id("grandparent/parent/file"), "e")
 
243
        self.assertEqual(btree.get_file("e").read(), "Extra cheese\n")
 
244
        self.assertEqual(btree.get_symlink_target('f'), 'venus')
 
245
 
 
246
    def test_adds2(self):
 
247
        """File/inventory adds, with patch-compatibile renames"""
 
248
        btree = self.make_tree_2()
 
249
        btree.contents_by_id = False
 
250
        add_patch = self.unified_diff(["Hello\n"], ["Extra cheese\n"])
 
251
        btree.note_patch("grandparent/parent/file", add_patch)
 
252
        btree.note_id('f', 'grandparent/parent/symlink', kind='symlink')
 
253
        btree.note_target('grandparent/parent/symlink', 'venus')
 
254
        self.adds_test(btree)
 
255
 
 
256
    def make_tree_3(self):
 
257
        btree, mtree = self.make_tree_1()
 
258
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
 
259
        btree.note_rename("grandparent/parent/file", 
 
260
                          "grandparent/alt_parent/file")
 
261
        btree.note_rename("grandparent/parent/topping", 
 
262
                          "grandparent/alt_parent/stopping")
 
263
        return btree
 
264
 
 
265
    def get_file_test(self, btree):
 
266
        self.assertEqual(btree.get_file("e").read(), "Lemon\n")
 
267
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
 
268
 
 
269
    def test_get_file(self):
 
270
        """Get file contents"""
 
271
        btree = self.make_tree_3()
 
272
        mod_patch = self.unified_diff(["Anchovies\n"], ["Lemon\n"])
 
273
        btree.note_patch("grandparent/alt_parent/stopping", mod_patch)
 
274
        self.get_file_test(btree)
 
275
 
 
276
    def test_get_file2(self):
 
277
        """Get file contents, with patch-compatibile renames"""
 
278
        btree = self.make_tree_3()
 
279
        btree.contents_by_id = False
 
280
        mod_patch = self.unified_diff([], ["Lemon\n"])
 
281
        btree.note_patch("grandparent/alt_parent/stopping", mod_patch)
 
282
        mod_patch = self.unified_diff([], ["Hello\n"])
 
283
        btree.note_patch("grandparent/alt_parent/file", mod_patch)
 
284
        self.get_file_test(btree)
 
285
 
 
286
    def test_delete(self):
 
287
        "Deletion by bundle"
 
288
        btree = self.make_tree_1()[0]
 
289
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
 
290
        btree.note_deletion("grandparent/parent/file")
 
291
        assert btree.id2path("c") is None
 
292
        assert btree.path2id("grandparent/parent/file") is None
 
293
 
 
294
    def sorted_ids(self, tree):
 
295
        ids = list(tree)
 
296
        ids.sort()
 
297
        return ids
 
298
 
 
299
    def test_iteration(self):
 
300
        """Ensure that iteration through ids works properly"""
 
301
        btree = self.make_tree_1()[0]
 
302
        self.assertEqual(self.sorted_ids(btree),
 
303
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
 
304
        btree.note_deletion("grandparent/parent/file")
 
305
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
 
306
        btree.note_last_changed("grandparent/alt_parent/fool", 
 
307
                                "revisionidiguess")
 
308
        self.assertEqual(self.sorted_ids(btree),
 
309
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
 
310
 
 
311
 
 
312
class BundleTester1(TestCaseWithTransport):
 
313
 
 
314
    def test_mismatched_bundle(self):
 
315
        format = bzrdir.BzrDirMetaFormat1()
 
316
        format.repository_format = repository.RepositoryFormatKnit2()
 
317
        serializer = BundleSerializerV08('0.8')
 
318
        b = self.make_branch('.', format=format)
 
319
        self.assertRaises(errors.IncompatibleFormat, serializer.write, 
 
320
                          b.repository, [], {}, StringIO())
 
321
 
 
322
    def test_matched_bundle(self):
 
323
        """Don't raise IncompatibleFormat for knit2 and bundle0.9"""
 
324
        format = bzrdir.BzrDirMetaFormat1()
 
325
        format.repository_format = repository.RepositoryFormatKnit2()
 
326
        serializer = BundleSerializerV09('0.9')
 
327
        b = self.make_branch('.', format=format)
 
328
        serializer.write(b.repository, [], {}, StringIO())
 
329
 
 
330
    def test_mismatched_model(self):
 
331
        """Try copying a bundle from knit2 to knit1"""
 
332
        format = bzrdir.BzrDirMetaFormat1()
 
333
        format.repository_format = repository.RepositoryFormatKnit2()
 
334
        source = self.make_branch_and_tree('source', format=format)
 
335
        source.commit('one', rev_id='one-id')
 
336
        source.commit('two', rev_id='two-id')
 
337
        text = StringIO()
 
338
        write_bundle(source.branch.repository, 'two-id', None, text, 
 
339
                     format='0.9')
 
340
        text.seek(0)
 
341
 
 
342
        format = bzrdir.BzrDirMetaFormat1()
 
343
        format.repository_format = repository.RepositoryFormatKnit1()
 
344
        target = self.make_branch('target', format=format)
 
345
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
346
                          target.repository, read_bundle(text))
 
347
 
 
348
 
 
349
class V08BundleTester(TestCaseWithTransport):
 
350
 
 
351
    format = '0.8'
 
352
 
 
353
    def bzrdir_format(self):
 
354
        format = bzrdir.BzrDirMetaFormat1()
 
355
        format.repository_format = repository.RepositoryFormatKnit1()
 
356
        return format
 
357
 
 
358
    def make_branch_and_tree(self, path, format=None):
 
359
        if format is None:
 
360
            format = self.bzrdir_format()
 
361
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
362
 
 
363
    def make_branch(self, path, format=None):
 
364
        if format is None:
 
365
            format = self.bzrdir_format()
 
366
        return TestCaseWithTransport.make_branch(self, path, format)
 
367
 
 
368
    def create_bundle_text(self, base_rev_id, rev_id):
 
369
        bundle_txt = StringIO()
 
370
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
371
                               bundle_txt, format=self.format)
 
372
        bundle_txt.seek(0)
 
373
        self.assertEqual(bundle_txt.readline(), 
 
374
                         '# Bazaar revision bundle v%s\n' % self.format)
 
375
        self.assertEqual(bundle_txt.readline(), '#\n')
 
376
 
 
377
        rev = self.b1.repository.get_revision(rev_id)
 
378
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
 
379
                         u'# message:\n')
 
380
 
 
381
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
 
382
        bundle_txt.seek(0)
 
383
        return bundle_txt, rev_ids
 
384
 
 
385
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
 
386
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
387
        Make sure that the text generated is valid, and that it
 
388
        can be applied against the base, and generate the same information.
 
389
        
 
390
        :return: The in-memory bundle 
 
391
        """
 
392
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
393
 
 
394
        # This should also validate the generated bundle 
 
395
        bundle = read_bundle(bundle_txt)
 
396
        repository = self.b1.repository
 
397
        for bundle_rev in bundle.real_revisions:
 
398
            # These really should have already been checked when we read the
 
399
            # bundle, since it computes the sha1 hash for the revision, which
 
400
            # only will match if everything is okay, but lets be explicit about
 
401
            # it
 
402
            branch_rev = repository.get_revision(bundle_rev.revision_id)
 
403
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
 
404
                      'timestamp', 'timezone', 'message', 'committer', 
 
405
                      'parent_ids', 'properties'):
 
406
                self.assertEqual(getattr(branch_rev, a), 
 
407
                                 getattr(bundle_rev, a))
 
408
            self.assertEqual(len(branch_rev.parent_ids), 
 
409
                             len(bundle_rev.parent_ids))
 
410
        self.assertEqual(rev_ids, 
 
411
                         [r.revision_id for r in bundle.real_revisions])
 
412
        self.valid_apply_bundle(base_rev_id, bundle,
 
413
                                   checkout_dir=checkout_dir)
 
414
 
 
415
        return bundle
 
416
 
 
417
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
418
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
419
        Munge the text so that it's invalid.
 
420
        
 
421
        :return: The in-memory bundle
 
422
        """
 
423
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
424
        new_text = bundle_txt.getvalue().replace('executable:no', 
 
425
                                               'executable:yes')
 
426
        bundle_txt = StringIO(new_text)
 
427
        bundle = read_bundle(bundle_txt)
 
428
        self.valid_apply_bundle(base_rev_id, bundle)
 
429
        return bundle 
 
430
 
 
431
    def test_non_bundle(self):
 
432
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
 
433
 
 
434
    def test_malformed(self):
 
435
        self.assertRaises(BadBundle, read_bundle, 
 
436
                          StringIO('# Bazaar revision bundle v'))
 
437
 
 
438
    def test_crlf_bundle(self):
 
439
        try:
 
440
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
 
441
        except BadBundle:
 
442
            # It is currently permitted for bundles with crlf line endings to
 
443
            # make read_bundle raise a BadBundle, but this should be fixed.
 
444
            # Anything else, especially NotABundle, is an error.
 
445
            pass
 
446
 
 
447
    def get_checkout(self, rev_id, checkout_dir=None):
 
448
        """Get a new tree, with the specified revision in it.
 
449
        """
 
450
 
 
451
        if checkout_dir is None:
 
452
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
 
453
        else:
 
454
            if not os.path.exists(checkout_dir):
 
455
                os.mkdir(checkout_dir)
 
456
        tree = self.make_branch_and_tree(checkout_dir)
 
457
        s = StringIO()
 
458
        ancestors = write_bundle(self.b1.repository, rev_id, None, s,
 
459
                                 format=self.format)
 
460
        s.seek(0)
 
461
        assert isinstance(s.getvalue(), str), (
 
462
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
 
463
        install_bundle(tree.branch.repository, read_bundle(s))
 
464
        for ancestor in ancestors:
 
465
            old = self.b1.repository.revision_tree(ancestor)
 
466
            new = tree.branch.repository.revision_tree(ancestor)
 
467
 
 
468
            # Check that there aren't any inventory level changes
 
469
            delta = new.changes_from(old)
 
470
            self.assertFalse(delta.has_changed(),
 
471
                             'Revision %s not copied correctly.'
 
472
                             % (ancestor,))
 
473
 
 
474
            # Now check that the file contents are all correct
 
475
            for inventory_id in old:
 
476
                try:
 
477
                    old_file = old.get_file(inventory_id)
 
478
                except NoSuchFile:
 
479
                    continue
 
480
                if old_file is None:
 
481
                    continue
 
482
                self.assertEqual(old_file.read(),
 
483
                                 new.get_file(inventory_id).read())
 
484
        if rev_id is not None:
 
485
            rh = self.b1.revision_history()
 
486
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
487
            tree.update()
 
488
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
 
489
            self.assertFalse(delta.has_changed(),
 
490
                             'Working tree has modifications')
 
491
        return tree
 
492
 
 
493
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
 
494
        """Get the base revision, apply the changes, and make
 
495
        sure everything matches the builtin branch.
 
496
        """
 
497
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
 
498
        original_parents = to_tree.get_parent_ids()
 
499
        repository = to_tree.branch.repository
 
500
        original_parents = to_tree.get_parent_ids()
 
501
        self.assertIs(repository.has_revision(base_rev_id), True)
 
502
        for rev in info.real_revisions:
 
503
            self.assert_(not repository.has_revision(rev.revision_id),
 
504
                'Revision {%s} present before applying bundle' 
 
505
                % rev.revision_id)
 
506
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
 
507
 
 
508
        for rev in info.real_revisions:
 
509
            self.assert_(repository.has_revision(rev.revision_id),
 
510
                'Missing revision {%s} after applying bundle' 
 
511
                % rev.revision_id)
 
512
 
 
513
        self.assert_(to_tree.branch.repository.has_revision(info.target))
 
514
        # Do we also want to verify that all the texts have been added?
 
515
 
 
516
        self.assertEqual(original_parents + [info.target],
 
517
            to_tree.get_parent_ids())
 
518
 
 
519
        rev = info.real_revisions[-1]
 
520
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
 
521
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
 
522
        
 
523
        # TODO: make sure the target tree is identical to base tree
 
524
        #       we might also check the working tree.
 
525
 
 
526
        base_files = list(base_tree.list_files())
 
527
        to_files = list(to_tree.list_files())
 
528
        self.assertEqual(len(base_files), len(to_files))
 
529
        for base_file, to_file in zip(base_files, to_files):
 
530
            self.assertEqual(base_file, to_file)
 
531
 
 
532
        for path, status, kind, fileid, entry in base_files:
 
533
            # Check that the meta information is the same
 
534
            self.assertEqual(base_tree.get_file_size(fileid),
 
535
                    to_tree.get_file_size(fileid))
 
536
            self.assertEqual(base_tree.get_file_sha1(fileid),
 
537
                    to_tree.get_file_sha1(fileid))
 
538
            # Check that the contents are the same
 
539
            # This is pretty expensive
 
540
            # self.assertEqual(base_tree.get_file(fileid).read(),
 
541
            #         to_tree.get_file(fileid).read())
 
542
 
 
543
    def test_bundle(self):
 
544
        self.tree1 = self.make_branch_and_tree('b1')
 
545
        self.b1 = self.tree1.branch
 
546
 
 
547
        open('b1/one', 'wb').write('one\n')
 
548
        self.tree1.add('one')
 
549
        self.tree1.commit('add one', rev_id='a@cset-0-1')
 
550
 
 
551
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
 
552
        # FIXME: The current write_bundle api no longer supports
 
553
        #        setting a custom summary message
 
554
        #        We should re-introduce the ability, and update
 
555
        #        the tests to make sure it works.
 
556
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
 
557
        #         message='With a specialized message')
 
558
 
 
559
        # Make sure we can handle files with spaces, tabs, other
 
560
        # bogus characters
 
561
        self.build_tree([
 
562
                'b1/with space.txt'
 
563
                , 'b1/dir/'
 
564
                , 'b1/dir/filein subdir.c'
 
565
                , 'b1/dir/WithCaps.txt'
 
566
                , 'b1/dir/ pre space'
 
567
                , 'b1/sub/'
 
568
                , 'b1/sub/sub/'
 
569
                , 'b1/sub/sub/nonempty.txt'
 
570
                ])
 
571
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
572
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
573
        tt = TreeTransform(self.tree1)
 
574
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
 
575
        tt.apply()
 
576
        self.tree1.add([
 
577
                'with space.txt'
 
578
                , 'dir'
 
579
                , 'dir/filein subdir.c'
 
580
                , 'dir/WithCaps.txt'
 
581
                , 'dir/ pre space'
 
582
                , 'dir/nolastnewline.txt'
 
583
                , 'sub'
 
584
                , 'sub/sub'
 
585
                , 'sub/sub/nonempty.txt'
 
586
                , 'sub/sub/emptyfile.txt'
 
587
                ])
 
588
        self.tree1.commit('add whitespace', rev_id='a@cset-0-2')
 
589
 
 
590
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
 
591
 
 
592
        # Check a rollup bundle 
 
593
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
 
594
 
 
595
        # Now delete entries
 
596
        self.tree1.remove(
 
597
                ['sub/sub/nonempty.txt'
 
598
                , 'sub/sub/emptyfile.txt'
 
599
                , 'sub/sub'
 
600
                ])
 
601
        tt = TreeTransform(self.tree1)
 
602
        trans_id = tt.trans_id_tree_file_id('exe-1')
 
603
        tt.set_executability(False, trans_id)
 
604
        tt.apply()
 
605
        self.tree1.commit('removed', rev_id='a@cset-0-3')
 
606
        
 
607
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
 
608
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
 
609
                          'a@cset-0-2', 'a@cset-0-3')
 
610
        # Check a rollup bundle 
 
611
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
 
612
 
 
613
        # Now move the directory
 
614
        self.tree1.rename_one('dir', 'sub/dir')
 
615
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
 
616
 
 
617
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
 
618
        # Check a rollup bundle 
 
619
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
 
620
 
 
621
        # Modified files
 
622
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
 
623
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
 
624
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
 
625
        self.tree1.rename_one('sub/dir/ pre space', 
 
626
                              'sub/ start space')
 
627
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
 
628
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
 
629
 
 
630
        self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
 
631
        self.tree1.rename_one('with space.txt', 'WithCaps.txt')
 
632
        self.tree1.rename_one('temp', 'with space.txt')
 
633
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
 
634
                          verbose=False)
 
635
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
 
636
        other = self.get_checkout('a@cset-0-5')
 
637
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
 
638
            'a@cset-0-5')
 
639
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
640
        self.assertEqualDiff(tree1_inv, tree2_inv)
 
641
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
 
642
        other.commit('rename file', rev_id='a@cset-0-6b')
 
643
        _merge_helper([other.basedir, -1], [None, None],
 
644
                      this_dir=self.tree1.basedir)
 
645
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
 
646
                          verbose=False)
 
647
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
 
648
 
 
649
    def test_symlink_bundle(self):
 
650
        if not has_symlinks():
 
651
            raise TestSkipped("No symlink support")
 
652
        self.tree1 = self.make_branch_and_tree('b1')
 
653
        self.b1 = self.tree1.branch
 
654
        tt = TreeTransform(self.tree1)
 
655
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
656
        tt.apply()
 
657
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
 
658
        self.get_valid_bundle(None, 'l@cset-0-1')
 
659
        tt = TreeTransform(self.tree1)
 
660
        trans_id = tt.trans_id_tree_file_id('link-1')
 
661
        tt.adjust_path('link2', tt.root, trans_id)
 
662
        tt.delete_contents(trans_id)
 
663
        tt.create_symlink('mars', trans_id)
 
664
        tt.apply()
 
665
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
 
666
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
667
        tt = TreeTransform(self.tree1)
 
668
        trans_id = tt.trans_id_tree_file_id('link-1')
 
669
        tt.delete_contents(trans_id)
 
670
        tt.create_symlink('jupiter', trans_id)
 
671
        tt.apply()
 
672
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
 
673
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
674
        tt = TreeTransform(self.tree1)
 
675
        trans_id = tt.trans_id_tree_file_id('link-1')
 
676
        tt.delete_contents(trans_id)
 
677
        tt.apply()
 
678
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
 
679
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
680
 
 
681
    def test_binary_bundle(self):
 
682
        self.tree1 = self.make_branch_and_tree('b1')
 
683
        self.b1 = self.tree1.branch
 
684
        tt = TreeTransform(self.tree1)
 
685
        
 
686
        # Add
 
687
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
 
688
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
 
689
        tt.apply()
 
690
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
 
691
        self.get_valid_bundle(None, 'b@cset-0-1')
 
692
 
 
693
        # Delete
 
694
        tt = TreeTransform(self.tree1)
 
695
        trans_id = tt.trans_id_tree_file_id('binary-1')
 
696
        tt.delete_contents(trans_id)
 
697
        tt.apply()
 
698
        self.tree1.commit('delete binary', rev_id='b@cset-0-2')
 
699
        self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
 
700
 
 
701
        # Rename & modify
 
702
        tt = TreeTransform(self.tree1)
 
703
        trans_id = tt.trans_id_tree_file_id('binary-2')
 
704
        tt.adjust_path('file3', tt.root, trans_id)
 
705
        tt.delete_contents(trans_id)
 
706
        tt.create_file('file\rcontents\x00\n\x00', trans_id)
 
707
        tt.apply()
 
708
        self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
 
709
        self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
 
710
 
 
711
        # Modify
 
712
        tt = TreeTransform(self.tree1)
 
713
        trans_id = tt.trans_id_tree_file_id('binary-2')
 
714
        tt.delete_contents(trans_id)
 
715
        tt.create_file('\x00file\rcontents', trans_id)
 
716
        tt.apply()
 
717
        self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
 
718
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
 
719
 
 
720
        # Rollup
 
721
        self.get_valid_bundle(None, 'b@cset-0-4')
 
722
 
 
723
    def test_last_modified(self):
 
724
        self.tree1 = self.make_branch_and_tree('b1')
 
725
        self.b1 = self.tree1.branch
 
726
        tt = TreeTransform(self.tree1)
 
727
        tt.new_file('file', tt.root, 'file', 'file')
 
728
        tt.apply()
 
729
        self.tree1.commit('create file', rev_id='a@lmod-0-1')
 
730
 
 
731
        tt = TreeTransform(self.tree1)
 
732
        trans_id = tt.trans_id_tree_file_id('file')
 
733
        tt.delete_contents(trans_id)
 
734
        tt.create_file('file2', trans_id)
 
735
        tt.apply()
 
736
        self.tree1.commit('modify text', rev_id='a@lmod-0-2a')
 
737
 
 
738
        other = self.get_checkout('a@lmod-0-1')
 
739
        tt = TreeTransform(other)
 
740
        trans_id = tt.trans_id_tree_file_id('file')
 
741
        tt.delete_contents(trans_id)
 
742
        tt.create_file('file2', trans_id)
 
743
        tt.apply()
 
744
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
 
745
        _merge_helper([other.basedir, -1], [None, None],
 
746
                      this_dir=self.tree1.basedir)
 
747
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
 
748
                          verbose=False)
 
749
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
 
750
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
 
751
 
 
752
    def test_hide_history(self):
 
753
        self.tree1 = self.make_branch_and_tree('b1')
 
754
        self.b1 = self.tree1.branch
 
755
 
 
756
        open('b1/one', 'wb').write('one\n')
 
757
        self.tree1.add('one')
 
758
        self.tree1.commit('add file', rev_id='a@cset-0-1')
 
759
        open('b1/one', 'wb').write('two\n')
 
760
        self.tree1.commit('modify', rev_id='a@cset-0-2')
 
761
        open('b1/one', 'wb').write('three\n')
 
762
        self.tree1.commit('modify', rev_id='a@cset-0-3')
 
763
        bundle_file = StringIO()
 
764
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
 
765
                               'a@cset-0-1', bundle_file, format=self.format)
 
766
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
 
767
        self.assertContainsRe(bundle_file.getvalue(), 'one')
 
768
        self.assertContainsRe(bundle_file.getvalue(), 'three')
 
769
 
 
770
    def test_unicode_bundle(self):
 
771
        # Handle international characters
 
772
        os.mkdir('b1')
 
773
        try:
 
774
            f = open(u'b1/with Dod\xe9', 'wb')
 
775
        except UnicodeEncodeError:
 
776
            raise TestSkipped("Filesystem doesn't support unicode")
 
777
 
 
778
        self.tree1 = self.make_branch_and_tree('b1')
 
779
        self.b1 = self.tree1.branch
 
780
 
 
781
        f.write((u'A file\n'
 
782
            u'With international man of mystery\n'
 
783
            u'William Dod\xe9\n').encode('utf-8'))
 
784
        f.close()
 
785
 
 
786
        self.tree1.add([u'with Dod\xe9'])
 
787
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
 
788
                          rev_id='i18n-1', committer=u'William Dod\xe9')
 
789
 
 
790
        # Add
 
791
        bundle = self.get_valid_bundle(None, 'i18n-1')
 
792
 
 
793
        # Modified
 
794
        f = open(u'b1/with Dod\xe9', 'wb')
 
795
        f.write(u'Modified \xb5\n'.encode('utf8'))
 
796
        f.close()
 
797
        self.tree1.commit(u'modified', rev_id='i18n-2')
 
798
 
 
799
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
 
800
        
 
801
        # Renamed
 
802
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
803
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
 
804
                          committer=u'Erik B\xe5gfors')
 
805
 
 
806
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
 
807
 
 
808
        # Removed
 
809
        self.tree1.remove([u'B\xe5gfors'])
 
810
        self.tree1.commit(u'removed', rev_id='i18n-4')
 
811
 
 
812
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
 
813
 
 
814
        # Rollup
 
815
        bundle = self.get_valid_bundle(None, 'i18n-4')
 
816
 
 
817
 
 
818
    def test_whitespace_bundle(self):
 
819
        if sys.platform in ('win32', 'cygwin'):
 
820
            raise TestSkipped('Windows doesn\'t support filenames'
 
821
                              ' with tabs or trailing spaces')
 
822
        self.tree1 = self.make_branch_and_tree('b1')
 
823
        self.b1 = self.tree1.branch
 
824
 
 
825
        self.build_tree(['b1/trailing space '])
 
826
        self.tree1.add(['trailing space '])
 
827
        # TODO: jam 20060701 Check for handling files with '\t' characters
 
828
        #       once we actually support them
 
829
 
 
830
        # Added
 
831
        self.tree1.commit('funky whitespace', rev_id='white-1')
 
832
 
 
833
        bundle = self.get_valid_bundle(None, 'white-1')
 
834
 
 
835
        # Modified
 
836
        open('b1/trailing space ', 'ab').write('add some text\n')
 
837
        self.tree1.commit('add text', rev_id='white-2')
 
838
 
 
839
        bundle = self.get_valid_bundle('white-1', 'white-2')
 
840
 
 
841
        # Renamed
 
842
        self.tree1.rename_one('trailing space ', ' start and end space ')
 
843
        self.tree1.commit('rename', rev_id='white-3')
 
844
 
 
845
        bundle = self.get_valid_bundle('white-2', 'white-3')
 
846
 
 
847
        # Removed
 
848
        self.tree1.remove([' start and end space '])
 
849
        self.tree1.commit('removed', rev_id='white-4')
 
850
 
 
851
        bundle = self.get_valid_bundle('white-3', 'white-4')
 
852
        
 
853
        # Now test a complet roll-up
 
854
        bundle = self.get_valid_bundle(None, 'white-4')
 
855
 
 
856
    def test_alt_timezone_bundle(self):
 
857
        self.tree1 = self.make_branch_and_memory_tree('b1')
 
858
        self.b1 = self.tree1.branch
 
859
        builder = treebuilder.TreeBuilder()
 
860
 
 
861
        self.tree1.lock_write()
 
862
        builder.start_tree(self.tree1)
 
863
        builder.build(['newfile'])
 
864
        builder.finish_tree()
 
865
 
 
866
        # Asia/Colombo offset = 5 hours 30 minutes
 
867
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
 
868
                          timezone=19800, timestamp=1152544886.0)
 
869
 
 
870
        bundle = self.get_valid_bundle(None, 'tz-1')
 
871
        
 
872
        rev = bundle.revisions[0]
 
873
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
 
874
        self.assertEqual(19800, rev.timezone)
 
875
        self.assertEqual(1152544886.0, rev.timestamp)
 
876
        self.tree1.unlock()
 
877
 
 
878
    def test_bundle_root_id(self):
 
879
        self.tree1 = self.make_branch_and_tree('b1')
 
880
        self.b1 = self.tree1.branch
 
881
        self.tree1.commit('message', rev_id='revid1')
 
882
        bundle = self.get_valid_bundle(None, 'revid1')
 
883
        tree = bundle.revision_tree(self.b1.repository, 'revid1')
 
884
        self.assertEqual('revid1', tree.inventory.root.revision)
 
885
 
 
886
 
 
887
class V09BundleKnit2Tester(V08BundleTester):
 
888
 
 
889
    format = '0.9'
 
890
 
 
891
    def bzrdir_format(self):
 
892
        format = bzrdir.BzrDirMetaFormat1()
 
893
        format.repository_format = repository.RepositoryFormatKnit2()
 
894
        return format
 
895
 
 
896
 
 
897
class V09BundleKnit1Tester(V08BundleTester):
 
898
 
 
899
    format = '0.9'
 
900
 
 
901
    def bzrdir_format(self):
 
902
        format = bzrdir.BzrDirMetaFormat1()
 
903
        format.repository_format = repository.RepositoryFormatKnit1()
 
904
        return format
 
905
 
 
906
 
 
907
class MungedBundleTester(TestCaseWithTransport):
 
908
 
 
909
    def build_test_bundle(self):
 
910
        wt = self.make_branch_and_tree('b1')
 
911
 
 
912
        self.build_tree(['b1/one'])
 
913
        wt.add('one')
 
914
        wt.commit('add one', rev_id='a@cset-0-1')
 
915
        self.build_tree(['b1/two'])
 
916
        wt.add('two')
 
917
        wt.commit('add two', rev_id='a@cset-0-2',
 
918
                  revprops={'branch-nick':'test'})
 
919
 
 
920
        bundle_txt = StringIO()
 
921
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
 
922
                               'a@cset-0-1', bundle_txt)
 
923
        self.assertEqual(['a@cset-0-2'], rev_ids)
 
924
        bundle_txt.seek(0, 0)
 
925
        return bundle_txt
 
926
 
 
927
    def check_valid(self, bundle):
 
928
        """Check that after whatever munging, the final object is valid."""
 
929
        self.assertEqual(['a@cset-0-2'],
 
930
            [r.revision_id for r in bundle.real_revisions])
 
931
 
 
932
    def test_extra_whitespace(self):
 
933
        bundle_txt = self.build_test_bundle()
 
934
 
 
935
        # Seek to the end of the file
 
936
        # Adding one extra newline used to give us
 
937
        # TypeError: float() argument must be a string or a number
 
938
        bundle_txt.seek(0, 2)
 
939
        bundle_txt.write('\n')
 
940
        bundle_txt.seek(0)
 
941
 
 
942
        bundle = read_bundle(bundle_txt)
 
943
        self.check_valid(bundle)
 
944
 
 
945
    def test_extra_whitespace_2(self):
 
946
        bundle_txt = self.build_test_bundle()
 
947
 
 
948
        # Seek to the end of the file
 
949
        # Adding two extra newlines used to give us
 
950
        # MalformedPatches: The first line of all patches should be ...
 
951
        bundle_txt.seek(0, 2)
 
952
        bundle_txt.write('\n\n')
 
953
        bundle_txt.seek(0)
 
954
 
 
955
        bundle = read_bundle(bundle_txt)
 
956
        self.check_valid(bundle)
 
957
 
 
958
    def test_missing_trailing_whitespace(self):
 
959
        bundle_txt = self.build_test_bundle()
 
960
 
 
961
        # Remove a trailing newline, it shouldn't kill the parser
 
962
        raw = bundle_txt.getvalue()
 
963
        # The contents of the bundle don't have to be this, but this
 
964
        # test is concerned with the exact case where the serializer
 
965
        # creates a blank line at the end, and fails if that
 
966
        # line is stripped
 
967
        self.assertEqual('\n\n', raw[-2:])
 
968
        bundle_txt = StringIO(raw[:-1])
 
969
 
 
970
        bundle = read_bundle(bundle_txt)
 
971
        self.check_valid(bundle)
 
972
 
 
973
    def test_opening_text(self):
 
974
        bundle_txt = self.build_test_bundle()
 
975
 
 
976
        bundle_txt = StringIO("Some random\nemail comments\n"
 
977
                              + bundle_txt.getvalue())
 
978
 
 
979
        bundle = read_bundle(bundle_txt)
 
980
        self.check_valid(bundle)
 
981
 
 
982
    def test_trailing_text(self):
 
983
        bundle_txt = self.build_test_bundle()
 
984
 
 
985
        bundle_txt = StringIO(bundle_txt.getvalue() +
 
986
                              "Some trailing\nrandom\ntext\n")
 
987
 
 
988
        bundle = read_bundle(bundle_txt)
 
989
        self.check_valid(bundle)
 
990