~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Patch Queue Manager
  • Date: 2014-02-12 18:22:22 UTC
  • mfrom: (6589.2.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20140212182222-beouo25gaf1cny76
(vila) The XDG Base Directory Specification uses the XDG_CACHE_HOME,
 not XDG_CACHE_DIR. (Andrew Starr-Bochicchio)

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
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(),
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')
657
691
                          verbose=False)
658
692
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
659
693
 
660
 
    def test_symlink_bundle(self):
661
 
        self.requireFeature(tests.SymlinkFeature)
 
694
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
 
695
        link_id = 'link-1'
 
696
 
 
697
        self.requireFeature(features.SymlinkFeature)
662
698
        self.tree1 = self.make_branch_and_tree('b1')
663
699
        self.b1 = self.tree1.branch
 
700
 
664
701
        tt = TreeTransform(self.tree1)
665
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
702
        tt.new_symlink(link_name, tt.root, link_target, link_id)
666
703
        tt.apply()
667
704
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
668
 
        self.get_valid_bundle('null:', 'l@cset-0-1')
 
705
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
 
706
        if getattr(bundle ,'revision_tree', None) is not None:
 
707
            # Not all bundle formats supports revision_tree
 
708
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
 
709
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
 
710
 
669
711
        tt = TreeTransform(self.tree1)
670
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
712
        trans_id = tt.trans_id_tree_file_id(link_id)
671
713
        tt.adjust_path('link2', tt.root, trans_id)
672
714
        tt.delete_contents(trans_id)
673
 
        tt.create_symlink('mars', trans_id)
 
715
        tt.create_symlink(new_link_target, trans_id)
674
716
        tt.apply()
675
717
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
676
 
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
718
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
719
        if getattr(bundle ,'revision_tree', None) is not None:
 
720
            # Not all bundle formats supports revision_tree
 
721
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
 
722
            self.assertEqual(new_link_target,
 
723
                             bund_tree.get_symlink_target(link_id))
 
724
 
677
725
        tt = TreeTransform(self.tree1)
678
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
726
        trans_id = tt.trans_id_tree_file_id(link_id)
679
727
        tt.delete_contents(trans_id)
680
728
        tt.create_symlink('jupiter', trans_id)
681
729
        tt.apply()
682
730
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
683
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
731
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
732
 
684
733
        tt = TreeTransform(self.tree1)
685
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
734
        trans_id = tt.trans_id_tree_file_id(link_id)
686
735
        tt.delete_contents(trans_id)
687
736
        tt.apply()
688
737
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
689
 
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
738
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
739
 
 
740
    def test_symlink_bundle(self):
 
741
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
 
742
 
 
743
    def test_unicode_symlink_bundle(self):
 
744
        self.requireFeature(features.UnicodeFilenameFeature)
 
745
        self._test_symlink_bundle(u'\N{Euro Sign}link',
 
746
                                  u'bar/\N{Euro Sign}foo',
 
747
                                  u'mars\N{Euro Sign}')
690
748
 
691
749
    def test_binary_bundle(self):
692
750
        self.tree1 = self.make_branch_and_tree('b1')
763
821
        self.tree1 = self.make_branch_and_tree('b1')
764
822
        self.b1 = self.tree1.branch
765
823
 
766
 
        open('b1/one', 'wb').write('one\n')
 
824
        with open('b1/one', 'wb') as f: f.write('one\n')
767
825
        self.tree1.add('one')
768
826
        self.tree1.commit('add file', rev_id='a@cset-0-1')
769
 
        open('b1/one', 'wb').write('two\n')
 
827
        with open('b1/one', 'wb') as f: f.write('two\n')
770
828
        self.tree1.commit('modify', rev_id='a@cset-0-2')
771
 
        open('b1/one', 'wb').write('three\n')
 
829
        with open('b1/one', 'wb') as f: f.write('three\n')
772
830
        self.tree1.commit('modify', rev_id='a@cset-0-3')
773
831
        bundle_file = StringIO()
774
832
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
790
848
        return bundle_file.getvalue()
791
849
 
792
850
    def test_unicode_bundle(self):
793
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
851
        self.requireFeature(features.UnicodeFilenameFeature)
794
852
        # Handle international characters
795
853
        os.mkdir('b1')
796
854
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
853
911
        bundle = self.get_valid_bundle('null:', 'white-1')
854
912
 
855
913
        # Modified
856
 
        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')
857
915
        self.tree1.commit('add text', rev_id='white-2')
858
916
 
859
917
        bundle = self.get_valid_bundle('white-1', 'white-2')
901
959
        self.tree1.commit('message', rev_id='revid1')
902
960
        bundle = self.get_valid_bundle('null:', 'revid1')
903
961
        tree = self.get_bundle_tree(bundle, 'revid1')
904
 
        self.assertEqual('revid1', tree.inventory.root.revision)
 
962
        root_revision = tree.get_file_revision(tree.get_root_id())
 
963
        self.assertEqual('revid1', root_revision)
905
964
 
906
965
    def test_install_revisions(self):
907
966
        self.tree1 = self.make_branch_and_tree('b1')
996
1055
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
997
1056
        repo = self.make_repository('repo', format='dirstate-with-subtree')
998
1057
        bundle.install_revisions(repo)
999
 
        inv_text = repo.get_inventory_xml('rev2')
 
1058
        inv_text = repo._get_inventory_xml('rev2')
1000
1059
        self.assertNotContainsRe(inv_text, 'format="5"')
1001
1060
        self.assertContainsRe(inv_text, 'format="7"')
1002
1061
 
1022
1081
 
1023
1082
    def test_inv_hash_across_serializers(self):
1024
1083
        repo = self.make_repo_with_installed_revisions()
1025
 
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1026
 
        xml = repo.get_inventory_xml('rev2')
 
1084
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
 
1085
        xml = repo._get_inventory_xml('rev2')
1027
1086
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1028
1087
 
1029
1088
    def test_across_models_incompatible(self):
1293
1352
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1294
1353
        new_text = new_text.replace('<file file_id="exe-1"',
1295
1354
                                    '<file executable="y" file_id="exe-1"')
1296
 
        new_text = new_text.replace('B222', 'B237')
 
1355
        new_text = new_text.replace('B260', 'B275')
1297
1356
        bundle_txt = StringIO()
1298
1357
        bundle_txt.write(serializer._get_bundle_header('4'))
1299
1358
        bundle_txt.write('\n')
1369
1428
        branch = tree_a.branch
1370
1429
        repo_a = branch.repository
1371
1430
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1372
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1431
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1373
1432
        try:
1374
1433
            from bzrlib.testament import Testament
1375
1434
            # monkey patch gpg signing mechanism
1376
1435
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1377
 
            new_config = test_commit.MustSignConfig(branch)
1378
 
            commit.Commit(config=new_config).commit(message="base",
 
1436
            new_config = test_commit.MustSignConfig()
 
1437
            commit.Commit(config_stack=new_config).commit(message="base",
1379
1438
                                                    allow_pointless=True,
1380
1439
                                                    rev_id='B',
1381
1440
                                                    working_tree=tree_a)
1399
1458
        install_bundle(repo_b, serializer.read(s))
1400
1459
 
1401
1460
 
1402
 
class V4WeaveBundleTester(V4BundleTester):
 
1461
class V4_2aBundleTester(V4BundleTester):
1403
1462
 
1404
1463
    def bzrdir_format(self):
1405
 
        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)
1406
1653
 
1407
1654
 
1408
1655
class MungedBundleTester(object):
1583
1830
            def look_up(self, name, url):
1584
1831
                return 'source'
1585
1832
        directories.register('foo:', FooService, 'Testing directory service')
1586
 
        self.addCleanup(lambda: directories.remove('foo:'))
 
1833
        self.addCleanup(directories.remove, 'foo:')
1587
1834
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1588
1835
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1589
1836
                          'foo:bar')
1590
1837
 
 
1838
    def test_infinite_redirects_are_not_a_bundle(self):
 
1839
        """If a URL causes TooManyRedirections then NotABundle is raised.
 
1840
        """
 
1841
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
 
1842
        server = RedirectingMemoryServer()
 
1843
        self.start_server(server)
 
1844
        url = server.get_url() + 'infinite-loop'
 
1845
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
 
1846
 
1591
1847
    def test_smart_server_connection_reset(self):
1592
1848
        """If a smart server connection fails during the attempt to read a
1593
1849
        bundle, then the ConnectionReset error should be propagated.
1594
1850
        """
1595
1851
        # Instantiate a server that will provoke a ConnectionReset
1596
 
        sock_server = _DisconnectingTCPServer()
1597
 
        sock_server.setUp()
1598
 
        self.addCleanup(sock_server.tearDown)
 
1852
        sock_server = DisconnectingServer()
 
1853
        self.start_server(sock_server)
1599
1854
        # We don't really care what the url is since the server will close the
1600
1855
        # connection without interpreting it
1601
1856
        url = sock_server.get_url()
1602
1857
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1603
1858
 
1604
1859
 
1605
 
class _DisconnectingTCPServer(object):
1606
 
    """A TCP server that immediately closes any connection made to it."""
1607
 
 
1608
 
    def setUp(self):
1609
 
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1610
 
        self.sock.bind(('127.0.0.1', 0))
1611
 
        self.sock.listen(1)
1612
 
        self.port = self.sock.getsockname()[1]
1613
 
        self.thread = threading.Thread(
1614
 
            name='%s (port %d)' % (self.__class__.__name__, self.port),
1615
 
            target=self.accept_and_close)
1616
 
        self.thread.start()
1617
 
 
1618
 
    def accept_and_close(self):
1619
 
        conn, addr = self.sock.accept()
1620
 
        conn.shutdown(socket.SHUT_RDWR)
1621
 
        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)
1622
1874
 
1623
1875
    def get_url(self):
1624
 
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1625
 
 
1626
 
    def tearDown(self):
1627
 
        try:
1628
 
            # make sure the thread dies by connecting to the listening socket,
1629
 
            # just in case the test failed to do so.
1630
 
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1631
 
            conn.connect(self.sock.getsockname())
1632
 
            conn.close()
1633
 
        except socket.error:
1634
 
            pass
1635
 
        self.sock.close()
1636
 
        self.thread.join()
1637
 
 
 
1876
        """Return the url of the server"""
 
1877
        return "bzr://%s:%d/" % self.server.server_address