/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_versionedfile.py

merge bzr.dev r4042

Show diffs side-by-side

added added

removed removed

Lines of Context:
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 itertools import chain
 
24
from itertools import chain, izip
25
25
from StringIO import StringIO
26
26
 
27
27
import bzrlib
47
47
from bzrlib.tests import (
48
48
    TestCase,
49
49
    TestCaseWithMemoryTransport,
 
50
    TestNotApplicable,
50
51
    TestScenarioApplier,
51
52
    TestSkipped,
52
53
    condition_isinstance,
94
95
                ConstantMapper('inventory')),
95
96
            'graph':True,
96
97
            'key_length':1,
 
98
            'support_partial_insertion': False,
97
99
            }),
98
100
        ('named-knit', {
99
101
            'cleanup':None,
100
102
            'factory':make_file_factory(False, ConstantMapper('revisions')),
101
103
            'graph':True,
102
104
            'key_length':1,
 
105
            'support_partial_insertion': False,
103
106
            }),
104
 
        ('named-nograph-knit-pack', {
 
107
        ('named-nograph-nodelta-knit-pack', {
105
108
            'cleanup':cleanup_pack_knit,
106
109
            'factory':make_pack_factory(False, False, 1),
107
110
            'graph':False,
108
111
            'key_length':1,
 
112
            'support_partial_insertion': False,
109
113
            }),
110
114
        ('named-graph-knit-pack', {
111
115
            'cleanup':cleanup_pack_knit,
112
116
            'factory':make_pack_factory(True, True, 1),
113
117
            'graph':True,
114
118
            'key_length':1,
 
119
            'support_partial_insertion': True,
115
120
            }),
116
121
        ('named-graph-nodelta-knit-pack', {
117
122
            'cleanup':cleanup_pack_knit,
118
123
            'factory':make_pack_factory(True, False, 1),
119
124
            'graph':True,
120
125
            'key_length':1,
 
126
            'support_partial_insertion': False,
121
127
            }),
122
128
        ]
123
129
    len_two_adapter.scenarios = [
127
133
                PrefixMapper()),
128
134
            'graph':True,
129
135
            'key_length':2,
 
136
            'support_partial_insertion': False,
130
137
            }),
131
138
        ('annotated-knit-escape', {
132
139
            'cleanup':None,
133
140
            'factory':make_file_factory(True, HashEscapedPrefixMapper()),
134
141
            'graph':True,
135
142
            'key_length':2,
 
143
            'support_partial_insertion': False,
136
144
            }),
137
145
        ('plain-knit-pack', {
138
146
            'cleanup':cleanup_pack_knit,
139
147
            'factory':make_pack_factory(True, True, 2),
140
148
            'graph':True,
141
149
            'key_length':2,
 
150
            'support_partial_insertion': True,
142
151
            }),
143
152
        ]
144
153
    for test in iter_suite_tests(to_adapt):
149
158
 
150
159
def get_diamond_vf(f, trailing_eol=True, left_only=False):
151
160
    """Get a diamond graph to exercise deltas and merges.
152
 
    
 
161
 
153
162
    :param trailing_eol: If True end the last line with \n.
154
163
    """
155
164
    parents = {
181
190
 
182
191
    This creates a 5-node graph in files. If files supports 2-length keys two
183
192
    graphs are made to exercise the support for multiple ids.
184
 
    
 
193
 
185
194
    :param trailing_eol: If True end the last line with \n.
186
195
    :param key_length: The length of keys in files. Currently supports length 1
187
196
        and 2 keys.
257
266
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
258
267
            self.assertEqual(2, len(f))
259
268
            self.assertEqual(2, f.num_versions())
260
 
    
 
269
 
261
270
            self.assertRaises(RevisionNotPresent,
262
271
                f.add_lines, 'r2', ['foo'], [])
263
272
            self.assertRaises(RevisionAlreadyPresent,
302
311
        verify_file(f)
303
312
 
304
313
    def test_add_unicode_content(self):
305
 
        # unicode content is not permitted in versioned files. 
 
314
        # unicode content is not permitted in versioned files.
306
315
        # versioned files version sequences of bytes only.
307
316
        vf = self.get_file()
308
317
        self.assertRaises(errors.BzrBadParameterUnicode,
331
340
    def test_inline_newline_throws(self):
332
341
        # \r characters are not permitted in lines being added
333
342
        vf = self.get_file()
334
 
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
 
343
        self.assertRaises(errors.BzrBadParameterContainsNewline,
335
344
            vf.add_lines, 'a', [], ['a\n\n'])
336
345
        self.assertRaises(
337
346
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
536
545
        f.add_lines('noeolbase', [], ['line'])
537
546
        # noeol preceeding its leftmost parent in the output:
538
547
        # this is done by making it a merge of two parents with no common
539
 
        # anestry: noeolbase and noeol with the 
 
548
        # anestry: noeolbase and noeol with the
540
549
        # later-inserted parent the leftmost.
541
550
        f.add_lines('eolbeforefirstparent', ['noeolbase', 'noeol'], ['line'])
542
551
        # two identical eol texts
623
632
        self._transaction = 'after'
624
633
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
625
634
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
626
 
        
 
635
 
627
636
    def test_copy_to(self):
628
637
        f = self.get_file()
629
638
        f.add_lines('0', [], ['a\n'])
702
711
 
703
712
    def test_iter_lines_added_or_present_in_versions(self):
704
713
        # test that we get at least an equalset of the lines added by
705
 
        # versions in the weave 
 
714
        # versions in the weave
706
715
        # the ordering here is to make a tree so that dumb searches have
707
716
        # more changes to muck up.
708
717
 
749
758
        self.assertTrue(lines[('child\n', 'child')] > 0)
750
759
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
751
760
        # we dont care if we got more than that.
752
 
        
 
761
 
753
762
        # test all lines
754
763
        lines = iter_with_versions(None, [('Walking content.', 0, 5),
755
764
                                          ('Walking content.', 1, 5),
832
841
                          'base',
833
842
                          [],
834
843
                          [])
835
 
    
 
844
 
836
845
    def test_get_sha1s(self):
837
846
        # check the sha1 data is available
838
847
        vf = self.get_file()
848
857
            'b': '3f786850e387550fdab836ed7e6dc881de23001b',
849
858
            },
850
859
            vf.get_sha1s(['a', 'c', 'b']))
851
 
        
 
860
 
852
861
 
853
862
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
854
863
 
861
870
            get_scope=self.get_transaction)
862
871
        w.add_lines('v1', [], ['hello\n'])
863
872
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
864
 
        
 
873
 
865
874
        # We are going to invasively corrupt the text
866
875
        # Make sure the internals of weave are the same
867
876
        self.assertEqual([('{', 0)
871
880
                        , 'there\n'
872
881
                        , ('}', None)
873
882
                        ], w._weave)
874
 
        
 
883
 
875
884
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
876
885
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
877
886
                        ], w._sha1s)
878
887
        w.check()
879
 
        
 
888
 
880
889
        # Corrupted
881
890
        w._weave[4] = 'There\n'
882
891
        return w
886
895
        # Corrected
887
896
        w._weave[4] = 'there\n'
888
897
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
889
 
        
 
898
 
890
899
        #Invalid checksum, first digit changed
891
900
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
892
901
        return w
1001
1010
 
1002
1011
        def addcrlf(x):
1003
1012
            return x + '\n'
1004
 
        
 
1013
 
1005
1014
        w = self.get_file()
1006
1015
        w.add_lines('text0', [], map(addcrlf, base))
1007
1016
        w.add_lines('text1', ['text0'], map(addcrlf, a))
1023
1032
 
1024
1033
        mp = map(addcrlf, mp)
1025
1034
        self.assertEqual(mt.readlines(), mp)
1026
 
        
1027
 
        
 
1035
 
 
1036
 
1028
1037
    def testOneInsert(self):
1029
1038
        self.doMerge([],
1030
1039
                     ['aa'],
1048
1057
                     ['aaa', 'xxx', 'yyy', 'bbb'],
1049
1058
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1050
1059
 
1051
 
        # really it ought to reduce this to 
 
1060
        # really it ought to reduce this to
1052
1061
        # ['aaa', 'xxx', 'yyy', 'bbb']
1053
1062
 
1054
1063
 
1056
1065
        self.doMerge(['aaa'],
1057
1066
                     ['xxx'],
1058
1067
                     ['yyy', 'zzz'],
1059
 
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
 
1068
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
1060
1069
                      '>>>>>>> '])
1061
1070
 
1062
1071
    def testNonClashInsert1(self):
1063
1072
        self.doMerge(['aaa'],
1064
1073
                     ['xxx', 'aaa'],
1065
1074
                     ['yyy', 'zzz'],
1066
 
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
 
1075
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
1067
1076
                      '>>>>>>> '])
1068
1077
 
1069
1078
    def testNonClashInsert2(self):
1083
1092
        #######################################
1084
1093
        # skippd, not working yet
1085
1094
        return
1086
 
        
 
1095
 
1087
1096
        self.doMerge(['aaa', 'bbb', 'ccc'],
1088
1097
                     ['aaa', 'ddd', 'ccc'],
1089
1098
                     ['aaa', 'ccc'],
1132
1141
    def test_deletion_overlap(self):
1133
1142
        """Delete overlapping regions with no other conflict.
1134
1143
 
1135
 
        Arguably it'd be better to treat these as agreement, rather than 
 
1144
        Arguably it'd be better to treat these as agreement, rather than
1136
1145
        conflict, but for now conflict is safer.
1137
1146
        """
1138
1147
        base = """\
1154
1163
            """
1155
1164
        result = """\
1156
1165
            start context
1157
 
<<<<<<< 
 
1166
<<<<<<<\x20
1158
1167
            int a() {}
1159
1168
=======
1160
1169
            int c() {}
1161
 
>>>>>>> 
 
1170
>>>>>>>\x20
1162
1171
            end context
1163
1172
            """
1164
1173
        self._test_merge_from_strings(base, a, b, result)
1190
1199
 
1191
1200
    def test_sync_on_deletion(self):
1192
1201
        """Specific case of merge where we can synchronize incorrectly.
1193
 
        
 
1202
 
1194
1203
        A previous version of the weave merge concluded that the two versions
1195
1204
        agreed on deleting line 2, and this could be a synchronization point.
1196
 
        Line 1 was then considered in isolation, and thought to be deleted on 
 
1205
        Line 1 was then considered in isolation, and thought to be deleted on
1197
1206
        both sides.
1198
1207
 
1199
1208
        It's better to consider the whole thing as a disagreement region.
1218
1227
            """
1219
1228
        result = """\
1220
1229
            start context
1221
 
<<<<<<< 
 
1230
<<<<<<<\x20
1222
1231
            base line 1
1223
1232
            a's replacement line 2
1224
1233
=======
1225
1234
            b replaces
1226
1235
            both lines
1227
 
>>>>>>> 
 
1236
>>>>>>>\x20
1228
1237
            end context
1229
1238
            """
1230
1239
        self._test_merge_from_strings(base, a, b, result)
1241
1250
        write_weave(w, tmpf)
1242
1251
        self.log(tmpf.getvalue())
1243
1252
 
1244
 
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
 
1253
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1245
1254
                                'xxx', '>>>>>>> ', 'bbb']
1246
1255
 
1247
1256
 
1279
1288
        # origin is a fulltext
1280
1289
        entries = f.get_record_stream([('origin',)], 'unordered', False)
1281
1290
        base = entries.next()
1282
 
        ft_data = ft_adapter.get_bytes(base, base.get_bytes_as(base.storage_kind))
 
1291
        ft_data = ft_adapter.get_bytes(base)
1283
1292
        # merged is both a delta and multiple parents.
1284
1293
        entries = f.get_record_stream([('merged',)], 'unordered', False)
1285
1294
        merged = entries.next()
1286
 
        delta_data = delta_adapter.get_bytes(merged,
1287
 
            merged.get_bytes_as(merged.storage_kind))
 
1295
        delta_data = delta_adapter.get_bytes(merged)
1288
1296
        return ft_data, delta_data
1289
1297
 
1290
1298
    def test_deannotation_noeol(self):
1357
1365
 
1358
1366
    def test_unannotated_to_fulltext(self):
1359
1367
        """Test adapting unannotated knits to full texts.
1360
 
        
 
1368
 
1361
1369
        This is used for -> weaves, and for -> annotated knits.
1362
1370
        """
1363
1371
        # we need a full text, and a delta
1376
1384
 
1377
1385
    def test_unannotated_to_fulltext_no_eol(self):
1378
1386
        """Test adapting unannotated knits to full texts.
1379
 
        
 
1387
 
1380
1388
        This is used for -> weaves, and for -> annotated knits.
1381
1389
        """
1382
1390
        # we need a full text, and a delta
1560
1568
            ['mpdiff', 'knit-annotated-ft', 'knit-annotated-delta',
1561
1569
             'knit-ft', 'knit-delta', 'chunked', 'fulltext',
1562
1570
             'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
1563
 
             'knit-delta-gz'])
 
1571
             'knit-delta-gz',
 
1572
             'knit-delta-closure', 'knit-delta-closure-ref'])
1564
1573
 
1565
1574
    def capture_stream(self, f, entries, on_seen, parents):
1566
1575
        """Capture a stream for testing."""
1709
1718
        entries = files.get_record_stream(keys, 'topological', False)
1710
1719
        self.assertAbsentRecord(files, keys, parent_map, entries)
1711
1720
 
 
1721
    def assertRecordHasContent(self, record, bytes):
 
1722
        """Assert that record has the bytes bytes."""
 
1723
        self.assertEqual(bytes, record.get_bytes_as('fulltext'))
 
1724
        self.assertEqual(bytes, ''.join(record.get_bytes_as('chunked')))
 
1725
 
 
1726
    def test_get_record_stream_native_formats_are_wire_ready_one_ft(self):
 
1727
        files = self.get_versionedfiles()
 
1728
        key = self.get_simple_key('foo')
 
1729
        files.add_lines(key, (), ['my text\n', 'content'])
 
1730
        stream = files.get_record_stream([key], 'unordered', False)
 
1731
        record = stream.next()
 
1732
        if record.storage_kind in ('chunked', 'fulltext'):
 
1733
            # chunked and fulltext representations are for direct use not wire
 
1734
            # serialisation: check they are able to be used directly. To send
 
1735
            # such records over the wire translation will be needed.
 
1736
            self.assertRecordHasContent(record, "my text\ncontent")
 
1737
        else:
 
1738
            bytes = [record.get_bytes_as(record.storage_kind)]
 
1739
            network_stream = versionedfile.NetworkRecordStream(bytes).read()
 
1740
            source_record = record
 
1741
            records = []
 
1742
            for record in network_stream:
 
1743
                records.append(record)
 
1744
                self.assertEqual(source_record.storage_kind,
 
1745
                    record.storage_kind)
 
1746
                self.assertEqual(source_record.parents, record.parents)
 
1747
                self.assertEqual(
 
1748
                    source_record.get_bytes_as(source_record.storage_kind),
 
1749
                    record.get_bytes_as(record.storage_kind))
 
1750
            self.assertEqual(1, len(records))
 
1751
 
 
1752
    def assertStreamMetaEqual(self, records, expected, stream):
 
1753
        """Assert that streams expected and stream have the same records.
 
1754
 
 
1755
        :param records: A list to collect the seen records.
 
1756
        :return: A generator of the records in stream.
 
1757
        """
 
1758
        # We make assertions during copying to catch things early for
 
1759
        # easier debugging.
 
1760
        for record, ref_record in izip(stream, expected):
 
1761
            records.append(record)
 
1762
            self.assertEqual(ref_record.key, record.key)
 
1763
            self.assertEqual(ref_record.storage_kind, record.storage_kind)
 
1764
            self.assertEqual(ref_record.parents, record.parents)
 
1765
            yield record
 
1766
 
 
1767
    def stream_to_bytes_or_skip_counter(self, skipped_records, full_texts,
 
1768
        stream):
 
1769
        """Convert a stream to a bytes iterator.
 
1770
 
 
1771
        :param skipped_records: A list with one element to increment when a
 
1772
            record is skipped.
 
1773
        :param full_texts: A dict from key->fulltext representation, for
 
1774
            checking chunked or fulltext stored records.
 
1775
        :param stream: A record_stream.
 
1776
        :return: An iterator over the bytes of each record.
 
1777
        """
 
1778
        for record in stream:
 
1779
            if record.storage_kind in ('chunked', 'fulltext'):
 
1780
                skipped_records[0] += 1
 
1781
                # check the content is correct for direct use.
 
1782
                self.assertRecordHasContent(record, full_texts[record.key])
 
1783
            else:
 
1784
                yield record.get_bytes_as(record.storage_kind)
 
1785
 
 
1786
    def test_get_record_stream_native_formats_are_wire_ready_ft_delta(self):
 
1787
        files = self.get_versionedfiles()
 
1788
        target_files = self.get_versionedfiles('target')
 
1789
        key = self.get_simple_key('ft')
 
1790
        key_delta = self.get_simple_key('delta')
 
1791
        files.add_lines(key, (), ['my text\n', 'content'])
 
1792
        if self.graph:
 
1793
            delta_parents = (key,)
 
1794
        else:
 
1795
            delta_parents = ()
 
1796
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
1797
        local = files.get_record_stream([key, key_delta], 'unordered', False)
 
1798
        ref = files.get_record_stream([key, key_delta], 'unordered', False)
 
1799
        skipped_records = [0]
 
1800
        full_texts = {
 
1801
            key: "my text\ncontent",
 
1802
            key_delta: "different\ncontent\n",
 
1803
            }
 
1804
        byte_stream = self.stream_to_bytes_or_skip_counter(
 
1805
            skipped_records, full_texts, local)
 
1806
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
 
1807
        records = []
 
1808
        # insert the stream from the network into a versioned files object so we can
 
1809
        # check the content was carried across correctly without doing delta
 
1810
        # inspection.
 
1811
        target_files.insert_record_stream(
 
1812
            self.assertStreamMetaEqual(records, ref, network_stream))
 
1813
        # No duplicates on the wire thank you!
 
1814
        self.assertEqual(2, len(records) + skipped_records[0])
 
1815
        if len(records):
 
1816
            # if any content was copied it all must have all been.
 
1817
            self.assertIdenticalVersionedFile(files, target_files)
 
1818
 
 
1819
    def test_get_record_stream_native_formats_are_wire_ready_delta(self):
 
1820
        # copy a delta over the wire
 
1821
        files = self.get_versionedfiles()
 
1822
        target_files = self.get_versionedfiles('target')
 
1823
        key = self.get_simple_key('ft')
 
1824
        key_delta = self.get_simple_key('delta')
 
1825
        files.add_lines(key, (), ['my text\n', 'content'])
 
1826
        if self.graph:
 
1827
            delta_parents = (key,)
 
1828
        else:
 
1829
            delta_parents = ()
 
1830
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
1831
        # Copy the basis text across so we can reconstruct the delta during
 
1832
        # insertion into target.
 
1833
        target_files.insert_record_stream(files.get_record_stream([key],
 
1834
            'unordered', False))
 
1835
        local = files.get_record_stream([key_delta], 'unordered', False)
 
1836
        ref = files.get_record_stream([key_delta], 'unordered', False)
 
1837
        skipped_records = [0]
 
1838
        full_texts = {
 
1839
            key_delta: "different\ncontent\n",
 
1840
            }
 
1841
        byte_stream = self.stream_to_bytes_or_skip_counter(
 
1842
            skipped_records, full_texts, local)
 
1843
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
 
1844
        records = []
 
1845
        # insert the stream from the network into a versioned files object so we can
 
1846
        # check the content was carried across correctly without doing delta
 
1847
        # inspection during check_stream.
 
1848
        target_files.insert_record_stream(
 
1849
            self.assertStreamMetaEqual(records, ref, network_stream))
 
1850
        # No duplicates on the wire thank you!
 
1851
        self.assertEqual(1, len(records) + skipped_records[0])
 
1852
        if len(records):
 
1853
            # if any content was copied it all must have all been
 
1854
            self.assertIdenticalVersionedFile(files, target_files)
 
1855
 
 
1856
    def test_get_record_stream_wire_ready_delta_closure_included(self):
 
1857
        # copy a delta over the wire with the ability to get its full text.
 
1858
        files = self.get_versionedfiles()
 
1859
        key = self.get_simple_key('ft')
 
1860
        key_delta = self.get_simple_key('delta')
 
1861
        files.add_lines(key, (), ['my text\n', 'content'])
 
1862
        if self.graph:
 
1863
            delta_parents = (key,)
 
1864
        else:
 
1865
            delta_parents = ()
 
1866
        files.add_lines(key_delta, delta_parents, ['different\n', 'content\n'])
 
1867
        local = files.get_record_stream([key_delta], 'unordered', True)
 
1868
        ref = files.get_record_stream([key_delta], 'unordered', True)
 
1869
        skipped_records = [0]
 
1870
        full_texts = {
 
1871
            key_delta: "different\ncontent\n",
 
1872
            }
 
1873
        byte_stream = self.stream_to_bytes_or_skip_counter(
 
1874
            skipped_records, full_texts, local)
 
1875
        network_stream = versionedfile.NetworkRecordStream(byte_stream).read()
 
1876
        records = []
 
1877
        # insert the stream from the network into a versioned files object so we can
 
1878
        # check the content was carried across correctly without doing delta
 
1879
        # inspection during check_stream.
 
1880
        for record in self.assertStreamMetaEqual(records, ref, network_stream):
 
1881
            # we have to be able to get the full text out:
 
1882
            self.assertRecordHasContent(record, full_texts[record.key])
 
1883
        # No duplicates on the wire thank you!
 
1884
        self.assertEqual(1, len(records) + skipped_records[0])
 
1885
 
1712
1886
    def assertAbsentRecord(self, files, keys, parents, entries):
1713
1887
        """Helper for test_get_record_stream_missing_records_are_absent."""
1714
1888
        seen = set()
1817
1991
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
1818
1992
            },
1819
1993
            files.get_sha1s(keys))
1820
 
        
 
1994
 
1821
1995
    def test_insert_record_stream_empty(self):
1822
1996
        """Inserting an empty record stream should work."""
1823
1997
        files = self.get_versionedfiles()
1969
2143
        else:
1970
2144
            self.assertIdenticalVersionedFile(source, files)
1971
2145
 
 
2146
    def get_knit_delta_source(self):
 
2147
        """Get a source that can produce a stream with knit delta records,
 
2148
        regardless of this test's scenario.
 
2149
        """
 
2150
        mapper = self.get_mapper()
 
2151
        source_transport = self.get_transport('source')
 
2152
        source_transport.mkdir('.')
 
2153
        source = make_file_factory(False, mapper)(source_transport)
 
2154
        get_diamond_files(source, self.key_length, trailing_eol=True,
 
2155
            nograph=False, left_only=False)
 
2156
        return source
 
2157
 
1972
2158
    def test_insert_record_stream_delta_missing_basis_no_corruption(self):
1973
 
        """Insertion where a needed basis is not included aborts safely."""
1974
 
        # We use a knit always here to be sure we are getting a binary delta.
1975
 
        mapper = self.get_mapper()
1976
 
        source_transport = self.get_transport('source')
1977
 
        source_transport.mkdir('.')
1978
 
        source = make_file_factory(False, mapper)(source_transport)
1979
 
        self.get_diamond_files(source)
1980
 
        entries = source.get_record_stream(['origin', 'merged'], 'unordered', False)
1981
 
        files = self.get_versionedfiles()
1982
 
        self.assertRaises(RevisionNotPresent, files.insert_record_stream,
1983
 
            entries)
 
2159
        """Insertion where a needed basis is not included notifies the caller
 
2160
        of the missing basis.  In the meantime a record missing its basis is
 
2161
        not added.
 
2162
        """
 
2163
        source = self.get_knit_delta_source()
 
2164
        keys = [self.get_simple_key('origin'), self.get_simple_key('merged')]
 
2165
        entries = source.get_record_stream(keys, 'unordered', False)
 
2166
        files = self.get_versionedfiles()
 
2167
        if self.support_partial_insertion:
 
2168
            self.assertEqual([],
 
2169
                list(files.get_missing_compression_parent_keys()))
 
2170
            files.insert_record_stream(entries)
 
2171
            missing_bases = files.get_missing_compression_parent_keys()
 
2172
            self.assertEqual(set([self.get_simple_key('left')]),
 
2173
                set(missing_bases))
 
2174
            self.assertEqual(set(keys), set(files.get_parent_map(keys)))
 
2175
        else:
 
2176
            self.assertRaises(
 
2177
                errors.RevisionNotPresent, files.insert_record_stream, entries)
 
2178
            files.check()
 
2179
 
 
2180
    def test_insert_record_stream_delta_missing_basis_can_be_added_later(self):
 
2181
        """Insertion where a needed basis is not included notifies the caller
 
2182
        of the missing basis.  That basis can be added in a second
 
2183
        insert_record_stream call that does not need to repeat records present
 
2184
        in the previous stream.  The record(s) that required that basis are
 
2185
        fully inserted once their basis is no longer missing.
 
2186
        """
 
2187
        if not self.support_partial_insertion:
 
2188
            raise TestNotApplicable(
 
2189
                'versioned file scenario does not support partial insertion')
 
2190
        source = self.get_knit_delta_source()
 
2191
        entries = source.get_record_stream([self.get_simple_key('origin'),
 
2192
            self.get_simple_key('merged')], 'unordered', False)
 
2193
        files = self.get_versionedfiles()
 
2194
        files.insert_record_stream(entries)
 
2195
        missing_bases = files.get_missing_compression_parent_keys()
 
2196
        self.assertEqual(set([self.get_simple_key('left')]),
 
2197
            set(missing_bases))
 
2198
        # 'merged' is inserted (although a commit of a write group involving
 
2199
        # this versionedfiles would fail).
 
2200
        merged_key = self.get_simple_key('merged')
 
2201
        self.assertEqual(
 
2202
            [merged_key], files.get_parent_map([merged_key]).keys())
 
2203
        # Add the full delta closure of the missing records
 
2204
        missing_entries = source.get_record_stream(
 
2205
            missing_bases, 'unordered', True)
 
2206
        files.insert_record_stream(missing_entries)
 
2207
        # Now 'merged' is fully inserted (and a commit would succeed).
 
2208
        self.assertEqual([], list(files.get_missing_compression_parent_keys()))
 
2209
        self.assertEqual(
 
2210
            [merged_key], files.get_parent_map([merged_key]).keys())
1984
2211
        files.check()
1985
 
        self.assertEqual({}, files.get_parent_map([]))
1986
2212
 
1987
2213
    def test_iter_lines_added_or_present_in_keys(self):
1988
2214
        # test that we get at least an equalset of the lines added by
2039
2265
        self.assertTrue(
2040
2266
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2041
2267
        # we dont care if we got more than that.
2042
 
        
 
2268
 
2043
2269
        # test all lines
2044
2270
        lines = iter_with_keys(files.keys(),
2045
2271
            [('Walking content.', 0, 5),
2086
2312
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2087
2313
        # noeol preceeding its leftmost parent in the output:
2088
2314
        # this is done by making it a merge of two parents with no common
2089
 
        # anestry: noeolbase and noeol with the 
 
2315
        # anestry: noeolbase and noeol with the
2090
2316
        # later-inserted parent the leftmost.
2091
2317
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2092
2318
            self.get_parents([self.get_simple_key('noeolbase'),
2178
2404
        TestCase.setUp(self)
2179
2405
        self._lines = {}
2180
2406
        self._parent_map = {}
2181
 
        self.texts = VirtualVersionedFiles(self._get_parent_map, 
 
2407
        self.texts = VirtualVersionedFiles(self._get_parent_map,
2182
2408
                                           self._lines.get)
2183
2409
 
2184
2410
    def test_add_lines(self):
2185
 
        self.assertRaises(NotImplementedError, 
 
2411
        self.assertRaises(NotImplementedError,
2186
2412
                self.texts.add_lines, "foo", [], [])
2187
2413
 
2188
2414
    def test_add_mpdiffs(self):
2189
 
        self.assertRaises(NotImplementedError, 
 
2415
        self.assertRaises(NotImplementedError,
2190
2416
                self.texts.add_mpdiffs, [])
2191
2417
 
2192
2418
    def test_check(self):
2206
2432
 
2207
2433
    def test_get_parent_map(self):
2208
2434
        self._parent_map = {"G": ("A", "B")}
2209
 
        self.assertEquals({("G",): (("A",),("B",))}, 
 
2435
        self.assertEquals({("G",): (("A",),("B",))},
2210
2436
                          self.texts.get_parent_map([("G",), ("L",)]))
2211
2437
 
2212
2438
    def test_get_record_stream(self):
2222
2448
        record = it.next()
2223
2449
        self.assertEquals("absent", record.storage_kind)
2224
2450
 
 
2451
    def test_iter_lines_added_or_present_in_keys(self):
 
2452
        self._lines["A"] = ["FOO", "BAR"]
 
2453
        self._lines["B"] = ["HEY"]
 
2454
        self._lines["C"] = ["Alberta"]
 
2455
        it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
 
2456
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
 
2457
            sorted(list(it)))
 
2458
 
2225
2459
 
2226
2460
class TestOrderingVersionedFilesDecorator(TestCaseWithMemoryTransport):
2227
2461