/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: John Arbash Meinel
  • Date: 2007-03-01 21:56:19 UTC
  • mto: (2255.7.84 dirstate)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: john@arbash-meinel.com-20070301215619-wpt6kz8yem3ypu1b
Update to dirstate locking.
Move all of WT4.lock_* functions locally, so that they can
properly interact and cleanup around when we lock/unlock the
dirstate file.
Change all Lock objects to be non-blocking. So that if someone
grabs a lock on the DirState we find out immediately, rather
than blocking.
Change WT4.unlock() so that if the dirstate is dirty, it will
save the contents even if it only has a read lock.
It does this by trying to take a write lock, if it fails
we just ignore it. If it succeeds, then we can flush to disk.
This is more important now that DirState tracks file changes.
It allows 'bzr status' to update the cached stat and sha values.

Show diffs side-by-side

added added

removed removed

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