/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

  • Committer: Jonathan Lange
  • Date: 2009-02-24 04:19:22 UTC
  • mto: This revision was merged to the branch mainline in revision 4070.
  • Revision ID: jml@canonical.com-20090224041922-54j5zkxiyim5pz4r
Add one precedence test.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 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
24
24
from itertools import chain, izip
25
25
from StringIO import StringIO
26
26
 
 
27
import bzrlib
27
28
from bzrlib import (
28
29
    errors,
29
 
    graph as _mod_graph,
30
 
    groupcompress,
31
 
    knit as _mod_knit,
32
30
    osutils,
33
31
    progress,
34
 
    ui,
35
32
    )
36
33
from bzrlib.errors import (
37
34
                           RevisionNotPresent,
38
35
                           RevisionAlreadyPresent,
39
36
                           WeaveParentMismatch
40
37
                           )
 
38
from bzrlib import knit as _mod_knit
41
39
from bzrlib.knit import (
42
40
    cleanup_pack_knit,
43
41
    make_file_factory,
45
43
    KnitAnnotateFactory,
46
44
    KnitPlainFactory,
47
45
    )
 
46
from bzrlib.symbol_versioning import one_four, one_five
48
47
from bzrlib.tests import (
49
48
    TestCase,
50
49
    TestCaseWithMemoryTransport,
51
50
    TestNotApplicable,
 
51
    TestScenarioApplier,
52
52
    TestSkipped,
53
53
    condition_isinstance,
54
54
    split_suite_by_condition,
55
 
    multiply_tests,
 
55
    iter_suite_tests,
56
56
    )
57
57
from bzrlib.tests.http_utils import TestCaseWithWebserver
58
58
from bzrlib.trace import mutter
76
76
    """Parameterize VersionedFiles tests for different implementations."""
77
77
    to_adapt, result = split_suite_by_condition(
78
78
        standard_tests, condition_isinstance(TestVersionedFiles))
 
79
    len_one_adapter = TestScenarioApplier()
 
80
    len_two_adapter = TestScenarioApplier()
79
81
    # We want to be sure of behaviour for:
80
82
    # weaves prefix layout (weave texts)
81
83
    # individually named weaves (weave inventories)
86
88
    # individual graph knits in packs (inventories)
87
89
    # individual graph nocompression knits in packs (revisions)
88
90
    # plain text knits in packs (texts)
89
 
    len_one_scenarios = [
 
91
    len_one_adapter.scenarios = [
90
92
        ('weave-named', {
91
93
            'cleanup':None,
92
94
            'factory':make_versioned_files_factory(WeaveFile,
123
125
            'key_length':1,
124
126
            'support_partial_insertion': False,
125
127
            }),
126
 
        ('groupcompress-nograph', {
127
 
            'cleanup':groupcompress.cleanup_pack_group,
128
 
            'factory':groupcompress.make_pack_factory(False, False, 1),
129
 
            'graph': False,
130
 
            'key_length':1,
131
 
            'support_partial_insertion':False,
132
 
            }),
133
128
        ]
134
 
    len_two_scenarios = [
 
129
    len_two_adapter.scenarios = [
135
130
        ('weave-prefix', {
136
131
            'cleanup':None,
137
132
            'factory':make_versioned_files_factory(WeaveFile,
154
149
            'key_length':2,
155
150
            'support_partial_insertion': True,
156
151
            }),
157
 
        ('groupcompress', {
158
 
            'cleanup':groupcompress.cleanup_pack_group,
159
 
            'factory':groupcompress.make_pack_factory(True, False, 1),
160
 
            'graph': True,
161
 
            'key_length':1,
162
 
            'support_partial_insertion':False,
163
 
            }),
164
152
        ]
165
 
    scenarios = len_one_scenarios + len_two_scenarios
166
 
    return multiply_tests(to_adapt, scenarios, result)
 
153
    for test in iter_suite_tests(to_adapt):
 
154
        result.addTests(len_one_adapter.adapt(test))
 
155
        result.addTests(len_two_adapter.adapt(test))
 
156
    return result
167
157
 
168
158
 
169
159
def get_diamond_vf(f, trailing_eol=True, left_only=False):
170
160
    """Get a diamond graph to exercise deltas and merges.
171
 
 
 
161
    
172
162
    :param trailing_eol: If True end the last line with \n.
173
163
    """
174
164
    parents = {
195
185
 
196
186
 
197
187
def get_diamond_files(files, key_length, trailing_eol=True, left_only=False,
198
 
    nograph=False, nokeys=False):
 
188
    nograph=False):
199
189
    """Get a diamond graph to exercise deltas and merges.
200
190
 
201
191
    This creates a 5-node graph in files. If files supports 2-length keys two
202
192
    graphs are made to exercise the support for multiple ids.
203
 
 
 
193
    
204
194
    :param trailing_eol: If True end the last line with \n.
205
195
    :param key_length: The length of keys in files. Currently supports length 1
206
196
        and 2 keys.
208
198
    :param nograph: If True, do not provide parents to the add_lines calls;
209
199
        this is useful for tests that need inserted data but have graphless
210
200
        stores.
211
 
    :param nokeys: If True, pass None is as the key for all insertions.
212
 
        Currently implies nograph.
213
201
    :return: The results of the add_lines calls.
214
202
    """
215
 
    if nokeys:
216
 
        nograph = True
217
203
    if key_length == 1:
218
204
        prefixes = [()]
219
205
    else:
230
216
        else:
231
217
            result = [prefix + suffix for suffix in suffix_list]
232
218
            return result
233
 
    def get_key(suffix):
234
 
        if nokeys:
235
 
            return (None, )
236
 
        else:
237
 
            return (suffix,)
238
219
    # we loop over each key because that spreads the inserts across prefixes,
239
220
    # which is how commit operates.
240
221
    for prefix in prefixes:
241
 
        result.append(files.add_lines(prefix + get_key('origin'), (),
 
222
        result.append(files.add_lines(prefix + ('origin',), (),
242
223
            ['origin' + last_char]))
243
224
    for prefix in prefixes:
244
 
        result.append(files.add_lines(prefix + get_key('base'),
 
225
        result.append(files.add_lines(prefix + ('base',),
245
226
            get_parents([('origin',)]), ['base' + last_char]))
246
227
    for prefix in prefixes:
247
 
        result.append(files.add_lines(prefix + get_key('left'),
 
228
        result.append(files.add_lines(prefix + ('left',),
248
229
            get_parents([('base',)]),
249
230
            ['base\n', 'left' + last_char]))
250
231
    if not left_only:
251
232
        for prefix in prefixes:
252
 
            result.append(files.add_lines(prefix + get_key('right'),
 
233
            result.append(files.add_lines(prefix + ('right',),
253
234
                get_parents([('base',)]),
254
235
                ['base\n', 'right' + last_char]))
255
236
        for prefix in prefixes:
256
 
            result.append(files.add_lines(prefix + get_key('merged'),
 
237
            result.append(files.add_lines(prefix + ('merged',),
257
238
                get_parents([('left',), ('right',)]),
258
239
                ['base\n', 'left\n', 'right\n', 'merged' + last_char]))
259
240
    return result
285
266
            self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
286
267
            self.assertEqual(2, len(f))
287
268
            self.assertEqual(2, f.num_versions())
288
 
 
 
269
    
289
270
            self.assertRaises(RevisionNotPresent,
290
271
                f.add_lines, 'r2', ['foo'], [])
291
272
            self.assertRaises(RevisionAlreadyPresent,
330
311
        verify_file(f)
331
312
 
332
313
    def test_add_unicode_content(self):
333
 
        # unicode content is not permitted in versioned files.
 
314
        # unicode content is not permitted in versioned files. 
334
315
        # versioned files version sequences of bytes only.
335
316
        vf = self.get_file()
336
317
        self.assertRaises(errors.BzrBadParameterUnicode,
359
340
    def test_inline_newline_throws(self):
360
341
        # \r characters are not permitted in lines being added
361
342
        vf = self.get_file()
362
 
        self.assertRaises(errors.BzrBadParameterContainsNewline,
 
343
        self.assertRaises(errors.BzrBadParameterContainsNewline, 
363
344
            vf.add_lines, 'a', [], ['a\n\n'])
364
345
        self.assertRaises(
365
346
            (errors.BzrBadParameterContainsNewline, NotImplementedError),
564
545
        f.add_lines('noeolbase', [], ['line'])
565
546
        # noeol preceeding its leftmost parent in the output:
566
547
        # this is done by making it a merge of two parents with no common
567
 
        # anestry: noeolbase and noeol with the
 
548
        # anestry: noeolbase and noeol with the 
568
549
        # later-inserted parent the leftmost.
569
550
        f.add_lines('eolbeforefirstparent', ['noeolbase', 'noeol'], ['line'])
570
551
        # two identical eol texts
651
632
        self._transaction = 'after'
652
633
        self.assertRaises(errors.OutSideTransaction, f.add_lines, '', [], [])
653
634
        self.assertRaises(errors.OutSideTransaction, f.add_lines_with_ghosts, '', [], [])
654
 
 
 
635
        
655
636
    def test_copy_to(self):
656
637
        f = self.get_file()
657
638
        f.add_lines('0', [], ['a\n'])
730
711
 
731
712
    def test_iter_lines_added_or_present_in_versions(self):
732
713
        # test that we get at least an equalset of the lines added by
733
 
        # versions in the weave
 
714
        # versions in the weave 
734
715
        # the ordering here is to make a tree so that dumb searches have
735
716
        # more changes to muck up.
736
717
 
737
 
        class InstrumentedProgress(progress.ProgressTask):
 
718
        class InstrumentedProgress(progress.DummyProgress):
738
719
 
739
720
            def __init__(self):
740
 
                progress.ProgressTask.__init__(self)
 
721
 
 
722
                progress.DummyProgress.__init__(self)
741
723
                self.updates = []
742
724
 
743
725
            def update(self, msg=None, current=None, total=None):
769
751
                self.assertEqual(expected, progress.updates)
770
752
            return lines
771
753
        lines = iter_with_versions(['child', 'otherchild'],
772
 
                                   [('Walking content', 0, 2),
773
 
                                    ('Walking content', 1, 2),
774
 
                                    ('Walking content', 2, 2)])
 
754
                                   [('Walking content.', 0, 2),
 
755
                                    ('Walking content.', 1, 2),
 
756
                                    ('Walking content.', 2, 2)])
775
757
        # we must see child and otherchild
776
758
        self.assertTrue(lines[('child\n', 'child')] > 0)
777
759
        self.assertTrue(lines[('otherchild\n', 'otherchild')] > 0)
778
760
        # we dont care if we got more than that.
779
 
 
 
761
        
780
762
        # test all lines
781
 
        lines = iter_with_versions(None, [('Walking content', 0, 5),
782
 
                                          ('Walking content', 1, 5),
783
 
                                          ('Walking content', 2, 5),
784
 
                                          ('Walking content', 3, 5),
785
 
                                          ('Walking content', 4, 5),
786
 
                                          ('Walking content', 5, 5)])
 
763
        lines = iter_with_versions(None, [('Walking content.', 0, 5),
 
764
                                          ('Walking content.', 1, 5),
 
765
                                          ('Walking content.', 2, 5),
 
766
                                          ('Walking content.', 3, 5),
 
767
                                          ('Walking content.', 4, 5),
 
768
                                          ('Walking content.', 5, 5)])
787
769
        # all lines must be seen at least once
788
770
        self.assertTrue(lines[('base\n', 'base')] > 0)
789
771
        self.assertTrue(lines[('lancestor\n', 'lancestor')] > 0)
859
841
                          'base',
860
842
                          [],
861
843
                          [])
862
 
 
 
844
    
863
845
    def test_get_sha1s(self):
864
846
        # check the sha1 data is available
865
847
        vf = self.get_file()
875
857
            'b': '3f786850e387550fdab836ed7e6dc881de23001b',
876
858
            },
877
859
            vf.get_sha1s(['a', 'c', 'b']))
878
 
 
 
860
        
879
861
 
880
862
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
881
863
 
888
870
            get_scope=self.get_transaction)
889
871
        w.add_lines('v1', [], ['hello\n'])
890
872
        w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
891
 
 
 
873
        
892
874
        # We are going to invasively corrupt the text
893
875
        # Make sure the internals of weave are the same
894
876
        self.assertEqual([('{', 0)
898
880
                        , 'there\n'
899
881
                        , ('}', None)
900
882
                        ], w._weave)
901
 
 
 
883
        
902
884
        self.assertEqual(['f572d396fae9206628714fb2ce00f72e94f2258f'
903
885
                        , '90f265c6e75f1c8f9ab76dcf85528352c5f215ef'
904
886
                        ], w._sha1s)
905
887
        w.check()
906
 
 
 
888
        
907
889
        # Corrupted
908
890
        w._weave[4] = 'There\n'
909
891
        return w
913
895
        # Corrected
914
896
        w._weave[4] = 'there\n'
915
897
        self.assertEqual('hello\nthere\n', w.get_text('v2'))
916
 
 
 
898
        
917
899
        #Invalid checksum, first digit changed
918
900
        w._sha1s[1] =  'f0f265c6e75f1c8f9ab76dcf85528352c5f215ef'
919
901
        return w
1028
1010
 
1029
1011
        def addcrlf(x):
1030
1012
            return x + '\n'
1031
 
 
 
1013
        
1032
1014
        w = self.get_file()
1033
1015
        w.add_lines('text0', [], map(addcrlf, base))
1034
1016
        w.add_lines('text1', ['text0'], map(addcrlf, a))
1050
1032
 
1051
1033
        mp = map(addcrlf, mp)
1052
1034
        self.assertEqual(mt.readlines(), mp)
1053
 
 
1054
 
 
 
1035
        
 
1036
        
1055
1037
    def testOneInsert(self):
1056
1038
        self.doMerge([],
1057
1039
                     ['aa'],
1075
1057
                     ['aaa', 'xxx', 'yyy', 'bbb'],
1076
1058
                     ['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1077
1059
 
1078
 
        # really it ought to reduce this to
 
1060
        # really it ought to reduce this to 
1079
1061
        # ['aaa', 'xxx', 'yyy', 'bbb']
1080
1062
 
1081
1063
 
1083
1065
        self.doMerge(['aaa'],
1084
1066
                     ['xxx'],
1085
1067
                     ['yyy', 'zzz'],
1086
 
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
 
1068
                     ['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz', 
1087
1069
                      '>>>>>>> '])
1088
1070
 
1089
1071
    def testNonClashInsert1(self):
1090
1072
        self.doMerge(['aaa'],
1091
1073
                     ['xxx', 'aaa'],
1092
1074
                     ['yyy', 'zzz'],
1093
 
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
 
1075
                     ['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz', 
1094
1076
                      '>>>>>>> '])
1095
1077
 
1096
1078
    def testNonClashInsert2(self):
1110
1092
        #######################################
1111
1093
        # skippd, not working yet
1112
1094
        return
1113
 
 
 
1095
        
1114
1096
        self.doMerge(['aaa', 'bbb', 'ccc'],
1115
1097
                     ['aaa', 'ddd', 'ccc'],
1116
1098
                     ['aaa', 'ccc'],
1153
1135
            """
1154
1136
        result = """\
1155
1137
            line 1
1156
 
<<<<<<<\x20
1157
 
            line 2
1158
 
=======
1159
 
>>>>>>>\x20
1160
1138
            """
1161
1139
        self._test_merge_from_strings(base, a, b, result)
1162
1140
 
1163
1141
    def test_deletion_overlap(self):
1164
1142
        """Delete overlapping regions with no other conflict.
1165
1143
 
1166
 
        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 
1167
1145
        conflict, but for now conflict is safer.
1168
1146
        """
1169
1147
        base = """\
1185
1163
            """
1186
1164
        result = """\
1187
1165
            start context
1188
 
<<<<<<<\x20
 
1166
<<<<<<< 
1189
1167
            int a() {}
1190
1168
=======
1191
1169
            int c() {}
1192
 
>>>>>>>\x20
 
1170
>>>>>>> 
1193
1171
            end context
1194
1172
            """
1195
1173
        self._test_merge_from_strings(base, a, b, result)
1221
1199
 
1222
1200
    def test_sync_on_deletion(self):
1223
1201
        """Specific case of merge where we can synchronize incorrectly.
1224
 
 
 
1202
        
1225
1203
        A previous version of the weave merge concluded that the two versions
1226
1204
        agreed on deleting line 2, and this could be a synchronization point.
1227
 
        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 
1228
1206
        both sides.
1229
1207
 
1230
1208
        It's better to consider the whole thing as a disagreement region.
1249
1227
            """
1250
1228
        result = """\
1251
1229
            start context
1252
 
<<<<<<<\x20
 
1230
<<<<<<< 
1253
1231
            base line 1
1254
1232
            a's replacement line 2
1255
1233
=======
1256
1234
            b replaces
1257
1235
            both lines
1258
 
>>>>>>>\x20
 
1236
>>>>>>> 
1259
1237
            end context
1260
1238
            """
1261
1239
        self._test_merge_from_strings(base, a, b, result)
1272
1250
        write_weave(w, tmpf)
1273
1251
        self.log(tmpf.getvalue())
1274
1252
 
1275
 
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
 
1253
    overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======', 
1276
1254
                                'xxx', '>>>>>>> ', 'bbb']
1277
1255
 
1278
1256
 
1387
1365
 
1388
1366
    def test_unannotated_to_fulltext(self):
1389
1367
        """Test adapting unannotated knits to full texts.
1390
 
 
 
1368
        
1391
1369
        This is used for -> weaves, and for -> annotated knits.
1392
1370
        """
1393
1371
        # we need a full text, and a delta
1406
1384
 
1407
1385
    def test_unannotated_to_fulltext_no_eol(self):
1408
1386
        """Test adapting unannotated knits to full texts.
1409
 
 
 
1387
        
1410
1388
        This is used for -> weaves, and for -> annotated knits.
1411
1389
        """
1412
1390
        # we need a full text, and a delta
1469
1447
            transport.mkdir('.')
1470
1448
        files = self.factory(transport)
1471
1449
        if self.cleanup is not None:
1472
 
            self.addCleanup(self.cleanup, files)
 
1450
            self.addCleanup(lambda:self.cleanup(files))
1473
1451
        return files
1474
1452
 
1475
 
    def get_simple_key(self, suffix):
1476
 
        """Return a key for the object under test."""
1477
 
        if self.key_length == 1:
1478
 
            return (suffix,)
1479
 
        else:
1480
 
            return ('FileA',) + (suffix,)
1481
 
 
1482
 
    def test_add_lines(self):
1483
 
        f = self.get_versionedfiles()
1484
 
        key0 = self.get_simple_key('r0')
1485
 
        key1 = self.get_simple_key('r1')
1486
 
        key2 = self.get_simple_key('r2')
1487
 
        keyf = self.get_simple_key('foo')
1488
 
        f.add_lines(key0, [], ['a\n', 'b\n'])
1489
 
        if self.graph:
1490
 
            f.add_lines(key1, [key0], ['b\n', 'c\n'])
1491
 
        else:
1492
 
            f.add_lines(key1, [], ['b\n', 'c\n'])
1493
 
        keys = f.keys()
1494
 
        self.assertTrue(key0 in keys)
1495
 
        self.assertTrue(key1 in keys)
1496
 
        records = []
1497
 
        for record in f.get_record_stream([key0, key1], 'unordered', True):
1498
 
            records.append((record.key, record.get_bytes_as('fulltext')))
1499
 
        records.sort()
1500
 
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
1501
 
 
1502
 
    def test__add_text(self):
1503
 
        f = self.get_versionedfiles()
1504
 
        key0 = self.get_simple_key('r0')
1505
 
        key1 = self.get_simple_key('r1')
1506
 
        key2 = self.get_simple_key('r2')
1507
 
        keyf = self.get_simple_key('foo')
1508
 
        f._add_text(key0, [], 'a\nb\n')
1509
 
        if self.graph:
1510
 
            f._add_text(key1, [key0], 'b\nc\n')
1511
 
        else:
1512
 
            f._add_text(key1, [], 'b\nc\n')
1513
 
        keys = f.keys()
1514
 
        self.assertTrue(key0 in keys)
1515
 
        self.assertTrue(key1 in keys)
1516
 
        records = []
1517
 
        for record in f.get_record_stream([key0, key1], 'unordered', True):
1518
 
            records.append((record.key, record.get_bytes_as('fulltext')))
1519
 
        records.sort()
1520
 
        self.assertEqual([(key0, 'a\nb\n'), (key1, 'b\nc\n')], records)
1521
 
 
1522
1453
    def test_annotate(self):
1523
1454
        files = self.get_versionedfiles()
1524
1455
        self.get_diamond_files(files)
1558
1489
        self.assertRaises(RevisionNotPresent,
1559
1490
            files.annotate, prefix + ('missing-key',))
1560
1491
 
1561
 
    def test_check_no_parameters(self):
1562
 
        files = self.get_versionedfiles()
1563
 
 
1564
 
    def test_check_progressbar_parameter(self):
1565
 
        """A progress bar can be supplied because check can be a generator."""
1566
 
        pb = ui.ui_factory.nested_progress_bar()
1567
 
        self.addCleanup(pb.finished)
1568
 
        files = self.get_versionedfiles()
1569
 
        files.check(progress_bar=pb)
1570
 
 
1571
 
    def test_check_with_keys_becomes_generator(self):
1572
 
        files = self.get_versionedfiles()
1573
 
        self.get_diamond_files(files)
1574
 
        keys = files.keys()
1575
 
        entries = files.check(keys=keys)
1576
 
        seen = set()
1577
 
        # Texts output should be fulltexts.
1578
 
        self.capture_stream(files, entries, seen.add,
1579
 
            files.get_parent_map(keys), require_fulltext=True)
1580
 
        # All texts should be output.
1581
 
        self.assertEqual(set(keys), seen)
1582
 
 
1583
 
    def test_clear_cache(self):
1584
 
        files = self.get_versionedfiles()
1585
 
        files.clear_cache()
1586
 
 
1587
1492
    def test_construct(self):
1588
1493
        """Each parameterised test can be constructed on a transport."""
1589
1494
        files = self.get_versionedfiles()
1590
1495
 
1591
 
    def get_diamond_files(self, files, trailing_eol=True, left_only=False,
1592
 
        nokeys=False):
 
1496
    def get_diamond_files(self, files, trailing_eol=True, left_only=False):
1593
1497
        return get_diamond_files(files, self.key_length,
1594
1498
            trailing_eol=trailing_eol, nograph=not self.graph,
1595
 
            left_only=left_only, nokeys=nokeys)
1596
 
 
1597
 
    def _add_content_nostoresha(self, add_lines):
1598
 
        """When nostore_sha is supplied using old content raises."""
1599
 
        vf = self.get_versionedfiles()
1600
 
        empty_text = ('a', [])
1601
 
        sample_text_nl = ('b', ["foo\n", "bar\n"])
1602
 
        sample_text_no_nl = ('c', ["foo\n", "bar"])
1603
 
        shas = []
1604
 
        for version, lines in (empty_text, sample_text_nl, sample_text_no_nl):
1605
 
            if add_lines:
1606
 
                sha, _, _ = vf.add_lines(self.get_simple_key(version), [],
1607
 
                                         lines)
1608
 
            else:
1609
 
                sha, _, _ = vf._add_text(self.get_simple_key(version), [],
1610
 
                                         ''.join(lines))
1611
 
            shas.append(sha)
1612
 
        # we now have a copy of all the lines in the vf.
1613
 
        for sha, (version, lines) in zip(
1614
 
            shas, (empty_text, sample_text_nl, sample_text_no_nl)):
1615
 
            new_key = self.get_simple_key(version + "2")
1616
 
            self.assertRaises(errors.ExistingContent,
1617
 
                vf.add_lines, new_key, [], lines,
1618
 
                nostore_sha=sha)
1619
 
            self.assertRaises(errors.ExistingContent,
1620
 
                vf._add_text, new_key, [], ''.join(lines),
1621
 
                nostore_sha=sha)
1622
 
            # and no new version should have been added.
1623
 
            record = vf.get_record_stream([new_key], 'unordered', True).next()
1624
 
            self.assertEqual('absent', record.storage_kind)
1625
 
 
1626
 
    def test_add_lines_nostoresha(self):
1627
 
        self._add_content_nostoresha(add_lines=True)
1628
 
 
1629
 
    def test__add_text_nostoresha(self):
1630
 
        self._add_content_nostoresha(add_lines=False)
 
1499
            left_only=left_only)
1631
1500
 
1632
1501
    def test_add_lines_return(self):
1633
1502
        files = self.get_versionedfiles()
1660
1529
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1661
1530
                results)
1662
1531
 
1663
 
    def test_add_lines_no_key_generates_chk_key(self):
1664
 
        files = self.get_versionedfiles()
1665
 
        # save code by using the stock data insertion helper.
1666
 
        adds = self.get_diamond_files(files, nokeys=True)
1667
 
        results = []
1668
 
        # We can only validate the first 2 elements returned from add_lines.
1669
 
        for add in adds:
1670
 
            self.assertEqual(3, len(add))
1671
 
            results.append(add[:2])
1672
 
        if self.key_length == 1:
1673
 
            self.assertEqual([
1674
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1675
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1676
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1677
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1678
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1679
 
                results)
1680
 
            # Check the added items got CHK keys.
1681
 
            self.assertEqual(set([
1682
 
                ('sha1:00e364d235126be43292ab09cb4686cf703ddc17',),
1683
 
                ('sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44',),
1684
 
                ('sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',),
1685
 
                ('sha1:a8478686da38e370e32e42e8a0c220e33ee9132f',),
1686
 
                ('sha1:ed8bce375198ea62444dc71952b22cfc2b09226d',),
1687
 
                ]),
1688
 
                files.keys())
1689
 
        elif self.key_length == 2:
1690
 
            self.assertEqual([
1691
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1692
 
                ('00e364d235126be43292ab09cb4686cf703ddc17', 7),
1693
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1694
 
                ('51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44', 5),
1695
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1696
 
                ('a8478686da38e370e32e42e8a0c220e33ee9132f', 10),
1697
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1698
 
                ('9ef09dfa9d86780bdec9219a22560c6ece8e0ef1', 11),
1699
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23),
1700
 
                ('ed8bce375198ea62444dc71952b22cfc2b09226d', 23)],
1701
 
                results)
1702
 
            # Check the added items got CHK keys.
1703
 
            self.assertEqual(set([
1704
 
                ('FileA', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
1705
 
                ('FileA', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
1706
 
                ('FileA', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
1707
 
                ('FileA', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
1708
 
                ('FileA', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
1709
 
                ('FileB', 'sha1:00e364d235126be43292ab09cb4686cf703ddc17'),
1710
 
                ('FileB', 'sha1:51c64a6f4fc375daf0d24aafbabe4d91b6f4bb44'),
1711
 
                ('FileB', 'sha1:9ef09dfa9d86780bdec9219a22560c6ece8e0ef1'),
1712
 
                ('FileB', 'sha1:a8478686da38e370e32e42e8a0c220e33ee9132f'),
1713
 
                ('FileB', 'sha1:ed8bce375198ea62444dc71952b22cfc2b09226d'),
1714
 
                ]),
1715
 
                files.keys())
1716
 
 
1717
1532
    def test_empty_lines(self):
1718
1533
        """Empty files can be stored."""
1719
1534
        f = self.get_versionedfiles()
1741
1556
            f.get_record_stream([key_b], 'unordered', True
1742
1557
                ).next().get_bytes_as('fulltext'))
1743
1558
 
1744
 
    def test_get_known_graph_ancestry(self):
1745
 
        f = self.get_versionedfiles()
1746
 
        if not self.graph:
1747
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
1748
 
        key_a = self.get_simple_key('a')
1749
 
        key_b = self.get_simple_key('b')
1750
 
        key_c = self.get_simple_key('c')
1751
 
        # A
1752
 
        # |\
1753
 
        # | B
1754
 
        # |/
1755
 
        # C
1756
 
        f.add_lines(key_a, [], ['\n'])
1757
 
        f.add_lines(key_b, [key_a], ['\n'])
1758
 
        f.add_lines(key_c, [key_a, key_b], ['\n'])
1759
 
        kg = f.get_known_graph_ancestry([key_c])
1760
 
        self.assertIsInstance(kg, _mod_graph.KnownGraph)
1761
 
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
1762
 
 
1763
 
    def test_known_graph_with_fallbacks(self):
1764
 
        f = self.get_versionedfiles('files')
1765
 
        if not self.graph:
1766
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
1767
 
        if getattr(f, 'add_fallback_versioned_files', None) is None:
1768
 
            raise TestNotApplicable("%s doesn't support fallbacks"
1769
 
                                    % (f.__class__.__name__,))
1770
 
        key_a = self.get_simple_key('a')
1771
 
        key_b = self.get_simple_key('b')
1772
 
        key_c = self.get_simple_key('c')
1773
 
        # A     only in fallback
1774
 
        # |\
1775
 
        # | B
1776
 
        # |/
1777
 
        # C
1778
 
        g = self.get_versionedfiles('fallback')
1779
 
        g.add_lines(key_a, [], ['\n'])
1780
 
        f.add_fallback_versioned_files(g)
1781
 
        f.add_lines(key_b, [key_a], ['\n'])
1782
 
        f.add_lines(key_c, [key_a, key_b], ['\n'])
1783
 
        kg = f.get_known_graph_ancestry([key_c])
1784
 
        self.assertEqual([key_a, key_b, key_c], list(kg.topo_sort()))
1785
 
 
1786
1559
    def test_get_record_stream_empty(self):
1787
1560
        """An empty stream can be requested without error."""
1788
1561
        f = self.get_versionedfiles()
1796
1569
             'knit-ft', 'knit-delta', 'chunked', 'fulltext',
1797
1570
             'knit-annotated-ft-gz', 'knit-annotated-delta-gz', 'knit-ft-gz',
1798
1571
             'knit-delta-gz',
1799
 
             'knit-delta-closure', 'knit-delta-closure-ref',
1800
 
             'groupcompress-block', 'groupcompress-block-ref'])
 
1572
             'knit-delta-closure', 'knit-delta-closure-ref'])
1801
1573
 
1802
 
    def capture_stream(self, f, entries, on_seen, parents,
1803
 
        require_fulltext=False):
 
1574
    def capture_stream(self, f, entries, on_seen, parents):
1804
1575
        """Capture a stream for testing."""
1805
1576
        for factory in entries:
1806
1577
            on_seen(factory.key)
1807
1578
            self.assertValidStorageKind(factory.storage_kind)
1808
 
            if factory.sha1 is not None:
1809
 
                self.assertEqual(f.get_sha1s([factory.key])[factory.key],
1810
 
                    factory.sha1)
 
1579
            self.assertEqual(f.get_sha1s([factory.key])[factory.key],
 
1580
                factory.sha1)
1811
1581
            self.assertEqual(parents[factory.key], factory.parents)
1812
1582
            self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
1813
1583
                str)
1814
 
            if require_fulltext:
1815
 
                factory.get_bytes_as('fulltext')
1816
1584
 
1817
1585
    def test_get_record_stream_interface(self):
1818
1586
        """each item in a stream has to provide a regular interface."""
1825
1593
        self.capture_stream(files, entries, seen.add, parent_map)
1826
1594
        self.assertEqual(set(keys), seen)
1827
1595
 
 
1596
    def get_simple_key(self, suffix):
 
1597
        """Return a key for the object under test."""
 
1598
        if self.key_length == 1:
 
1599
            return (suffix,)
 
1600
        else:
 
1601
            return ('FileA',) + (suffix,)
 
1602
 
1828
1603
    def get_keys_and_sort_order(self):
1829
1604
        """Get diamond test keys list, and their sort ordering."""
1830
1605
        if self.key_length == 1:
1845
1620
                }
1846
1621
        return keys, sort_order
1847
1622
 
1848
 
    def get_keys_and_groupcompress_sort_order(self):
1849
 
        """Get diamond test keys list, and their groupcompress sort ordering."""
1850
 
        if self.key_length == 1:
1851
 
            keys = [('merged',), ('left',), ('right',), ('base',)]
1852
 
            sort_order = {('merged',):0, ('left',):1, ('right',):1, ('base',):2}
1853
 
        else:
1854
 
            keys = [
1855
 
                ('FileA', 'merged'), ('FileA', 'left'), ('FileA', 'right'),
1856
 
                ('FileA', 'base'),
1857
 
                ('FileB', 'merged'), ('FileB', 'left'), ('FileB', 'right'),
1858
 
                ('FileB', 'base'),
1859
 
                ]
1860
 
            sort_order = {
1861
 
                ('FileA', 'merged'):0, ('FileA', 'left'):1, ('FileA', 'right'):1,
1862
 
                ('FileA', 'base'):2,
1863
 
                ('FileB', 'merged'):3, ('FileB', 'left'):4, ('FileB', 'right'):4,
1864
 
                ('FileB', 'base'):5,
1865
 
                }
1866
 
        return keys, sort_order
1867
 
 
1868
1623
    def test_get_record_stream_interface_ordered(self):
1869
1624
        """each item in a stream has to provide a regular interface."""
1870
1625
        files = self.get_versionedfiles()
1898
1653
 
1899
1654
        self.assertStreamOrder(sort_order, seen, keys)
1900
1655
 
1901
 
    def test_get_record_stream_interface_groupcompress(self):
1902
 
        """each item in a stream has to provide a regular interface."""
1903
 
        files = self.get_versionedfiles()
1904
 
        self.get_diamond_files(files)
1905
 
        keys, sort_order = self.get_keys_and_groupcompress_sort_order()
1906
 
        parent_map = files.get_parent_map(keys)
1907
 
        entries = files.get_record_stream(keys, 'groupcompress', False)
1908
 
        seen = []
1909
 
        self.capture_stream(files, entries, seen.append, parent_map)
1910
 
        self.assertStreamOrder(sort_order, seen, keys)
1911
 
 
1912
1656
    def assertStreamOrder(self, sort_order, seen, keys):
1913
1657
        self.assertEqual(len(set(seen)), len(keys))
1914
1658
        if self.key_length == 1:
1945
1689
        for factory in entries:
1946
1690
            seen.add(factory.key)
1947
1691
            self.assertValidStorageKind(factory.storage_kind)
1948
 
            if factory.sha1 is not None:
1949
 
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
1950
 
                                 factory.sha1)
 
1692
            self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1693
                factory.sha1)
1951
1694
            self.assertEqual(parent_map[factory.key], factory.parents)
1952
1695
            # currently no stream emits mpdiff
1953
1696
            self.assertRaises(errors.UnavailableRepresentation,
2008
1751
 
2009
1752
    def assertStreamMetaEqual(self, records, expected, stream):
2010
1753
        """Assert that streams expected and stream have the same records.
2011
 
 
 
1754
        
2012
1755
        :param records: A list to collect the seen records.
2013
1756
        :return: A generator of the records in stream.
2014
1757
        """
2027
1770
 
2028
1771
        :param skipped_records: A list with one element to increment when a
2029
1772
            record is skipped.
2030
 
        :param full_texts: A dict from key->fulltext representation, for
 
1773
        :param full_texts: A dict from key->fulltext representation, for 
2031
1774
            checking chunked or fulltext stored records.
2032
1775
        :param stream: A record_stream.
2033
1776
        :return: An iterator over the bytes of each record.
2151
1894
                self.assertEqual(None, factory.parents)
2152
1895
            else:
2153
1896
                self.assertValidStorageKind(factory.storage_kind)
2154
 
                if factory.sha1 is not None:
2155
 
                    sha1 = files.get_sha1s([factory.key])[factory.key]
2156
 
                    self.assertEqual(sha1, factory.sha1)
 
1897
                self.assertEqual(files.get_sha1s([factory.key])[factory.key],
 
1898
                    factory.sha1)
2157
1899
                self.assertEqual(parents[factory.key], factory.parents)
2158
1900
                self.assertIsInstance(factory.get_bytes_as(factory.storage_kind),
2159
1901
                    str)
2193
1935
        else:
2194
1936
            return None
2195
1937
 
2196
 
    def test_get_annotator(self):
2197
 
        files = self.get_versionedfiles()
2198
 
        self.get_diamond_files(files)
2199
 
        origin_key = self.get_simple_key('origin')
2200
 
        base_key = self.get_simple_key('base')
2201
 
        left_key = self.get_simple_key('left')
2202
 
        right_key = self.get_simple_key('right')
2203
 
        merged_key = self.get_simple_key('merged')
2204
 
        # annotator = files.get_annotator()
2205
 
        # introduced full text
2206
 
        origins, lines = files.get_annotator().annotate(origin_key)
2207
 
        self.assertEqual([(origin_key,)], origins)
2208
 
        self.assertEqual(['origin\n'], lines)
2209
 
        # a delta
2210
 
        origins, lines = files.get_annotator().annotate(base_key)
2211
 
        self.assertEqual([(base_key,)], origins)
2212
 
        # a merge
2213
 
        origins, lines = files.get_annotator().annotate(merged_key)
2214
 
        if self.graph:
2215
 
            self.assertEqual([
2216
 
                (base_key,),
2217
 
                (left_key,),
2218
 
                (right_key,),
2219
 
                (merged_key,),
2220
 
                ], origins)
2221
 
        else:
2222
 
            # Without a graph everything is new.
2223
 
            self.assertEqual([
2224
 
                (merged_key,),
2225
 
                (merged_key,),
2226
 
                (merged_key,),
2227
 
                (merged_key,),
2228
 
                ], origins)
2229
 
        self.assertRaises(RevisionNotPresent,
2230
 
            files.get_annotator().annotate, self.get_simple_key('missing-key'))
2231
 
 
2232
1938
    def test_get_parent_map(self):
2233
1939
        files = self.get_versionedfiles()
2234
1940
        if self.key_length == 1:
2285
1991
            keys[4]: '9ef09dfa9d86780bdec9219a22560c6ece8e0ef1',
2286
1992
            },
2287
1993
            files.get_sha1s(keys))
2288
 
 
 
1994
        
2289
1995
    def test_insert_record_stream_empty(self):
2290
1996
        """Inserting an empty record stream should work."""
2291
1997
        files = self.get_versionedfiles()
2437
2143
        else:
2438
2144
            self.assertIdenticalVersionedFile(source, files)
2439
2145
 
2440
 
    def test_insert_record_stream_long_parent_chain_out_of_order(self):
2441
 
        """An out of order stream can either error or work."""
2442
 
        if not self.graph:
2443
 
            raise TestNotApplicable('ancestry info only relevant with graph.')
2444
 
        # Create a reasonably long chain of records based on each other, where
2445
 
        # most will be deltas.
2446
 
        source = self.get_versionedfiles('source')
2447
 
        parents = ()
2448
 
        keys = []
2449
 
        content = [('same same %d\n' % n) for n in range(500)]
2450
 
        for letter in 'abcdefghijklmnopqrstuvwxyz':
2451
 
            key = ('key-' + letter,)
2452
 
            if self.key_length == 2:
2453
 
                key = ('prefix',) + key
2454
 
            content.append('content for ' + letter + '\n')
2455
 
            source.add_lines(key, parents, content)
2456
 
            keys.append(key)
2457
 
            parents = (key,)
2458
 
        # Create a stream of these records, excluding the first record that the
2459
 
        # rest ultimately depend upon, and insert it into a new vf.
2460
 
        streams = []
2461
 
        for key in reversed(keys):
2462
 
            streams.append(source.get_record_stream([key], 'unordered', False))
2463
 
        deltas = chain(*streams[:-1])
2464
 
        files = self.get_versionedfiles()
2465
 
        try:
2466
 
            files.insert_record_stream(deltas)
2467
 
        except RevisionNotPresent:
2468
 
            # Must not have corrupted the file.
2469
 
            files.check()
2470
 
        else:
2471
 
            # Must only report either just the first key as a missing parent,
2472
 
            # no key as missing (for nodelta scenarios).
2473
 
            missing = set(files.get_missing_compression_parent_keys())
2474
 
            missing.discard(keys[0])
2475
 
            self.assertEqual(set(), missing)
2476
 
 
2477
2146
    def get_knit_delta_source(self):
2478
2147
        """Get a source that can produce a stream with knit delta records,
2479
2148
        regardless of this test's scenario.
2547
2216
        # the ordering here is to make a tree so that dumb searches have
2548
2217
        # more changes to muck up.
2549
2218
 
2550
 
        class InstrumentedProgress(progress.ProgressTask):
 
2219
        class InstrumentedProgress(progress.DummyProgress):
2551
2220
 
2552
2221
            def __init__(self):
2553
 
                progress.ProgressTask.__init__(self)
 
2222
 
 
2223
                progress.DummyProgress.__init__(self)
2554
2224
                self.updates = []
2555
2225
 
2556
2226
            def update(self, msg=None, current=None, total=None):
2587
2257
            return lines
2588
2258
        lines = iter_with_keys(
2589
2259
            [self.get_simple_key('child'), self.get_simple_key('otherchild')],
2590
 
            [('Walking content', 0, 2),
2591
 
             ('Walking content', 1, 2),
2592
 
             ('Walking content', 2, 2)])
 
2260
            [('Walking content.', 0, 2),
 
2261
             ('Walking content.', 1, 2),
 
2262
             ('Walking content.', 2, 2)])
2593
2263
        # we must see child and otherchild
2594
2264
        self.assertTrue(lines[('child\n', self.get_simple_key('child'))] > 0)
2595
2265
        self.assertTrue(
2596
2266
            lines[('otherchild\n', self.get_simple_key('otherchild'))] > 0)
2597
2267
        # we dont care if we got more than that.
2598
 
 
 
2268
        
2599
2269
        # test all lines
2600
2270
        lines = iter_with_keys(files.keys(),
2601
 
            [('Walking content', 0, 5),
2602
 
             ('Walking content', 1, 5),
2603
 
             ('Walking content', 2, 5),
2604
 
             ('Walking content', 3, 5),
2605
 
             ('Walking content', 4, 5),
2606
 
             ('Walking content', 5, 5)])
 
2271
            [('Walking content.', 0, 5),
 
2272
             ('Walking content.', 1, 5),
 
2273
             ('Walking content.', 2, 5),
 
2274
             ('Walking content.', 3, 5),
 
2275
             ('Walking content.', 4, 5),
 
2276
             ('Walking content.', 5, 5)])
2607
2277
        # all lines must be seen at least once
2608
2278
        self.assertTrue(lines[('base\n', self.get_simple_key('base'))] > 0)
2609
2279
        self.assertTrue(
2642
2312
        files.add_lines(self.get_simple_key('noeolbase'), [], ['line'])
2643
2313
        # noeol preceeding its leftmost parent in the output:
2644
2314
        # this is done by making it a merge of two parents with no common
2645
 
        # anestry: noeolbase and noeol with the
 
2315
        # anestry: noeolbase and noeol with the 
2646
2316
        # later-inserted parent the leftmost.
2647
2317
        files.add_lines(self.get_simple_key('eolbeforefirstparent'),
2648
2318
            self.get_parents([self.get_simple_key('noeolbase'),
2734
2404
        TestCase.setUp(self)
2735
2405
        self._lines = {}
2736
2406
        self._parent_map = {}
2737
 
        self.texts = VirtualVersionedFiles(self._get_parent_map,
 
2407
        self.texts = VirtualVersionedFiles(self._get_parent_map, 
2738
2408
                                           self._lines.get)
2739
2409
 
2740
2410
    def test_add_lines(self):
2741
 
        self.assertRaises(NotImplementedError,
 
2411
        self.assertRaises(NotImplementedError, 
2742
2412
                self.texts.add_lines, "foo", [], [])
2743
2413
 
2744
2414
    def test_add_mpdiffs(self):
2745
 
        self.assertRaises(NotImplementedError,
 
2415
        self.assertRaises(NotImplementedError, 
2746
2416
                self.texts.add_mpdiffs, [])
2747
2417
 
2748
 
    def test_check_noerrors(self):
2749
 
        self.texts.check()
 
2418
    def test_check(self):
 
2419
        self.assertTrue(self.texts.check())
2750
2420
 
2751
2421
    def test_insert_record_stream(self):
2752
2422
        self.assertRaises(NotImplementedError, self.texts.insert_record_stream,
2762
2432
 
2763
2433
    def test_get_parent_map(self):
2764
2434
        self._parent_map = {"G": ("A", "B")}
2765
 
        self.assertEquals({("G",): (("A",),("B",))},
 
2435
        self.assertEquals({("G",): (("A",),("B",))}, 
2766
2436
                          self.texts.get_parent_map([("G",), ("L",)]))
2767
2437
 
2768
2438
    def test_get_record_stream(self):
2783
2453
        self._lines["B"] = ["HEY"]
2784
2454
        self._lines["C"] = ["Alberta"]
2785
2455
        it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
2786
 
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
 
2456
        self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]), 
2787
2457
            sorted(list(it)))
2788
2458
 
2789
2459