~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Vincent Ladeuil
  • Date: 2017-01-30 14:30:10 UTC
  • mfrom: (6615.3.7 merges)
  • mto: This revision was merged to the branch mainline in revision 6621.
  • Revision ID: v.ladeuil+lp@free.fr-20170130143010-p31t1ranfeqbaeki
Merge  2.7 into trunk including fix for bug #1657238

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-2013, 2016 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
16
16
 
17
17
from cStringIO import StringIO
18
18
import os
19
 
import socket
 
19
import SocketServer
20
20
import sys
21
 
import threading
22
21
 
23
22
from bzrlib import (
24
23
    bzrdir,
27
26
    inventory,
28
27
    merge,
29
28
    osutils,
30
 
    repository,
31
29
    revision as _mod_revision,
32
30
    tests,
33
31
    treebuilder,
35
33
from bzrlib.bundle import read_mergeable_from_url
36
34
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
35
from bzrlib.bundle.bundle_data import BundleTree
38
 
from bzrlib.bzrdir import BzrDir
39
36
from bzrlib.directory_service import directories
40
37
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
41
38
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
42
39
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
43
40
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
44
 
from bzrlib.branch import Branch
45
41
from bzrlib.repofmt import knitrepo
46
42
from bzrlib.tests import (
 
43
    features,
 
44
    test_commit,
47
45
    test_read_bundle,
48
 
    test_commit,
 
46
    test_server,
49
47
    )
50
48
from bzrlib.transform import TreeTransform
51
49
 
52
50
 
 
51
def get_text(vf, key):
 
52
    """Get the fulltext for a given revision id that is present in the vf"""
 
53
    stream = vf.get_record_stream([key], 'unordered', True)
 
54
    record = stream.next()
 
55
    return record.get_bytes_as('fulltext')
 
56
 
 
57
 
 
58
def get_inventory_text(repo, revision_id):
 
59
    """Get the fulltext for the inventory at revision id"""
 
60
    repo.lock_read()
 
61
    try:
 
62
        return get_text(repo.inventories, (revision_id,))
 
63
    finally:
 
64
        repo.unlock()
 
65
 
 
66
 
53
67
class MockTree(object):
 
68
 
54
69
    def __init__(self):
55
70
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
56
71
        object.__init__(self)
60
75
        self.root = InventoryDirectory(ROOT_ID, '', None)
61
76
 
62
77
    inventory = property(lambda x:x)
63
 
 
64
 
    def __iter__(self):
65
 
        return self.paths.iterkeys()
 
78
    root_inventory = property(lambda x:x)
 
79
 
 
80
    def get_root_id(self):
 
81
        return self.root.file_id
 
82
 
 
83
    def all_file_ids(self):
 
84
        return set(self.paths.keys())
 
85
 
 
86
    def is_executable(self, file_id):
 
87
        # Not all the files are executable.
 
88
        return False
66
89
 
67
90
    def __getitem__(self, file_id):
68
91
        if file_id == self.root.file_id:
80
103
        for path, file_id in self.ids.iteritems():
81
104
            yield path, self[file_id]
82
105
 
83
 
    def get_file_kind(self, file_id):
 
106
    def kind(self, file_id):
84
107
        if file_id in self.contents:
85
108
            kind = 'file'
86
109
        else:
88
111
        return kind
89
112
 
90
113
    def make_entry(self, file_id, path):
91
 
        from bzrlib.inventory import (InventoryEntry, InventoryFile
92
 
                                    , InventoryDirectory, InventoryLink)
 
114
        from bzrlib.inventory import (InventoryFile , InventoryDirectory,
 
115
            InventoryLink)
93
116
        name = os.path.basename(path)
94
 
        kind = self.get_file_kind(file_id)
 
117
        kind = self.kind(file_id)
95
118
        parent_id = self.parent_id(file_id)
96
119
        text_sha_1, text_size = self.contents_stats(file_id)
97
120
        if kind == 'directory':
98
121
            ie = InventoryDirectory(file_id, name, parent_id)
99
122
        elif kind == 'file':
100
123
            ie = InventoryFile(file_id, name, parent_id)
 
124
            ie.text_sha1 = text_sha_1
 
125
            ie.text_size = text_size
101
126
        elif kind == 'symlink':
102
127
            ie = InventoryLink(file_id, name, parent_id)
103
128
        else:
104
129
            raise errors.BzrError('unknown kind %r' % kind)
105
 
        ie.text_sha1 = text_sha_1
106
 
        ie.text_size = text_size
107
130
        return ie
108
131
 
109
132
    def add_dir(self, file_id, path):
129
152
        result.seek(0,0)
130
153
        return result
131
154
 
 
155
    def get_file_revision(self, file_id):
 
156
        return self.inventory[file_id].revision
 
157
 
 
158
    def get_file_size(self, file_id):
 
159
        return self.inventory[file_id].text_size
 
160
 
 
161
    def get_file_sha1(self, file_id):
 
162
        return self.inventory[file_id].text_sha1
 
163
 
132
164
    def contents_stats(self, file_id):
133
165
        if file_id not in self.contents:
134
166
            return None, None
297
329
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
298
330
 
299
331
    def sorted_ids(self, tree):
300
 
        ids = list(tree)
 
332
        ids = list(tree.all_file_ids())
301
333
        ids.sort()
302
334
        return ids
303
335
 
476
508
                                 % (ancestor,))
477
509
 
478
510
                # Now check that the file contents are all correct
479
 
                for inventory_id in old:
 
511
                for inventory_id in old.all_file_ids():
480
512
                    try:
481
513
                        old_file = old.get_file(inventory_id)
482
514
                    except errors.NoSuchFile:
489
521
                new.unlock()
490
522
                old.unlock()
491
523
        if not _mod_revision.is_null(rev_id):
492
 
            rh = self.b1.revision_history()
493
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
524
            tree.branch.generate_revision_history(rev_id)
494
525
            tree.update()
495
526
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
496
527
            self.assertFalse(delta.has_changed(),
514
545
        original_parents = to_tree.get_parent_ids()
515
546
        self.assertIs(repository.has_revision(base_rev_id), True)
516
547
        for rev in info.real_revisions:
517
 
            self.assert_(not repository.has_revision(rev.revision_id),
518
 
                'Revision {%s} present before applying bundle'
519
 
                % rev.revision_id)
 
548
            self.assertTrue(not repository.has_revision(rev.revision_id),
 
549
                            'Revision {%s} present before applying bundle'
 
550
                            % rev.revision_id)
520
551
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
521
552
 
522
553
        for rev in info.real_revisions:
523
 
            self.assert_(repository.has_revision(rev.revision_id),
524
 
                'Missing revision {%s} after applying bundle'
525
 
                % rev.revision_id)
 
554
            self.assertTrue(repository.has_revision(rev.revision_id),
 
555
                            'Missing revision {%s} after applying bundle'
 
556
                            % rev.revision_id)
526
557
 
527
 
        self.assert_(to_tree.branch.repository.has_revision(info.target))
 
558
        self.assertTrue(to_tree.branch.repository.has_revision(info.target))
528
559
        # Do we also want to verify that all the texts have been added?
529
560
 
530
561
        self.assertEqual(original_parents + [info.target],
531
 
            to_tree.get_parent_ids())
 
562
                         to_tree.get_parent_ids())
532
563
 
533
564
        rev = info.real_revisions[-1]
534
565
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
558
589
        self.tree1 = self.make_branch_and_tree('b1')
559
590
        self.b1 = self.tree1.branch
560
591
 
561
 
        open('b1/one', 'wb').write('one\n')
562
 
        self.tree1.add('one')
 
592
        self.build_tree_contents([('b1/one', 'one\n')])
 
593
        self.tree1.add('one', 'one-id')
 
594
        self.tree1.set_root_id('root-id')
563
595
        self.tree1.commit('add one', rev_id='a@cset-0-1')
564
596
 
565
597
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
576
608
                , 'b1/sub/sub/'
577
609
                , 'b1/sub/sub/nonempty.txt'
578
610
                ])
579
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
580
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
611
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
 
612
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
581
613
        tt = TreeTransform(self.tree1)
582
614
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
583
615
        tt.apply()
616
648
 
617
649
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
618
650
        self.assertRaises((errors.TestamentMismatch,
619
 
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
651
            errors.VersionedFileInvalidChecksum,
 
652
            errors.BadBundle), self.get_invalid_bundle,
620
653
            'a@cset-0-2', 'a@cset-0-3')
621
654
        # Check a rollup bundle
622
655
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
630
663
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
631
664
 
632
665
        # Modified files
633
 
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
634
 
        open('b1/sub/dir/ pre space', 'ab').write(
 
666
        with open('b1/sub/dir/WithCaps.txt', 'ab') as f: f.write('\nAdding some text\n')
 
667
        with open('b1/sub/dir/ pre space', 'ab') as f: f.write(
635
668
             '\r\nAdding some\r\nDOS format lines\r\n')
636
 
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
 
669
        with open('b1/sub/dir/nolastnewline.txt', 'ab') as f: f.write('\n')
637
670
        self.tree1.rename_one('sub/dir/ pre space',
638
671
                              'sub/ start space')
639
672
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
646
679
                          verbose=False)
647
680
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
648
681
        other = self.get_checkout('a@cset-0-5')
649
 
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
650
 
            'a@cset-0-5')
651
 
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
682
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
 
683
                                       'a@cset-0-5')
 
684
        tree2_inv = get_inventory_text(other.branch.repository,
 
685
                                       'a@cset-0-5')
652
686
        self.assertEqualDiff(tree1_inv, tree2_inv)
653
687
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
654
688
        other.commit('rename file', rev_id='a@cset-0-6b')
660
694
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
661
695
        link_id = 'link-1'
662
696
 
663
 
        self.requireFeature(tests.SymlinkFeature)
 
697
        self.requireFeature(features.SymlinkFeature)
664
698
        self.tree1 = self.make_branch_and_tree('b1')
665
699
        self.b1 = self.tree1.branch
666
700
 
707
741
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
708
742
 
709
743
    def test_unicode_symlink_bundle(self):
710
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
744
        self.requireFeature(features.UnicodeFilenameFeature)
711
745
        self._test_symlink_bundle(u'\N{Euro Sign}link',
712
746
                                  u'bar/\N{Euro Sign}foo',
713
747
                                  u'mars\N{Euro Sign}')
787
821
        self.tree1 = self.make_branch_and_tree('b1')
788
822
        self.b1 = self.tree1.branch
789
823
 
790
 
        open('b1/one', 'wb').write('one\n')
 
824
        with open('b1/one', 'wb') as f: f.write('one\n')
791
825
        self.tree1.add('one')
792
826
        self.tree1.commit('add file', rev_id='a@cset-0-1')
793
 
        open('b1/one', 'wb').write('two\n')
 
827
        with open('b1/one', 'wb') as f: f.write('two\n')
794
828
        self.tree1.commit('modify', rev_id='a@cset-0-2')
795
 
        open('b1/one', 'wb').write('three\n')
 
829
        with open('b1/one', 'wb') as f: f.write('three\n')
796
830
        self.tree1.commit('modify', rev_id='a@cset-0-3')
797
831
        bundle_file = StringIO()
798
832
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
814
848
        return bundle_file.getvalue()
815
849
 
816
850
    def test_unicode_bundle(self):
817
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
851
        self.requireFeature(features.UnicodeFilenameFeature)
818
852
        # Handle international characters
819
853
        os.mkdir('b1')
820
854
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
877
911
        bundle = self.get_valid_bundle('null:', 'white-1')
878
912
 
879
913
        # Modified
880
 
        open('b1/trailing space ', 'ab').write('add some text\n')
 
914
        with open('b1/trailing space ', 'ab') as f: f.write('add some text\n')
881
915
        self.tree1.commit('add text', rev_id='white-2')
882
916
 
883
917
        bundle = self.get_valid_bundle('white-1', 'white-2')
925
959
        self.tree1.commit('message', rev_id='revid1')
926
960
        bundle = self.get_valid_bundle('null:', 'revid1')
927
961
        tree = self.get_bundle_tree(bundle, 'revid1')
928
 
        self.assertEqual('revid1', tree.inventory.root.revision)
 
962
        root_revision = tree.get_file_revision(tree.get_root_id())
 
963
        self.assertEqual('revid1', root_revision)
929
964
 
930
965
    def test_install_revisions(self):
931
966
        self.tree1 = self.make_branch_and_tree('b1')
1020
1055
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1021
1056
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1022
1057
        bundle.install_revisions(repo)
1023
 
        inv_text = repo.get_inventory_xml('rev2')
 
1058
        inv_text = repo._get_inventory_xml('rev2')
1024
1059
        self.assertNotContainsRe(inv_text, 'format="5"')
1025
1060
        self.assertContainsRe(inv_text, 'format="7"')
1026
1061
 
1046
1081
 
1047
1082
    def test_inv_hash_across_serializers(self):
1048
1083
        repo = self.make_repo_with_installed_revisions()
1049
 
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1050
 
        xml = repo.get_inventory_xml('rev2')
 
1084
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
 
1085
        xml = repo._get_inventory_xml('rev2')
1051
1086
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1052
1087
 
1053
1088
    def test_across_models_incompatible(self):
1317
1352
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1318
1353
        new_text = new_text.replace('<file file_id="exe-1"',
1319
1354
                                    '<file executable="y" file_id="exe-1"')
1320
 
        new_text = new_text.replace('B222', 'B237')
 
1355
        new_text = new_text.replace('B260', 'B275')
1321
1356
        bundle_txt = StringIO()
1322
1357
        bundle_txt.write(serializer._get_bundle_header('4'))
1323
1358
        bundle_txt.write('\n')
1393
1428
        branch = tree_a.branch
1394
1429
        repo_a = branch.repository
1395
1430
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1396
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1431
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1397
1432
        try:
1398
1433
            from bzrlib.testament import Testament
1399
1434
            # monkey patch gpg signing mechanism
1400
1435
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1401
 
            new_config = test_commit.MustSignConfig(branch)
1402
 
            commit.Commit(config=new_config).commit(message="base",
 
1436
            new_config = test_commit.MustSignConfig()
 
1437
            commit.Commit(config_stack=new_config).commit(message="base",
1403
1438
                                                    allow_pointless=True,
1404
1439
                                                    rev_id='B',
1405
1440
                                                    working_tree=tree_a)
1423
1458
        install_bundle(repo_b, serializer.read(s))
1424
1459
 
1425
1460
 
1426
 
class V4WeaveBundleTester(V4BundleTester):
 
1461
class V4_2aBundleTester(V4BundleTester):
1427
1462
 
1428
1463
    def bzrdir_format(self):
1429
 
        return 'metaweave'
 
1464
        return '2a'
 
1465
 
 
1466
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1467
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1468
        Munge the text so that it's invalid.
 
1469
 
 
1470
        :return: The in-memory bundle
 
1471
        """
 
1472
        from bzrlib.bundle import serializer
 
1473
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1474
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1475
        # We are going to be replacing some text to set the executable bit on a
 
1476
        # file. Make sure the text replacement actually works correctly.
 
1477
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
 
1478
        new_text = new_text.replace('<file file_id="exe-1"',
 
1479
                                    '<file executable="y" file_id="exe-1"')
 
1480
        new_text = new_text.replace('B244', 'B259')
 
1481
        bundle_txt = StringIO()
 
1482
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1483
        bundle_txt.write('\n')
 
1484
        bundle_txt.write(new_text.encode('bz2'))
 
1485
        bundle_txt.seek(0)
 
1486
        bundle = read_bundle(bundle_txt)
 
1487
        self.valid_apply_bundle(base_rev_id, bundle)
 
1488
        return bundle
 
1489
 
 
1490
    def make_merged_branch(self):
 
1491
        builder = self.make_branch_builder('source')
 
1492
        builder.start_series()
 
1493
        builder.build_snapshot('a@cset-0-1', None, [
 
1494
            ('add', ('', 'root-id', 'directory', None)),
 
1495
            ('add', ('file', 'file-id', 'file', 'original content\n')),
 
1496
            ])
 
1497
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
 
1498
            ('modify', ('file-id', 'new-content\n')),
 
1499
            ])
 
1500
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
 
1501
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1502
            ])
 
1503
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
 
1504
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1505
            ])
 
1506
        builder.finish_series()
 
1507
        self.b1 = builder.get_branch()
 
1508
        self.b1.lock_read()
 
1509
        self.addCleanup(self.b1.unlock)
 
1510
 
 
1511
    def make_bundle_just_inventories(self, base_revision_id,
 
1512
                                     target_revision_id,
 
1513
                                     revision_ids):
 
1514
        sio = StringIO()
 
1515
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
 
1516
                                         self.b1.repository, sio)
 
1517
        writer.bundle.begin()
 
1518
        writer._add_inventory_mpdiffs_from_serializer(revision_ids)
 
1519
        writer.bundle.end()
 
1520
        sio.seek(0)
 
1521
        return sio
 
1522
 
 
1523
    def test_single_inventory_multiple_parents_as_xml(self):
 
1524
        self.make_merged_branch()
 
1525
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1526
                                                ['a@cset-0-3'])
 
1527
        reader = v4.BundleReader(sio, stream_input=False)
 
1528
        records = list(reader.iter_records())
 
1529
        self.assertEqual(1, len(records))
 
1530
        (bytes, metadata, repo_kind, revision_id,
 
1531
         file_id) = records[0]
 
1532
        self.assertIs(None, file_id)
 
1533
        self.assertEqual('a@cset-0-3', revision_id)
 
1534
        self.assertEqual('inventory', repo_kind)
 
1535
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1536
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1537
                          'storage_kind': 'mpdiff',
 
1538
                         }, metadata)
 
1539
        # We should have an mpdiff that takes some lines from both parents.
 
1540
        self.assertEqualDiff(
 
1541
            'i 1\n'
 
1542
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1543
            '\n'
 
1544
            'c 0 1 1 2\n'
 
1545
            'c 1 3 3 2\n', bytes)
 
1546
 
 
1547
    def test_single_inv_no_parents_as_xml(self):
 
1548
        self.make_merged_branch()
 
1549
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
 
1550
                                                ['a@cset-0-1'])
 
1551
        reader = v4.BundleReader(sio, stream_input=False)
 
1552
        records = list(reader.iter_records())
 
1553
        self.assertEqual(1, len(records))
 
1554
        (bytes, metadata, repo_kind, revision_id,
 
1555
         file_id) = records[0]
 
1556
        self.assertIs(None, file_id)
 
1557
        self.assertEqual('a@cset-0-1', revision_id)
 
1558
        self.assertEqual('inventory', repo_kind)
 
1559
        self.assertEqual({'parents': [],
 
1560
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
 
1561
                          'storage_kind': 'mpdiff',
 
1562
                         }, metadata)
 
1563
        # We should have an mpdiff that takes some lines from both parents.
 
1564
        self.assertEqualDiff(
 
1565
            'i 4\n'
 
1566
            '<inventory format="10" revision_id="a@cset-0-1">\n'
 
1567
            '<directory file_id="root-id" name=""'
 
1568
                ' revision="a@cset-0-1" />\n'
 
1569
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1570
                ' revision="a@cset-0-1"'
 
1571
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
 
1572
                ' text_size="17" />\n'
 
1573
            '</inventory>\n'
 
1574
            '\n', bytes)
 
1575
 
 
1576
    def test_multiple_inventories_as_xml(self):
 
1577
        self.make_merged_branch()
 
1578
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1579
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
 
1580
        reader = v4.BundleReader(sio, stream_input=False)
 
1581
        records = list(reader.iter_records())
 
1582
        self.assertEqual(3, len(records))
 
1583
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
 
1584
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
 
1585
                         revision_ids)
 
1586
        metadata_2a = records[0][1]
 
1587
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1588
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
 
1589
                          'storage_kind': 'mpdiff',
 
1590
                         }, metadata_2a)
 
1591
        metadata_2b = records[1][1]
 
1592
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1593
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
 
1594
                          'storage_kind': 'mpdiff',
 
1595
                         }, metadata_2b)
 
1596
        metadata_3 = records[2][1]
 
1597
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1598
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1599
                          'storage_kind': 'mpdiff',
 
1600
                         }, metadata_3)
 
1601
        bytes_2a = records[0][0]
 
1602
        self.assertEqualDiff(
 
1603
            'i 1\n'
 
1604
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
 
1605
            '\n'
 
1606
            'c 0 1 1 1\n'
 
1607
            'i 1\n'
 
1608
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1609
                ' revision="a@cset-0-2a"'
 
1610
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
 
1611
                ' text_size="12" />\n'
 
1612
            '\n'
 
1613
            'c 0 3 3 1\n', bytes_2a)
 
1614
        bytes_2b = records[1][0]
 
1615
        self.assertEqualDiff(
 
1616
            'i 1\n'
 
1617
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
 
1618
            '\n'
 
1619
            'c 0 1 1 2\n'
 
1620
            'i 1\n'
 
1621
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
 
1622
                ' revision="a@cset-0-2b"'
 
1623
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
 
1624
                ' text_size="14" />\n'
 
1625
            '\n'
 
1626
            'c 0 3 4 1\n', bytes_2b)
 
1627
        bytes_3 = records[2][0]
 
1628
        self.assertEqualDiff(
 
1629
            'i 1\n'
 
1630
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1631
            '\n'
 
1632
            'c 0 1 1 2\n'
 
1633
            'c 1 3 3 2\n', bytes_3)
 
1634
 
 
1635
    def test_creating_bundle_preserves_chk_pages(self):
 
1636
        self.make_merged_branch()
 
1637
        target = self.b1.bzrdir.sprout('target',
 
1638
                                       revision_id='a@cset-0-2a').open_branch()
 
1639
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
 
1640
                                                      'a@cset-0-3')
 
1641
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
 
1642
        bundle = read_bundle(bundle_txt)
 
1643
        target.lock_write()
 
1644
        self.addCleanup(target.unlock)
 
1645
        install_bundle(target.repository, bundle)
 
1646
        inv1 = self.b1.repository.inventories.get_record_stream([
 
1647
            ('a@cset-0-3',)], 'unordered',
 
1648
            True).next().get_bytes_as('fulltext')
 
1649
        inv2 = target.repository.inventories.get_record_stream([
 
1650
            ('a@cset-0-3',)], 'unordered',
 
1651
            True).next().get_bytes_as('fulltext')
 
1652
        self.assertEqualDiff(inv1, inv2)
1430
1653
 
1431
1654
 
1432
1655
class MungedBundleTester(object):
1607
1830
            def look_up(self, name, url):
1608
1831
                return 'source'
1609
1832
        directories.register('foo:', FooService, 'Testing directory service')
1610
 
        self.addCleanup(lambda: directories.remove('foo:'))
 
1833
        self.addCleanup(directories.remove, 'foo:')
1611
1834
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1612
1835
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1613
1836
                          'foo:bar')
1617
1840
        """
1618
1841
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
1619
1842
        server = RedirectingMemoryServer()
1620
 
        server.setUp()
 
1843
        self.start_server(server)
1621
1844
        url = server.get_url() + 'infinite-loop'
1622
 
        self.addCleanup(server.tearDown)
1623
1845
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
1624
1846
 
1625
1847
    def test_smart_server_connection_reset(self):
1627
1849
        bundle, then the ConnectionReset error should be propagated.
1628
1850
        """
1629
1851
        # Instantiate a server that will provoke a ConnectionReset
1630
 
        sock_server = _DisconnectingTCPServer()
1631
 
        sock_server.setUp()
1632
 
        self.addCleanup(sock_server.tearDown)
 
1852
        sock_server = DisconnectingServer()
 
1853
        self.start_server(sock_server)
1633
1854
        # We don't really care what the url is since the server will close the
1634
1855
        # connection without interpreting it
1635
1856
        url = sock_server.get_url()
1636
1857
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1637
1858
 
1638
1859
 
1639
 
class _DisconnectingTCPServer(object):
1640
 
    """A TCP server that immediately closes any connection made to it."""
1641
 
 
1642
 
    def setUp(self):
1643
 
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1644
 
        self.sock.bind(('127.0.0.1', 0))
1645
 
        self.sock.listen(1)
1646
 
        self.port = self.sock.getsockname()[1]
1647
 
        self.thread = threading.Thread(
1648
 
            name='%s (port %d)' % (self.__class__.__name__, self.port),
1649
 
            target=self.accept_and_close)
1650
 
        self.thread.start()
1651
 
 
1652
 
    def accept_and_close(self):
1653
 
        conn, addr = self.sock.accept()
1654
 
        conn.shutdown(socket.SHUT_RDWR)
1655
 
        conn.close()
 
1860
class DisconnectingHandler(SocketServer.BaseRequestHandler):
 
1861
    """A request handler that immediately closes any connection made to it."""
 
1862
 
 
1863
    def handle(self):
 
1864
        self.request.close()
 
1865
 
 
1866
 
 
1867
class DisconnectingServer(test_server.TestingTCPServerInAThread):
 
1868
 
 
1869
    def __init__(self):
 
1870
        super(DisconnectingServer, self).__init__(
 
1871
            ('127.0.0.1', 0),
 
1872
            test_server.TestingTCPServer,
 
1873
            DisconnectingHandler)
1656
1874
 
1657
1875
    def get_url(self):
1658
 
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1659
 
 
1660
 
    def tearDown(self):
1661
 
        try:
1662
 
            # make sure the thread dies by connecting to the listening socket,
1663
 
            # just in case the test failed to do so.
1664
 
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1665
 
            conn.connect(self.sock.getsockname())
1666
 
            conn.close()
1667
 
        except socket.error:
1668
 
            pass
1669
 
        self.sock.close()
1670
 
        self.thread.join()
1671
 
 
 
1876
        """Return the url of the server"""
 
1877
        return "bzr://%s:%d/" % self.server.server_address