~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_repository.py

  • Committer: John Arbash Meinel
  • Date: 2008-09-09 15:09:12 UTC
  • mto: This revision was merged to the branch mainline in revision 3699.
  • Revision ID: john@arbash-meinel.com-20080909150912-wyttm8he1zsls2ck
Use the right timing function on win32

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Tests for the Repository facility that are not interface tests.
18
18
 
19
 
For interface tests see tests/per_repository/*.py.
 
19
For interface tests see tests/repository_implementations/*.py.
20
20
 
21
21
For concrete class tests see this file, and for storage formats tests
22
22
also see this file.
23
23
"""
24
24
 
 
25
import md5
25
26
from stat import S_ISDIR
26
27
from StringIO import StringIO
27
28
 
31
32
                           UnknownFormatError,
32
33
                           UnsupportedFormatError,
33
34
                           )
34
 
from bzrlib import (
35
 
    graph,
36
 
    tests,
37
 
    )
38
 
from bzrlib.branchbuilder import BranchBuilder
39
 
from bzrlib.btree_index import BTreeBuilder, BTreeGraphIndex
 
35
from bzrlib import graph
40
36
from bzrlib.index import GraphIndex, InMemoryGraphIndex
41
37
from bzrlib.repository import RepositoryFormat
42
38
from bzrlib.smart import server
51
47
    get_transport,
52
48
    )
53
49
from bzrlib.transport.memory import MemoryServer
 
50
from bzrlib.util import bencode
54
51
from bzrlib import (
55
 
    bencode,
56
52
    bzrdir,
57
53
    errors,
58
54
    inventory,
59
 
    osutils,
60
55
    progress,
61
56
    repository,
62
57
    revision as _mod_revision,
64
59
    upgrade,
65
60
    workingtree,
66
61
    )
67
 
from bzrlib.repofmt import (
68
 
    groupcompress_repo,
69
 
    knitrepo,
70
 
    pack_repo,
71
 
    weaverepo,
72
 
    )
 
62
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
73
63
 
74
64
 
75
65
class TestDefaultFormat(TestCase):
104
94
class SampleRepositoryFormat(repository.RepositoryFormat):
105
95
    """A sample format
106
96
 
107
 
    this format is initializable, unsupported to aid in testing the
 
97
    this format is initializable, unsupported to aid in testing the 
108
98
    open and open(unsupported=True) routines.
109
99
    """
110
100
 
131
121
    def test_find_format(self):
132
122
        # is the right format object found for a repository?
133
123
        # create a branch with a few known format objects.
134
 
        # this is not quite the same as
 
124
        # this is not quite the same as 
135
125
        self.build_tree(["foo/", "bar/"])
136
126
        def check_format(format, url):
137
127
            dir = format._matchingbzrdir.initialize(url)
140
130
            found_format = repository.RepositoryFormat.find_format(dir)
141
131
            self.failUnless(isinstance(found_format, format.__class__))
142
132
        check_format(weaverepo.RepositoryFormat7(), "bar")
143
 
 
 
133
        
144
134
    def test_find_format_no_repository(self):
145
135
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
146
136
        self.assertRaises(errors.NoRepositoryPresent,
176
166
        """Weaves need topological data insertion."""
177
167
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
178
168
        repo = weaverepo.RepositoryFormat6().initialize(control)
179
 
        self.assertEqual('topological', repo._format._fetch_order)
 
169
        self.assertEqual('topological', repo._fetch_order)
180
170
 
181
171
    def test_attribute__fetch_uses_deltas(self):
182
172
        """Weaves do not reuse deltas."""
183
173
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
184
174
        repo = weaverepo.RepositoryFormat6().initialize(control)
185
 
        self.assertEqual(False, repo._format._fetch_uses_deltas)
 
175
        self.assertEqual(False, repo._fetch_uses_deltas)
186
176
 
187
177
    def test_attribute__fetch_reconcile(self):
188
178
        """Weave repositories need a reconcile after fetch."""
189
179
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
190
180
        repo = weaverepo.RepositoryFormat6().initialize(control)
191
 
        self.assertEqual(True, repo._format._fetch_reconcile)
 
181
        self.assertEqual(True, repo._fetch_reconcile)
192
182
 
193
183
    def test_no_ancestry_weave(self):
194
184
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
211
201
        """Weaves need topological data insertion."""
212
202
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
213
203
        repo = weaverepo.RepositoryFormat7().initialize(control)
214
 
        self.assertEqual('topological', repo._format._fetch_order)
 
204
        self.assertEqual('topological', repo._fetch_order)
215
205
 
216
206
    def test_attribute__fetch_uses_deltas(self):
217
207
        """Weaves do not reuse deltas."""
218
208
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
219
209
        repo = weaverepo.RepositoryFormat7().initialize(control)
220
 
        self.assertEqual(False, repo._format._fetch_uses_deltas)
 
210
        self.assertEqual(False, repo._fetch_uses_deltas)
221
211
 
222
212
    def test_attribute__fetch_reconcile(self):
223
213
        """Weave repositories need a reconcile after fetch."""
224
214
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
225
215
        repo = weaverepo.RepositoryFormat7().initialize(control)
226
 
        self.assertEqual(True, repo._format._fetch_reconcile)
 
216
        self.assertEqual(True, repo._fetch_reconcile)
227
217
 
228
218
    def test_disk_layout(self):
229
219
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
293
283
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
294
284
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
295
285
        t = control.get_repository_transport(None)
296
 
        # TODO: Should check there is a 'lock' toplevel directory,
 
286
        # TODO: Should check there is a 'lock' toplevel directory, 
297
287
        # regardless of contents
298
288
        self.assertFalse(t.has('lock/held/info'))
299
289
        repo.lock_write()
352
342
 
353
343
 
354
344
class TestFormatKnit1(TestCaseWithTransport):
355
 
 
 
345
    
356
346
    def test_attribute__fetch_order(self):
357
347
        """Knits need topological data insertion."""
358
348
        repo = self.make_repository('.',
359
349
                format=bzrdir.format_registry.get('knit')())
360
 
        self.assertEqual('topological', repo._format._fetch_order)
 
350
        self.assertEqual('topological', repo._fetch_order)
361
351
 
362
352
    def test_attribute__fetch_uses_deltas(self):
363
353
        """Knits reuse deltas."""
364
354
        repo = self.make_repository('.',
365
355
                format=bzrdir.format_registry.get('knit')())
366
 
        self.assertEqual(True, repo._format._fetch_uses_deltas)
 
356
        self.assertEqual(True, repo._fetch_uses_deltas)
367
357
 
368
358
    def test_disk_layout(self):
369
359
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
482
472
class DummyRepository(object):
483
473
    """A dummy repository for testing."""
484
474
 
485
 
    _format = None
486
475
    _serializer = None
487
476
 
488
477
    def supports_rich_root(self):
489
 
        if self._format is not None:
490
 
            return self._format.rich_root_data
491
478
        return False
492
479
 
493
 
    def get_graph(self):
494
 
        raise NotImplementedError
495
 
 
496
 
    def get_parent_map(self, revision_ids):
497
 
        raise NotImplementedError
498
 
 
499
480
 
500
481
class InterDummy(repository.InterRepository):
501
482
    """An inter-repository optimised code path for DummyRepository.
508
489
    @staticmethod
509
490
    def is_compatible(repo_source, repo_target):
510
491
        """InterDummy is compatible with DummyRepository."""
511
 
        return (isinstance(repo_source, DummyRepository) and
 
492
        return (isinstance(repo_source, DummyRepository) and 
512
493
            isinstance(repo_target, DummyRepository))
513
494
 
514
495
 
527
508
 
528
509
    def assertGetsDefaultInterRepository(self, repo_a, repo_b):
529
510
        """Asserts that InterRepository.get(repo_a, repo_b) -> the default.
530
 
 
 
511
        
531
512
        The effective default is now InterSameDataRepository because there is
532
513
        no actual sane default in the presence of incompatible data models.
533
514
        """
544
525
        # pair that it returns true on for the is_compatible static method
545
526
        # check
546
527
        dummy_a = DummyRepository()
547
 
        dummy_a._format = RepositoryFormat()
548
528
        dummy_b = DummyRepository()
549
 
        dummy_b._format = RepositoryFormat()
550
529
        repo = self.make_repository('.')
551
530
        # hack dummies to look like repo somewhat.
552
531
        dummy_a._serializer = repo._serializer
553
 
        dummy_a._format.supports_tree_reference = repo._format.supports_tree_reference
554
 
        dummy_a._format.rich_root_data = repo._format.rich_root_data
555
532
        dummy_b._serializer = repo._serializer
556
 
        dummy_b._format.supports_tree_reference = repo._format.supports_tree_reference
557
 
        dummy_b._format.rich_root_data = repo._format.rich_root_data
558
533
        repository.InterRepository.register_optimiser(InterDummy)
559
534
        try:
560
535
            # we should get the default for something InterDummy returns False
623
598
 
624
599
 
625
600
class TestMisc(TestCase):
626
 
 
 
601
    
627
602
    def test_unescape_xml(self):
628
603
        """We get some kind of error when malformed entities are passed"""
629
 
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;')
 
604
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;') 
630
605
 
631
606
 
632
607
class TestRepositoryFormatKnit3(TestCaseWithTransport):
636
611
        format = bzrdir.BzrDirMetaFormat1()
637
612
        format.repository_format = knitrepo.RepositoryFormatKnit3()
638
613
        repo = self.make_repository('.', format=format)
639
 
        self.assertEqual('topological', repo._format._fetch_order)
 
614
        self.assertEqual('topological', repo._fetch_order)
640
615
 
641
616
    def test_attribute__fetch_uses_deltas(self):
642
617
        """Knits reuse deltas."""
643
618
        format = bzrdir.BzrDirMetaFormat1()
644
619
        format.repository_format = knitrepo.RepositoryFormatKnit3()
645
620
        repo = self.make_repository('.', format=format)
646
 
        self.assertEqual(True, repo._format._fetch_uses_deltas)
 
621
        self.assertEqual(True, repo._fetch_uses_deltas)
647
622
 
648
623
    def test_convert(self):
649
624
        """Ensure the upgrade adds weaves for roots"""
681
656
        self.assertFalse(repo._format.supports_external_lookups)
682
657
 
683
658
 
684
 
class Test2a(TestCaseWithTransport):
685
 
 
686
 
    def test_format_pack_compresses_True(self):
687
 
        repo = self.make_repository('repo', format='2a')
688
 
        self.assertTrue(repo._format.pack_compresses)
689
 
 
690
 
    def test_inventories_use_chk_map_with_parent_base_dict(self):
691
 
        tree = self.make_branch_and_tree('repo', format="2a")
692
 
        revid = tree.commit("foo")
693
 
        tree.lock_read()
694
 
        self.addCleanup(tree.unlock)
695
 
        inv = tree.branch.repository.get_inventory(revid)
696
 
        self.assertNotEqual(None, inv.parent_id_basename_to_file_id)
697
 
        inv.parent_id_basename_to_file_id._ensure_root()
698
 
        inv.id_to_entry._ensure_root()
699
 
        self.assertEqual(65536, inv.id_to_entry._root_node.maximum_size)
700
 
        self.assertEqual(65536,
701
 
            inv.parent_id_basename_to_file_id._root_node.maximum_size)
702
 
 
703
 
    def test_autopack_unchanged_chk_nodes(self):
704
 
        # at 20 unchanged commits, chk pages are packed that are split into
705
 
        # two groups such that the new pack being made doesn't have all its
706
 
        # pages in the source packs (though they are in the repository).
707
 
        tree = self.make_branch_and_tree('tree', format='2a')
708
 
        for pos in range(20):
709
 
            tree.commit(str(pos))
710
 
 
711
 
    def test_pack_with_hint(self):
712
 
        tree = self.make_branch_and_tree('tree', format='2a')
713
 
        # 1 commit to leave untouched
714
 
        tree.commit('1')
715
 
        to_keep = tree.branch.repository._pack_collection.names()
716
 
        # 2 to combine
717
 
        tree.commit('2')
718
 
        tree.commit('3')
719
 
        all = tree.branch.repository._pack_collection.names()
720
 
        combine = list(set(all) - set(to_keep))
721
 
        self.assertLength(3, all)
722
 
        self.assertLength(2, combine)
723
 
        tree.branch.repository.pack(hint=combine)
724
 
        final = tree.branch.repository._pack_collection.names()
725
 
        self.assertLength(2, final)
726
 
        self.assertFalse(combine[0] in final)
727
 
        self.assertFalse(combine[1] in final)
728
 
        self.assertSubset(to_keep, final)
729
 
 
730
 
    def test_stream_source_to_gc(self):
731
 
        source = self.make_repository('source', format='2a')
732
 
        target = self.make_repository('target', format='2a')
733
 
        stream = source._get_source(target._format)
734
 
        self.assertIsInstance(stream, groupcompress_repo.GroupCHKStreamSource)
735
 
 
736
 
    def test_stream_source_to_non_gc(self):
737
 
        source = self.make_repository('source', format='2a')
738
 
        target = self.make_repository('target', format='rich-root-pack')
739
 
        stream = source._get_source(target._format)
740
 
        # We don't want the child GroupCHKStreamSource
741
 
        self.assertIs(type(stream), repository.StreamSource)
742
 
 
743
 
    def test_get_stream_for_missing_keys_includes_all_chk_refs(self):
744
 
        source_builder = self.make_branch_builder('source',
745
 
                            format='2a')
746
 
        # We have to build a fairly large tree, so that we are sure the chk
747
 
        # pages will have split into multiple pages.
748
 
        entries = [('add', ('', 'a-root-id', 'directory', None))]
749
 
        for i in 'abcdefghijklmnopqrstuvwxyz123456789':
750
 
            for j in 'abcdefghijklmnopqrstuvwxyz123456789':
751
 
                fname = i + j
752
 
                fid = fname + '-id'
753
 
                content = 'content for %s\n' % (fname,)
754
 
                entries.append(('add', (fname, fid, 'file', content)))
755
 
        source_builder.start_series()
756
 
        source_builder.build_snapshot('rev-1', None, entries)
757
 
        # Now change a few of them, so we get a few new pages for the second
758
 
        # revision
759
 
        source_builder.build_snapshot('rev-2', ['rev-1'], [
760
 
            ('modify', ('aa-id', 'new content for aa-id\n')),
761
 
            ('modify', ('cc-id', 'new content for cc-id\n')),
762
 
            ('modify', ('zz-id', 'new content for zz-id\n')),
763
 
            ])
764
 
        source_builder.finish_series()
765
 
        source_branch = source_builder.get_branch()
766
 
        source_branch.lock_read()
767
 
        self.addCleanup(source_branch.unlock)
768
 
        target = self.make_repository('target', format='2a')
769
 
        source = source_branch.repository._get_source(target._format)
770
 
        self.assertIsInstance(source, groupcompress_repo.GroupCHKStreamSource)
771
 
 
772
 
        # On a regular pass, getting the inventories and chk pages for rev-2
773
 
        # would only get the newly created chk pages
774
 
        search = graph.SearchResult(set(['rev-2']), set(['rev-1']), 1,
775
 
                                    set(['rev-2']))
776
 
        simple_chk_records = []
777
 
        for vf_name, substream in source.get_stream(search):
778
 
            if vf_name == 'chk_bytes':
779
 
                for record in substream:
780
 
                    simple_chk_records.append(record.key)
781
 
            else:
782
 
                for _ in substream:
783
 
                    continue
784
 
        # 3 pages, the root (InternalNode), + 2 pages which actually changed
785
 
        self.assertEqual([('sha1:91481f539e802c76542ea5e4c83ad416bf219f73',),
786
 
                          ('sha1:4ff91971043668583985aec83f4f0ab10a907d3f',),
787
 
                          ('sha1:81e7324507c5ca132eedaf2d8414ee4bb2226187',),
788
 
                          ('sha1:b101b7da280596c71a4540e9a1eeba8045985ee0',)],
789
 
                         simple_chk_records)
790
 
        # Now, when we do a similar call using 'get_stream_for_missing_keys'
791
 
        # we should get a much larger set of pages.
792
 
        missing = [('inventories', 'rev-2')]
793
 
        full_chk_records = []
794
 
        for vf_name, substream in source.get_stream_for_missing_keys(missing):
795
 
            if vf_name == 'inventories':
796
 
                for record in substream:
797
 
                    self.assertEqual(('rev-2',), record.key)
798
 
            elif vf_name == 'chk_bytes':
799
 
                for record in substream:
800
 
                    full_chk_records.append(record.key)
801
 
            else:
802
 
                self.fail('Should not be getting a stream of %s' % (vf_name,))
803
 
        # We have 257 records now. This is because we have 1 root page, and 256
804
 
        # leaf pages in a complete listing.
805
 
        self.assertEqual(257, len(full_chk_records))
806
 
        self.assertSubset(simple_chk_records, full_chk_records)
807
 
 
808
 
    def test_inconsistency_fatal(self):
809
 
        repo = self.make_repository('repo', format='2a')
810
 
        self.assertTrue(repo.revisions._index._inconsistency_fatal)
811
 
        self.assertFalse(repo.texts._index._inconsistency_fatal)
812
 
        self.assertFalse(repo.inventories._index._inconsistency_fatal)
813
 
        self.assertFalse(repo.signatures._index._inconsistency_fatal)
814
 
        self.assertFalse(repo.chk_bytes._index._inconsistency_fatal)
815
 
 
816
 
 
817
 
class TestKnitPackStreamSource(tests.TestCaseWithMemoryTransport):
818
 
 
819
 
    def test_source_to_exact_pack_092(self):
820
 
        source = self.make_repository('source', format='pack-0.92')
821
 
        target = self.make_repository('target', format='pack-0.92')
822
 
        stream_source = source._get_source(target._format)
823
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
824
 
 
825
 
    def test_source_to_exact_pack_rich_root_pack(self):
826
 
        source = self.make_repository('source', format='rich-root-pack')
827
 
        target = self.make_repository('target', format='rich-root-pack')
828
 
        stream_source = source._get_source(target._format)
829
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
830
 
 
831
 
    def test_source_to_exact_pack_19(self):
832
 
        source = self.make_repository('source', format='1.9')
833
 
        target = self.make_repository('target', format='1.9')
834
 
        stream_source = source._get_source(target._format)
835
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
836
 
 
837
 
    def test_source_to_exact_pack_19_rich_root(self):
838
 
        source = self.make_repository('source', format='1.9-rich-root')
839
 
        target = self.make_repository('target', format='1.9-rich-root')
840
 
        stream_source = source._get_source(target._format)
841
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
842
 
 
843
 
    def test_source_to_remote_exact_pack_19(self):
844
 
        trans = self.make_smart_server('target')
845
 
        trans.ensure_base()
846
 
        source = self.make_repository('source', format='1.9')
847
 
        target = self.make_repository('target', format='1.9')
848
 
        target = repository.Repository.open(trans.base)
849
 
        stream_source = source._get_source(target._format)
850
 
        self.assertIsInstance(stream_source, pack_repo.KnitPackStreamSource)
851
 
 
852
 
    def test_stream_source_to_non_exact(self):
853
 
        source = self.make_repository('source', format='pack-0.92')
854
 
        target = self.make_repository('target', format='1.9')
855
 
        stream = source._get_source(target._format)
856
 
        self.assertIs(type(stream), repository.StreamSource)
857
 
 
858
 
    def test_stream_source_to_non_exact_rich_root(self):
859
 
        source = self.make_repository('source', format='1.9')
860
 
        target = self.make_repository('target', format='1.9-rich-root')
861
 
        stream = source._get_source(target._format)
862
 
        self.assertIs(type(stream), repository.StreamSource)
863
 
 
864
 
    def test_source_to_remote_non_exact_pack_19(self):
865
 
        trans = self.make_smart_server('target')
866
 
        trans.ensure_base()
867
 
        source = self.make_repository('source', format='1.9')
868
 
        target = self.make_repository('target', format='1.6')
869
 
        target = repository.Repository.open(trans.base)
870
 
        stream_source = source._get_source(target._format)
871
 
        self.assertIs(type(stream_source), repository.StreamSource)
872
 
 
873
 
    def test_stream_source_to_knit(self):
874
 
        source = self.make_repository('source', format='pack-0.92')
875
 
        target = self.make_repository('target', format='dirstate')
876
 
        stream = source._get_source(target._format)
877
 
        self.assertIs(type(stream), repository.StreamSource)
878
 
 
879
 
 
880
 
class TestDevelopment6FindParentIdsOfRevisions(TestCaseWithTransport):
881
 
    """Tests for _find_parent_ids_of_revisions."""
882
 
 
883
 
    def setUp(self):
884
 
        super(TestDevelopment6FindParentIdsOfRevisions, self).setUp()
885
 
        self.builder = self.make_branch_builder('source',
886
 
            format='development6-rich-root')
887
 
        self.builder.start_series()
888
 
        self.builder.build_snapshot('initial', None,
889
 
            [('add', ('', 'tree-root', 'directory', None))])
890
 
        self.repo = self.builder.get_branch().repository
891
 
        self.addCleanup(self.builder.finish_series)
892
 
 
893
 
    def assertParentIds(self, expected_result, rev_set):
894
 
        self.assertEqual(sorted(expected_result),
895
 
            sorted(self.repo._find_parent_ids_of_revisions(rev_set)))
896
 
 
897
 
    def test_simple(self):
898
 
        self.builder.build_snapshot('revid1', None, [])
899
 
        self.builder.build_snapshot('revid2', ['revid1'], [])
900
 
        rev_set = ['revid2']
901
 
        self.assertParentIds(['revid1'], rev_set)
902
 
 
903
 
    def test_not_first_parent(self):
904
 
        self.builder.build_snapshot('revid1', None, [])
905
 
        self.builder.build_snapshot('revid2', ['revid1'], [])
906
 
        self.builder.build_snapshot('revid3', ['revid2'], [])
907
 
        rev_set = ['revid3', 'revid2']
908
 
        self.assertParentIds(['revid1'], rev_set)
909
 
 
910
 
    def test_not_null(self):
911
 
        rev_set = ['initial']
912
 
        self.assertParentIds([], rev_set)
913
 
 
914
 
    def test_not_null_set(self):
915
 
        self.builder.build_snapshot('revid1', None, [])
916
 
        rev_set = [_mod_revision.NULL_REVISION]
917
 
        self.assertParentIds([], rev_set)
918
 
 
919
 
    def test_ghost(self):
920
 
        self.builder.build_snapshot('revid1', None, [])
921
 
        rev_set = ['ghost', 'revid1']
922
 
        self.assertParentIds(['initial'], rev_set)
923
 
 
924
 
    def test_ghost_parent(self):
925
 
        self.builder.build_snapshot('revid1', None, [])
926
 
        self.builder.build_snapshot('revid2', ['revid1', 'ghost'], [])
927
 
        rev_set = ['revid2', 'revid1']
928
 
        self.assertParentIds(['ghost', 'initial'], rev_set)
929
 
 
930
 
    def test_righthand_parent(self):
931
 
        self.builder.build_snapshot('revid1', None, [])
932
 
        self.builder.build_snapshot('revid2a', ['revid1'], [])
933
 
        self.builder.build_snapshot('revid2b', ['revid1'], [])
934
 
        self.builder.build_snapshot('revid3', ['revid2a', 'revid2b'], [])
935
 
        rev_set = ['revid3', 'revid2a']
936
 
        self.assertParentIds(['revid1', 'revid2b'], rev_set)
937
 
 
938
 
 
939
659
class TestWithBrokenRepo(TestCaseWithTransport):
940
660
    """These tests seem to be more appropriate as interface tests?"""
941
661
 
1016
736
        """
1017
737
        broken_repo = self.make_broken_repository()
1018
738
        empty_repo = self.make_repository('empty-repo')
1019
 
        try:
1020
 
            empty_repo.fetch(broken_repo)
1021
 
        except (errors.RevisionNotPresent, errors.BzrCheckError):
1022
 
            # Test successful: compression parent not being copied leads to
1023
 
            # error.
1024
 
            return
1025
 
        empty_repo.lock_read()
1026
 
        self.addCleanup(empty_repo.unlock)
1027
 
        text = empty_repo.texts.get_record_stream(
1028
 
            [('file2-id', 'rev3')], 'topological', True).next()
1029
 
        self.assertEqual('line\n', text.get_bytes_as('fulltext'))
 
739
        self.assertRaises(errors.RevisionNotPresent, empty_repo.fetch, broken_repo)
1030
740
 
1031
741
 
1032
742
class TestRepositoryPackCollection(TestCaseWithTransport):
1034
744
    def get_format(self):
1035
745
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
1036
746
 
1037
 
    def get_packs(self):
1038
 
        format = self.get_format()
1039
 
        repo = self.make_repository('.', format=format)
1040
 
        return repo._pack_collection
1041
 
 
1042
 
    def make_packs_and_alt_repo(self, write_lock=False):
1043
 
        """Create a pack repo with 3 packs, and access it via a second repo."""
1044
 
        tree = self.make_branch_and_tree('.', format=self.get_format())
1045
 
        tree.lock_write()
1046
 
        self.addCleanup(tree.unlock)
1047
 
        rev1 = tree.commit('one')
1048
 
        rev2 = tree.commit('two')
1049
 
        rev3 = tree.commit('three')
1050
 
        r = repository.Repository.open('.')
1051
 
        if write_lock:
1052
 
            r.lock_write()
1053
 
        else:
1054
 
            r.lock_read()
1055
 
        self.addCleanup(r.unlock)
1056
 
        packs = r._pack_collection
1057
 
        packs.ensure_loaded()
1058
 
        return tree, r, packs, [rev1, rev2, rev3]
1059
 
 
1060
747
    def test__max_pack_count(self):
1061
748
        """The maximum pack count is a function of the number of revisions."""
 
749
        format = self.get_format()
 
750
        repo = self.make_repository('.', format=format)
 
751
        packs = repo._pack_collection
1062
752
        # no revisions - one pack, so that we can have a revision free repo
1063
753
        # without it blowing up
1064
 
        packs = self.get_packs()
1065
754
        self.assertEqual(1, packs._max_pack_count(0))
1066
755
        # after that the sum of the digits, - check the first 1-9
1067
756
        self.assertEqual(1, packs._max_pack_count(1))
1083
772
        self.assertEqual(25, packs._max_pack_count(112894))
1084
773
 
1085
774
    def test_pack_distribution_zero(self):
1086
 
        packs = self.get_packs()
 
775
        format = self.get_format()
 
776
        repo = self.make_repository('.', format=format)
 
777
        packs = repo._pack_collection
1087
778
        self.assertEqual([0], packs.pack_distribution(0))
1088
779
 
1089
780
    def test_ensure_loaded_unlocked(self):
1090
 
        packs = self.get_packs()
 
781
        format = self.get_format()
 
782
        repo = self.make_repository('.', format=format)
1091
783
        self.assertRaises(errors.ObjectNotLocked,
1092
 
                          packs.ensure_loaded)
 
784
                          repo._pack_collection.ensure_loaded)
1093
785
 
1094
786
    def test_pack_distribution_one_to_nine(self):
1095
 
        packs = self.get_packs()
 
787
        format = self.get_format()
 
788
        repo = self.make_repository('.', format=format)
 
789
        packs = repo._pack_collection
1096
790
        self.assertEqual([1],
1097
791
            packs.pack_distribution(1))
1098
792
        self.assertEqual([1, 1],
1114
808
 
1115
809
    def test_pack_distribution_stable_at_boundaries(self):
1116
810
        """When there are multi-rev packs the counts are stable."""
1117
 
        packs = self.get_packs()
 
811
        format = self.get_format()
 
812
        repo = self.make_repository('.', format=format)
 
813
        packs = repo._pack_collection
1118
814
        # in 10s:
1119
815
        self.assertEqual([10], packs.pack_distribution(10))
1120
816
        self.assertEqual([10, 1], packs.pack_distribution(11))
1129
825
        self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
1130
826
 
1131
827
    def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
1132
 
        packs = self.get_packs()
 
828
        format = self.get_format()
 
829
        repo = self.make_repository('.', format=format)
 
830
        packs = repo._pack_collection
1133
831
        existing_packs = [(2000, "big"), (9, "medium")]
1134
832
        # rev count - 2009 -> 2x1000 + 9x1
1135
833
        pack_operations = packs.plan_autopack_combinations(
1137
835
        self.assertEqual([], pack_operations)
1138
836
 
1139
837
    def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
1140
 
        packs = self.get_packs()
 
838
        format = self.get_format()
 
839
        repo = self.make_repository('.', format=format)
 
840
        packs = repo._pack_collection
1141
841
        existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
1142
842
        # rev count - 2010 -> 2x1000 + 1x10
1143
843
        pack_operations = packs.plan_autopack_combinations(
1145
845
        self.assertEqual([], pack_operations)
1146
846
 
1147
847
    def test_plan_pack_operations_2010_combines_smallest_two(self):
1148
 
        packs = self.get_packs()
 
848
        format = self.get_format()
 
849
        repo = self.make_repository('.', format=format)
 
850
        packs = repo._pack_collection
1149
851
        existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
1150
852
            (1, "single1")]
1151
853
        # rev count - 2010 -> 2x1000 + 1x10 (3)
1152
854
        pack_operations = packs.plan_autopack_combinations(
1153
855
            existing_packs, [1000, 1000, 10])
1154
 
        self.assertEqual([[2, ["single2", "single1"]]], pack_operations)
1155
 
 
1156
 
    def test_plan_pack_operations_creates_a_single_op(self):
1157
 
        packs = self.get_packs()
1158
 
        existing_packs = [(50, 'a'), (40, 'b'), (30, 'c'), (10, 'd'),
1159
 
                          (10, 'e'), (6, 'f'), (4, 'g')]
1160
 
        # rev count 150 -> 1x100 and 5x10
1161
 
        # The two size 10 packs do not need to be touched. The 50, 40, 30 would
1162
 
        # be combined into a single 120 size pack, and the 6 & 4 would
1163
 
        # becombined into a size 10 pack. However, if we have to rewrite them,
1164
 
        # we save a pack file with no increased I/O by putting them into the
1165
 
        # same file.
1166
 
        distribution = packs.pack_distribution(150)
1167
 
        pack_operations = packs.plan_autopack_combinations(existing_packs,
1168
 
                                                           distribution)
1169
 
        self.assertEqual([[130, ['a', 'b', 'c', 'f', 'g']]], pack_operations)
 
856
        self.assertEqual([[2, ["single2", "single1"]], [0, []]], pack_operations)
1170
857
 
1171
858
    def test_all_packs_none(self):
1172
859
        format = self.get_format()
1210
897
        tree.lock_read()
1211
898
        self.addCleanup(tree.unlock)
1212
899
        packs = tree.branch.repository._pack_collection
1213
 
        packs.reset()
1214
900
        packs.ensure_loaded()
1215
901
        name = packs.names()[0]
1216
902
        pack_1 = packs.get_pack_by_name(name)
1225
911
        # and the same instance should be returned on successive calls.
1226
912
        self.assertTrue(pack_1 is packs.get_pack_by_name(name))
1227
913
 
1228
 
    def test_reload_pack_names_new_entry(self):
1229
 
        tree, r, packs, revs = self.make_packs_and_alt_repo()
1230
 
        names = packs.names()
1231
 
        # Add a new pack file into the repository
1232
 
        rev4 = tree.commit('four')
1233
 
        new_names = tree.branch.repository._pack_collection.names()
1234
 
        new_name = set(new_names).difference(names)
1235
 
        self.assertEqual(1, len(new_name))
1236
 
        new_name = new_name.pop()
1237
 
        # The old collection hasn't noticed yet
1238
 
        self.assertEqual(names, packs.names())
1239
 
        self.assertTrue(packs.reload_pack_names())
1240
 
        self.assertEqual(new_names, packs.names())
1241
 
        # And the repository can access the new revision
1242
 
        self.assertEqual({rev4:(revs[-1],)}, r.get_parent_map([rev4]))
1243
 
        self.assertFalse(packs.reload_pack_names())
1244
 
 
1245
 
    def test_reload_pack_names_added_and_removed(self):
1246
 
        tree, r, packs, revs = self.make_packs_and_alt_repo()
1247
 
        names = packs.names()
1248
 
        # Now repack the whole thing
1249
 
        tree.branch.repository.pack()
1250
 
        new_names = tree.branch.repository._pack_collection.names()
1251
 
        # The other collection hasn't noticed yet
1252
 
        self.assertEqual(names, packs.names())
1253
 
        self.assertTrue(packs.reload_pack_names())
1254
 
        self.assertEqual(new_names, packs.names())
1255
 
        self.assertEqual({revs[-1]:(revs[-2],)}, r.get_parent_map([revs[-1]]))
1256
 
        self.assertFalse(packs.reload_pack_names())
1257
 
 
1258
 
    def test_autopack_reloads_and_stops(self):
1259
 
        tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True)
1260
 
        # After we have determined what needs to be autopacked, trigger a
1261
 
        # full-pack via the other repo which will cause us to re-evaluate and
1262
 
        # decide we don't need to do anything
1263
 
        orig_execute = packs._execute_pack_operations
1264
 
        def _munged_execute_pack_ops(*args, **kwargs):
1265
 
            tree.branch.repository.pack()
1266
 
            return orig_execute(*args, **kwargs)
1267
 
        packs._execute_pack_operations = _munged_execute_pack_ops
1268
 
        packs._max_pack_count = lambda x: 1
1269
 
        packs.pack_distribution = lambda x: [10]
1270
 
        self.assertFalse(packs.autopack())
1271
 
        self.assertEqual(1, len(packs.names()))
1272
 
        self.assertEqual(tree.branch.repository._pack_collection.names(),
1273
 
                         packs.names())
1274
 
 
1275
914
 
1276
915
class TestPack(TestCaseWithTransport):
1277
916
    """Tests for the Pack object."""
1331
970
        pack_transport = self.get_transport('pack')
1332
971
        index_transport = self.get_transport('index')
1333
972
        upload_transport.mkdir('.')
1334
 
        collection = pack_repo.RepositoryPackCollection(
1335
 
            repo=None,
1336
 
            transport=self.get_transport('.'),
1337
 
            index_transport=index_transport,
1338
 
            upload_transport=upload_transport,
1339
 
            pack_transport=pack_transport,
1340
 
            index_builder_class=BTreeBuilder,
1341
 
            index_class=BTreeGraphIndex,
1342
 
            use_chk_index=False)
1343
 
        pack = pack_repo.NewPack(collection)
1344
 
        self.assertIsInstance(pack.revision_index, BTreeBuilder)
1345
 
        self.assertIsInstance(pack.inventory_index, BTreeBuilder)
1346
 
        self.assertIsInstance(pack._hash, type(osutils.md5()))
 
973
        pack = pack_repo.NewPack(upload_transport, index_transport,
 
974
            pack_transport)
 
975
        self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
 
976
        self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
 
977
        self.assertIsInstance(pack._hash, type(md5.new()))
1347
978
        self.assertTrue(pack.upload_transport is upload_transport)
1348
979
        self.assertTrue(pack.index_transport is index_transport)
1349
980
        self.assertTrue(pack.pack_transport is pack_transport)
1356
987
class TestPacker(TestCaseWithTransport):
1357
988
    """Tests for the packs repository Packer class."""
1358
989
 
1359
 
    def test_pack_optimizes_pack_order(self):
1360
 
        builder = self.make_branch_builder('.', format="1.9")
1361
 
        builder.start_series()
1362
 
        builder.build_snapshot('A', None, [
1363
 
            ('add', ('', 'root-id', 'directory', None)),
1364
 
            ('add', ('f', 'f-id', 'file', 'content\n'))])
1365
 
        builder.build_snapshot('B', ['A'],
1366
 
            [('modify', ('f-id', 'new-content\n'))])
1367
 
        builder.build_snapshot('C', ['B'],
1368
 
            [('modify', ('f-id', 'third-content\n'))])
1369
 
        builder.build_snapshot('D', ['C'],
1370
 
            [('modify', ('f-id', 'fourth-content\n'))])
1371
 
        b = builder.get_branch()
1372
 
        b.lock_read()
1373
 
        builder.finish_series()
1374
 
        self.addCleanup(b.unlock)
1375
 
        # At this point, we should have 4 pack files available
1376
 
        # Because of how they were built, they correspond to
1377
 
        # ['D', 'C', 'B', 'A']
1378
 
        packs = b.repository._pack_collection.packs
1379
 
        packer = pack_repo.Packer(b.repository._pack_collection,
1380
 
                                  packs, 'testing',
1381
 
                                  revision_ids=['B', 'C'])
1382
 
        # Now, when we are copying the B & C revisions, their pack files should
1383
 
        # be moved to the front of the stack
1384
 
        # The new ordering moves B & C to the front of the .packs attribute,
1385
 
        # and leaves the others in the original order.
1386
 
        new_packs = [packs[1], packs[2], packs[0], packs[3]]
1387
 
        new_pack = packer.pack()
1388
 
        self.assertEqual(new_packs, packer.packs)
1389
 
 
1390
 
 
1391
 
class TestOptimisingPacker(TestCaseWithTransport):
1392
 
    """Tests for the OptimisingPacker class."""
1393
 
 
1394
 
    def get_pack_collection(self):
1395
 
        repo = self.make_repository('.')
1396
 
        return repo._pack_collection
1397
 
 
1398
 
    def test_open_pack_will_optimise(self):
1399
 
        packer = pack_repo.OptimisingPacker(self.get_pack_collection(),
1400
 
                                            [], '.test')
1401
 
        new_pack = packer.open_pack()
1402
 
        self.assertIsInstance(new_pack, pack_repo.NewPack)
1403
 
        self.assertTrue(new_pack.revision_index._optimize_for_size)
1404
 
        self.assertTrue(new_pack.inventory_index._optimize_for_size)
1405
 
        self.assertTrue(new_pack.text_index._optimize_for_size)
1406
 
        self.assertTrue(new_pack.signature_index._optimize_for_size)
1407
 
 
1408
 
 
1409
 
class TestCrossFormatPacks(TestCaseWithTransport):
1410
 
 
1411
 
    def log_pack(self, hint=None):
1412
 
        self.calls.append(('pack', hint))
1413
 
        self.orig_pack(hint=hint)
1414
 
        if self.expect_hint:
1415
 
            self.assertTrue(hint)
1416
 
 
1417
 
    def run_stream(self, src_fmt, target_fmt, expect_pack_called):
1418
 
        self.expect_hint = expect_pack_called
1419
 
        self.calls = []
1420
 
        source_tree = self.make_branch_and_tree('src', format=src_fmt)
1421
 
        source_tree.lock_write()
1422
 
        self.addCleanup(source_tree.unlock)
1423
 
        tip = source_tree.commit('foo')
1424
 
        target = self.make_repository('target', format=target_fmt)
1425
 
        target.lock_write()
1426
 
        self.addCleanup(target.unlock)
1427
 
        source = source_tree.branch.repository._get_source(target._format)
1428
 
        self.orig_pack = target.pack
1429
 
        target.pack = self.log_pack
1430
 
        search = target.search_missing_revision_ids(
1431
 
            source_tree.branch.repository, tip)
1432
 
        stream = source.get_stream(search)
1433
 
        from_format = source_tree.branch.repository._format
1434
 
        sink = target._get_sink()
1435
 
        sink.insert_stream(stream, from_format, [])
1436
 
        if expect_pack_called:
1437
 
            self.assertLength(1, self.calls)
1438
 
        else:
1439
 
            self.assertLength(0, self.calls)
1440
 
 
1441
 
    def run_fetch(self, src_fmt, target_fmt, expect_pack_called):
1442
 
        self.expect_hint = expect_pack_called
1443
 
        self.calls = []
1444
 
        source_tree = self.make_branch_and_tree('src', format=src_fmt)
1445
 
        source_tree.lock_write()
1446
 
        self.addCleanup(source_tree.unlock)
1447
 
        tip = source_tree.commit('foo')
1448
 
        target = self.make_repository('target', format=target_fmt)
1449
 
        target.lock_write()
1450
 
        self.addCleanup(target.unlock)
1451
 
        source = source_tree.branch.repository
1452
 
        self.orig_pack = target.pack
1453
 
        target.pack = self.log_pack
1454
 
        target.fetch(source)
1455
 
        if expect_pack_called:
1456
 
            self.assertLength(1, self.calls)
1457
 
        else:
1458
 
            self.assertLength(0, self.calls)
1459
 
 
1460
 
    def test_sink_format_hint_no(self):
1461
 
        # When the target format says packing makes no difference, pack is not
1462
 
        # called.
1463
 
        self.run_stream('1.9', 'rich-root-pack', False)
1464
 
 
1465
 
    def test_sink_format_hint_yes(self):
1466
 
        # When the target format says packing makes a difference, pack is
1467
 
        # called.
1468
 
        self.run_stream('1.9', '2a', True)
1469
 
 
1470
 
    def test_sink_format_same_no(self):
1471
 
        # When the formats are the same, pack is not called.
1472
 
        self.run_stream('2a', '2a', False)
1473
 
 
1474
 
    def test_IDS_format_hint_no(self):
1475
 
        # When the target format says packing makes no difference, pack is not
1476
 
        # called.
1477
 
        self.run_fetch('1.9', 'rich-root-pack', False)
1478
 
 
1479
 
    def test_IDS_format_hint_yes(self):
1480
 
        # When the target format says packing makes a difference, pack is
1481
 
        # called.
1482
 
        self.run_fetch('1.9', '2a', True)
1483
 
 
1484
 
    def test_IDS_format_same_no(self):
1485
 
        # When the formats are the same, pack is not called.
1486
 
        self.run_fetch('2a', '2a', False)
 
990
    # To date, this class has been factored out and nothing new added to it;
 
991
    # thus there are not yet any tests.
 
992
 
 
993
 
 
994
class TestInterDifferingSerializer(TestCaseWithTransport):
 
995
 
 
996
    def test_progress_bar(self):
 
997
        tree = self.make_branch_and_tree('tree')
 
998
        tree.commit('rev1', rev_id='rev-1')
 
999
        tree.commit('rev2', rev_id='rev-2')
 
1000
        tree.commit('rev3', rev_id='rev-3')
 
1001
        repo = self.make_repository('repo')
 
1002
        inter_repo = repository.InterDifferingSerializer(
 
1003
            tree.branch.repository, repo)
 
1004
        pb = progress.InstrumentedProgress(to_file=StringIO())
 
1005
        pb.never_throttle = True
 
1006
        inter_repo.fetch('rev-1', pb)
 
1007
        self.assertEqual('Transferring revisions', pb.last_msg)
 
1008
        self.assertEqual(1, pb.last_cnt)
 
1009
        self.assertEqual(1, pb.last_total)
 
1010
        inter_repo.fetch('rev-3', pb)
 
1011
        self.assertEqual(2, pb.last_cnt)
 
1012
        self.assertEqual(2, pb.last_total)