~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_knit.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-10 15:46:03 UTC
  • mfrom: (4985.3.21 update)
  • mto: This revision was merged to the branch mainline in revision 5021.
  • Revision ID: v.ladeuil+lp@free.fr-20100210154603-k4no1gvfuqpzrw7p
Update performs two merges in a more logical order but stop on conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests for Knit data structure"""
18
18
 
28
28
    multiparent,
29
29
    osutils,
30
30
    pack,
 
31
    tests,
31
32
    )
32
33
from bzrlib.errors import (
33
34
    RevisionAlreadyPresent,
42
43
    KnitSequenceMatcher,
43
44
    KnitVersionedFiles,
44
45
    PlainKnitContent,
 
46
    _VFContentMapGenerator,
45
47
    _DirectPackAccess,
46
48
    _KndxIndex,
47
49
    _KnitGraphIndex,
63
65
from bzrlib.versionedfile import (
64
66
    AbsentContentFactory,
65
67
    ConstantMapper,
 
68
    network_bytes_to_kind_and_offset,
66
69
    RecordingVersionedFilesDecorator,
67
70
    )
68
71
 
69
72
 
70
 
class _CompiledKnitFeature(Feature):
71
 
 
72
 
    def _probe(self):
73
 
        try:
74
 
            import bzrlib._knit_load_data_c
75
 
        except ImportError:
76
 
            return False
77
 
        return True
78
 
 
79
 
    def feature_name(self):
80
 
        return 'bzrlib._knit_load_data_c'
81
 
 
82
 
CompiledKnitFeature = _CompiledKnitFeature()
 
73
compiled_knit_feature = tests.ModuleAvailableFeature(
 
74
                            'bzrlib._knit_load_data_pyx')
83
75
 
84
76
 
85
77
class KnitContentTestsMixin(object):
297
289
        access = self.get_access()
298
290
        memos = access.add_raw_records([('key', 10)], '1234567890')
299
291
        self.assertEqual(['1234567890'], list(access.get_raw_records(memos)))
300
 
 
 
292
 
301
293
    def test_add_several_raw_records(self):
302
294
        """add_raw_records with many records and read some back."""
303
295
        access = self.get_access()
364
356
        :return: (versioned_file, reload_counter)
365
357
            versioned_file  a KnitVersionedFiles using the packs for access
366
358
        """
367
 
        tree = self.make_branch_and_memory_tree('tree')
368
 
        tree.lock_write()
369
 
        try:
370
 
            tree.add([''], ['root-id'])
371
 
            tree.commit('one', rev_id='rev-1')
372
 
            tree.commit('two', rev_id='rev-2')
373
 
            tree.commit('three', rev_id='rev-3')
374
 
            # Pack these two revisions into another pack file, but don't remove
375
 
            # the originials
376
 
            repo = tree.branch.repository
377
 
            collection = repo._pack_collection
378
 
            collection.ensure_loaded()
379
 
            orig_packs = collection.packs
380
 
            packer = pack_repo.Packer(collection, orig_packs, '.testpack')
381
 
            new_pack = packer.pack()
382
 
 
383
 
            vf = tree.branch.repository.revisions
384
 
        finally:
385
 
            tree.unlock()
386
 
        tree.branch.repository.lock_read()
387
 
        self.addCleanup(tree.branch.repository.unlock)
388
 
        del tree
 
359
        builder = self.make_branch_builder('.', format="1.9")
 
360
        builder.start_series()
 
361
        builder.build_snapshot('rev-1', None, [
 
362
            ('add', ('', 'root-id', 'directory', None)),
 
363
            ('add', ('file', 'file-id', 'file', 'content\nrev 1\n')),
 
364
            ])
 
365
        builder.build_snapshot('rev-2', ['rev-1'], [
 
366
            ('modify', ('file-id', 'content\nrev 2\n')),
 
367
            ])
 
368
        builder.build_snapshot('rev-3', ['rev-2'], [
 
369
            ('modify', ('file-id', 'content\nrev 3\n')),
 
370
            ])
 
371
        builder.finish_series()
 
372
        b = builder.get_branch()
 
373
        b.lock_write()
 
374
        self.addCleanup(b.unlock)
 
375
        # Pack these three revisions into another pack file, but don't remove
 
376
        # the originals
 
377
        repo = b.repository
 
378
        collection = repo._pack_collection
 
379
        collection.ensure_loaded()
 
380
        orig_packs = collection.packs
 
381
        packer = pack_repo.Packer(collection, orig_packs, '.testpack')
 
382
        new_pack = packer.pack()
 
383
        # forget about the new pack
 
384
        collection.reset()
 
385
        repo.refresh_data()
 
386
        vf = repo.revisions
389
387
        # Set up a reload() function that switches to using the new pack file
390
388
        new_index = new_pack.revision_index
391
389
        access_tuple = new_pack.access_tuple()
864
862
 
865
863
    def get_knit_index(self, transport, name, mode):
866
864
        mapper = ConstantMapper(name)
867
 
        orig = knit._load_data
868
 
        def reset():
869
 
            knit._load_data = orig
870
 
        self.addCleanup(reset)
871
865
        from bzrlib._knit_load_data_py import _load_data_py
872
 
        knit._load_data = _load_data_py
 
866
        self.overrideAttr(knit, '_load_data', _load_data_py)
873
867
        allow_writes = lambda: 'w' in mode
874
868
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
875
869
 
1091
1085
            call[1][1].getvalue())
1092
1086
        self.assertEqual({'create_parent_dir': True}, call[2])
1093
1087
 
 
1088
    def assertTotalBuildSize(self, size, keys, positions):
 
1089
        self.assertEqual(size,
 
1090
                         knit._get_total_build_size(None, keys, positions))
 
1091
 
 
1092
    def test__get_total_build_size(self):
 
1093
        positions = {
 
1094
            ('a',): (('fulltext', False), (('a',), 0, 100), None),
 
1095
            ('b',): (('line-delta', False), (('b',), 100, 21), ('a',)),
 
1096
            ('c',): (('line-delta', False), (('c',), 121, 35), ('b',)),
 
1097
            ('d',): (('line-delta', False), (('d',), 156, 12), ('b',)),
 
1098
            }
 
1099
        self.assertTotalBuildSize(100, [('a',)], positions)
 
1100
        self.assertTotalBuildSize(121, [('b',)], positions)
 
1101
        # c needs both a & b
 
1102
        self.assertTotalBuildSize(156, [('c',)], positions)
 
1103
        # we shouldn't count 'b' twice
 
1104
        self.assertTotalBuildSize(156, [('b',), ('c',)], positions)
 
1105
        self.assertTotalBuildSize(133, [('d',)], positions)
 
1106
        self.assertTotalBuildSize(168, [('c',), ('d',)], positions)
 
1107
 
1094
1108
    def test_get_position(self):
1095
1109
        transport = MockTransport([
1096
1110
            _KndxIndex.HEADER,
1237
1251
            else:
1238
1252
                raise
1239
1253
 
 
1254
    def test_scan_unvalidated_index_not_implemented(self):
 
1255
        transport = MockTransport()
 
1256
        index = self.get_knit_index(transport, 'filename', 'r')
 
1257
        self.assertRaises(
 
1258
            NotImplementedError, index.scan_unvalidated_index,
 
1259
            'dummy graph_index')
 
1260
        self.assertRaises(
 
1261
            NotImplementedError, index.get_missing_compression_parents)
 
1262
 
1240
1263
    def test_short_line(self):
1241
1264
        transport = MockTransport([
1242
1265
            _KndxIndex.HEADER,
1271
1294
 
1272
1295
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1273
1296
 
1274
 
    _test_needs_features = [CompiledKnitFeature]
 
1297
    _test_needs_features = [compiled_knit_feature]
1275
1298
 
1276
1299
    def get_knit_index(self, transport, name, mode):
1277
1300
        mapper = ConstantMapper(name)
1278
 
        orig = knit._load_data
1279
 
        def reset():
1280
 
            knit._load_data = orig
1281
 
        self.addCleanup(reset)
1282
 
        from bzrlib._knit_load_data_c import _load_data_c
1283
 
        knit._load_data = _load_data_c
 
1301
        from bzrlib._knit_load_data_pyx import _load_data_c
 
1302
        self.overrideAttr(knit, '_load_data', _load_data_c)
1284
1303
        allow_writes = lambda: mode == 'w'
1285
 
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
 
1304
        return _KndxIndex(transport, mapper, lambda:None,
 
1305
                          allow_writes, lambda:True)
 
1306
 
 
1307
 
 
1308
class Test_KnitAnnotator(TestCaseWithMemoryTransport):
 
1309
 
 
1310
    def make_annotator(self):
 
1311
        factory = knit.make_pack_factory(True, True, 1)
 
1312
        vf = factory(self.get_transport())
 
1313
        return knit._KnitAnnotator(vf)
 
1314
 
 
1315
    def test__expand_fulltext(self):
 
1316
        ann = self.make_annotator()
 
1317
        rev_key = ('rev-id',)
 
1318
        ann._num_compression_children[rev_key] = 1
 
1319
        res = ann._expand_record(rev_key, (('parent-id',),), None,
 
1320
                           ['line1\n', 'line2\n'], ('fulltext', True))
 
1321
        # The content object and text lines should be cached appropriately
 
1322
        self.assertEqual(['line1\n', 'line2'], res)
 
1323
        content_obj = ann._content_objects[rev_key]
 
1324
        self.assertEqual(['line1\n', 'line2\n'], content_obj._lines)
 
1325
        self.assertEqual(res, content_obj.text())
 
1326
        self.assertEqual(res, ann._text_cache[rev_key])
 
1327
 
 
1328
    def test__expand_delta_comp_parent_not_available(self):
 
1329
        # Parent isn't available yet, so we return nothing, but queue up this
 
1330
        # node for later processing
 
1331
        ann = self.make_annotator()
 
1332
        rev_key = ('rev-id',)
 
1333
        parent_key = ('parent-id',)
 
1334
        record = ['0,1,1\n', 'new-line\n']
 
1335
        details = ('line-delta', False)
 
1336
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
 
1337
                                 record, details)
 
1338
        self.assertEqual(None, res)
 
1339
        self.assertTrue(parent_key in ann._pending_deltas)
 
1340
        pending = ann._pending_deltas[parent_key]
 
1341
        self.assertEqual(1, len(pending))
 
1342
        self.assertEqual((rev_key, (parent_key,), record, details), pending[0])
 
1343
 
 
1344
    def test__expand_record_tracks_num_children(self):
 
1345
        ann = self.make_annotator()
 
1346
        rev_key = ('rev-id',)
 
1347
        rev2_key = ('rev2-id',)
 
1348
        parent_key = ('parent-id',)
 
1349
        record = ['0,1,1\n', 'new-line\n']
 
1350
        details = ('line-delta', False)
 
1351
        ann._num_compression_children[parent_key] = 2
 
1352
        ann._expand_record(parent_key, (), None, ['line1\n', 'line2\n'],
 
1353
                           ('fulltext', False))
 
1354
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
 
1355
                                 record, details)
 
1356
        self.assertEqual({parent_key: 1}, ann._num_compression_children)
 
1357
        # Expanding the second child should remove the content object, and the
 
1358
        # num_compression_children entry
 
1359
        res = ann._expand_record(rev2_key, (parent_key,), parent_key,
 
1360
                                 record, details)
 
1361
        self.assertFalse(parent_key in ann._content_objects)
 
1362
        self.assertEqual({}, ann._num_compression_children)
 
1363
        # We should not cache the content_objects for rev2 and rev, because
 
1364
        # they do not have compression children of their own.
 
1365
        self.assertEqual({}, ann._content_objects)
 
1366
 
 
1367
    def test__expand_delta_records_blocks(self):
 
1368
        ann = self.make_annotator()
 
1369
        rev_key = ('rev-id',)
 
1370
        parent_key = ('parent-id',)
 
1371
        record = ['0,1,1\n', 'new-line\n']
 
1372
        details = ('line-delta', True)
 
1373
        ann._num_compression_children[parent_key] = 2
 
1374
        ann._expand_record(parent_key, (), None,
 
1375
                           ['line1\n', 'line2\n', 'line3\n'],
 
1376
                           ('fulltext', False))
 
1377
        ann._expand_record(rev_key, (parent_key,), parent_key, record, details)
 
1378
        self.assertEqual({(rev_key, parent_key): [(1, 1, 1), (3, 3, 0)]},
 
1379
                         ann._matching_blocks)
 
1380
        rev2_key = ('rev2-id',)
 
1381
        record = ['0,1,1\n', 'new-line\n']
 
1382
        details = ('line-delta', False)
 
1383
        ann._expand_record(rev2_key, (parent_key,), parent_key, record, details)
 
1384
        self.assertEqual([(1, 1, 2), (3, 3, 0)],
 
1385
                         ann._matching_blocks[(rev2_key, parent_key)])
 
1386
 
 
1387
    def test__get_parent_ann_uses_matching_blocks(self):
 
1388
        ann = self.make_annotator()
 
1389
        rev_key = ('rev-id',)
 
1390
        parent_key = ('parent-id',)
 
1391
        parent_ann = [(parent_key,)]*3
 
1392
        block_key = (rev_key, parent_key)
 
1393
        ann._annotations_cache[parent_key] = parent_ann
 
1394
        ann._matching_blocks[block_key] = [(0, 1, 1), (3, 3, 0)]
 
1395
        # We should not try to access any parent_lines content, because we know
 
1396
        # we already have the matching blocks
 
1397
        par_ann, blocks = ann._get_parent_annotations_and_matches(rev_key,
 
1398
                                        ['1\n', '2\n', '3\n'], parent_key)
 
1399
        self.assertEqual(parent_ann, par_ann)
 
1400
        self.assertEqual([(0, 1, 1), (3, 3, 0)], blocks)
 
1401
        self.assertEqual({}, ann._matching_blocks)
 
1402
 
 
1403
    def test__process_pending(self):
 
1404
        ann = self.make_annotator()
 
1405
        rev_key = ('rev-id',)
 
1406
        p1_key = ('p1-id',)
 
1407
        p2_key = ('p2-id',)
 
1408
        record = ['0,1,1\n', 'new-line\n']
 
1409
        details = ('line-delta', False)
 
1410
        p1_record = ['line1\n', 'line2\n']
 
1411
        ann._num_compression_children[p1_key] = 1
 
1412
        res = ann._expand_record(rev_key, (p1_key,p2_key), p1_key,
 
1413
                                 record, details)
 
1414
        self.assertEqual(None, res)
 
1415
        # self.assertTrue(p1_key in ann._pending_deltas)
 
1416
        self.assertEqual({}, ann._pending_annotation)
 
1417
        # Now insert p1, and we should be able to expand the delta
 
1418
        res = ann._expand_record(p1_key, (), None, p1_record,
 
1419
                                 ('fulltext', False))
 
1420
        self.assertEqual(p1_record, res)
 
1421
        ann._annotations_cache[p1_key] = [(p1_key,)]*2
 
1422
        res = ann._process_pending(p1_key)
 
1423
        self.assertEqual([], res)
 
1424
        self.assertFalse(p1_key in ann._pending_deltas)
 
1425
        self.assertTrue(p2_key in ann._pending_annotation)
 
1426
        self.assertEqual({p2_key: [(rev_key, (p1_key, p2_key))]},
 
1427
                         ann._pending_annotation)
 
1428
        # Now fill in parent 2, and pending annotation should be satisfied
 
1429
        res = ann._expand_record(p2_key, (), None, [], ('fulltext', False))
 
1430
        ann._annotations_cache[p2_key] = []
 
1431
        res = ann._process_pending(p2_key)
 
1432
        self.assertEqual([rev_key], res)
 
1433
        self.assertEqual({}, ann._pending_annotation)
 
1434
        self.assertEqual({}, ann._pending_deltas)
 
1435
 
 
1436
    def test_record_delta_removes_basis(self):
 
1437
        ann = self.make_annotator()
 
1438
        ann._expand_record(('parent-id',), (), None,
 
1439
                           ['line1\n', 'line2\n'], ('fulltext', False))
 
1440
        ann._num_compression_children['parent-id'] = 2
 
1441
 
 
1442
    def test_annotate_special_text(self):
 
1443
        ann = self.make_annotator()
 
1444
        vf = ann._vf
 
1445
        rev1_key = ('rev-1',)
 
1446
        rev2_key = ('rev-2',)
 
1447
        rev3_key = ('rev-3',)
 
1448
        spec_key = ('special:',)
 
1449
        vf.add_lines(rev1_key, [], ['initial content\n'])
 
1450
        vf.add_lines(rev2_key, [rev1_key], ['initial content\n',
 
1451
                                            'common content\n',
 
1452
                                            'content in 2\n'])
 
1453
        vf.add_lines(rev3_key, [rev1_key], ['initial content\n',
 
1454
                                            'common content\n',
 
1455
                                            'content in 3\n'])
 
1456
        spec_text = ('initial content\n'
 
1457
                     'common content\n'
 
1458
                     'content in 2\n'
 
1459
                     'content in 3\n')
 
1460
        ann.add_special_text(spec_key, [rev2_key, rev3_key], spec_text)
 
1461
        anns, lines = ann.annotate(spec_key)
 
1462
        self.assertEqual([(rev1_key,),
 
1463
                          (rev2_key, rev3_key),
 
1464
                          (rev2_key,),
 
1465
                          (rev3_key,),
 
1466
                         ], anns)
 
1467
        self.assertEqualDiff(spec_text, ''.join(lines))
1286
1468
 
1287
1469
 
1288
1470
class KnitTests(TestCaseWithTransport):
1296
1478
class TestBadShaError(KnitTests):
1297
1479
    """Tests for handling of sha errors."""
1298
1480
 
1299
 
    def test_exception_has_text(self):
 
1481
    def test_sha_exception_has_text(self):
1300
1482
        # having the failed text included in the error allows for recovery.
1301
1483
        source = self.make_test_knit()
1302
1484
        target = self.make_test_knit(name="target")
1313
1495
        target.insert_record_stream(
1314
1496
            source.get_record_stream([broken], 'unordered', False))
1315
1497
        err = self.assertRaises(errors.KnitCorrupt,
1316
 
            target.get_record_stream([broken], 'unordered', True).next)
 
1498
            target.get_record_stream([broken], 'unordered', True
 
1499
            ).next().get_bytes_as, 'chunked')
1317
1500
        self.assertEqual(['gam\n', 'bar\n'], err.content)
1318
1501
        # Test for formatting with live data
1319
1502
        self.assertStartsWith(str(err), "Knit ")
1524
1707
            [('parent',)])])
1525
1708
        # but neither should have added data:
1526
1709
        self.assertEqual([[], [], [], []], self.caught_entries)
1527
 
        
 
1710
 
1528
1711
    def test_add_version_different_dup(self):
1529
1712
        index = self.two_graph_index(deltas=True, catch_adds=True)
1530
1713
        # change options
1536
1719
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1537
1720
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1538
1721
        self.assertEqual([], self.caught_entries)
1539
 
        
 
1722
 
1540
1723
    def test_add_versions_nodeltas(self):
1541
1724
        index = self.two_graph_index(catch_adds=True)
1542
1725
        index.add_records([
1584
1767
            [('parent',)])])
1585
1768
        # but neither should have added data.
1586
1769
        self.assertEqual([[], [], [], []], self.caught_entries)
1587
 
        
 
1770
 
1588
1771
    def test_add_versions_different_dup(self):
1589
1772
        index = self.two_graph_index(deltas=True, catch_adds=True)
1590
1773
        # change options
1601
1784
             (('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1602
1785
        self.assertEqual([], self.caught_entries)
1603
1786
 
 
1787
    def make_g_index_missing_compression_parent(self):
 
1788
        graph_index = self.make_g_index('missing_comp', 2,
 
1789
            [(('tip', ), ' 100 78',
 
1790
              ([('missing-parent', ), ('ghost', )], [('missing-parent', )]))])
 
1791
        return graph_index
 
1792
 
 
1793
    def make_g_index_missing_parent(self):
 
1794
        graph_index = self.make_g_index('missing_parent', 2,
 
1795
            [(('parent', ), ' 100 78', ([], [])),
 
1796
             (('tip', ), ' 100 78',
 
1797
              ([('parent', ), ('missing-parent', )], [('parent', )])),
 
1798
              ])
 
1799
        return graph_index
 
1800
 
 
1801
    def make_g_index_no_external_refs(self):
 
1802
        graph_index = self.make_g_index('no_external_refs', 2,
 
1803
            [(('rev', ), ' 100 78',
 
1804
              ([('parent', ), ('ghost', )], []))])
 
1805
        return graph_index
 
1806
 
 
1807
    def test_add_good_unvalidated_index(self):
 
1808
        unvalidated = self.make_g_index_no_external_refs()
 
1809
        combined = CombinedGraphIndex([unvalidated])
 
1810
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
 
1811
        index.scan_unvalidated_index(unvalidated)
 
1812
        self.assertEqual(frozenset(), index.get_missing_compression_parents())
 
1813
 
 
1814
    def test_add_missing_compression_parent_unvalidated_index(self):
 
1815
        unvalidated = self.make_g_index_missing_compression_parent()
 
1816
        combined = CombinedGraphIndex([unvalidated])
 
1817
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
 
1818
        index.scan_unvalidated_index(unvalidated)
 
1819
        # This also checks that its only the compression parent that is
 
1820
        # examined, otherwise 'ghost' would also be reported as a missing
 
1821
        # parent.
 
1822
        self.assertEqual(
 
1823
            frozenset([('missing-parent',)]),
 
1824
            index.get_missing_compression_parents())
 
1825
 
 
1826
    def test_add_missing_noncompression_parent_unvalidated_index(self):
 
1827
        unvalidated = self.make_g_index_missing_parent()
 
1828
        combined = CombinedGraphIndex([unvalidated])
 
1829
        index = _KnitGraphIndex(combined, lambda: True, deltas=True,
 
1830
            track_external_parent_refs=True)
 
1831
        index.scan_unvalidated_index(unvalidated)
 
1832
        self.assertEqual(
 
1833
            frozenset([('missing-parent',)]), index.get_missing_parents())
 
1834
 
 
1835
    def test_track_external_parent_refs(self):
 
1836
        g_index = self.make_g_index('empty', 2, [])
 
1837
        combined = CombinedGraphIndex([g_index])
 
1838
        index = _KnitGraphIndex(combined, lambda: True, deltas=True,
 
1839
            add_callback=self.catch_add, track_external_parent_refs=True)
 
1840
        self.caught_entries = []
 
1841
        index.add_records([
 
1842
            (('new-key',), 'fulltext,no-eol', (None, 50, 60),
 
1843
             [('parent-1',), ('parent-2',)])])
 
1844
        self.assertEqual(
 
1845
            frozenset([('parent-1',), ('parent-2',)]),
 
1846
            index.get_missing_parents())
 
1847
 
 
1848
    def test_add_unvalidated_index_with_present_external_references(self):
 
1849
        index = self.two_graph_index(deltas=True)
 
1850
        # Ugly hack to get at one of the underlying GraphIndex objects that
 
1851
        # two_graph_index built.
 
1852
        unvalidated = index._graph_index._indices[1]
 
1853
        # 'parent' is an external ref of _indices[1] (unvalidated), but is
 
1854
        # present in _indices[0].
 
1855
        index.scan_unvalidated_index(unvalidated)
 
1856
        self.assertEqual(frozenset(), index.get_missing_compression_parents())
 
1857
 
 
1858
    def make_new_missing_parent_g_index(self, name):
 
1859
        missing_parent = name + '-missing-parent'
 
1860
        graph_index = self.make_g_index(name, 2,
 
1861
            [((name + 'tip', ), ' 100 78',
 
1862
              ([(missing_parent, ), ('ghost', )], [(missing_parent, )]))])
 
1863
        return graph_index
 
1864
 
 
1865
    def test_add_mulitiple_unvalidated_indices_with_missing_parents(self):
 
1866
        g_index_1 = self.make_new_missing_parent_g_index('one')
 
1867
        g_index_2 = self.make_new_missing_parent_g_index('two')
 
1868
        combined = CombinedGraphIndex([g_index_1, g_index_2])
 
1869
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
 
1870
        index.scan_unvalidated_index(g_index_1)
 
1871
        index.scan_unvalidated_index(g_index_2)
 
1872
        self.assertEqual(
 
1873
            frozenset([('one-missing-parent',), ('two-missing-parent',)]),
 
1874
            index.get_missing_compression_parents())
 
1875
 
 
1876
    def test_add_mulitiple_unvalidated_indices_with_mutual_dependencies(self):
 
1877
        graph_index_a = self.make_g_index('one', 2,
 
1878
            [(('parent-one', ), ' 100 78', ([('non-compression-parent',)], [])),
 
1879
             (('child-of-two', ), ' 100 78',
 
1880
              ([('parent-two',)], [('parent-two',)]))])
 
1881
        graph_index_b = self.make_g_index('two', 2,
 
1882
            [(('parent-two', ), ' 100 78', ([('non-compression-parent',)], [])),
 
1883
             (('child-of-one', ), ' 100 78',
 
1884
              ([('parent-one',)], [('parent-one',)]))])
 
1885
        combined = CombinedGraphIndex([graph_index_a, graph_index_b])
 
1886
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
 
1887
        index.scan_unvalidated_index(graph_index_a)
 
1888
        index.scan_unvalidated_index(graph_index_b)
 
1889
        self.assertEqual(
 
1890
            frozenset([]), index.get_missing_compression_parents())
 
1891
 
1604
1892
 
1605
1893
class TestNoParentsGraphIndexKnit(KnitTests):
1606
1894
    """Tests for knits using _KnitGraphIndex with no parents."""
1614
1902
        size = trans.put_file(name, stream)
1615
1903
        return GraphIndex(trans, name, size)
1616
1904
 
 
1905
    def test_add_good_unvalidated_index(self):
 
1906
        unvalidated = self.make_g_index('unvalidated')
 
1907
        combined = CombinedGraphIndex([unvalidated])
 
1908
        index = _KnitGraphIndex(combined, lambda: True, parents=False)
 
1909
        index.scan_unvalidated_index(unvalidated)
 
1910
        self.assertEqual(frozenset(),
 
1911
            index.get_missing_compression_parents())
 
1912
 
1617
1913
    def test_parents_deltas_incompatible(self):
1618
1914
        index = CombinedGraphIndex([])
1619
1915
        self.assertRaises(errors.KnitError, _KnitGraphIndex, lambda:True,
1700
1996
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
1701
1997
        # but neither should have added data.
1702
1998
        self.assertEqual([[], [], [], []], self.caught_entries)
1703
 
        
 
1999
 
1704
2000
    def test_add_version_different_dup(self):
1705
2001
        index = self.two_graph_index(catch_adds=True)
1706
2002
        # change options
1714
2010
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1715
2011
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1716
2012
        self.assertEqual([], self.caught_entries)
1717
 
        
 
2013
 
1718
2014
    def test_add_versions(self):
1719
2015
        index = self.two_graph_index(catch_adds=True)
1720
2016
        index.add_records([
1752
2048
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
1753
2049
        # but neither should have added data.
1754
2050
        self.assertEqual([[], [], [], []], self.caught_entries)
1755
 
        
 
2051
 
1756
2052
    def test_add_versions_different_dup(self):
1757
2053
        index = self.two_graph_index(catch_adds=True)
1758
2054
        # change options
1772
2068
        self.assertEqual([], self.caught_entries)
1773
2069
 
1774
2070
 
 
2071
class TestKnitVersionedFiles(KnitTests):
 
2072
 
 
2073
    def assertGroupKeysForIo(self, exp_groups, keys, non_local_keys,
 
2074
                             positions, _min_buffer_size=None):
 
2075
        kvf = self.make_test_knit()
 
2076
        if _min_buffer_size is None:
 
2077
            _min_buffer_size = knit._STREAM_MIN_BUFFER_SIZE
 
2078
        self.assertEqual(exp_groups, kvf._group_keys_for_io(keys,
 
2079
                                        non_local_keys, positions,
 
2080
                                        _min_buffer_size=_min_buffer_size))
 
2081
 
 
2082
    def assertSplitByPrefix(self, expected_map, expected_prefix_order,
 
2083
                            keys):
 
2084
        split, prefix_order = KnitVersionedFiles._split_by_prefix(keys)
 
2085
        self.assertEqual(expected_map, split)
 
2086
        self.assertEqual(expected_prefix_order, prefix_order)
 
2087
 
 
2088
    def test__group_keys_for_io(self):
 
2089
        ft_detail = ('fulltext', False)
 
2090
        ld_detail = ('line-delta', False)
 
2091
        f_a = ('f', 'a')
 
2092
        f_b = ('f', 'b')
 
2093
        f_c = ('f', 'c')
 
2094
        g_a = ('g', 'a')
 
2095
        g_b = ('g', 'b')
 
2096
        g_c = ('g', 'c')
 
2097
        positions = {
 
2098
            f_a: (ft_detail, (f_a, 0, 100), None),
 
2099
            f_b: (ld_detail, (f_b, 100, 21), f_a),
 
2100
            f_c: (ld_detail, (f_c, 180, 15), f_b),
 
2101
            g_a: (ft_detail, (g_a, 121, 35), None),
 
2102
            g_b: (ld_detail, (g_b, 156, 12), g_a),
 
2103
            g_c: (ld_detail, (g_c, 195, 13), g_a),
 
2104
            }
 
2105
        self.assertGroupKeysForIo([([f_a], set())],
 
2106
                                  [f_a], [], positions)
 
2107
        self.assertGroupKeysForIo([([f_a], set([f_a]))],
 
2108
                                  [f_a], [f_a], positions)
 
2109
        self.assertGroupKeysForIo([([f_a, f_b], set([]))],
 
2110
                                  [f_a, f_b], [], positions)
 
2111
        self.assertGroupKeysForIo([([f_a, f_b], set([f_b]))],
 
2112
                                  [f_a, f_b], [f_b], positions)
 
2113
        self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
 
2114
                                  [f_a, g_a, f_b, g_b], [], positions)
 
2115
        self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
 
2116
                                  [f_a, g_a, f_b, g_b], [], positions,
 
2117
                                  _min_buffer_size=150)
 
2118
        self.assertGroupKeysForIo([([f_a, f_b], set()), ([g_a, g_b], set())],
 
2119
                                  [f_a, g_a, f_b, g_b], [], positions,
 
2120
                                  _min_buffer_size=100)
 
2121
        self.assertGroupKeysForIo([([f_c], set()), ([g_b], set())],
 
2122
                                  [f_c, g_b], [], positions,
 
2123
                                  _min_buffer_size=125)
 
2124
        self.assertGroupKeysForIo([([g_b, f_c], set())],
 
2125
                                  [g_b, f_c], [], positions,
 
2126
                                  _min_buffer_size=125)
 
2127
 
 
2128
    def test__split_by_prefix(self):
 
2129
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
 
2130
                                  'g': [('g', 'b'), ('g', 'a')],
 
2131
                                 }, ['f', 'g'],
 
2132
                                 [('f', 'a'), ('g', 'b'),
 
2133
                                  ('g', 'a'), ('f', 'b')])
 
2134
 
 
2135
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
 
2136
                                  'g': [('g', 'b'), ('g', 'a')],
 
2137
                                 }, ['f', 'g'],
 
2138
                                 [('f', 'a'), ('f', 'b'),
 
2139
                                  ('g', 'b'), ('g', 'a')])
 
2140
 
 
2141
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
 
2142
                                  'g': [('g', 'b'), ('g', 'a')],
 
2143
                                 }, ['f', 'g'],
 
2144
                                 [('f', 'a'), ('f', 'b'),
 
2145
                                  ('g', 'b'), ('g', 'a')])
 
2146
 
 
2147
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
 
2148
                                  'g': [('g', 'b'), ('g', 'a')],
 
2149
                                  '': [('a',), ('b',)]
 
2150
                                 }, ['f', 'g', ''],
 
2151
                                 [('f', 'a'), ('g', 'b'),
 
2152
                                  ('a',), ('b',),
 
2153
                                  ('g', 'a'), ('f', 'b')])
 
2154
 
 
2155
 
1775
2156
class TestStacking(KnitTests):
1776
2157
 
1777
2158
    def get_basis_and_test_knit(self):
1832
2213
        # self.assertEqual([("annotate", key_basis)], basis.calls)
1833
2214
        self.assertEqual([('get_parent_map', set([key_basis])),
1834
2215
            ('get_parent_map', set([key_basis])),
1835
 
            ('get_parent_map', set([key_basis])),
1836
 
            ('get_record_stream', [key_basis], 'unordered', True)],
 
2216
            ('get_record_stream', [key_basis], 'topological', True)],
1837
2217
            basis.calls)
1838
2218
 
1839
2219
    def test_check(self):
1840
2220
        # At the moment checking a stacked knit does implicitly check the
1841
 
        # fallback files.  
 
2221
        # fallback files.
1842
2222
        basis, test = self.get_basis_and_test_knit()
1843
2223
        test.check()
1844
2224
 
1936
2316
                True).next()
1937
2317
            self.assertEqual(record.key, result[0])
1938
2318
            self.assertEqual(record.sha1, result[1])
1939
 
            self.assertEqual(record.storage_kind, result[2])
 
2319
            # We used to check that the storage kind matched, but actually it
 
2320
            # depends on whether it was sourced from the basis, or in a single
 
2321
            # group, because asking for full texts returns proxy objects to a
 
2322
            # _ContentMapGenerator object; so checking the kind is unneeded.
1940
2323
            self.assertEqual(record.get_bytes_as('fulltext'), result[3])
1941
2324
        # It's not strictly minimal, but it seems reasonable for now for it to
1942
2325
        # ask which fallbacks have which parents.
1943
2326
        self.assertEqual([
1944
2327
            ("get_parent_map", set([key_basis, key_basis_2, key_missing])),
1945
 
            # unordered is asked for by the underlying worker as it still
1946
 
            # buffers everything while answering - which is a problem!
1947
 
            ("get_record_stream", [key_basis_2, key_basis], 'unordered', True)],
 
2328
            # topological is requested from the fallback, because that is what
 
2329
            # was requested at the top level.
 
2330
            ("get_record_stream", [key_basis_2, key_basis], 'topological', True)],
1948
2331
            calls)
1949
2332
 
1950
2333
    def test_get_record_stream_unordered_deltas(self):
2079
2462
 
2080
2463
    def test_iter_lines_added_or_present_in_keys(self):
2081
2464
        # Lines from the basis are returned, and lines for a given key are only
2082
 
        # returned once. 
 
2465
        # returned once.
2083
2466
        key1 = ('foo1',)
2084
2467
        key2 = ('foo2',)
2085
2468
        # all sources are asked for keys:
2171
2554
        last_call = basis.calls[-1]
2172
2555
        self.assertEqual('get_record_stream', last_call[0])
2173
2556
        self.assertEqual(set([key_left, key_right]), set(last_call[1]))
2174
 
        self.assertEqual('unordered', last_call[2])
 
2557
        self.assertEqual('topological', last_call[2])
2175
2558
        self.assertEqual(True, last_call[3])
 
2559
 
 
2560
 
 
2561
class TestNetworkBehaviour(KnitTests):
 
2562
    """Tests for getting data out of/into knits over the network."""
 
2563
 
 
2564
    def test_include_delta_closure_generates_a_knit_delta_closure(self):
 
2565
        vf = self.make_test_knit(name='test')
 
2566
        # put in three texts, giving ft, delta, delta
 
2567
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
 
2568
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
 
2569
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
 
2570
        # But heuristics could interfere, so check what happened:
 
2571
        self.assertEqual(['knit-ft-gz', 'knit-delta-gz', 'knit-delta-gz'],
 
2572
            [record.storage_kind for record in
 
2573
             vf.get_record_stream([('base',), ('d1',), ('d2',)],
 
2574
                'topological', False)])
 
2575
        # generate a stream of just the deltas include_delta_closure=True,
 
2576
        # serialise to the network, and check that we get a delta closure on the wire.
 
2577
        stream = vf.get_record_stream([('d1',), ('d2',)], 'topological', True)
 
2578
        netb = [record.get_bytes_as(record.storage_kind) for record in stream]
 
2579
        # The first bytes should be a memo from _ContentMapGenerator, and the
 
2580
        # second bytes should be empty (because its a API proxy not something
 
2581
        # for wire serialisation.
 
2582
        self.assertEqual('', netb[1])
 
2583
        bytes = netb[0]
 
2584
        kind, line_end = network_bytes_to_kind_and_offset(bytes)
 
2585
        self.assertEqual('knit-delta-closure', kind)
 
2586
 
 
2587
 
 
2588
class TestContentMapGenerator(KnitTests):
 
2589
    """Tests for ContentMapGenerator"""
 
2590
 
 
2591
    def test_get_record_stream_gives_records(self):
 
2592
        vf = self.make_test_knit(name='test')
 
2593
        # put in three texts, giving ft, delta, delta
 
2594
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
 
2595
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
 
2596
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
 
2597
        keys = [('d1',), ('d2',)]
 
2598
        generator = _VFContentMapGenerator(vf, keys,
 
2599
            global_map=vf.get_parent_map(keys))
 
2600
        for record in generator.get_record_stream():
 
2601
            if record.key == ('d1',):
 
2602
                self.assertEqual('d1\n', record.get_bytes_as('fulltext'))
 
2603
            else:
 
2604
                self.assertEqual('d2\n', record.get_bytes_as('fulltext'))
 
2605
 
 
2606
    def test_get_record_stream_kinds_are_raw(self):
 
2607
        vf = self.make_test_knit(name='test')
 
2608
        # put in three texts, giving ft, delta, delta
 
2609
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
 
2610
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
 
2611
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
 
2612
        keys = [('base',), ('d1',), ('d2',)]
 
2613
        generator = _VFContentMapGenerator(vf, keys,
 
2614
            global_map=vf.get_parent_map(keys))
 
2615
        kinds = {('base',): 'knit-delta-closure',
 
2616
            ('d1',): 'knit-delta-closure-ref',
 
2617
            ('d2',): 'knit-delta-closure-ref',
 
2618
            }
 
2619
        for record in generator.get_record_stream():
 
2620
            self.assertEqual(kinds[record.key], record.storage_kind)