/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_knit.py

  • Committer: Andrew Bennetts
  • Date: 2010-01-12 03:53:21 UTC
  • mfrom: (4948 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4964.
  • Revision ID: andrew.bennetts@canonical.com-20100112035321-hofpz5p10224ryj3
Merge lp:bzr, resolving 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()
1091
1089
            call[1][1].getvalue())
1092
1090
        self.assertEqual({'create_parent_dir': True}, call[2])
1093
1091
 
 
1092
    def assertTotalBuildSize(self, size, keys, positions):
 
1093
        self.assertEqual(size,
 
1094
                         knit._get_total_build_size(None, keys, positions))
 
1095
 
 
1096
    def test__get_total_build_size(self):
 
1097
        positions = {
 
1098
            ('a',): (('fulltext', False), (('a',), 0, 100), None),
 
1099
            ('b',): (('line-delta', False), (('b',), 100, 21), ('a',)),
 
1100
            ('c',): (('line-delta', False), (('c',), 121, 35), ('b',)),
 
1101
            ('d',): (('line-delta', False), (('d',), 156, 12), ('b',)),
 
1102
            }
 
1103
        self.assertTotalBuildSize(100, [('a',)], positions)
 
1104
        self.assertTotalBuildSize(121, [('b',)], positions)
 
1105
        # c needs both a & b
 
1106
        self.assertTotalBuildSize(156, [('c',)], positions)
 
1107
        # we shouldn't count 'b' twice
 
1108
        self.assertTotalBuildSize(156, [('b',), ('c',)], positions)
 
1109
        self.assertTotalBuildSize(133, [('d',)], positions)
 
1110
        self.assertTotalBuildSize(168, [('c',), ('d',)], positions)
 
1111
 
1094
1112
    def test_get_position(self):
1095
1113
        transport = MockTransport([
1096
1114
            _KndxIndex.HEADER,
1237
1255
            else:
1238
1256
                raise
1239
1257
 
 
1258
    def test_scan_unvalidated_index_not_implemented(self):
 
1259
        transport = MockTransport()
 
1260
        index = self.get_knit_index(transport, 'filename', 'r')
 
1261
        self.assertRaises(
 
1262
            NotImplementedError, index.scan_unvalidated_index,
 
1263
            'dummy graph_index')
 
1264
        self.assertRaises(
 
1265
            NotImplementedError, index.get_missing_compression_parents)
 
1266
 
1240
1267
    def test_short_line(self):
1241
1268
        transport = MockTransport([
1242
1269
            _KndxIndex.HEADER,
1271
1298
 
1272
1299
class LowLevelKnitIndexTests_c(LowLevelKnitIndexTests):
1273
1300
 
1274
 
    _test_needs_features = [CompiledKnitFeature]
 
1301
    _test_needs_features = [compiled_knit_feature]
1275
1302
 
1276
1303
    def get_knit_index(self, transport, name, mode):
1277
1304
        mapper = ConstantMapper(name)
1279
1306
        def reset():
1280
1307
            knit._load_data = orig
1281
1308
        self.addCleanup(reset)
1282
 
        from bzrlib._knit_load_data_c import _load_data_c
 
1309
        from bzrlib._knit_load_data_pyx import _load_data_c
1283
1310
        knit._load_data = _load_data_c
1284
1311
        allow_writes = lambda: mode == 'w'
1285
1312
        return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)
1286
1313
 
1287
1314
 
 
1315
class Test_KnitAnnotator(TestCaseWithMemoryTransport):
 
1316
 
 
1317
    def make_annotator(self):
 
1318
        factory = knit.make_pack_factory(True, True, 1)
 
1319
        vf = factory(self.get_transport())
 
1320
        return knit._KnitAnnotator(vf)
 
1321
 
 
1322
    def test__expand_fulltext(self):
 
1323
        ann = self.make_annotator()
 
1324
        rev_key = ('rev-id',)
 
1325
        ann._num_compression_children[rev_key] = 1
 
1326
        res = ann._expand_record(rev_key, (('parent-id',),), None,
 
1327
                           ['line1\n', 'line2\n'], ('fulltext', True))
 
1328
        # The content object and text lines should be cached appropriately
 
1329
        self.assertEqual(['line1\n', 'line2'], res)
 
1330
        content_obj = ann._content_objects[rev_key]
 
1331
        self.assertEqual(['line1\n', 'line2\n'], content_obj._lines)
 
1332
        self.assertEqual(res, content_obj.text())
 
1333
        self.assertEqual(res, ann._text_cache[rev_key])
 
1334
 
 
1335
    def test__expand_delta_comp_parent_not_available(self):
 
1336
        # Parent isn't available yet, so we return nothing, but queue up this
 
1337
        # node for later processing
 
1338
        ann = self.make_annotator()
 
1339
        rev_key = ('rev-id',)
 
1340
        parent_key = ('parent-id',)
 
1341
        record = ['0,1,1\n', 'new-line\n']
 
1342
        details = ('line-delta', False)
 
1343
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
 
1344
                                 record, details)
 
1345
        self.assertEqual(None, res)
 
1346
        self.assertTrue(parent_key in ann._pending_deltas)
 
1347
        pending = ann._pending_deltas[parent_key]
 
1348
        self.assertEqual(1, len(pending))
 
1349
        self.assertEqual((rev_key, (parent_key,), record, details), pending[0])
 
1350
 
 
1351
    def test__expand_record_tracks_num_children(self):
 
1352
        ann = self.make_annotator()
 
1353
        rev_key = ('rev-id',)
 
1354
        rev2_key = ('rev2-id',)
 
1355
        parent_key = ('parent-id',)
 
1356
        record = ['0,1,1\n', 'new-line\n']
 
1357
        details = ('line-delta', False)
 
1358
        ann._num_compression_children[parent_key] = 2
 
1359
        ann._expand_record(parent_key, (), None, ['line1\n', 'line2\n'],
 
1360
                           ('fulltext', False))
 
1361
        res = ann._expand_record(rev_key, (parent_key,), parent_key,
 
1362
                                 record, details)
 
1363
        self.assertEqual({parent_key: 1}, ann._num_compression_children)
 
1364
        # Expanding the second child should remove the content object, and the
 
1365
        # num_compression_children entry
 
1366
        res = ann._expand_record(rev2_key, (parent_key,), parent_key,
 
1367
                                 record, details)
 
1368
        self.assertFalse(parent_key in ann._content_objects)
 
1369
        self.assertEqual({}, ann._num_compression_children)
 
1370
        # We should not cache the content_objects for rev2 and rev, because
 
1371
        # they do not have compression children of their own.
 
1372
        self.assertEqual({}, ann._content_objects)
 
1373
 
 
1374
    def test__expand_delta_records_blocks(self):
 
1375
        ann = self.make_annotator()
 
1376
        rev_key = ('rev-id',)
 
1377
        parent_key = ('parent-id',)
 
1378
        record = ['0,1,1\n', 'new-line\n']
 
1379
        details = ('line-delta', True)
 
1380
        ann._num_compression_children[parent_key] = 2
 
1381
        ann._expand_record(parent_key, (), None,
 
1382
                           ['line1\n', 'line2\n', 'line3\n'],
 
1383
                           ('fulltext', False))
 
1384
        ann._expand_record(rev_key, (parent_key,), parent_key, record, details)
 
1385
        self.assertEqual({(rev_key, parent_key): [(1, 1, 1), (3, 3, 0)]},
 
1386
                         ann._matching_blocks)
 
1387
        rev2_key = ('rev2-id',)
 
1388
        record = ['0,1,1\n', 'new-line\n']
 
1389
        details = ('line-delta', False)
 
1390
        ann._expand_record(rev2_key, (parent_key,), parent_key, record, details)
 
1391
        self.assertEqual([(1, 1, 2), (3, 3, 0)],
 
1392
                         ann._matching_blocks[(rev2_key, parent_key)])
 
1393
 
 
1394
    def test__get_parent_ann_uses_matching_blocks(self):
 
1395
        ann = self.make_annotator()
 
1396
        rev_key = ('rev-id',)
 
1397
        parent_key = ('parent-id',)
 
1398
        parent_ann = [(parent_key,)]*3
 
1399
        block_key = (rev_key, parent_key)
 
1400
        ann._annotations_cache[parent_key] = parent_ann
 
1401
        ann._matching_blocks[block_key] = [(0, 1, 1), (3, 3, 0)]
 
1402
        # We should not try to access any parent_lines content, because we know
 
1403
        # we already have the matching blocks
 
1404
        par_ann, blocks = ann._get_parent_annotations_and_matches(rev_key,
 
1405
                                        ['1\n', '2\n', '3\n'], parent_key)
 
1406
        self.assertEqual(parent_ann, par_ann)
 
1407
        self.assertEqual([(0, 1, 1), (3, 3, 0)], blocks)
 
1408
        self.assertEqual({}, ann._matching_blocks)
 
1409
 
 
1410
    def test__process_pending(self):
 
1411
        ann = self.make_annotator()
 
1412
        rev_key = ('rev-id',)
 
1413
        p1_key = ('p1-id',)
 
1414
        p2_key = ('p2-id',)
 
1415
        record = ['0,1,1\n', 'new-line\n']
 
1416
        details = ('line-delta', False)
 
1417
        p1_record = ['line1\n', 'line2\n']
 
1418
        ann._num_compression_children[p1_key] = 1
 
1419
        res = ann._expand_record(rev_key, (p1_key,p2_key), p1_key,
 
1420
                                 record, details)
 
1421
        self.assertEqual(None, res)
 
1422
        # self.assertTrue(p1_key in ann._pending_deltas)
 
1423
        self.assertEqual({}, ann._pending_annotation)
 
1424
        # Now insert p1, and we should be able to expand the delta
 
1425
        res = ann._expand_record(p1_key, (), None, p1_record,
 
1426
                                 ('fulltext', False))
 
1427
        self.assertEqual(p1_record, res)
 
1428
        ann._annotations_cache[p1_key] = [(p1_key,)]*2
 
1429
        res = ann._process_pending(p1_key)
 
1430
        self.assertEqual([], res)
 
1431
        self.assertFalse(p1_key in ann._pending_deltas)
 
1432
        self.assertTrue(p2_key in ann._pending_annotation)
 
1433
        self.assertEqual({p2_key: [(rev_key, (p1_key, p2_key))]},
 
1434
                         ann._pending_annotation)
 
1435
        # Now fill in parent 2, and pending annotation should be satisfied
 
1436
        res = ann._expand_record(p2_key, (), None, [], ('fulltext', False))
 
1437
        ann._annotations_cache[p2_key] = []
 
1438
        res = ann._process_pending(p2_key)
 
1439
        self.assertEqual([rev_key], res)
 
1440
        self.assertEqual({}, ann._pending_annotation)
 
1441
        self.assertEqual({}, ann._pending_deltas)
 
1442
 
 
1443
    def test_record_delta_removes_basis(self):
 
1444
        ann = self.make_annotator()
 
1445
        ann._expand_record(('parent-id',), (), None,
 
1446
                           ['line1\n', 'line2\n'], ('fulltext', False))
 
1447
        ann._num_compression_children['parent-id'] = 2
 
1448
 
 
1449
    def test_annotate_special_text(self):
 
1450
        ann = self.make_annotator()
 
1451
        vf = ann._vf
 
1452
        rev1_key = ('rev-1',)
 
1453
        rev2_key = ('rev-2',)
 
1454
        rev3_key = ('rev-3',)
 
1455
        spec_key = ('special:',)
 
1456
        vf.add_lines(rev1_key, [], ['initial content\n'])
 
1457
        vf.add_lines(rev2_key, [rev1_key], ['initial content\n',
 
1458
                                            'common content\n',
 
1459
                                            'content in 2\n'])
 
1460
        vf.add_lines(rev3_key, [rev1_key], ['initial content\n',
 
1461
                                            'common content\n',
 
1462
                                            'content in 3\n'])
 
1463
        spec_text = ('initial content\n'
 
1464
                     'common content\n'
 
1465
                     'content in 2\n'
 
1466
                     'content in 3\n')
 
1467
        ann.add_special_text(spec_key, [rev2_key, rev3_key], spec_text)
 
1468
        anns, lines = ann.annotate(spec_key)
 
1469
        self.assertEqual([(rev1_key,),
 
1470
                          (rev2_key, rev3_key),
 
1471
                          (rev2_key,),
 
1472
                          (rev3_key,),
 
1473
                         ], anns)
 
1474
        self.assertEqualDiff(spec_text, ''.join(lines))
 
1475
 
 
1476
 
1288
1477
class KnitTests(TestCaseWithTransport):
1289
1478
    """Class containing knit test helper routines."""
1290
1479
 
1296
1485
class TestBadShaError(KnitTests):
1297
1486
    """Tests for handling of sha errors."""
1298
1487
 
1299
 
    def test_exception_has_text(self):
 
1488
    def test_sha_exception_has_text(self):
1300
1489
        # having the failed text included in the error allows for recovery.
1301
1490
        source = self.make_test_knit()
1302
1491
        target = self.make_test_knit(name="target")
1313
1502
        target.insert_record_stream(
1314
1503
            source.get_record_stream([broken], 'unordered', False))
1315
1504
        err = self.assertRaises(errors.KnitCorrupt,
1316
 
            target.get_record_stream([broken], 'unordered', True).next)
 
1505
            target.get_record_stream([broken], 'unordered', True
 
1506
            ).next().get_bytes_as, 'chunked')
1317
1507
        self.assertEqual(['gam\n', 'bar\n'], err.content)
1318
1508
        # Test for formatting with live data
1319
1509
        self.assertStartsWith(str(err), "Knit ")
1524
1714
            [('parent',)])])
1525
1715
        # but neither should have added data:
1526
1716
        self.assertEqual([[], [], [], []], self.caught_entries)
1527
 
        
 
1717
 
1528
1718
    def test_add_version_different_dup(self):
1529
1719
        index = self.two_graph_index(deltas=True, catch_adds=True)
1530
1720
        # change options
1536
1726
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1537
1727
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [])])
1538
1728
        self.assertEqual([], self.caught_entries)
1539
 
        
 
1729
 
1540
1730
    def test_add_versions_nodeltas(self):
1541
1731
        index = self.two_graph_index(catch_adds=True)
1542
1732
        index.add_records([
1584
1774
            [('parent',)])])
1585
1775
        # but neither should have added data.
1586
1776
        self.assertEqual([[], [], [], []], self.caught_entries)
1587
 
        
 
1777
 
1588
1778
    def test_add_versions_different_dup(self):
1589
1779
        index = self.two_graph_index(deltas=True, catch_adds=True)
1590
1780
        # change options
1601
1791
             (('tip',), 'line-delta', (None, 0, 100), [('parent',)])])
1602
1792
        self.assertEqual([], self.caught_entries)
1603
1793
 
 
1794
    def make_g_index_missing_compression_parent(self):
 
1795
        graph_index = self.make_g_index('missing_comp', 2,
 
1796
            [(('tip', ), ' 100 78',
 
1797
              ([('missing-parent', ), ('ghost', )], [('missing-parent', )]))])
 
1798
        return graph_index
 
1799
 
 
1800
    def make_g_index_missing_parent(self):
 
1801
        graph_index = self.make_g_index('missing_parent', 2,
 
1802
            [(('parent', ), ' 100 78', ([], [])),
 
1803
             (('tip', ), ' 100 78',
 
1804
              ([('parent', ), ('missing-parent', )], [('parent', )])),
 
1805
              ])
 
1806
        return graph_index
 
1807
 
 
1808
    def make_g_index_no_external_refs(self):
 
1809
        graph_index = self.make_g_index('no_external_refs', 2,
 
1810
            [(('rev', ), ' 100 78',
 
1811
              ([('parent', ), ('ghost', )], []))])
 
1812
        return graph_index
 
1813
 
 
1814
    def test_add_good_unvalidated_index(self):
 
1815
        unvalidated = self.make_g_index_no_external_refs()
 
1816
        combined = CombinedGraphIndex([unvalidated])
 
1817
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
 
1818
        index.scan_unvalidated_index(unvalidated)
 
1819
        self.assertEqual(frozenset(), index.get_missing_compression_parents())
 
1820
 
 
1821
    def test_add_missing_compression_parent_unvalidated_index(self):
 
1822
        unvalidated = self.make_g_index_missing_compression_parent()
 
1823
        combined = CombinedGraphIndex([unvalidated])
 
1824
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
 
1825
        index.scan_unvalidated_index(unvalidated)
 
1826
        # This also checks that its only the compression parent that is
 
1827
        # examined, otherwise 'ghost' would also be reported as a missing
 
1828
        # parent.
 
1829
        self.assertEqual(
 
1830
            frozenset([('missing-parent',)]),
 
1831
            index.get_missing_compression_parents())
 
1832
 
 
1833
    def test_add_missing_noncompression_parent_unvalidated_index(self):
 
1834
        unvalidated = self.make_g_index_missing_parent()
 
1835
        combined = CombinedGraphIndex([unvalidated])
 
1836
        index = _KnitGraphIndex(combined, lambda: True, deltas=True,
 
1837
            track_external_parent_refs=True)
 
1838
        index.scan_unvalidated_index(unvalidated)
 
1839
        self.assertEqual(
 
1840
            frozenset([('missing-parent',)]), index.get_missing_parents())
 
1841
 
 
1842
    def test_track_external_parent_refs(self):
 
1843
        g_index = self.make_g_index('empty', 2, [])
 
1844
        combined = CombinedGraphIndex([g_index])
 
1845
        index = _KnitGraphIndex(combined, lambda: True, deltas=True,
 
1846
            add_callback=self.catch_add, track_external_parent_refs=True)
 
1847
        self.caught_entries = []
 
1848
        index.add_records([
 
1849
            (('new-key',), 'fulltext,no-eol', (None, 50, 60),
 
1850
             [('parent-1',), ('parent-2',)])])
 
1851
        self.assertEqual(
 
1852
            frozenset([('parent-1',), ('parent-2',)]),
 
1853
            index.get_missing_parents())
 
1854
 
 
1855
    def test_add_unvalidated_index_with_present_external_references(self):
 
1856
        index = self.two_graph_index(deltas=True)
 
1857
        # Ugly hack to get at one of the underlying GraphIndex objects that
 
1858
        # two_graph_index built.
 
1859
        unvalidated = index._graph_index._indices[1]
 
1860
        # 'parent' is an external ref of _indices[1] (unvalidated), but is
 
1861
        # present in _indices[0].
 
1862
        index.scan_unvalidated_index(unvalidated)
 
1863
        self.assertEqual(frozenset(), index.get_missing_compression_parents())
 
1864
 
 
1865
    def make_new_missing_parent_g_index(self, name):
 
1866
        missing_parent = name + '-missing-parent'
 
1867
        graph_index = self.make_g_index(name, 2,
 
1868
            [((name + 'tip', ), ' 100 78',
 
1869
              ([(missing_parent, ), ('ghost', )], [(missing_parent, )]))])
 
1870
        return graph_index
 
1871
 
 
1872
    def test_add_mulitiple_unvalidated_indices_with_missing_parents(self):
 
1873
        g_index_1 = self.make_new_missing_parent_g_index('one')
 
1874
        g_index_2 = self.make_new_missing_parent_g_index('two')
 
1875
        combined = CombinedGraphIndex([g_index_1, g_index_2])
 
1876
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
 
1877
        index.scan_unvalidated_index(g_index_1)
 
1878
        index.scan_unvalidated_index(g_index_2)
 
1879
        self.assertEqual(
 
1880
            frozenset([('one-missing-parent',), ('two-missing-parent',)]),
 
1881
            index.get_missing_compression_parents())
 
1882
 
 
1883
    def test_add_mulitiple_unvalidated_indices_with_mutual_dependencies(self):
 
1884
        graph_index_a = self.make_g_index('one', 2,
 
1885
            [(('parent-one', ), ' 100 78', ([('non-compression-parent',)], [])),
 
1886
             (('child-of-two', ), ' 100 78',
 
1887
              ([('parent-two',)], [('parent-two',)]))])
 
1888
        graph_index_b = self.make_g_index('two', 2,
 
1889
            [(('parent-two', ), ' 100 78', ([('non-compression-parent',)], [])),
 
1890
             (('child-of-one', ), ' 100 78',
 
1891
              ([('parent-one',)], [('parent-one',)]))])
 
1892
        combined = CombinedGraphIndex([graph_index_a, graph_index_b])
 
1893
        index = _KnitGraphIndex(combined, lambda: True, deltas=True)
 
1894
        index.scan_unvalidated_index(graph_index_a)
 
1895
        index.scan_unvalidated_index(graph_index_b)
 
1896
        self.assertEqual(
 
1897
            frozenset([]), index.get_missing_compression_parents())
 
1898
 
1604
1899
 
1605
1900
class TestNoParentsGraphIndexKnit(KnitTests):
1606
1901
    """Tests for knits using _KnitGraphIndex with no parents."""
1614
1909
        size = trans.put_file(name, stream)
1615
1910
        return GraphIndex(trans, name, size)
1616
1911
 
 
1912
    def test_add_good_unvalidated_index(self):
 
1913
        unvalidated = self.make_g_index('unvalidated')
 
1914
        combined = CombinedGraphIndex([unvalidated])
 
1915
        index = _KnitGraphIndex(combined, lambda: True, parents=False)
 
1916
        index.scan_unvalidated_index(unvalidated)
 
1917
        self.assertEqual(frozenset(),
 
1918
            index.get_missing_compression_parents())
 
1919
 
1617
1920
    def test_parents_deltas_incompatible(self):
1618
1921
        index = CombinedGraphIndex([])
1619
1922
        self.assertRaises(errors.KnitError, _KnitGraphIndex, lambda:True,
1700
2003
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
1701
2004
        # but neither should have added data.
1702
2005
        self.assertEqual([[], [], [], []], self.caught_entries)
1703
 
        
 
2006
 
1704
2007
    def test_add_version_different_dup(self):
1705
2008
        index = self.two_graph_index(catch_adds=True)
1706
2009
        # change options
1714
2017
        self.assertRaises(errors.KnitCorrupt, index.add_records,
1715
2018
            [(('tip',), 'fulltext,no-eol', (None, 0, 100), [('parent',)])])
1716
2019
        self.assertEqual([], self.caught_entries)
1717
 
        
 
2020
 
1718
2021
    def test_add_versions(self):
1719
2022
        index = self.two_graph_index(catch_adds=True)
1720
2023
        index.add_records([
1752
2055
        index.add_records([(('tip',), 'fulltext,no-eol', (None, 0, 1000), [])])
1753
2056
        # but neither should have added data.
1754
2057
        self.assertEqual([[], [], [], []], self.caught_entries)
1755
 
        
 
2058
 
1756
2059
    def test_add_versions_different_dup(self):
1757
2060
        index = self.two_graph_index(catch_adds=True)
1758
2061
        # change options
1772
2075
        self.assertEqual([], self.caught_entries)
1773
2076
 
1774
2077
 
 
2078
class TestKnitVersionedFiles(KnitTests):
 
2079
 
 
2080
    def assertGroupKeysForIo(self, exp_groups, keys, non_local_keys,
 
2081
                             positions, _min_buffer_size=None):
 
2082
        kvf = self.make_test_knit()
 
2083
        if _min_buffer_size is None:
 
2084
            _min_buffer_size = knit._STREAM_MIN_BUFFER_SIZE
 
2085
        self.assertEqual(exp_groups, kvf._group_keys_for_io(keys,
 
2086
                                        non_local_keys, positions,
 
2087
                                        _min_buffer_size=_min_buffer_size))
 
2088
 
 
2089
    def assertSplitByPrefix(self, expected_map, expected_prefix_order,
 
2090
                            keys):
 
2091
        split, prefix_order = KnitVersionedFiles._split_by_prefix(keys)
 
2092
        self.assertEqual(expected_map, split)
 
2093
        self.assertEqual(expected_prefix_order, prefix_order)
 
2094
 
 
2095
    def test__group_keys_for_io(self):
 
2096
        ft_detail = ('fulltext', False)
 
2097
        ld_detail = ('line-delta', False)
 
2098
        f_a = ('f', 'a')
 
2099
        f_b = ('f', 'b')
 
2100
        f_c = ('f', 'c')
 
2101
        g_a = ('g', 'a')
 
2102
        g_b = ('g', 'b')
 
2103
        g_c = ('g', 'c')
 
2104
        positions = {
 
2105
            f_a: (ft_detail, (f_a, 0, 100), None),
 
2106
            f_b: (ld_detail, (f_b, 100, 21), f_a),
 
2107
            f_c: (ld_detail, (f_c, 180, 15), f_b),
 
2108
            g_a: (ft_detail, (g_a, 121, 35), None),
 
2109
            g_b: (ld_detail, (g_b, 156, 12), g_a),
 
2110
            g_c: (ld_detail, (g_c, 195, 13), g_a),
 
2111
            }
 
2112
        self.assertGroupKeysForIo([([f_a], set())],
 
2113
                                  [f_a], [], positions)
 
2114
        self.assertGroupKeysForIo([([f_a], set([f_a]))],
 
2115
                                  [f_a], [f_a], positions)
 
2116
        self.assertGroupKeysForIo([([f_a, f_b], set([]))],
 
2117
                                  [f_a, f_b], [], positions)
 
2118
        self.assertGroupKeysForIo([([f_a, f_b], set([f_b]))],
 
2119
                                  [f_a, f_b], [f_b], positions)
 
2120
        self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
 
2121
                                  [f_a, g_a, f_b, g_b], [], positions)
 
2122
        self.assertGroupKeysForIo([([f_a, f_b, g_a, g_b], set())],
 
2123
                                  [f_a, g_a, f_b, g_b], [], positions,
 
2124
                                  _min_buffer_size=150)
 
2125
        self.assertGroupKeysForIo([([f_a, f_b], set()), ([g_a, g_b], set())],
 
2126
                                  [f_a, g_a, f_b, g_b], [], positions,
 
2127
                                  _min_buffer_size=100)
 
2128
        self.assertGroupKeysForIo([([f_c], set()), ([g_b], set())],
 
2129
                                  [f_c, g_b], [], positions,
 
2130
                                  _min_buffer_size=125)
 
2131
        self.assertGroupKeysForIo([([g_b, f_c], set())],
 
2132
                                  [g_b, f_c], [], positions,
 
2133
                                  _min_buffer_size=125)
 
2134
 
 
2135
    def test__split_by_prefix(self):
 
2136
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
 
2137
                                  'g': [('g', 'b'), ('g', 'a')],
 
2138
                                 }, ['f', 'g'],
 
2139
                                 [('f', 'a'), ('g', 'b'),
 
2140
                                  ('g', 'a'), ('f', 'b')])
 
2141
 
 
2142
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
 
2143
                                  'g': [('g', 'b'), ('g', 'a')],
 
2144
                                 }, ['f', 'g'],
 
2145
                                 [('f', 'a'), ('f', 'b'),
 
2146
                                  ('g', 'b'), ('g', 'a')])
 
2147
 
 
2148
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
 
2149
                                  'g': [('g', 'b'), ('g', 'a')],
 
2150
                                 }, ['f', 'g'],
 
2151
                                 [('f', 'a'), ('f', 'b'),
 
2152
                                  ('g', 'b'), ('g', 'a')])
 
2153
 
 
2154
        self.assertSplitByPrefix({'f': [('f', 'a'), ('f', 'b')],
 
2155
                                  'g': [('g', 'b'), ('g', 'a')],
 
2156
                                  '': [('a',), ('b',)]
 
2157
                                 }, ['f', 'g', ''],
 
2158
                                 [('f', 'a'), ('g', 'b'),
 
2159
                                  ('a',), ('b',),
 
2160
                                  ('g', 'a'), ('f', 'b')])
 
2161
 
 
2162
 
1775
2163
class TestStacking(KnitTests):
1776
2164
 
1777
2165
    def get_basis_and_test_knit(self):
1832
2220
        # self.assertEqual([("annotate", key_basis)], basis.calls)
1833
2221
        self.assertEqual([('get_parent_map', set([key_basis])),
1834
2222
            ('get_parent_map', set([key_basis])),
1835
 
            ('get_parent_map', set([key_basis])),
1836
 
            ('get_record_stream', [key_basis], 'unordered', True)],
 
2223
            ('get_record_stream', [key_basis], 'topological', True)],
1837
2224
            basis.calls)
1838
2225
 
1839
2226
    def test_check(self):
1840
2227
        # At the moment checking a stacked knit does implicitly check the
1841
 
        # fallback files.  
 
2228
        # fallback files.
1842
2229
        basis, test = self.get_basis_and_test_knit()
1843
2230
        test.check()
1844
2231
 
1936
2323
                True).next()
1937
2324
            self.assertEqual(record.key, result[0])
1938
2325
            self.assertEqual(record.sha1, result[1])
1939
 
            self.assertEqual(record.storage_kind, result[2])
 
2326
            # We used to check that the storage kind matched, but actually it
 
2327
            # depends on whether it was sourced from the basis, or in a single
 
2328
            # group, because asking for full texts returns proxy objects to a
 
2329
            # _ContentMapGenerator object; so checking the kind is unneeded.
1940
2330
            self.assertEqual(record.get_bytes_as('fulltext'), result[3])
1941
2331
        # It's not strictly minimal, but it seems reasonable for now for it to
1942
2332
        # ask which fallbacks have which parents.
1943
2333
        self.assertEqual([
1944
2334
            ("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)],
 
2335
            # topological is requested from the fallback, because that is what
 
2336
            # was requested at the top level.
 
2337
            ("get_record_stream", [key_basis_2, key_basis], 'topological', True)],
1948
2338
            calls)
1949
2339
 
1950
2340
    def test_get_record_stream_unordered_deltas(self):
2079
2469
 
2080
2470
    def test_iter_lines_added_or_present_in_keys(self):
2081
2471
        # Lines from the basis are returned, and lines for a given key are only
2082
 
        # returned once. 
 
2472
        # returned once.
2083
2473
        key1 = ('foo1',)
2084
2474
        key2 = ('foo2',)
2085
2475
        # all sources are asked for keys:
2171
2561
        last_call = basis.calls[-1]
2172
2562
        self.assertEqual('get_record_stream', last_call[0])
2173
2563
        self.assertEqual(set([key_left, key_right]), set(last_call[1]))
2174
 
        self.assertEqual('unordered', last_call[2])
 
2564
        self.assertEqual('topological', last_call[2])
2175
2565
        self.assertEqual(True, last_call[3])
 
2566
 
 
2567
 
 
2568
class TestNetworkBehaviour(KnitTests):
 
2569
    """Tests for getting data out of/into knits over the network."""
 
2570
 
 
2571
    def test_include_delta_closure_generates_a_knit_delta_closure(self):
 
2572
        vf = self.make_test_knit(name='test')
 
2573
        # put in three texts, giving ft, delta, delta
 
2574
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
 
2575
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
 
2576
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
 
2577
        # But heuristics could interfere, so check what happened:
 
2578
        self.assertEqual(['knit-ft-gz', 'knit-delta-gz', 'knit-delta-gz'],
 
2579
            [record.storage_kind for record in
 
2580
             vf.get_record_stream([('base',), ('d1',), ('d2',)],
 
2581
                'topological', False)])
 
2582
        # generate a stream of just the deltas include_delta_closure=True,
 
2583
        # serialise to the network, and check that we get a delta closure on the wire.
 
2584
        stream = vf.get_record_stream([('d1',), ('d2',)], 'topological', True)
 
2585
        netb = [record.get_bytes_as(record.storage_kind) for record in stream]
 
2586
        # The first bytes should be a memo from _ContentMapGenerator, and the
 
2587
        # second bytes should be empty (because its a API proxy not something
 
2588
        # for wire serialisation.
 
2589
        self.assertEqual('', netb[1])
 
2590
        bytes = netb[0]
 
2591
        kind, line_end = network_bytes_to_kind_and_offset(bytes)
 
2592
        self.assertEqual('knit-delta-closure', kind)
 
2593
 
 
2594
 
 
2595
class TestContentMapGenerator(KnitTests):
 
2596
    """Tests for ContentMapGenerator"""
 
2597
 
 
2598
    def test_get_record_stream_gives_records(self):
 
2599
        vf = self.make_test_knit(name='test')
 
2600
        # put in three texts, giving ft, delta, delta
 
2601
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
 
2602
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
 
2603
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
 
2604
        keys = [('d1',), ('d2',)]
 
2605
        generator = _VFContentMapGenerator(vf, keys,
 
2606
            global_map=vf.get_parent_map(keys))
 
2607
        for record in generator.get_record_stream():
 
2608
            if record.key == ('d1',):
 
2609
                self.assertEqual('d1\n', record.get_bytes_as('fulltext'))
 
2610
            else:
 
2611
                self.assertEqual('d2\n', record.get_bytes_as('fulltext'))
 
2612
 
 
2613
    def test_get_record_stream_kinds_are_raw(self):
 
2614
        vf = self.make_test_knit(name='test')
 
2615
        # put in three texts, giving ft, delta, delta
 
2616
        vf.add_lines(('base',), (), ['base\n', 'content\n'])
 
2617
        vf.add_lines(('d1',), (('base',),), ['d1\n'])
 
2618
        vf.add_lines(('d2',), (('d1',),), ['d2\n'])
 
2619
        keys = [('base',), ('d1',), ('d2',)]
 
2620
        generator = _VFContentMapGenerator(vf, keys,
 
2621
            global_map=vf.get_parent_map(keys))
 
2622
        kinds = {('base',): 'knit-delta-closure',
 
2623
            ('d1',): 'knit-delta-closure-ref',
 
2624
            ('d2',): 'knit-delta-closure-ref',
 
2625
            }
 
2626
        for record in generator.get_record_stream():
 
2627
            self.assertEqual(kinds[record.key], record.storage_kind)