~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Vincent Ladeuil
  • Date: 2011-08-12 09:49:24 UTC
  • mfrom: (6015.9.10 2.4)
  • mto: This revision was merged to the branch mainline in revision 6066.
  • Revision ID: v.ladeuil+lp@free.fr-20110812094924-knc5s0g7vs31a2f1
Merge 2.4 into trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import os
 
19
import socket
19
20
import sys
 
21
import threading
20
22
 
21
23
from bzrlib import (
22
24
    bzrdir,
 
25
    diff,
23
26
    errors,
24
27
    inventory,
 
28
    merge,
25
29
    osutils,
26
 
    repository,
27
30
    revision as _mod_revision,
 
31
    symbol_versioning,
 
32
    tests,
28
33
    treebuilder,
29
34
    )
30
 
from bzrlib.bzrdir import BzrDir
31
35
from bzrlib.bundle import read_mergeable_from_url
32
36
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
33
37
from bzrlib.bundle.bundle_data import BundleTree
36
40
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
37
41
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
38
42
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
39
 
from bzrlib.branch import Branch
40
 
from bzrlib.diff import internal_diff
41
 
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
42
 
                           NoSuchFile,)
43
 
from bzrlib.merge import Merge3Merger
44
43
from bzrlib.repofmt import knitrepo
45
 
from bzrlib.osutils import sha_file, sha_string
46
44
from bzrlib.tests import (
47
 
    SymlinkFeature,
48
 
    TestCase,
49
 
    TestCaseInTempDir,
50
 
    TestCaseWithTransport,
51
 
    TestSkipped,
52
45
    test_read_bundle,
53
46
    test_commit,
54
47
    )
55
48
from bzrlib.transform import TreeTransform
 
49
from bzrlib.tests import (
 
50
    features,
 
51
    )
 
52
 
 
53
 
 
54
def get_text(vf, key):
 
55
    """Get the fulltext for a given revision id that is present in the vf"""
 
56
    stream = vf.get_record_stream([key], 'unordered', True)
 
57
    record = stream.next()
 
58
    return record.get_bytes_as('fulltext')
 
59
 
 
60
 
 
61
def get_inventory_text(repo, revision_id):
 
62
    """Get the fulltext for the inventory at revision id"""
 
63
    repo.lock_read()
 
64
    try:
 
65
        return get_text(repo.inventories, (revision_id,))
 
66
    finally:
 
67
        repo.unlock()
56
68
 
57
69
 
58
70
class MockTree(object):
 
71
 
59
72
    def __init__(self):
60
73
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
61
74
        object.__init__(self)
66
79
 
67
80
    inventory = property(lambda x:x)
68
81
 
69
 
    def __iter__(self):
70
 
        return self.paths.iterkeys()
 
82
    def all_file_ids(self):
 
83
        return set(self.paths.keys())
71
84
 
72
85
    def __getitem__(self, file_id):
73
86
        if file_id == self.root.file_id:
103
116
            ie = InventoryDirectory(file_id, name, parent_id)
104
117
        elif kind == 'file':
105
118
            ie = InventoryFile(file_id, name, parent_id)
 
119
            ie.text_sha1 = text_sha_1
 
120
            ie.text_size = text_size
106
121
        elif kind == 'symlink':
107
122
            ie = InventoryLink(file_id, name, parent_id)
108
123
        else:
109
 
            raise BzrError('unknown kind %r' % kind)
110
 
        ie.text_sha1 = text_sha_1
111
 
        ie.text_size = text_size
 
124
            raise errors.BzrError('unknown kind %r' % kind)
112
125
        return ie
113
126
 
114
127
    def add_dir(self, file_id, path):
115
128
        self.paths[file_id] = path
116
129
        self.ids[path] = file_id
117
 
    
 
130
 
118
131
    def add_file(self, file_id, path, contents):
119
132
        self.add_dir(file_id, path)
120
133
        self.contents[file_id] = contents
134
147
        result.seek(0,0)
135
148
        return result
136
149
 
 
150
    def get_file_revision(self, file_id):
 
151
        return self.inventory[file_id].revision
 
152
 
137
153
    def contents_stats(self, file_id):
138
154
        if file_id not in self.contents:
139
155
            return None, None
140
 
        text_sha1 = sha_file(self.get_file(file_id))
 
156
        text_sha1 = osutils.sha_file(self.get_file(file_id))
141
157
        return text_sha1, len(self.contents[file_id])
142
158
 
143
159
 
144
 
class BTreeTester(TestCase):
 
160
class BTreeTester(tests.TestCase):
145
161
    """A simple unittest tester for the BundleTree class."""
146
162
 
147
163
    def make_tree_1(self):
151
167
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
152
168
        mtree.add_dir("d", "grandparent/alt_parent")
153
169
        return BundleTree(mtree, ''), mtree
154
 
        
 
170
 
155
171
    def test_renames(self):
156
172
        """Ensure that file renames have the proper effect on children"""
157
173
        btree = self.make_tree_1()[0]
158
174
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
159
 
        self.assertEqual(btree.old_path("grandparent/parent"), 
 
175
        self.assertEqual(btree.old_path("grandparent/parent"),
160
176
                         "grandparent/parent")
161
177
        self.assertEqual(btree.old_path("grandparent/parent/file"),
162
178
                         "grandparent/parent/file")
202
218
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
203
219
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
204
220
 
205
 
        btree.note_rename("grandparent/parent/file", 
 
221
        btree.note_rename("grandparent/parent/file",
206
222
                          "grandparent2/parent2/file2")
207
223
        self.assertEqual(btree.id2path("a"), "grandparent2")
208
224
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
217
233
    def test_moves(self):
218
234
        """Ensure that file moves have the proper effect on children"""
219
235
        btree = self.make_tree_1()[0]
220
 
        btree.note_rename("grandparent/parent/file", 
 
236
        btree.note_rename("grandparent/parent/file",
221
237
                          "grandparent/alt_parent/file")
222
238
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
223
239
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
225
241
 
226
242
    def unified_diff(self, old, new):
227
243
        out = StringIO()
228
 
        internal_diff("old", old, "new", new, out)
 
244
        diff.internal_diff("old", old, "new", new, out)
229
245
        out.seek(0,0)
230
246
        return out.read()
231
247
 
232
248
    def make_tree_2(self):
233
249
        btree = self.make_tree_1()[0]
234
 
        btree.note_rename("grandparent/parent/file", 
 
250
        btree.note_rename("grandparent/parent/file",
235
251
                          "grandparent/alt_parent/file")
236
252
        self.assertTrue(btree.id2path("e") is None)
237
253
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
266
282
    def make_tree_3(self):
267
283
        btree, mtree = self.make_tree_1()
268
284
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
269
 
        btree.note_rename("grandparent/parent/file", 
 
285
        btree.note_rename("grandparent/parent/file",
270
286
                          "grandparent/alt_parent/file")
271
 
        btree.note_rename("grandparent/parent/topping", 
 
287
        btree.note_rename("grandparent/parent/topping",
272
288
                          "grandparent/alt_parent/stopping")
273
289
        return btree
274
290
 
313
329
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
314
330
        btree.note_deletion("grandparent/parent/file")
315
331
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
316
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
 
332
        btree.note_last_changed("grandparent/alt_parent/fool",
317
333
                                "revisionidiguess")
318
334
        self.assertEqual(self.sorted_ids(btree),
319
335
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
320
336
 
321
337
 
322
 
class BundleTester1(TestCaseWithTransport):
 
338
class BundleTester1(tests.TestCaseWithTransport):
323
339
 
324
340
    def test_mismatched_bundle(self):
325
341
        format = bzrdir.BzrDirMetaFormat1()
326
342
        format.repository_format = knitrepo.RepositoryFormatKnit3()
327
343
        serializer = BundleSerializerV08('0.8')
328
344
        b = self.make_branch('.', format=format)
329
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
345
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
330
346
                          b.repository, [], {}, StringIO())
331
347
 
332
348
    def test_matched_bundle(self):
352
368
        format = bzrdir.BzrDirMetaFormat1()
353
369
        format.repository_format = knitrepo.RepositoryFormatKnit1()
354
370
        target = self.make_branch('target', format=format)
355
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
371
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
356
372
                          target.repository, read_bundle(text))
357
373
 
358
374
 
366
382
    def make_branch_and_tree(self, path, format=None):
367
383
        if format is None:
368
384
            format = self.bzrdir_format()
369
 
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
385
        return tests.TestCaseWithTransport.make_branch_and_tree(
 
386
            self, path, format)
370
387
 
371
388
    def make_branch(self, path, format=None):
372
389
        if format is None:
373
390
            format = self.bzrdir_format()
374
 
        return TestCaseWithTransport.make_branch(self, path, format)
 
391
        return tests.TestCaseWithTransport.make_branch(self, path, format)
375
392
 
376
393
    def create_bundle_text(self, base_rev_id, rev_id):
377
394
        bundle_txt = StringIO()
378
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
395
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
379
396
                               bundle_txt, format=self.format)
380
397
        bundle_txt.seek(0)
381
 
        self.assertEqual(bundle_txt.readline(), 
 
398
        self.assertEqual(bundle_txt.readline(),
382
399
                         '# Bazaar revision bundle v%s\n' % self.format)
383
400
        self.assertEqual(bundle_txt.readline(), '#\n')
384
401
 
392
409
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
393
410
        Make sure that the text generated is valid, and that it
394
411
        can be applied against the base, and generate the same information.
395
 
        
396
 
        :return: The in-memory bundle 
 
412
 
 
413
        :return: The in-memory bundle
397
414
        """
398
415
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
399
416
 
400
 
        # This should also validate the generated bundle 
 
417
        # This should also validate the generated bundle
401
418
        bundle = read_bundle(bundle_txt)
402
419
        repository = self.b1.repository
403
420
        for bundle_rev in bundle.real_revisions:
407
424
            # it
408
425
            branch_rev = repository.get_revision(bundle_rev.revision_id)
409
426
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
410
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
427
                      'timestamp', 'timezone', 'message', 'committer',
411
428
                      'parent_ids', 'properties'):
412
 
                self.assertEqual(getattr(branch_rev, a), 
 
429
                self.assertEqual(getattr(branch_rev, a),
413
430
                                 getattr(bundle_rev, a))
414
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
431
            self.assertEqual(len(branch_rev.parent_ids),
415
432
                             len(bundle_rev.parent_ids))
416
 
        self.assertEqual(rev_ids, 
 
433
        self.assertEqual(rev_ids,
417
434
                         [r.revision_id for r in bundle.real_revisions])
418
435
        self.valid_apply_bundle(base_rev_id, bundle,
419
436
                                   checkout_dir=checkout_dir)
423
440
    def get_invalid_bundle(self, base_rev_id, rev_id):
424
441
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
425
442
        Munge the text so that it's invalid.
426
 
        
 
443
 
427
444
        :return: The in-memory bundle
428
445
        """
429
446
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
430
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
 
447
        new_text = bundle_txt.getvalue().replace('executable:no',
431
448
                                               'executable:yes')
432
449
        bundle_txt = StringIO(new_text)
433
450
        bundle = read_bundle(bundle_txt)
434
451
        self.valid_apply_bundle(base_rev_id, bundle)
435
 
        return bundle 
 
452
        return bundle
436
453
 
437
454
    def test_non_bundle(self):
438
 
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
 
455
        self.assertRaises(errors.NotABundle,
 
456
                          read_bundle, StringIO('#!/bin/sh\n'))
439
457
 
440
458
    def test_malformed(self):
441
 
        self.assertRaises(BadBundle, read_bundle, 
 
459
        self.assertRaises(errors.BadBundle, read_bundle,
442
460
                          StringIO('# Bazaar revision bundle v'))
443
461
 
444
462
    def test_crlf_bundle(self):
445
463
        try:
446
464
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
447
 
        except BadBundle:
 
465
        except errors.BadBundle:
448
466
            # It is currently permitted for bundles with crlf line endings to
449
467
            # make read_bundle raise a BadBundle, but this should be fixed.
450
468
            # Anything else, especially NotABundle, is an error.
479
497
                                 % (ancestor,))
480
498
 
481
499
                # Now check that the file contents are all correct
482
 
                for inventory_id in old:
 
500
                for inventory_id in old.all_file_ids():
483
501
                    try:
484
502
                        old_file = old.get_file(inventory_id)
485
 
                    except NoSuchFile:
 
503
                    except errors.NoSuchFile:
486
504
                        continue
487
505
                    if old_file is None:
488
506
                        continue
493
511
                old.unlock()
494
512
        if not _mod_revision.is_null(rev_id):
495
513
            rh = self.b1.revision_history()
496
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
514
            self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
 
515
                tree.branch.set_revision_history, rh[:rh.index(rev_id)+1])
497
516
            tree.update()
498
517
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
499
518
            self.assertFalse(delta.has_changed(),
518
537
        self.assertIs(repository.has_revision(base_rev_id), True)
519
538
        for rev in info.real_revisions:
520
539
            self.assert_(not repository.has_revision(rev.revision_id),
521
 
                'Revision {%s} present before applying bundle' 
 
540
                'Revision {%s} present before applying bundle'
522
541
                % rev.revision_id)
523
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
 
542
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
524
543
 
525
544
        for rev in info.real_revisions:
526
545
            self.assert_(repository.has_revision(rev.revision_id),
527
 
                'Missing revision {%s} after applying bundle' 
 
546
                'Missing revision {%s} after applying bundle'
528
547
                % rev.revision_id)
529
548
 
530
549
        self.assert_(to_tree.branch.repository.has_revision(info.target))
536
555
        rev = info.real_revisions[-1]
537
556
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
538
557
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
539
 
        
 
558
 
540
559
        # TODO: make sure the target tree is identical to base tree
541
560
        #       we might also check the working tree.
542
561
 
561
580
        self.tree1 = self.make_branch_and_tree('b1')
562
581
        self.b1 = self.tree1.branch
563
582
 
564
 
        open('b1/one', 'wb').write('one\n')
565
 
        self.tree1.add('one')
 
583
        self.build_tree_contents([('b1/one', 'one\n')])
 
584
        self.tree1.add('one', 'one-id')
 
585
        self.tree1.set_root_id('root-id')
566
586
        self.tree1.commit('add one', rev_id='a@cset-0-1')
567
587
 
568
588
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
579
599
                , 'b1/sub/sub/'
580
600
                , 'b1/sub/sub/nonempty.txt'
581
601
                ])
582
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
583
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
602
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
 
603
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
584
604
        tt = TreeTransform(self.tree1)
585
605
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
586
606
        tt.apply()
602
622
 
603
623
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
604
624
 
605
 
        # Check a rollup bundle 
 
625
        # Check a rollup bundle
606
626
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
607
627
 
608
628
        # Now delete entries
616
636
        tt.set_executability(False, trans_id)
617
637
        tt.apply()
618
638
        self.tree1.commit('removed', rev_id='a@cset-0-3')
619
 
        
 
639
 
620
640
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
621
 
        self.assertRaises((TestamentMismatch,
622
 
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
641
        self.assertRaises((errors.TestamentMismatch,
 
642
            errors.VersionedFileInvalidChecksum,
 
643
            errors.BadBundle), self.get_invalid_bundle,
623
644
            'a@cset-0-2', 'a@cset-0-3')
624
 
        # Check a rollup bundle 
 
645
        # Check a rollup bundle
625
646
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
626
647
 
627
648
        # Now move the directory
629
650
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
630
651
 
631
652
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
632
 
        # Check a rollup bundle 
 
653
        # Check a rollup bundle
633
654
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
634
655
 
635
656
        # Modified files
637
658
        open('b1/sub/dir/ pre space', 'ab').write(
638
659
             '\r\nAdding some\r\nDOS format lines\r\n')
639
660
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
640
 
        self.tree1.rename_one('sub/dir/ pre space', 
 
661
        self.tree1.rename_one('sub/dir/ pre space',
641
662
                              'sub/ start space')
642
663
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
643
664
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
649
670
                          verbose=False)
650
671
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
651
672
        other = self.get_checkout('a@cset-0-5')
652
 
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
653
 
            'a@cset-0-5')
654
 
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
673
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
 
674
                                       'a@cset-0-5')
 
675
        tree2_inv = get_inventory_text(other.branch.repository,
 
676
                                       'a@cset-0-5')
655
677
        self.assertEqualDiff(tree1_inv, tree2_inv)
656
678
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
657
679
        other.commit('rename file', rev_id='a@cset-0-6b')
660
682
                          verbose=False)
661
683
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
662
684
 
663
 
    def test_symlink_bundle(self):
664
 
        self.requireFeature(SymlinkFeature)
 
685
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
 
686
        link_id = 'link-1'
 
687
 
 
688
        self.requireFeature(features.SymlinkFeature)
665
689
        self.tree1 = self.make_branch_and_tree('b1')
666
690
        self.b1 = self.tree1.branch
 
691
 
667
692
        tt = TreeTransform(self.tree1)
668
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
693
        tt.new_symlink(link_name, tt.root, link_target, link_id)
669
694
        tt.apply()
670
695
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
671
 
        self.get_valid_bundle('null:', 'l@cset-0-1')
 
696
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
 
697
        if getattr(bundle ,'revision_tree', None) is not None:
 
698
            # Not all bundle formats supports revision_tree
 
699
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
 
700
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
 
701
 
672
702
        tt = TreeTransform(self.tree1)
673
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
703
        trans_id = tt.trans_id_tree_file_id(link_id)
674
704
        tt.adjust_path('link2', tt.root, trans_id)
675
705
        tt.delete_contents(trans_id)
676
 
        tt.create_symlink('mars', trans_id)
 
706
        tt.create_symlink(new_link_target, trans_id)
677
707
        tt.apply()
678
708
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
679
 
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
709
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
710
        if getattr(bundle ,'revision_tree', None) is not None:
 
711
            # Not all bundle formats supports revision_tree
 
712
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
 
713
            self.assertEqual(new_link_target,
 
714
                             bund_tree.get_symlink_target(link_id))
 
715
 
680
716
        tt = TreeTransform(self.tree1)
681
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
717
        trans_id = tt.trans_id_tree_file_id(link_id)
682
718
        tt.delete_contents(trans_id)
683
719
        tt.create_symlink('jupiter', trans_id)
684
720
        tt.apply()
685
721
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
686
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
722
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
723
 
687
724
        tt = TreeTransform(self.tree1)
688
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
725
        trans_id = tt.trans_id_tree_file_id(link_id)
689
726
        tt.delete_contents(trans_id)
690
727
        tt.apply()
691
728
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
692
 
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
729
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
730
 
 
731
    def test_symlink_bundle(self):
 
732
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
 
733
 
 
734
    def test_unicode_symlink_bundle(self):
 
735
        self.requireFeature(features.UnicodeFilenameFeature)
 
736
        self._test_symlink_bundle(u'\N{Euro Sign}link',
 
737
                                  u'bar/\N{Euro Sign}foo',
 
738
                                  u'mars\N{Euro Sign}')
693
739
 
694
740
    def test_binary_bundle(self):
695
741
        self.tree1 = self.make_branch_and_tree('b1')
696
742
        self.b1 = self.tree1.branch
697
743
        tt = TreeTransform(self.tree1)
698
 
        
 
744
 
699
745
        # Add
700
746
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
701
747
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
793
839
        return bundle_file.getvalue()
794
840
 
795
841
    def test_unicode_bundle(self):
 
842
        self.requireFeature(features.UnicodeFilenameFeature)
796
843
        # Handle international characters
797
844
        os.mkdir('b1')
798
 
        try:
799
 
            f = open(u'b1/with Dod\xe9', 'wb')
800
 
        except UnicodeEncodeError:
801
 
            raise TestSkipped("Filesystem doesn't support unicode")
 
845
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
802
846
 
803
847
        self.tree1 = self.make_branch_and_tree('b1')
804
848
        self.b1 = self.tree1.branch
808
852
            u'William Dod\xe9\n').encode('utf-8'))
809
853
        f.close()
810
854
 
811
 
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
855
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
812
856
        self.tree1.commit(u'i18n commit from William Dod\xe9',
813
857
                          rev_id='i18n-1', committer=u'William Dod\xe9')
814
858
 
815
 
        if sys.platform == 'darwin':
816
 
            from bzrlib.workingtree import WorkingTree3
817
 
            if type(self.tree1) is WorkingTree3:
818
 
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
819
 
 
820
 
            # On Mac the '\xe9' gets changed to 'e\u0301'
821
 
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
822
 
                             sorted(os.listdir(u'b1')))
823
 
            delta = self.tree1.changes_from(self.tree1.basis_tree())
824
 
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
825
 
                             delta.removed)
826
 
            self.knownFailure("Mac OSX doesn't preserve unicode"
827
 
                              " combining characters.")
828
 
 
829
859
        # Add
830
860
        bundle = self.get_valid_bundle('null:', 'i18n-1')
831
861
 
832
862
        # Modified
833
 
        f = open(u'b1/with Dod\xe9', 'wb')
 
863
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
834
864
        f.write(u'Modified \xb5\n'.encode('utf8'))
835
865
        f.close()
836
866
        self.tree1.commit(u'modified', rev_id='i18n-2')
837
867
 
838
868
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
839
 
        
 
869
 
840
870
        # Renamed
841
 
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
871
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
842
872
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
843
873
                          committer=u'Erik B\xe5gfors')
844
874
 
845
875
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
846
876
 
847
877
        # Removed
848
 
        self.tree1.remove([u'B\xe5gfors'])
 
878
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
849
879
        self.tree1.commit(u'removed', rev_id='i18n-4')
850
880
 
851
881
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
856
886
 
857
887
    def test_whitespace_bundle(self):
858
888
        if sys.platform in ('win32', 'cygwin'):
859
 
            raise TestSkipped('Windows doesn\'t support filenames'
860
 
                              ' with tabs or trailing spaces')
 
889
            raise tests.TestSkipped('Windows doesn\'t support filenames'
 
890
                                    ' with tabs or trailing spaces')
861
891
        self.tree1 = self.make_branch_and_tree('b1')
862
892
        self.b1 = self.tree1.branch
863
893
 
888
918
        self.tree1.commit('removed', rev_id='white-4')
889
919
 
890
920
        bundle = self.get_valid_bundle('white-3', 'white-4')
891
 
        
 
921
 
892
922
        # Now test a complet roll-up
893
923
        bundle = self.get_valid_bundle('null:', 'white-4')
894
924
 
907
937
                          timezone=19800, timestamp=1152544886.0)
908
938
 
909
939
        bundle = self.get_valid_bundle('null:', 'tz-1')
910
 
        
 
940
 
911
941
        rev = bundle.revisions[0]
912
942
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
913
943
        self.assertEqual(19800, rev.timezone)
1015
1045
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1016
1046
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1017
1047
        bundle.install_revisions(repo)
1018
 
        inv_text = repo.get_inventory_xml('rev2')
 
1048
        inv_text = repo._get_inventory_xml('rev2')
1019
1049
        self.assertNotContainsRe(inv_text, 'format="5"')
1020
1050
        self.assertContainsRe(inv_text, 'format="7"')
1021
1051
 
1041
1071
 
1042
1072
    def test_inv_hash_across_serializers(self):
1043
1073
        repo = self.make_repo_with_installed_revisions()
1044
 
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1045
 
        xml = repo.get_inventory_xml('rev2')
1046
 
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
 
1074
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
 
1075
        xml = repo._get_inventory_xml('rev2')
 
1076
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1047
1077
 
1048
1078
    def test_across_models_incompatible(self):
1049
1079
        tree = self.make_simple_tree('dirstate-with-subtree')
1052
1082
        try:
1053
1083
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1054
1084
        except errors.IncompatibleBundleFormat:
1055
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1085
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1056
1086
        repo = self.make_repository('repo', format='knit')
1057
1087
        bundle.install_revisions(repo)
1058
1088
 
1079
1109
        try:
1080
1110
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1081
1111
        except errors.IncompatibleBundleFormat:
1082
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1112
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1083
1113
        if isinstance(bundle, v09.BundleInfo09):
1084
 
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1114
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1085
1115
        repo = self.make_repository('repo', format='knit')
1086
1116
        self.assertRaises(errors.IncompatibleRevision,
1087
1117
                          bundle.install_revisions, repo)
1094
1124
        try:
1095
1125
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1096
1126
        except ValueError:
1097
 
            raise TestSkipped("Repository doesn't support revision ids with"
1098
 
                              " slashes")
 
1127
            raise tests.TestSkipped(
 
1128
                "Repository doesn't support revision ids with slashes")
1099
1129
        bundle = self.get_valid_bundle('null:', 'rev/id')
1100
1130
 
1101
1131
    def test_skip_file(self):
1117
1147
        self.tree1.commit('rev3', rev_id='rev3')
1118
1148
        bundle = self.get_valid_bundle('reva', 'rev3')
1119
1149
        if getattr(bundle, 'get_bundle_reader', None) is None:
1120
 
            raise TestSkipped('Bundle format cannot provide reader')
 
1150
            raise tests.TestSkipped('Bundle format cannot provide reader')
1121
1151
        # be sure that file1 comes before file2
1122
1152
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1123
1153
            if f == 'file3-id':
1126
1156
        bundle.install_revisions(target.branch.repository)
1127
1157
 
1128
1158
 
1129
 
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1159
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1130
1160
 
1131
1161
    format = '0.8'
1132
1162
 
1265
1295
        return format
1266
1296
 
1267
1297
 
1268
 
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1298
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
1269
1299
 
1270
1300
    format = '4'
1271
1301
 
1273
1303
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1274
1304
        Make sure that the text generated is valid, and that it
1275
1305
        can be applied against the base, and generate the same information.
1276
 
        
1277
 
        :return: The in-memory bundle 
 
1306
 
 
1307
        :return: The in-memory bundle
1278
1308
        """
1279
1309
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1280
1310
 
1281
 
        # This should also validate the generated bundle 
 
1311
        # This should also validate the generated bundle
1282
1312
        bundle = read_bundle(bundle_txt)
1283
1313
        repository = self.b1.repository
1284
1314
        for bundle_rev in bundle.real_revisions:
1288
1318
            # it
1289
1319
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1290
1320
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1291
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
1321
                      'timestamp', 'timezone', 'message', 'committer',
1292
1322
                      'parent_ids', 'properties'):
1293
 
                self.assertEqual(getattr(branch_rev, a), 
 
1323
                self.assertEqual(getattr(branch_rev, a),
1294
1324
                                 getattr(bundle_rev, a))
1295
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
1325
            self.assertEqual(len(branch_rev.parent_ids),
1296
1326
                             len(bundle_rev.parent_ids))
1297
1327
        self.assertEqual(set(rev_ids),
1298
1328
                         set([r.revision_id for r in bundle.real_revisions]))
1312
1342
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1313
1343
        new_text = new_text.replace('<file file_id="exe-1"',
1314
1344
                                    '<file executable="y" file_id="exe-1"')
1315
 
        new_text = new_text.replace('B222', 'B237')
 
1345
        new_text = new_text.replace('B260', 'B275')
1316
1346
        bundle_txt = StringIO()
1317
1347
        bundle_txt.write(serializer._get_bundle_header('4'))
1318
1348
        bundle_txt.write('\n')
1324
1354
 
1325
1355
    def create_bundle_text(self, base_rev_id, rev_id):
1326
1356
        bundle_txt = StringIO()
1327
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1357
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1328
1358
                               bundle_txt, format=self.format)
1329
1359
        bundle_txt.seek(0)
1330
 
        self.assertEqual(bundle_txt.readline(), 
 
1360
        self.assertEqual(bundle_txt.readline(),
1331
1361
                         '# Bazaar revision bundle v%s\n' % self.format)
1332
1362
        self.assertEqual(bundle_txt.readline(), '#\n')
1333
1363
        rev = self.b1.repository.get_revision(rev_id)
1355
1385
        install_bundle(target_repo, serializer.read(s))
1356
1386
        target_repo.lock_read()
1357
1387
        self.addCleanup(target_repo.unlock)
 
1388
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
 
1389
        repo_texts = dict((i, ''.join(content)) for i, content
 
1390
                          in target_repo.iter_files_bytes(
 
1391
                                [('fileid-2', 'rev1', '1'),
 
1392
                                 ('fileid-2', 'rev2', '2')]))
1358
1393
        self.assertEqual({'1':'contents1\nstatic\n',
1359
 
            '2':'contents2\nstatic\n'},
1360
 
            dict(target_repo.iter_files_bytes(
1361
 
                [('fileid-2', 'rev1', '1'), ('fileid-2', 'rev2', '2')])))
 
1394
                          '2':'contents2\nstatic\n'},
 
1395
                         repo_texts)
1362
1396
        rtree = target_repo.revision_tree('rev2')
1363
1397
        inventory_vf = target_repo.inventories
1364
1398
        # If the inventory store has a graph, it must match the revision graph.
1384
1418
        branch = tree_a.branch
1385
1419
        repo_a = branch.repository
1386
1420
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1387
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1421
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1388
1422
        try:
1389
1423
            from bzrlib.testament import Testament
1390
1424
            # monkey patch gpg signing mechanism
1414
1448
        install_bundle(repo_b, serializer.read(s))
1415
1449
 
1416
1450
 
1417
 
class V4WeaveBundleTester(V4BundleTester):
 
1451
class V4_2aBundleTester(V4BundleTester):
1418
1452
 
1419
1453
    def bzrdir_format(self):
1420
 
        return 'metaweave'
 
1454
        return '2a'
 
1455
 
 
1456
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1457
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1458
        Munge the text so that it's invalid.
 
1459
 
 
1460
        :return: The in-memory bundle
 
1461
        """
 
1462
        from bzrlib.bundle import serializer
 
1463
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1464
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1465
        # We are going to be replacing some text to set the executable bit on a
 
1466
        # file. Make sure the text replacement actually works correctly.
 
1467
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
 
1468
        new_text = new_text.replace('<file file_id="exe-1"',
 
1469
                                    '<file executable="y" file_id="exe-1"')
 
1470
        new_text = new_text.replace('B244', 'B259')
 
1471
        bundle_txt = StringIO()
 
1472
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1473
        bundle_txt.write('\n')
 
1474
        bundle_txt.write(new_text.encode('bz2'))
 
1475
        bundle_txt.seek(0)
 
1476
        bundle = read_bundle(bundle_txt)
 
1477
        self.valid_apply_bundle(base_rev_id, bundle)
 
1478
        return bundle
 
1479
 
 
1480
    def make_merged_branch(self):
 
1481
        builder = self.make_branch_builder('source')
 
1482
        builder.start_series()
 
1483
        builder.build_snapshot('a@cset-0-1', None, [
 
1484
            ('add', ('', 'root-id', 'directory', None)),
 
1485
            ('add', ('file', 'file-id', 'file', 'original content\n')),
 
1486
            ])
 
1487
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
 
1488
            ('modify', ('file-id', 'new-content\n')),
 
1489
            ])
 
1490
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
 
1491
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1492
            ])
 
1493
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
 
1494
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1495
            ])
 
1496
        builder.finish_series()
 
1497
        self.b1 = builder.get_branch()
 
1498
        self.b1.lock_read()
 
1499
        self.addCleanup(self.b1.unlock)
 
1500
 
 
1501
    def make_bundle_just_inventories(self, base_revision_id,
 
1502
                                     target_revision_id,
 
1503
                                     revision_ids):
 
1504
        sio = StringIO()
 
1505
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
 
1506
                                         self.b1.repository, sio)
 
1507
        writer.bundle.begin()
 
1508
        writer._add_inventory_mpdiffs_from_serializer(revision_ids)
 
1509
        writer.bundle.end()
 
1510
        sio.seek(0)
 
1511
        return sio
 
1512
 
 
1513
    def test_single_inventory_multiple_parents_as_xml(self):
 
1514
        self.make_merged_branch()
 
1515
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1516
                                                ['a@cset-0-3'])
 
1517
        reader = v4.BundleReader(sio, stream_input=False)
 
1518
        records = list(reader.iter_records())
 
1519
        self.assertEqual(1, len(records))
 
1520
        (bytes, metadata, repo_kind, revision_id,
 
1521
         file_id) = records[0]
 
1522
        self.assertIs(None, file_id)
 
1523
        self.assertEqual('a@cset-0-3', revision_id)
 
1524
        self.assertEqual('inventory', repo_kind)
 
1525
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1526
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1527
                          'storage_kind': 'mpdiff',
 
1528
                         }, metadata)
 
1529
        # We should have an mpdiff that takes some lines from both parents.
 
1530
        self.assertEqualDiff(
 
1531
            'i 1\n'
 
1532
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1533
            '\n'
 
1534
            'c 0 1 1 2\n'
 
1535
            'c 1 3 3 2\n', bytes)
 
1536
 
 
1537
    def test_single_inv_no_parents_as_xml(self):
 
1538
        self.make_merged_branch()
 
1539
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
 
1540
                                                ['a@cset-0-1'])
 
1541
        reader = v4.BundleReader(sio, stream_input=False)
 
1542
        records = list(reader.iter_records())
 
1543
        self.assertEqual(1, len(records))
 
1544
        (bytes, metadata, repo_kind, revision_id,
 
1545
         file_id) = records[0]
 
1546
        self.assertIs(None, file_id)
 
1547
        self.assertEqual('a@cset-0-1', revision_id)
 
1548
        self.assertEqual('inventory', repo_kind)
 
1549
        self.assertEqual({'parents': [],
 
1550
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
 
1551
                          'storage_kind': 'mpdiff',
 
1552
                         }, metadata)
 
1553
        # We should have an mpdiff that takes some lines from both parents.
 
1554
        self.assertEqualDiff(
 
1555
            'i 4\n'
 
1556
            '<inventory format="10" revision_id="a@cset-0-1">\n'
 
1557
            '<directory file_id="root-id" name=""'
 
1558
                ' revision="a@cset-0-1" />\n'
 
1559
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1560
                ' revision="a@cset-0-1"'
 
1561
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
 
1562
                ' text_size="17" />\n'
 
1563
            '</inventory>\n'
 
1564
            '\n', bytes)
 
1565
 
 
1566
    def test_multiple_inventories_as_xml(self):
 
1567
        self.make_merged_branch()
 
1568
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1569
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
 
1570
        reader = v4.BundleReader(sio, stream_input=False)
 
1571
        records = list(reader.iter_records())
 
1572
        self.assertEqual(3, len(records))
 
1573
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
 
1574
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
 
1575
                         revision_ids)
 
1576
        metadata_2a = records[0][1]
 
1577
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1578
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
 
1579
                          'storage_kind': 'mpdiff',
 
1580
                         }, metadata_2a)
 
1581
        metadata_2b = records[1][1]
 
1582
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1583
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
 
1584
                          'storage_kind': 'mpdiff',
 
1585
                         }, metadata_2b)
 
1586
        metadata_3 = records[2][1]
 
1587
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1588
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1589
                          'storage_kind': 'mpdiff',
 
1590
                         }, metadata_3)
 
1591
        bytes_2a = records[0][0]
 
1592
        self.assertEqualDiff(
 
1593
            'i 1\n'
 
1594
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
 
1595
            '\n'
 
1596
            'c 0 1 1 1\n'
 
1597
            'i 1\n'
 
1598
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1599
                ' revision="a@cset-0-2a"'
 
1600
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
 
1601
                ' text_size="12" />\n'
 
1602
            '\n'
 
1603
            'c 0 3 3 1\n', bytes_2a)
 
1604
        bytes_2b = records[1][0]
 
1605
        self.assertEqualDiff(
 
1606
            'i 1\n'
 
1607
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
 
1608
            '\n'
 
1609
            'c 0 1 1 2\n'
 
1610
            'i 1\n'
 
1611
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
 
1612
                ' revision="a@cset-0-2b"'
 
1613
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
 
1614
                ' text_size="14" />\n'
 
1615
            '\n'
 
1616
            'c 0 3 4 1\n', bytes_2b)
 
1617
        bytes_3 = records[2][0]
 
1618
        self.assertEqualDiff(
 
1619
            'i 1\n'
 
1620
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1621
            '\n'
 
1622
            'c 0 1 1 2\n'
 
1623
            'c 1 3 3 2\n', bytes_3)
 
1624
 
 
1625
    def test_creating_bundle_preserves_chk_pages(self):
 
1626
        self.make_merged_branch()
 
1627
        target = self.b1.bzrdir.sprout('target',
 
1628
                                       revision_id='a@cset-0-2a').open_branch()
 
1629
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
 
1630
                                                      'a@cset-0-3')
 
1631
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
 
1632
        bundle = read_bundle(bundle_txt)
 
1633
        target.lock_write()
 
1634
        self.addCleanup(target.unlock)
 
1635
        install_bundle(target.repository, bundle)
 
1636
        inv1 = self.b1.repository.inventories.get_record_stream([
 
1637
            ('a@cset-0-3',)], 'unordered',
 
1638
            True).next().get_bytes_as('fulltext')
 
1639
        inv2 = target.repository.inventories.get_record_stream([
 
1640
            ('a@cset-0-3',)], 'unordered',
 
1641
            True).next().get_bytes_as('fulltext')
 
1642
        self.assertEqualDiff(inv1, inv2)
1421
1643
 
1422
1644
 
1423
1645
class MungedBundleTester(object):
1472
1694
        self.check_valid(bundle)
1473
1695
 
1474
1696
 
1475
 
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1697
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1476
1698
 
1477
1699
    format = '0.9'
1478
1700
 
1510
1732
        self.check_valid(bundle)
1511
1733
 
1512
1734
 
1513
 
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1735
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
1514
1736
 
1515
1737
    format = '4'
1516
1738
 
1517
1739
 
1518
 
class TestBundleWriterReader(TestCase):
 
1740
class TestBundleWriterReader(tests.TestCase):
1519
1741
 
1520
1742
    def test_roundtrip_record(self):
1521
1743
        fileobj = StringIO()
1583
1805
        record = record_iter.next()
1584
1806
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1585
1807
            'info', None, None), record)
1586
 
        self.assertRaises(BadBundle, record_iter.next)
1587
 
 
1588
 
 
1589
 
class TestReadMergeableFromUrl(TestCaseWithTransport):
 
1808
        self.assertRaises(errors.BadBundle, record_iter.next)
 
1809
 
 
1810
 
 
1811
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
1590
1812
 
1591
1813
    def test_read_mergeable_skips_local(self):
1592
1814
        """A local bundle named like the URL should not be read.
1598
1820
            def look_up(self, name, url):
1599
1821
                return 'source'
1600
1822
        directories.register('foo:', FooService, 'Testing directory service')
1601
 
        self.addCleanup(lambda: directories.remove('foo:'))
 
1823
        self.addCleanup(directories.remove, 'foo:')
1602
1824
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1603
1825
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1604
1826
                          'foo:bar')
 
1827
 
 
1828
    def test_infinite_redirects_are_not_a_bundle(self):
 
1829
        """If a URL causes TooManyRedirections then NotABundle is raised.
 
1830
        """
 
1831
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
 
1832
        server = RedirectingMemoryServer()
 
1833
        self.start_server(server)
 
1834
        url = server.get_url() + 'infinite-loop'
 
1835
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
 
1836
 
 
1837
    def test_smart_server_connection_reset(self):
 
1838
        """If a smart server connection fails during the attempt to read a
 
1839
        bundle, then the ConnectionReset error should be propagated.
 
1840
        """
 
1841
        # Instantiate a server that will provoke a ConnectionReset
 
1842
        sock_server = _DisconnectingTCPServer()
 
1843
        self.start_server(sock_server)
 
1844
        # We don't really care what the url is since the server will close the
 
1845
        # connection without interpreting it
 
1846
        url = sock_server.get_url()
 
1847
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
 
1848
 
 
1849
 
 
1850
class _DisconnectingTCPServer(object):
 
1851
    """A TCP server that immediately closes any connection made to it."""
 
1852
 
 
1853
    def start_server(self):
 
1854
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
1855
        self.sock.bind(('127.0.0.1', 0))
 
1856
        self.sock.listen(1)
 
1857
        self.port = self.sock.getsockname()[1]
 
1858
        self.thread = threading.Thread(
 
1859
            name='%s (port %d)' % (self.__class__.__name__, self.port),
 
1860
            target=self.accept_and_close)
 
1861
        self.thread.start()
 
1862
 
 
1863
    def accept_and_close(self):
 
1864
        conn, addr = self.sock.accept()
 
1865
        conn.shutdown(socket.SHUT_RDWR)
 
1866
        conn.close()
 
1867
 
 
1868
    def get_url(self):
 
1869
        return 'bzr://127.0.0.1:%d/' % (self.port,)
 
1870
 
 
1871
    def stop_server(self):
 
1872
        try:
 
1873
            # make sure the thread dies by connecting to the listening socket,
 
1874
            # just in case the test failed to do so.
 
1875
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
1876
            conn.connect(self.sock.getsockname())
 
1877
            conn.close()
 
1878
        except socket.error:
 
1879
            pass
 
1880
        self.sock.close()
 
1881
        self.thread.join()