~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_versionedfile.py

  • Committer: Andrew Bennetts
  • Date: 2008-07-03 07:56:02 UTC
  • mto: This revision was merged to the branch mainline in revision 3520.
  • Revision ID: andrew.bennetts@canonical.com-20080703075602-8n055qsfkjijcz6i
Better tests for {pre,post}_change_branch_tip hooks.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# Authors:
4
4
#   Johan Rydberg <jrydberg@gnu.org>
15
15
#
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
19
 
20
20
 
21
21
# TODO: might be nice to create a versionedfile with some type of corruption
22
22
# considered typical and check that it can be detected/corrected.
23
23
 
24
 
from gzip import GzipFile
25
 
from itertools import chain, izip
 
24
from itertools import chain
26
25
from StringIO import StringIO
27
26
 
 
27
import bzrlib
28
28
from bzrlib import (
29
29
    errors,
30
 
    graph as _mod_graph,
31
 
    groupcompress,
32
 
    knit as _mod_knit,
33
30
    osutils,
34
31
    progress,
35
 
    transport,
36
 
    ui,
37
32
    )
38
33
from bzrlib.errors import (
39
34
                           RevisionNotPresent,
40
35
                           RevisionAlreadyPresent,
 
36
                           WeaveParentMismatch
41
37
                           )
 
38
from bzrlib import knit as _mod_knit
42
39
from bzrlib.knit import (
43
40
    cleanup_pack_knit,
44
41
    make_file_factory,
45
42
    make_pack_factory,
 
43
    KnitAnnotateFactory,
 
44
    KnitPlainFactory,
46
45
    )
 
46
from bzrlib.symbol_versioning import one_four, one_five
47
47
from bzrlib.tests import (
48
 
    TestCase,
49
48
    TestCaseWithMemoryTransport,
50
 
    TestNotApplicable,
 
49
    TestScenarioApplier,
51
50
    TestSkipped,
 
51
    condition_isinstance,
 
52
    split_suite_by_condition,
 
53
    iter_suite_tests,
52
54
    )
53
55
from bzrlib.tests.http_utils import TestCaseWithWebserver
 
56
from bzrlib.trace import mutter
 
57
from bzrlib.transport import get_transport
54
58
from bzrlib.transport.memory import MemoryTransport
 
59
from bzrlib.tsort import topo_sort
 
60
from bzrlib.tuned_gzip import GzipFile
55
61
import bzrlib.versionedfile as versionedfile
56
62
from bzrlib.versionedfile import (
57
63
    ConstantMapper,
58
64
    HashEscapedPrefixMapper,
59
65
    PrefixMapper,
60
 
    VirtualVersionedFiles,
61
66
    make_versioned_files_factory,
62
67
    )
63
68
from bzrlib.weave import WeaveFile
64
 
from bzrlib.weavefile import write_weave
65
 
from bzrlib.tests.scenarios import load_tests_apply_scenarios
66
 
 
67
 
 
68
 
load_tests = load_tests_apply_scenarios
 
69
from bzrlib.weavefile import read_weave, write_weave
 
70
 
 
71
 
 
72
def load_tests(standard_tests, module, loader):
 
73
    """Parameterize VersionedFiles tests for different implementations."""
 
74
    to_adapt, result = split_suite_by_condition(
 
75
        standard_tests, condition_isinstance(TestVersionedFiles))
 
76
    len_one_adapter = TestScenarioApplier()
 
77
    len_two_adapter = TestScenarioApplier()
 
78
    # We want to be sure of behaviour for:
 
79
    # weaves prefix layout (weave texts)
 
80
    # individually named weaves (weave inventories)
 
81
    # annotated knits - prefix|hash|hash-escape layout, we test the third only
 
82
    #                   as it is the most complex mapper.
 
83
    # individually named knits
 
84
    # individual no-graph knits in packs (signatures)
 
85
    # individual graph knits in packs (inventories)
 
86
    # individual graph nocompression knits in packs (revisions)
 
87
    # plain text knits in packs (texts)
 
88
    len_one_adapter.scenarios = [
 
89
        ('weave-named', {
 
90
            'cleanup':None,
 
91
            'factory':make_versioned_files_factory(WeaveFile,
 
92
                ConstantMapper('inventory')),
 
93
            'graph':True,
 
94
            'key_length':1,
 
95
            }),
 
96
        ('named-knit', {
 
97
            'cleanup':None,
 
98
            'factory':make_file_factory(False, ConstantMapper('revisions')),
 
99
            'graph':True,
 
100
            'key_length':1,
 
101
            }),
 
102
        ('named-nograph-knit-pack', {
 
103
            'cleanup':cleanup_pack_knit,
 
104
            'factory':make_pack_factory(False, False, 1),
 
105
            'graph':False,
 
106
            'key_length':1,
 
107
            }),
 
108
        ('named-graph-knit-pack', {
 
109
            'cleanup':cleanup_pack_knit,
 
110
            'factory':make_pack_factory(True, True, 1),
 
111
            'graph':True,
 
112
            'key_length':1,
 
113
            }),
 
114
        ('named-graph-nodelta-knit-pack', {
 
115
            'cleanup':cleanup_pack_knit,
 
116
            'factory':make_pack_factory(True, False, 1),
 
117
            'graph':True,
 
118
            'key_length':1,
 
119
            }),
 
120
        ]
 
121
    len_two_adapter.scenarios = [
 
122
        ('weave-prefix', {
 
123
            'cleanup':None,
 
124
            'factory':make_versioned_files_factory(WeaveFile,
 
125
                PrefixMapper()),
 
126
            'graph':True,
 
127
            'key_length':2,
 
128
            }),
 
129
        ('annotated-knit-escape', {
 
130
            'cleanup':None,
 
131
            'factory':make_file_factory(True, HashEscapedPrefixMapper()),
 
132
            'graph':True,
 
133
            'key_length':2,
 
134
            }),
 
135
        ('plain-knit-pack', {
 
136
            'cleanup':cleanup_pack_knit,
 
137
            'factory':make_pack_factory(True, True, 2),
 
138
            'graph':True,
 
139
            'key_length':2,
 
140
            }),
 
141
        ]
 
142
    for test in iter_suite_tests(to_adapt):
 
143
        result.addTests(len_one_adapter.adapt(test))
 
144
        result.addTests(len_two_adapter.adapt(test))
 
145
    return result
69
146
 
70
147
 
71
148
def get_diamond_vf(f, trailing_eol=True, left_only=False):
72
149
    """Get a diamond graph to exercise deltas and merges.
73
 
 
 
150
    
74
151
    :param trailing_eol: If True end the last line with \n.
75
152
    """
76
153
    parents = {
97
174
 
98
175
 
99
176
def get_diamond_files(files, key_length, trailing_eol=True, left_only=False,
100
 
    nograph=False, nokeys=False):
 
177
    nograph=False):
101
178
    """Get a diamond graph to exercise deltas and merges.
102
179
 
103
180
    This creates a 5-node graph in files. If files supports 2-length keys two
104
181
    graphs are made to exercise the support for multiple ids.
105
 
 
 
182
    
106
183
    :param trailing_eol: If True end the last line with \n.
107
184
    :param key_length: The length of keys in files. Currently supports length 1
108
185
        and 2 keys.
110
187
    :param nograph: If True, do not provide parents to the add_lines calls;
111
188
        this is useful for tests that need inserted data but have graphless
112
189
        stores.
113
 
    :param nokeys: If True, pass None is as the key for all insertions.
114
 
        Currently implies nograph.
115
190
    :return: The results of the add_lines calls.
116
191
    """
117
 
    if nokeys:
118
 
        nograph = True
119
192
    if key_length == 1:
120
193
        prefixes = [()]
121
194
    else:
132
205
        else:
133
206
            result = [prefix + suffix for suffix in suffix_list]
134
207
            return result
135
 
    def get_key(suffix):
136
 
        if nokeys:
137
 
            return (None, )
138
 
        else:
139
 
            return (suffix,)
140
208
    # we loop over each key because that spreads the inserts across prefixes,
141
209
    # which is how commit operates.
142
210
    for prefix in prefixes:
143
 
        result.append(files.add_lines(prefix + get_key('origin'), (),
 
211
        result.append(files.add_lines(prefix + ('origin',), (),
144
212
            ['origin' + last_char]))
145
213
    for prefix in prefixes:
146
 
        result.append(files.add_lines(prefix + get_key('base'),
 
214
        result.append(files.add_lines(prefix + ('base',),
147
215
            get_parents([('origin',)]), ['base' + last_char]))
148
216
    for prefix in prefixes:
149
 
        result.append(files.add_lines(prefix + get_key('left'),
 
217
        result.append(files.add_lines(prefix + ('left',),
150
218
            get_parents([('base',)]),
151
219
            ['base\n', 'left' + last_char]))
152
220
    if not left_only:
153
221
        for prefix in prefixes:
154
 
            result.append(files.add_lines(prefix + get_key('right'),
 
222
            result.append(files.add_lines(prefix + ('right',),
155
223
                get_parents([('base',)]),
156
224
                ['base\n', 'right' + last_char]))
157
225
        for prefix in prefixes:
158
 
            result.append(files.add_lines(prefix + get_key('merged'),
 
226
            result.append(files.add_lines(prefix + ('merged',),
159
227
                get_parents([('left',), ('right',)]),
160
228
                ['base\n', 'left\n', 'right\n', 'merged' + last_char]))
161
229
    return result
187
255
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
188
256
            self.assertEqual(2, len(f))
189
257
            self.assertEqual(2, f.num_versions())
190
 
 
 
258
    
191
259
            self.assertRaises(RevisionNotPresent,
192
260
                f.add_lines, 'r2', ['foo'], [])
193
261
            self.assertRaises(RevisionAlreadyPresent,
232
300
        verify_file(f)
233
301
 
234
302
    def test_add_unicode_content(self):
235
 
        # unicode content is not permitted in versioned files.
 
303
        # unicode content is not permitted in versioned files. 
236
304
        # versioned files version sequences of bytes only.
237
305
        vf = self.get_file()
238
306
        self.assertRaises(errors.BzrBadParameterUnicode,
261
329
    def test_inline_newline_throws(self):
262
330
        # \r characters are not permitted in lines being added
263
331
        vf = self.get_file()
264
 
        self.assertRaises(errors.BzrBadParameterContainsNewline,
 
332
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
265
333
            vf.add_lines, 'a', [], ['a\n\n'])
266
334
        self.assertRaises(
267
335
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
466
534
        f.add_lines('noeolbase', [], ['line'])
467
535
        # noeol preceeding its leftmost parent in the output:
468
536
        # this is done by making it a merge of two parents with no common
469
 
        # anestry: noeolbase and noeol with the
 
537
        # anestry: noeolbase and noeol with the 
470
538
        # later-inserted parent the leftmost.
471
539
        f.add_lines('eolbeforefirstparent', ['noeolbase', 'noeol'], ['line'])
472
540
        # two identical eol texts
553
621
        self._transaction = 'after'
554
622
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
555
623
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
556
 
 
 
624
        
557
625
    def test_copy_to(self):
558
626
        f = self.get_file()
559
627
        f.add_lines('0', [], ['a\n'])
632
700
 
633
701
    def test_iter_lines_added_or_present_in_versions(self):
634
702
        # test that we get at least an equalset of the lines added by
635
 
        # versions in the weave
 
703
        # versions in the weave 
636
704
        # the ordering here is to make a tree so that dumb searches have
637
705
        # more changes to muck up.
638
706
 
639
 
        class InstrumentedProgress(progress.ProgressTask):
 
707
        class InstrumentedProgress(progress.DummyProgress):
640
708
 
641
709
            def __init__(self):
642
 
                progress.ProgressTask.__init__(self)
 
710
 
 
711
                progress.DummyProgress.__init__(self)
643
712
                self.updates = []
644
713
 
645
714
            def update(self, msg=None, current=None, total=None):
671
740
                self.assertEqual(expected, progress.updates)
672
741
            return lines
673
742
        lines = iter_with_versions(['child', 'otherchild'],
674
 
                                   [('Walking content', 0, 2),
675
 
                                    ('Walking content', 1, 2),
676
 
                                    ('Walking content', 2, 2)])
 
743
                                   [('Walking content.', 0, 2),
 
744
                                    ('Walking content.', 1, 2),
 
745
                                    ('Walking content.', 2, 2)])
677
746
        # we must see child and otherchild
678
747
        self.assertTrue(lines[('child\n', 'child')] > 0)
679
748
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
680
749
        # we dont care if we got more than that.
681
 
 
 
750
        
682
751
        # test all lines
683
 
        lines = iter_with_versions(None, [('Walking content', 0, 5),
684
 
                                          ('Walking content', 1, 5),
685
 
                                          ('Walking content', 2, 5),
686
 
                                          ('Walking content', 3, 5),
687
 
                                          ('Walking content', 4, 5),
688
 
                                          ('Walking content', 5, 5)])
 
752
        lines = iter_with_versions(None, [('Walking content.', 0, 5),
 
753
                                          ('Walking content.', 1, 5),
 
754
                                          ('Walking content.', 2, 5),
 
755
                                          ('Walking content.', 3, 5),
 
756
                                          ('Walking content.', 4, 5),
 
757
                                          ('Walking content.', 5, 5)])
689
758
        # all lines must be seen at least once
690
759
        self.assertTrue(lines[('base\n', 'base')] > 0)
691
760
        self.assertTrue(lines[('lancestor\n', 'lancestor')] > 0)
751
820
        self.assertEquals(('references_ghost', 'line_c\n'), origins[2])
752
821
 
753
822
    def test_readonly_mode(self):
754
 
        t = self.get_transport()
 
823
        transport = get_transport(self.get_url('.'))
755
824
        factory = self.get_factory()
756
 
        vf = factory('id', t, 0777, create=True, access_mode='w')
757
 
        vf = factory('id', t, access_mode='r')
 
825
        vf = factory('id', transport, 0777, create=True, access_mode='w')
 
826
        vf = factory('id', transport, access_mode='r')
758
827
        self.assertRaises(errors.ReadOnlyError, vf.add_lines, 'base', [], [])
759
828
        self.assertRaises(errors.ReadOnlyError,
760
829
                          vf.add_lines_with_ghosts,
761
830
                          'base',
762
831
                          [],
763
832
                          [])
764
 
 
 
833
    
765
834
    def test_get_sha1s(self):
766
835
        # check the sha1 data is available
767
836
        vf = self.get_file()
777
846
            'b': '3f786850e387550fdab836ed7e6dc881de23001b',
778
847
            },
779
848
            vf.get_sha1s(['a', 'c', 'b']))
780
 
 
 
849
        
781
850
 
782
851
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
783
852
 
784
853
    def get_file(self, name='foo'):
785
 
        return WeaveFile(name, self.get_transport(),
786
 
                         create=True,
787
 
                         get_scope=self.get_transaction)
 
854
        return WeaveFile(name, get_transport(self.get_url('.')), create=True,
 
855
            get_scope=self.get_transaction)
788
856
 
789
857
    def get_file_corrupted_text(self):
790
 
        w = WeaveFile('foo', self.get_transport(),
791
 
                      create=True,
792
 
                      get_scope=self.get_transaction)
 
858
        w = WeaveFile('foo', get_transport(self.get_url('.')), create=True,
 
859
            get_scope=self.get_transaction)
793
860
        w.add_lines('v1', [], ['hello\n'])
794
861
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
795
 
 
 
862
        
796
863
        # We are going to invasively corrupt the text
797
864
        # Make sure the internals of weave are the same
798
865
        self.assertEqual([('{', 0)
802
869
                        , 'there\n'
803
870
                        , ('}', None)
804
871
                        ], w._weave)
805
 
 
 
872
        
806
873
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
807
874
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
808
875
                        ], w._sha1s)
809
876
        w.check()
810
 
 
 
877
        
811
878
        # Corrupted
812
879
        w._weave[4] = 'There\n'
813
880
        return w
817
884
        # Corrected
818
885
        w._weave[4] = 'there\n'
819
886
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
820
 
 
 
887
        
821
888
        #Invalid checksum, first digit changed
822
889
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
823
890
        return w
824
891
 
825
892
    def reopen_file(self, name='foo', create=False):
826
 
        return WeaveFile(name, self.get_transport(),
827
 
                         create=create,
828
 
                         get_scope=self.get_transaction)
 
893
        return WeaveFile(name, get_transport(self.get_url('.')), create=create,
 
894
            get_scope=self.get_transaction)
829
895
 
830
896
    def test_no_implicit_create(self):
831
897
        self.assertRaises(errors.NoSuchFile,
832
898
                          WeaveFile,
833
899
                          'foo',
834
 
                          self.get_transport(),
 
900
                          get_transport(self.get_url('.')),
835
901
                          get_scope=self.get_transaction)
836
902
 
837
903
    def get_factory(self):
904
970
        # we should be able to read from http with a versioned file.
905
971
        vf = self.get_file()
906
972
        # try an empty file access
907
 
        readonly_vf = self.get_factory()('foo', transport.get_transport(
908
 
                self.get_readonly_url('.')))
 
973
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
909
974
        self.assertEqual([], readonly_vf.versions())
910
 
 
911
 
    def test_readonly_http_works_with_feeling(self):
912
 
        # we should be able to read from http with a versioned file.
913
 
        vf = self.get_file()
914
975
        # now with feeling.
915
976
        vf.add_lines('1', [], ['a\n'])
916
977
        vf.add_lines('2', ['1'], ['b\n', 'a\n'])
917
 
        readonly_vf = self.get_factory()('foo', transport.get_transport(
918
 
                self.get_readonly_url('.')))
 
978
        readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
919
979
        self.assertEqual(['1', '2'], vf.versions())
920
 
        self.assertEqual(['1', '2'], readonly_vf.versions())
921
980
        for version in readonly_vf.versions():
922
981
            readonly_vf.get_lines(version)
923
982
 
925
984
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
926
985
 
927
986
    def get_file(self):
928
 
        return WeaveFile('foo', self.get_transport(),
929
 
                         create=True,
930
 
                         get_scope=self.get_transaction)
 
987
        return WeaveFile('foo', get_transport(self.get_url('.')), create=True,
 
988
            get_scope=self.get_transaction)
931
989
 
932
990
    def get_factory(self):
933
991
        return WeaveFile
941
999
 
942
1000
        def addcrlf(x):
943
1001
            return x + '\n'
944
 
 
 
1002
        
945
1003
        w = self.get_file()
946
1004
        w.add_lines('text0', [], map(addcrlf, base))
947
1005
        w.add_lines('text1', ['text0'], map(addcrlf, a))
963
1021
 
964
1022
        mp = map(addcrlf, mp)
965
1023
        self.assertEqual(mt.readlines(), mp)
966
 
 
967
 
 
 
1024
        
 
1025
        
968
1026
    def testOneInsert(self):
969
1027
        self.doMerge([],
970
1028
                     ['aa'],
988
1046
                     ['aaa', 'xxx', 'yyy', 'bbb'],
989
1047
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
990
1048
 
991
 
        # really it ought to reduce this to
 
1049
        # really it ought to reduce this to 
992
1050
        # ['aaa', 'xxx', 'yyy', 'bbb']
993
1051
 
994
1052
 
996
1054
        self.doMerge(['aaa'],
997
1055
                     ['xxx'],
998
1056
                     ['yyy', 'zzz'],
999
 
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
 
1057
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
1000
1058
                      '>>>>>>> '])
1001
1059
 
1002
1060
    def testNonClashInsert1(self):
1003
1061
        self.doMerge(['aaa'],
1004
1062
                     ['xxx', 'aaa'],
1005
1063
                     ['yyy', 'zzz'],
1006
 
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
 
1064
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
1007
1065
                      '>>>>>>> '])
1008
1066
 
1009
1067
    def testNonClashInsert2(self):
1023
1081
        #######################################
1024
1082
        # skippd, not working yet
1025
1083
        return
1026
 
 
 
1084
        
1027
1085
        self.doMerge(['aaa', 'bbb', 'ccc'],
1028
1086
                     ['aaa', 'ddd', 'ccc'],
1029
1087
                     ['aaa', 'ccc'],
1066
1124
            """
1067
1125
        result = """\
1068
1126
            line 1
1069
 
<<<<<<<\x20
1070
 
            line 2
1071
 
=======
1072
 
>>>>>>>\x20
1073
1127
            """
1074
1128
        self._test_merge_from_strings(base, a, b, result)
1075
1129
 
1076
1130
    def test_deletion_overlap(self):
1077
1131
        """Delete overlapping regions with no other conflict.
1078
1132
 
1079
 
        Arguably it'd be better to treat these as agreement, rather than
 
1133
        Arguably it'd be better to treat these as agreement, rather than 
1080
1134
        conflict, but for now conflict is safer.
1081
1135
        """
1082
1136
        base = """\
1098
1152
            """
1099
1153
        result = """\
1100
1154
            start context
1101
 
<<<<<<<\x20
 
1155
<<<<<<< 
1102
1156
            int a() {}
1103
1157
=======
1104
1158
            int c() {}
1105
 
>>>>>>>\x20
 
1159
>>>>>>> 
1106
1160
            end context
1107
1161
            """
1108
1162
        self._test_merge_from_strings(base, a, b, result)
1134
1188
 
1135
1189
    def test_sync_on_deletion(self):
1136
1190
        """Specific case of merge where we can synchronize incorrectly.
1137
 
 
 
1191
        
1138
1192
        A previous version of the weave merge concluded that the two versions
1139
1193
        agreed on deleting line 2, and this could be a synchronization point.
1140
 
        Line 1 was then considered in isolation, and thought to be deleted on
 
1194
        Line 1 was then considered in isolation, and thought to be deleted on 
1141
1195
        both sides.
1142
1196
 
1143
1197
        It's better to consider the whole thing as a disagreement region.
1162
1216
            """
1163
1217
        result = """\
1164
1218
            start context
1165
 
<<<<<<<\x20
 
1219
<<<<<<< 
1166
1220
            base line 1
1167
1221
            a's replacement line 2
1168
1222
=======
1169
1223
            b replaces
1170
1224
            both lines
1171
 
>>>>>>>\x20
 
1225
>>>>>>> 
1172
1226
            end context
1173
1227
            """
1174
1228
        self._test_merge_from_strings(base, a, b, result)
1177
1231
class TestWeaveMerge(TestCaseWithMemoryTransport, MergeCasesMixin):
1178
1232
 
1179
1233
    def get_file(self, name='foo'):
1180
 
        return WeaveFile(name, self.get_transport(),
1181
 
                         create=True)
 
1234
        return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1182
1235
 
1183
1236
    def log_contents(self, w):
1184
1237
        self.log('weave is:')
1186
1239
        write_weave(w, tmpf)
1187
1240
        self.log(tmpf.getvalue())
1188
1241
 
1189
 
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
 
1242
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
1190
1243
                                'xxx', '>>>>>>> ', 'bbb']
1191
1244
 
1192
1245
 
1224
1277
        # origin is a fulltext
1225
1278
        entries = f.get_record_stream([('origin',)], 'unordered', False)
1226
1279
        base = entries.next()
1227
 
        ft_data = ft_adapter.get_bytes(base)
 
1280
        ft_data = ft_adapter.get_bytes(base, base.get_bytes_as(base.storage_kind))
1228
1281
        # merged is both a delta and multiple parents.
1229
1282
        entries = f.get_record_stream([('merged',)], 'unordered', False)
1230
1283
        merged = entries.next()
1231
 
        delta_data = delta_adapter.get_bytes(merged)
 
1284
        delta_data = delta_adapter.get_bytes(merged,
 
1285
            merged.get_bytes_as(merged.storage_kind))
1232
1286
        return ft_data, delta_data
1233
1287
 
1234
1288
    def test_deannotation_noeol(self):
1301
1355
 
1302
1356
    def test_unannotated_to_fulltext(self):
1303
1357
        """Test adapting unannotated knits to full texts.
1304
 
 
 
1358
        
1305
1359
        This is used for -> weaves, and for -> annotated knits.
1306
1360
        """
1307
1361
        # we need a full text, and a delta
1320
1374
 
1321
1375
    def test_unannotated_to_fulltext_no_eol(self):
1322
1376
        """Test adapting unannotated knits to full texts.
1323
 
 
 
1377
        
1324
1378
        This is used for -> weaves, and for -> annotated knits.
1325
1379
        """
1326
1380
        # we need a full text, and a delta
1377
1431
class TestVersionedFiles(TestCaseWithMemoryTransport):
1378
1432
    """Tests for the multiple-file variant of VersionedFile."""
1379
1433
 
1380
 
    # We want to be sure of behaviour for:
1381
 
    # weaves prefix layout (weave texts)
1382
 
    # individually named weaves (weave inventories)
1383
 
    # annotated knits - prefix|hash|hash-escape layout, we test the third only
1384
 
    #                   as it is the most complex mapper.
1385
 
    # individually named knits
1386
 
    # individual no-graph knits in packs (signatures)
1387
 
    # individual graph knits in packs (inventories)
1388
 
    # individual graph nocompression knits in packs (revisions)
1389
 
    # plain text knits in packs (texts)
1390
 
    len_one_scenarios = [
1391
 
        ('weave-named', {
1392
 
            'cleanup':None,
1393
 
            'factory':make_versioned_files_factory(WeaveFile,
1394
 
                ConstantMapper('inventory')),
1395
 
            'graph':True,
1396
 
            'key_length':1,
1397
 
            'support_partial_insertion': False,
1398
 
            }),
1399
 
        ('named-knit', {
1400
 
            'cleanup':None,
1401
 
            'factory':make_file_factory(False, ConstantMapper('revisions')),
1402
 
            'graph':True,
1403
 
            'key_length':1,
1404
 
            'support_partial_insertion': False,
1405
 
            }),
1406
 
        ('named-nograph-nodelta-knit-pack', {
1407
 
            'cleanup':cleanup_pack_knit,
1408
 
            'factory':make_pack_factory(False, False, 1),
1409
 
            'graph':False,
1410
 
            'key_length':1,
1411
 
            'support_partial_insertion': False,
1412
 
            }),
1413
 
        ('named-graph-knit-pack', {
1414
 
            'cleanup':cleanup_pack_knit,
1415
 
            'factory':make_pack_factory(True, True, 1),
1416
 
            'graph':True,
1417
 
            'key_length':1,
1418
 
            'support_partial_insertion': True,
1419
 
            }),
1420
 
        ('named-graph-nodelta-knit-pack', {
1421
 
            'cleanup':cleanup_pack_knit,
1422
 
            'factory':make_pack_factory(True, False, 1),
1423
 
            'graph':True,
1424
 
            'key_length':1,
1425
 
            'support_partial_insertion': False,
1426
 
            }),
1427
 
        ('groupcompress-nograph', {
1428
 
            'cleanup':groupcompress.cleanup_pack_group,
1429
 
            'factory':groupcompress.make_pack_factory(False, False, 1),
1430
 
            'graph': False,
1431
 
            'key_length':1,
1432
 
            'support_partial_insertion':False,
1433
 
            }),
1434
 
        ]
1435
 
    len_two_scenarios = [
1436
 
        ('weave-prefix', {
1437
 
            'cleanup':None,
1438
 
            'factory':make_versioned_files_factory(WeaveFile,
1439
 
                PrefixMapper()),
1440
 
            'graph':True,
1441
 
            'key_length':2,
1442
 
            'support_partial_insertion': False,
1443
 
            }),
1444
 
        ('annotated-knit-escape', {
1445
 
            'cleanup':None,
1446
 
            'factory':make_file_factory(True, HashEscapedPrefixMapper()),
1447
 
            'graph':True,
1448
 
            'key_length':2,
1449
 
            'support_partial_insertion': False,
1450
 
            }),
1451
 
        ('plain-knit-pack', {
1452
 
            'cleanup':cleanup_pack_knit,
1453
 
            'factory':make_pack_factory(True, True, 2),
1454
 
            'graph':True,
1455
 
            'key_length':2,
1456
 
            'support_partial_insertion': True,
1457
 
            }),
1458
 
        ('groupcompress', {
1459
 
            'cleanup':groupcompress.cleanup_pack_group,
1460
 
            'factory':groupcompress.make_pack_factory(True, False, 1),
1461
 
            'graph': True,
1462
 
            'key_length':1,
1463
 
            'support_partial_insertion':False,
1464
 
            }),
1465
 
        ]
1466
 
 
1467
 
    scenarios = len_one_scenarios + len_two_scenarios
1468
 
 
1469
1434
    def get_versionedfiles(self, relpath='files'):
1470
1435
        transport = self.get_transport(relpath)
1471
1436
        if relpath != '.':
1472
1437
            transport.mkdir('.')
1473
1438
        files = self.factory(transport)
1474
1439
        if self.cleanup is not None:
1475
 
            self.addCleanup(self.cleanup, files)
 
1440
            self.addCleanup(lambda:self.cleanup(files))
1476
1441
        return files
1477
1442
 
1478
 
    def get_simple_key(self, suffix):
1479
 
        """Return a key for the object under test."""
1480
 
        if self.key_length == 1:
1481
 
            return (suffix,)
1482
 
        else:
1483
 
            return ('FileA',) + (suffix,)
1484
 
 
1485
 
    def test_add_lines(self):
1486
 
        f = self.get_versionedfiles()
1487
 
        key0 = self.get_simple_key('r0')
1488
 
        key1 = self.get_simple_key('r1')
1489
 
        key2 = self.get_simple_key('r2')
1490
 
        keyf = self.get_simple_key('foo')
1491
 
        f.add_lines(key0, [], ['a\n', 'b\n'])
1492
 
        if self.graph:
1493
 
            f.add_lines(key1, [key0], ['b\n', 'c\n'])
1494
 
        else:
1495
 
            f.add_lines(key1, [], ['b\n', 'c\n'])
1496
 
        keys = f.keys()
1497
 
        self.assertTrue(key0 in keys)
1498
 
        self.assertTrue(key1 in keys)
1499
 
        records = []
1500
 
        for record in f.get_record_stream([key0, key1], 'unordered', True):
1501
 
            records.append((record.key, record.get_bytes_as('fulltext')))
1502
 
        records.sort()
1503
 
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
1504
 
 
1505
 
    def test__add_text(self):
1506
 
        f = self.get_versionedfiles()
1507
 
        key0 = self.get_simple_key('r0')
1508
 
        key1 = self.get_simple_key('r1')
1509
 
        key2 = self.get_simple_key('r2')
1510
 
        keyf = self.get_simple_key('foo')
1511
 
        f._add_text(key0, [], 'a\nb\n')
1512
 
        if self.graph:
1513
 
            f._add_text(key1, [key0], 'b\nc\n')
1514
 
        else:
1515
 
            f._add_text(key1, [], 'b\nc\n')
1516
 
        keys = f.keys()
1517
 
        self.assertTrue(key0 in keys)
1518
 
        self.assertTrue(key1 in keys)
1519
 
        records = []
1520
 
        for record in f.get_record_stream([key0, key1], 'unordered', True):
1521
 
            records.append((record.key, record.get_bytes_as('fulltext')))
1522
 
        records.sort()
1523
 
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
1524
 
 
1525
1443
    def test_annotate(self):
1526
1444
        files = self.get_versionedfiles()
1527
1445
        self.get_diamond_files(files)
1561
1479
        self.assertRaises(RevisionNotPresent,
1562
1480
            files.annotate, prefix + ('missing-key',))
1563
1481
 
1564
 
    def test_check_no_parameters(self):
1565
 
        files = self.get_versionedfiles()
1566
 
 
1567
 
    def test_check_progressbar_parameter(self):
1568
 
        """A progress bar can be supplied because check can be a generator."""
1569
 
        pb = ui.ui_factory.nested_progress_bar()
1570
 
        self.addCleanup(pb.finished)
1571
 
        files = self.get_versionedfiles()
1572
 
        files.check(progress_bar=pb)
1573
 
 
1574
 
    def test_check_with_keys_becomes_generator(self):
1575
 
        files = self.get_versionedfiles()
1576
 
        self.get_diamond_files(files)
1577
 
        keys = files.keys()
1578
 
        entries = files.check(keys=keys)
1579
 
        seen = set()
1580
 
        # Texts output should be fulltexts.
1581
 
        self.capture_stream(files, entries, seen.add,
1582
 
            files.get_parent_map(keys), require_fulltext=True)
1583
 
        # All texts should be output.
1584
 
        self.assertEqual(set(keys), seen)
1585
 
 
1586
 
    def test_clear_cache(self):
1587
 
        files = self.get_versionedfiles()
1588
 
        files.clear_cache()
1589
 
 
1590
1482
    def test_construct(self):
1591
1483
        """Each parameterised test can be constructed on a transport."""
1592
1484
        files = self.get_versionedfiles()
1593
1485
 
1594
 
    def get_diamond_files(self, files, trailing_eol=True, left_only=False,
1595
 
        nokeys=False):
 
1486
    def get_diamond_files(self, files, trailing_eol=True, left_only=False):
1596
1487
        return get_diamond_files(files, self.key_length,
1597
1488
            trailing_eol=trailing_eol, nograph=not self.graph,
1598
 
            left_only=left_only, nokeys=nokeys)
1599
 
 
1600
 
    def _add_content_nostoresha(self, add_lines):
1601
 
        """When nostore_sha is supplied using old content raises."""
1602
 
        vf = self.get_versionedfiles()
1603
 
        empty_text = ('a', [])
1604
 
        sample_text_nl = ('b', ["foo\n", "bar\n"])
1605
 
        sample_text_no_nl = ('c', ["foo\n", "bar"])
1606
 
        shas = []
1607
 
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
1608
 
            if add_lines:
1609
 
                sha, _, _ = vf.add_lines(self.get_simple_key(version), [],
1610
 
                                         lines)
1611
 
            else:
1612
 
                sha, _, _ = vf._add_text(self.get_simple_key(version), [],
1613
 
                                         ''.join(lines))
1614
 
            shas.append(sha)
1615
 
        # we now have a copy of all the lines in the vf.
1616
 
        for sha, (version, lines) in zip(
1617
 
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
1618
 
            new_key = self.get_simple_key(version + "2")
1619
 
            self.assertRaises(errors.ExistingContent,
1620
 
                vf.add_lines, new_key, [], lines,
1621
 
                nostore_sha=sha)
1622
 
            self.assertRaises(errors.ExistingContent,
1623
 
                vf._add_text, new_key, [], ''.join(lines),
1624
 
                nostore_sha=sha)
1625
 
            # and no new version should have been added.
1626
 
            record = vf.get_record_stream([new_key], 'unordered', True).next()
1627
 
            self.assertEqual('absent', record.storage_kind)
1628
 
 
1629
 
    def test_add_lines_nostoresha(self):
1630
 
        self._add_content_nostoresha(add_lines=True)
1631
 
 
1632
 
    def test__add_text_nostoresha(self):
1633
 
        self._add_content_nostoresha(add_lines=False)
 
1489
            left_only=left_only)
1634
1490
 
1635
1491
    def test_add_lines_return(self):
1636
1492
        files = self.get_versionedfiles()
1663
1519
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1664
1520
                results)
1665
1521
 
1666
 
    def test_add_lines_no_key_generates_chk_key(self):
1667
 
        files = self.get_versionedfiles()
1668
 
        # save code by using the stock data insertion helper.
1669
 
        adds = self.get_diamond_files(files, nokeys=True)
1670
 
        results = []
1671
 
        # We can only validate the first 2 elements returned from add_lines.
1672
 
        for add in adds:
1673
 
            self.assertEqual(3, len(add))
1674
 
            results.append(add[:2])
1675
 
        if self.key_length == 1:
1676
 
            self.assertEqual([
1677
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1678
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1679
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1680
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1681
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1682
 
                results)
1683
 
            # Check the added items got CHK keys.
1684
 
            self.assertEqual(set([
1685
 
                ('sha1:00e364d235126be43292ab09cb4686cf703ddc17',),
1686
 
                ('sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',),
1687
 
                ('sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',),
1688
 
                ('sha1:a8478686da38e370e32e42e8a0c220e33ee9132f',),
1689
 
                ('sha1:ed8bce375198ea62444dc71952b22cfc2b09226d',),
1690
 
                ]),
1691
 
                files.keys())
1692
 
        elif self.key_length == 2:
1693
 
            self.assertEqual([
1694
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1695
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1696
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1697
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1698
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1699
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1700
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1701
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1702
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
1703
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1704
 
                results)
1705
 
            # Check the added items got CHK keys.
1706
 
            self.assertEqual(set([
1707
 
                ('FileA', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
1708
 
                ('FileA', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
1709
 
                ('FileA', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
1710
 
                ('FileA', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
1711
 
                ('FileA', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
1712
 
                ('FileB', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
1713
 
                ('FileB', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
1714
 
                ('FileB', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
1715
 
                ('FileB', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
1716
 
                ('FileB', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
1717
 
                ]),
1718
 
                files.keys())
1719
 
 
1720
1522
    def test_empty_lines(self):
1721
1523
        """Empty files can be stored."""
1722
1524
        f = self.get_versionedfiles()
1744
1546
            f.get_record_stream([key_b], 'unordered', True
1745
1547
                ).next().get_bytes_as('fulltext'))
1746
1548
 
1747
 
    def test_get_known_graph_ancestry(self):
1748
 
        f = self.get_versionedfiles()
1749
 
        if not self.graph:
1750
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
1751
 
        key_a = self.get_simple_key('a')
1752
 
        key_b = self.get_simple_key('b')
1753
 
        key_c = self.get_simple_key('c')
1754
 
        # A
1755
 
        # |\
1756
 
        # | B
1757
 
        # |/
1758
 
        # C
1759
 
        f.add_lines(key_a, [], ['\n'])
1760
 
        f.add_lines(key_b, [key_a], ['\n'])
1761
 
        f.add_lines(key_c, [key_a, key_b], ['\n'])
1762
 
        kg = f.get_known_graph_ancestry([key_c])
1763
 
        self.assertIsInstance(kg, _mod_graph.KnownGraph)
1764
 
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
1765
 
 
1766
 
    def test_known_graph_with_fallbacks(self):
1767
 
        f = self.get_versionedfiles('files')
1768
 
        if not self.graph:
1769
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
1770
 
        if getattr(f, 'add_fallback_versioned_files', None) is None:
1771
 
            raise TestNotApplicable("%s doesn't support fallbacks"
1772
 
                                    % (f.__class__.__name__,))
1773
 
        key_a = self.get_simple_key('a')
1774
 
        key_b = self.get_simple_key('b')
1775
 
        key_c = self.get_simple_key('c')
1776
 
        # A     only in fallback
1777
 
        # |\
1778
 
        # | B
1779
 
        # |/
1780
 
        # C
1781
 
        g = self.get_versionedfiles('fallback')
1782
 
        g.add_lines(key_a, [], ['\n'])
1783
 
        f.add_fallback_versioned_files(g)
1784
 
        f.add_lines(key_b, [key_a], ['\n'])
1785
 
        f.add_lines(key_c, [key_a, key_b], ['\n'])
1786
 
        kg = f.get_known_graph_ancestry([key_c])
1787
 
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
1788
 
 
1789
1549
    def test_get_record_stream_empty(self):
1790
1550
        """An empty stream can be requested without error."""
1791
1551
        f = self.get_versionedfiles()
1796
1556
        """Assert that storage_kind is a valid storage_kind."""
1797
1557
        self.assertSubset([storage_kind],
1798
1558
            ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
1799
 
             'knit-ft', 'knit-delta', 'chunked', 'fulltext',
1800
 
             'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
1801
 
             'knit-delta-gz',
1802
 
             'knit-delta-closure', 'knit-delta-closure-ref',
1803
 
             'groupcompress-block', 'groupcompress-block-ref'])
 
1559
             'knit-ft', 'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
 
1560
             'knit-annotated-delta-gz', 'knit-ft-gz', 'knit-delta-gz'])
1804
1561
 
1805
 
    def capture_stream(self, f, entries, on_seen, parents,
1806
 
        require_fulltext=False):
 
1562
    def capture_stream(self, f, entries, on_seen, parents):
1807
1563
        """Capture a stream for testing."""
1808
1564
        for factory in entries:
1809
1565
            on_seen(factory.key)
1810
1566
            self.assertValidStorageKind(factory.storage_kind)
1811
 
            if factory.sha1 is not None:
1812
 
                self.assertEqual(f.get_sha1s([factory.key])[factory.key],
1813
 
                    factory.sha1)
 
1567
            self.assertEqual(f.get_sha1s([factory.key])[factory.key],
 
1568
                factory.sha1)
1814
1569
            self.assertEqual(parents[factory.key], factory.parents)
1815
1570
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1816
1571
                str)
1817
 
            if require_fulltext:
1818
 
                factory.get_bytes_as('fulltext')
1819
1572
 
1820
1573
    def test_get_record_stream_interface(self):
1821
1574
        """each item in a stream has to provide a regular interface."""
1828
1581
        self.capture_stream(files, entries, seen.add, parent_map)
1829
1582
        self.assertEqual(set(keys), seen)
1830
1583
 
 
1584
    def get_simple_key(self, suffix):
 
1585
        """Return a key for the object under test."""
 
1586
        if self.key_length == 1:
 
1587
            return (suffix,)
 
1588
        else:
 
1589
            return ('FileA',) + (suffix,)
 
1590
 
1831
1591
    def get_keys_and_sort_order(self):
1832
1592
        """Get diamond test keys list, and their sort ordering."""
1833
1593
        if self.key_length == 1:
1848
1608
                }
1849
1609
        return keys, sort_order
1850
1610
 
1851
 
    def get_keys_and_groupcompress_sort_order(self):
1852
 
        """Get diamond test keys list, and their groupcompress sort ordering."""
1853
 
        if self.key_length == 1:
1854
 
            keys = [('merged',), ('left',), ('right',), ('base',)]
1855
 
            sort_order = {('merged',):0, ('left',):1, ('right',):1, ('base',):2}
1856
 
        else:
1857
 
            keys = [
1858
 
                ('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1859
 
                ('FileA', 'base'),
1860
 
                ('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1861
 
                ('FileB', 'base'),
1862
 
                ]
1863
 
            sort_order = {
1864
 
                ('FileA', 'merged'):0, ('FileA', 'left'):1, ('FileA', 'right'):1,
1865
 
                ('FileA', 'base'):2,
1866
 
                ('FileB', 'merged'):3, ('FileB', 'left'):4, ('FileB', 'right'):4,
1867
 
                ('FileB', 'base'):5,
1868
 
                }
1869
 
        return keys, sort_order
1870
 
 
1871
1611
    def test_get_record_stream_interface_ordered(self):
1872
1612
        """each item in a stream has to provide a regular interface."""
1873
1613
        files = self.get_versionedfiles()
1894
1634
                [None, files.get_sha1s([factory.key])[factory.key]])
1895
1635
            self.assertEqual(parent_map[factory.key], factory.parents)
1896
1636
            # self.assertEqual(files.get_text(factory.key),
1897
 
            ft_bytes = factory.get_bytes_as('fulltext')
1898
 
            self.assertIsInstance(ft_bytes, str)
1899
 
            chunked_bytes = factory.get_bytes_as('chunked')
1900
 
            self.assertEqualDiff(ft_bytes, ''.join(chunked_bytes))
1901
 
 
1902
 
        self.assertStreamOrder(sort_order, seen, keys)
1903
 
 
1904
 
    def test_get_record_stream_interface_groupcompress(self):
1905
 
        """each item in a stream has to provide a regular interface."""
1906
 
        files = self.get_versionedfiles()
1907
 
        self.get_diamond_files(files)
1908
 
        keys, sort_order = self.get_keys_and_groupcompress_sort_order()
1909
 
        parent_map = files.get_parent_map(keys)
1910
 
        entries = files.get_record_stream(keys, 'groupcompress', False)
1911
 
        seen = []
1912
 
        self.capture_stream(files, entries, seen.append, parent_map)
 
1637
            self.assertIsInstance(factory.get_bytes_as('fulltext'), str)
 
1638
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
 
1639
                str)
1913
1640
        self.assertStreamOrder(sort_order, seen, keys)
1914
1641
 
1915
1642
    def assertStreamOrder(self, sort_order, seen, keys):
1948
1675
        for factory in entries:
1949
1676
            seen.add(factory.key)
1950
1677
            self.assertValidStorageKind(factory.storage_kind)
1951
 
            if factory.sha1 is not None:
1952
 
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1953
 
                                 factory.sha1)
 
1678
            self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1679
                factory.sha1)
1954
1680
            self.assertEqual(parent_map[factory.key], factory.parents)
1955
1681
            # currently no stream emits mpdiff
1956
1682
            self.assertRaises(errors.UnavailableRepresentation,
1978
1704
        entries = files.get_record_stream(keys, 'topological', False)
1979
1705
        self.assertAbsentRecord(files, keys, parent_map, entries)
1980
1706
 
1981
 
    def assertRecordHasContent(self, record, bytes):
1982
 
        """Assert that record has the bytes bytes."""
1983
 
        self.assertEqual(bytes, record.get_bytes_as('fulltext'))
1984
 
        self.assertEqual(bytes, ''.join(record.get_bytes_as('chunked')))
1985
 
 
1986
 
    def test_get_record_stream_native_formats_are_wire_ready_one_ft(self):
1987
 
        files = self.get_versionedfiles()
1988
 
        key = self.get_simple_key('foo')
1989
 
        files.add_lines(key, (), ['my text\n', 'content'])
1990
 
        stream = files.get_record_stream([key], 'unordered', False)
1991
 
        record = stream.next()
1992
 
        if record.storage_kind in ('chunked', 'fulltext'):
1993
 
            # chunked and fulltext representations are for direct use not wire
1994
 
            # serialisation: check they are able to be used directly. To send
1995
 
            # such records over the wire translation will be needed.
1996
 
            self.assertRecordHasContent(record, "my text\ncontent")
1997
 
        else:
1998
 
            bytes = [record.get_bytes_as(record.storage_kind)]
1999
 
            network_stream = versionedfile.NetworkRecordStream(bytes).read()
2000
 
            source_record = record
2001
 
            records = []
2002
 
            for record in network_stream:
2003
 
                records.append(record)
2004
 
                self.assertEqual(source_record.storage_kind,
2005
 
                    record.storage_kind)
2006
 
                self.assertEqual(source_record.parents, record.parents)
2007
 
                self.assertEqual(
2008
 
                    source_record.get_bytes_as(source_record.storage_kind),
2009
 
                    record.get_bytes_as(record.storage_kind))
2010
 
            self.assertEqual(1, len(records))
2011
 
 
2012
 
    def assertStreamMetaEqual(self, records, expected, stream):
2013
 
        """Assert that streams expected and stream have the same records.
2014
 
 
2015
 
        :param records: A list to collect the seen records.
2016
 
        :return: A generator of the records in stream.
2017
 
        """
2018
 
        # We make assertions during copying to catch things early for
2019
 
        # easier debugging.
2020
 
        for record, ref_record in izip(stream, expected):
2021
 
            records.append(record)
2022
 
            self.assertEqual(ref_record.key, record.key)
2023
 
            self.assertEqual(ref_record.storage_kind, record.storage_kind)
2024
 
            self.assertEqual(ref_record.parents, record.parents)
2025
 
            yield record
2026
 
 
2027
 
    def stream_to_bytes_or_skip_counter(self, skipped_records, full_texts,
2028
 
        stream):
2029
 
        """Convert a stream to a bytes iterator.
2030
 
 
2031
 
        :param skipped_records: A list with one element to increment when a
2032
 
            record is skipped.
2033
 
        :param full_texts: A dict from key->fulltext representation, for
2034
 
            checking chunked or fulltext stored records.
2035
 
        :param stream: A record_stream.
2036
 
        :return: An iterator over the bytes of each record.
2037
 
        """
2038
 
        for record in stream:
2039
 
            if record.storage_kind in ('chunked', 'fulltext'):
2040
 
                skipped_records[0] += 1
2041
 
                # check the content is correct for direct use.
2042
 
                self.assertRecordHasContent(record, full_texts[record.key])
2043
 
            else:
2044
 
                yield record.get_bytes_as(record.storage_kind)
2045
 
 
2046
 
    def test_get_record_stream_native_formats_are_wire_ready_ft_delta(self):
2047
 
        files = self.get_versionedfiles()
2048
 
        target_files = self.get_versionedfiles('target')
2049
 
        key = self.get_simple_key('ft')
2050
 
        key_delta = self.get_simple_key('delta')
2051
 
        files.add_lines(key, (), ['my text\n', 'content'])
2052
 
        if self.graph:
2053
 
            delta_parents = (key,)
2054
 
        else:
2055
 
            delta_parents = ()
2056
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
2057
 
        local = files.get_record_stream([key, key_delta], 'unordered', False)
2058
 
        ref = files.get_record_stream([key, key_delta], 'unordered', False)
2059
 
        skipped_records = [0]
2060
 
        full_texts = {
2061
 
            key: "my text\ncontent",
2062
 
            key_delta: "different\ncontent\n",
2063
 
            }
2064
 
        byte_stream = self.stream_to_bytes_or_skip_counter(
2065
 
            skipped_records, full_texts, local)
2066
 
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
2067
 
        records = []
2068
 
        # insert the stream from the network into a versioned files object so we can
2069
 
        # check the content was carried across correctly without doing delta
2070
 
        # inspection.
2071
 
        target_files.insert_record_stream(
2072
 
            self.assertStreamMetaEqual(records, ref, network_stream))
2073
 
        # No duplicates on the wire thank you!
2074
 
        self.assertEqual(2, len(records) + skipped_records[0])
2075
 
        if len(records):
2076
 
            # if any content was copied it all must have all been.
2077
 
            self.assertIdenticalVersionedFile(files, target_files)
2078
 
 
2079
 
    def test_get_record_stream_native_formats_are_wire_ready_delta(self):
2080
 
        # copy a delta over the wire
2081
 
        files = self.get_versionedfiles()
2082
 
        target_files = self.get_versionedfiles('target')
2083
 
        key = self.get_simple_key('ft')
2084
 
        key_delta = self.get_simple_key('delta')
2085
 
        files.add_lines(key, (), ['my text\n', 'content'])
2086
 
        if self.graph:
2087
 
            delta_parents = (key,)
2088
 
        else:
2089
 
            delta_parents = ()
2090
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
2091
 
        # Copy the basis text across so we can reconstruct the delta during
2092
 
        # insertion into target.
2093
 
        target_files.insert_record_stream(files.get_record_stream([key],
2094
 
            'unordered', False))
2095
 
        local = files.get_record_stream([key_delta], 'unordered', False)
2096
 
        ref = files.get_record_stream([key_delta], 'unordered', False)
2097
 
        skipped_records = [0]
2098
 
        full_texts = {
2099
 
            key_delta: "different\ncontent\n",
2100
 
            }
2101
 
        byte_stream = self.stream_to_bytes_or_skip_counter(
2102
 
            skipped_records, full_texts, local)
2103
 
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
2104
 
        records = []
2105
 
        # insert the stream from the network into a versioned files object so we can
2106
 
        # check the content was carried across correctly without doing delta
2107
 
        # inspection during check_stream.
2108
 
        target_files.insert_record_stream(
2109
 
            self.assertStreamMetaEqual(records, ref, network_stream))
2110
 
        # No duplicates on the wire thank you!
2111
 
        self.assertEqual(1, len(records) + skipped_records[0])
2112
 
        if len(records):
2113
 
            # if any content was copied it all must have all been
2114
 
            self.assertIdenticalVersionedFile(files, target_files)
2115
 
 
2116
 
    def test_get_record_stream_wire_ready_delta_closure_included(self):
2117
 
        # copy a delta over the wire with the ability to get its full text.
2118
 
        files = self.get_versionedfiles()
2119
 
        key = self.get_simple_key('ft')
2120
 
        key_delta = self.get_simple_key('delta')
2121
 
        files.add_lines(key, (), ['my text\n', 'content'])
2122
 
        if self.graph:
2123
 
            delta_parents = (key,)
2124
 
        else:
2125
 
            delta_parents = ()
2126
 
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
2127
 
        local = files.get_record_stream([key_delta], 'unordered', True)
2128
 
        ref = files.get_record_stream([key_delta], 'unordered', True)
2129
 
        skipped_records = [0]
2130
 
        full_texts = {
2131
 
            key_delta: "different\ncontent\n",
2132
 
            }
2133
 
        byte_stream = self.stream_to_bytes_or_skip_counter(
2134
 
            skipped_records, full_texts, local)
2135
 
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
2136
 
        records = []
2137
 
        # insert the stream from the network into a versioned files object so we can
2138
 
        # check the content was carried across correctly without doing delta
2139
 
        # inspection during check_stream.
2140
 
        for record in self.assertStreamMetaEqual(records, ref, network_stream):
2141
 
            # we have to be able to get the full text out:
2142
 
            self.assertRecordHasContent(record, full_texts[record.key])
2143
 
        # No duplicates on the wire thank you!
2144
 
        self.assertEqual(1, len(records) + skipped_records[0])
2145
 
 
2146
1707
    def assertAbsentRecord(self, files, keys, parents, entries):
2147
1708
        """Helper for test_get_record_stream_missing_records_are_absent."""
2148
1709
        seen = set()
2154
1715
                self.assertEqual(None, factory.parents)
2155
1716
            else:
2156
1717
                self.assertValidStorageKind(factory.storage_kind)
2157
 
                if factory.sha1 is not None:
2158
 
                    sha1 = files.get_sha1s([factory.key])[factory.key]
2159
 
                    self.assertEqual(sha1, factory.sha1)
 
1718
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1719
                    factory.sha1)
2160
1720
                self.assertEqual(parents[factory.key], factory.parents)
2161
1721
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
2162
1722
                    str)
2196
1756
        else:
2197
1757
            return None
2198
1758
 
2199
 
    def test_get_annotator(self):
2200
 
        files = self.get_versionedfiles()
2201
 
        self.get_diamond_files(files)
2202
 
        origin_key = self.get_simple_key('origin')
2203
 
        base_key = self.get_simple_key('base')
2204
 
        left_key = self.get_simple_key('left')
2205
 
        right_key = self.get_simple_key('right')
2206
 
        merged_key = self.get_simple_key('merged')
2207
 
        # annotator = files.get_annotator()
2208
 
        # introduced full text
2209
 
        origins, lines = files.get_annotator().annotate(origin_key)
2210
 
        self.assertEqual([(origin_key,)], origins)
2211
 
        self.assertEqual(['origin\n'], lines)
2212
 
        # a delta
2213
 
        origins, lines = files.get_annotator().annotate(base_key)
2214
 
        self.assertEqual([(base_key,)], origins)
2215
 
        # a merge
2216
 
        origins, lines = files.get_annotator().annotate(merged_key)
2217
 
        if self.graph:
2218
 
            self.assertEqual([
2219
 
                (base_key,),
2220
 
                (left_key,),
2221
 
                (right_key,),
2222
 
                (merged_key,),
2223
 
                ], origins)
2224
 
        else:
2225
 
            # Without a graph everything is new.
2226
 
            self.assertEqual([
2227
 
                (merged_key,),
2228
 
                (merged_key,),
2229
 
                (merged_key,),
2230
 
                (merged_key,),
2231
 
                ], origins)
2232
 
        self.assertRaises(RevisionNotPresent,
2233
 
            files.get_annotator().annotate, self.get_simple_key('missing-key'))
2234
 
 
2235
1759
    def test_get_parent_map(self):
2236
1760
        files = self.get_versionedfiles()
2237
1761
        if self.key_length == 1:
2288
1812
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
2289
1813
            },
2290
1814
            files.get_sha1s(keys))
2291
 
 
 
1815
        
2292
1816
    def test_insert_record_stream_empty(self):
2293
1817
        """Inserting an empty record stream should work."""
2294
1818
        files = self.get_versionedfiles()
2440
1964
        else:
2441
1965
            self.assertIdenticalVersionedFile(source, files)
2442
1966
 
2443
 
    def test_insert_record_stream_long_parent_chain_out_of_order(self):
2444
 
        """An out of order stream can either error or work."""
2445
 
        if not self.graph:
2446
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
2447
 
        # Create a reasonably long chain of records based on each other, where
2448
 
        # most will be deltas.
2449
 
        source = self.get_versionedfiles('source')
2450
 
        parents = ()
2451
 
        keys = []
2452
 
        content = [('same same %d\n' % n) for n in range(500)]
2453
 
        for letter in 'abcdefghijklmnopqrstuvwxyz':
2454
 
            key = ('key-' + letter,)
2455
 
            if self.key_length == 2:
2456
 
                key = ('prefix',) + key
2457
 
            content.append('content for ' + letter + '\n')
2458
 
            source.add_lines(key, parents, content)
2459
 
            keys.append(key)
2460
 
            parents = (key,)
2461
 
        # Create a stream of these records, excluding the first record that the
2462
 
        # rest ultimately depend upon, and insert it into a new vf.
2463
 
        streams = []
2464
 
        for key in reversed(keys):
2465
 
            streams.append(source.get_record_stream([key], 'unordered', False))
2466
 
        deltas = chain(*streams[:-1])
2467
 
        files = self.get_versionedfiles()
2468
 
        try:
2469
 
            files.insert_record_stream(deltas)
2470
 
        except RevisionNotPresent:
2471
 
            # Must not have corrupted the file.
2472
 
            files.check()
2473
 
        else:
2474
 
            # Must only report either just the first key as a missing parent,
2475
 
            # no key as missing (for nodelta scenarios).
2476
 
            missing = set(files.get_missing_compression_parent_keys())
2477
 
            missing.discard(keys[0])
2478
 
            self.assertEqual(set(), missing)
2479
 
 
2480
 
    def get_knit_delta_source(self):
2481
 
        """Get a source that can produce a stream with knit delta records,
2482
 
        regardless of this test's scenario.
2483
 
        """
2484
 
        mapper = self.get_mapper()
2485
 
        source_transport = self.get_transport('source')
2486
 
        source_transport.mkdir('.')
2487
 
        source = make_file_factory(False, mapper)(source_transport)
2488
 
        get_diamond_files(source, self.key_length, trailing_eol=True,
2489
 
            nograph=False, left_only=False)
2490
 
        return source
2491
 
 
2492
1967
    def test_insert_record_stream_delta_missing_basis_no_corruption(self):
2493
 
        """Insertion where a needed basis is not included notifies the caller
2494
 
        of the missing basis.  In the meantime a record missing its basis is
2495
 
        not added.
2496
 
        """
2497
 
        source = self.get_knit_delta_source()
2498
 
        keys = [self.get_simple_key('origin'), self.get_simple_key('merged')]
2499
 
        entries = source.get_record_stream(keys, 'unordered', False)
2500
 
        files = self.get_versionedfiles()
2501
 
        if self.support_partial_insertion:
2502
 
            self.assertEqual([],
2503
 
                list(files.get_missing_compression_parent_keys()))
2504
 
            files.insert_record_stream(entries)
2505
 
            missing_bases = files.get_missing_compression_parent_keys()
2506
 
            self.assertEqual(set([self.get_simple_key('left')]),
2507
 
                set(missing_bases))
2508
 
            self.assertEqual(set(keys), set(files.get_parent_map(keys)))
2509
 
        else:
2510
 
            self.assertRaises(
2511
 
                errors.RevisionNotPresent, files.insert_record_stream, entries)
2512
 
            files.check()
2513
 
 
2514
 
    def test_insert_record_stream_delta_missing_basis_can_be_added_later(self):
2515
 
        """Insertion where a needed basis is not included notifies the caller
2516
 
        of the missing basis.  That basis can be added in a second
2517
 
        insert_record_stream call that does not need to repeat records present
2518
 
        in the previous stream.  The record(s) that required that basis are
2519
 
        fully inserted once their basis is no longer missing.
2520
 
        """
2521
 
        if not self.support_partial_insertion:
2522
 
            raise TestNotApplicable(
2523
 
                'versioned file scenario does not support partial insertion')
2524
 
        source = self.get_knit_delta_source()
2525
 
        entries = source.get_record_stream([self.get_simple_key('origin'),
2526
 
            self.get_simple_key('merged')], 'unordered', False)
2527
 
        files = self.get_versionedfiles()
2528
 
        files.insert_record_stream(entries)
2529
 
        missing_bases = files.get_missing_compression_parent_keys()
2530
 
        self.assertEqual(set([self.get_simple_key('left')]),
2531
 
            set(missing_bases))
2532
 
        # 'merged' is inserted (although a commit of a write group involving
2533
 
        # this versionedfiles would fail).
2534
 
        merged_key = self.get_simple_key('merged')
2535
 
        self.assertEqual(
2536
 
            [merged_key], files.get_parent_map([merged_key]).keys())
2537
 
        # Add the full delta closure of the missing records
2538
 
        missing_entries = source.get_record_stream(
2539
 
            missing_bases, 'unordered', True)
2540
 
        files.insert_record_stream(missing_entries)
2541
 
        # Now 'merged' is fully inserted (and a commit would succeed).
2542
 
        self.assertEqual([], list(files.get_missing_compression_parent_keys()))
2543
 
        self.assertEqual(
2544
 
            [merged_key], files.get_parent_map([merged_key]).keys())
 
1968
        """Insertion where a needed basis is not included aborts safely."""
 
1969
        # We use a knit always here to be sure we are getting a binary delta.
 
1970
        mapper = self.get_mapper()
 
1971
        source_transport = self.get_transport('source')
 
1972
        source_transport.mkdir('.')
 
1973
        source = make_file_factory(False, mapper)(source_transport)
 
1974
        self.get_diamond_files(source)
 
1975
        entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
 
1976
        files = self.get_versionedfiles()
 
1977
        self.assertRaises(RevisionNotPresent, files.insert_record_stream,
 
1978
            entries)
2545
1979
        files.check()
 
1980
        self.assertEqual({}, files.get_parent_map([]))
2546
1981
 
2547
1982
    def test_iter_lines_added_or_present_in_keys(self):
2548
1983
        # test that we get at least an equalset of the lines added by
2550
1985
        # the ordering here is to make a tree so that dumb searches have
2551
1986
        # more changes to muck up.
2552
1987
 
2553
 
        class InstrumentedProgress(progress.ProgressTask):
 
1988
        class InstrumentedProgress(progress.DummyProgress):
2554
1989
 
2555
1990
            def __init__(self):
2556
 
                progress.ProgressTask.__init__(self)
 
1991
 
 
1992
                progress.DummyProgress.__init__(self)
2557
1993
                self.updates = []
2558
1994
 
2559
1995
            def update(self, msg=None, current=None, total=None):
2590
2026
            return lines
2591
2027
        lines = iter_with_keys(
2592
2028
            [self.get_simple_key('child'), self.get_simple_key('otherchild')],
2593
 
            [('Walking content', 0, 2),
2594
 
             ('Walking content', 1, 2),
2595
 
             ('Walking content', 2, 2)])
 
2029
            [('Walking content.', 0, 2),
 
2030
             ('Walking content.', 1, 2),
 
2031
             ('Walking content.', 2, 2)])
2596
2032
        # we must see child and otherchild
2597
2033
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2598
2034
        self.assertTrue(
2599
2035
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2600
2036
        # we dont care if we got more than that.
2601
 
 
 
2037
        
2602
2038
        # test all lines
2603
2039
        lines = iter_with_keys(files.keys(),
2604
 
            [('Walking content', 0, 5),
2605
 
             ('Walking content', 1, 5),
2606
 
             ('Walking content', 2, 5),
2607
 
             ('Walking content', 3, 5),
2608
 
             ('Walking content', 4, 5),
2609
 
             ('Walking content', 5, 5)])
 
2040
            [('Walking content.', 0, 5),
 
2041
             ('Walking content.', 1, 5),
 
2042
             ('Walking content.', 2, 5),
 
2043
             ('Walking content.', 3, 5),
 
2044
             ('Walking content.', 4, 5),
 
2045
             ('Walking content.', 5, 5)])
2610
2046
        # all lines must be seen at least once
2611
2047
        self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2612
2048
        self.assertTrue(
2645
2081
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2646
2082
        # noeol preceeding its leftmost parent in the output:
2647
2083
        # this is done by making it a merge of two parents with no common
2648
 
        # anestry: noeolbase and noeol with the
 
2084
        # anestry: noeolbase and noeol with the 
2649
2085
        # later-inserted parent the leftmost.
2650
2086
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2651
2087
            self.get_parents([self.get_simple_key('noeolbase'),
2721
2157
            key = ('foo', 'bar',)
2722
2158
        files.add_lines(key, (), [])
2723
2159
        self.assertEqual(set([key]), set(files.keys()))
2724
 
 
2725
 
 
2726
 
class VirtualVersionedFilesTests(TestCase):
2727
 
    """Basic tests for the VirtualVersionedFiles implementations."""
2728
 
 
2729
 
    def _get_parent_map(self, keys):
2730
 
        ret = {}
2731
 
        for k in keys:
2732
 
            if k in self._parent_map:
2733
 
                ret[k] = self._parent_map[k]
2734
 
        return ret
2735
 
 
2736
 
    def setUp(self):
2737
 
        TestCase.setUp(self)
2738
 
        self._lines = {}
2739
 
        self._parent_map = {}
2740
 
        self.texts = VirtualVersionedFiles(self._get_parent_map,
2741
 
                                           self._lines.get)
2742
 
 
2743
 
    def test_add_lines(self):
2744
 
        self.assertRaises(NotImplementedError,
2745
 
                self.texts.add_lines, "foo", [], [])
2746
 
 
2747
 
    def test_add_mpdiffs(self):
2748
 
        self.assertRaises(NotImplementedError,
2749
 
                self.texts.add_mpdiffs, [])
2750
 
 
2751
 
    def test_check_noerrors(self):
2752
 
        self.texts.check()
2753
 
 
2754
 
    def test_insert_record_stream(self):
2755
 
        self.assertRaises(NotImplementedError, self.texts.insert_record_stream,
2756
 
                          [])
2757
 
 
2758
 
    def test_get_sha1s_nonexistent(self):
2759
 
        self.assertEquals({}, self.texts.get_sha1s([("NONEXISTENT",)]))
2760
 
 
2761
 
    def test_get_sha1s(self):
2762
 
        self._lines["key"] = ["dataline1", "dataline2"]
2763
 
        self.assertEquals({("key",): osutils.sha_strings(self._lines["key"])},
2764
 
                           self.texts.get_sha1s([("key",)]))
2765
 
 
2766
 
    def test_get_parent_map(self):
2767
 
        self._parent_map = {"G": ("A", "B")}
2768
 
        self.assertEquals({("G",): (("A",),("B",))},
2769
 
                          self.texts.get_parent_map([("G",), ("L",)]))
2770
 
 
2771
 
    def test_get_record_stream(self):
2772
 
        self._lines["A"] = ["FOO", "BAR"]
2773
 
        it = self.texts.get_record_stream([("A",)], "unordered", True)
2774
 
        record = it.next()
2775
 
        self.assertEquals("chunked", record.storage_kind)
2776
 
        self.assertEquals("FOOBAR", record.get_bytes_as("fulltext"))
2777
 
        self.assertEquals(["FOO", "BAR"], record.get_bytes_as("chunked"))
2778
 
 
2779
 
    def test_get_record_stream_absent(self):
2780
 
        it = self.texts.get_record_stream([("A",)], "unordered", True)
2781
 
        record = it.next()
2782
 
        self.assertEquals("absent", record.storage_kind)
2783
 
 
2784
 
    def test_iter_lines_added_or_present_in_keys(self):
2785
 
        self._lines["A"] = ["FOO", "BAR"]
2786
 
        self._lines["B"] = ["HEY"]
2787
 
        self._lines["C"] = ["Alberta"]
2788
 
        it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
2789
 
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
2790
 
            sorted(list(it)))
2791
 
 
2792
 
 
2793
 
class TestOrderingVersionedFilesDecorator(TestCaseWithMemoryTransport):
2794
 
 
2795
 
    def get_ordering_vf(self, key_priority):
2796
 
        builder = self.make_branch_builder('test')
2797
 
        builder.start_series()
2798
 
        builder.build_snapshot('A', None, [
2799
 
            ('add', ('', 'TREE_ROOT', 'directory', None))])
2800
 
        builder.build_snapshot('B', ['A'], [])
2801
 
        builder.build_snapshot('C', ['B'], [])
2802
 
        builder.build_snapshot('D', ['C'], [])
2803
 
        builder.finish_series()
2804
 
        b = builder.get_branch()
2805
 
        b.lock_read()
2806
 
        self.addCleanup(b.unlock)
2807
 
        vf = b.repository.inventories
2808
 
        return versionedfile.OrderingVersionedFilesDecorator(vf, key_priority)
2809
 
 
2810
 
    def test_get_empty(self):
2811
 
        vf = self.get_ordering_vf({})
2812
 
        self.assertEqual([], vf.calls)
2813
 
 
2814
 
    def test_get_record_stream_topological(self):
2815
 
        vf = self.get_ordering_vf({('A',): 3, ('B',): 2, ('C',): 4, ('D',): 1})
2816
 
        request_keys = [('B',), ('C',), ('D',), ('A',)]
2817
 
        keys = [r.key for r in vf.get_record_stream(request_keys,
2818
 
                                    'topological', False)]
2819
 
        # We should have gotten the keys in topological order
2820
 
        self.assertEqual([('A',), ('B',), ('C',), ('D',)], keys)
2821
 
        # And recorded that the request was made
2822
 
        self.assertEqual([('get_record_stream', request_keys, 'topological',
2823
 
                           False)], vf.calls)
2824
 
 
2825
 
    def test_get_record_stream_ordered(self):
2826
 
        vf = self.get_ordering_vf({('A',): 3, ('B',): 2, ('C',): 4, ('D',): 1})
2827
 
        request_keys = [('B',), ('C',), ('D',), ('A',)]
2828
 
        keys = [r.key for r in vf.get_record_stream(request_keys,
2829
 
                                   'unordered', False)]
2830
 
        # They should be returned based on their priority
2831
 
        self.assertEqual([('D',), ('B',), ('A',), ('C',)], keys)
2832
 
        # And the request recorded
2833
 
        self.assertEqual([('get_record_stream', request_keys, 'unordered',
2834
 
                           False)], vf.calls)
2835
 
 
2836
 
    def test_get_record_stream_implicit_order(self):
2837
 
        vf = self.get_ordering_vf({('B',): 2, ('D',): 1})
2838
 
        request_keys = [('B',), ('C',), ('D',), ('A',)]
2839
 
        keys = [r.key for r in vf.get_record_stream(request_keys,
2840
 
                                   'unordered', False)]
2841
 
        # A and C are not in the map, so they get sorted to the front. A comes
2842
 
        # before C alphabetically, so it comes back first
2843
 
        self.assertEqual([('A',), ('C',), ('D',), ('B',)], keys)
2844
 
        # And the request recorded
2845
 
        self.assertEqual([('get_record_stream', request_keys, 'unordered',
2846
 
                           False)], vf.calls)